/**
 * PATCH请求处理器模块
 * 
 * 本模块负责处理TUS协议中的PATCH请求，用于上传文件的分块数据。
 * 主要功能包括：验证请求头、处理文件偏移量、写入数据到存储、处理上传完成事件等。
 * 
 * 使用场景：
 * - 大文件分块上传
 * - 断点续传
 * - 文件上传进度管理
 */
import debug from 'debug'
import { BaseHandler } from './BaseHandler'
import type http from 'node:http'
import { CancellationContext, ERRORS, Upload, EVENTS } from '../utils'

const log = debug('tus-node-server:handlers:patch')

/**
 * PATCH请求处理器类
 * 
 * 继承自BaseHandler，专门处理TUS协议的PATCH请求。
 * 采用责任链模式，与其它处理器协同工作。
 * 
 * 设计模式解析：
 * - 继承：扩展基础处理器功能
 * - 异步编程：使用async/await处理异步操作
 * - 事件驱动：通过EVENTS触发相关事件
 * 
 * 使用示例：
 * const handler = new PatchHandler(store, options)
 * handler.send(req, res, context)
 */
export class PatchHandler extends BaseHandler {
  /**
   * 处理PATCH请求的核心方法
   * 
   * 功能描述：
   * 1. 验证请求头信息
   * 2. 获取文件上传偏移量
   * 3. 写入数据到存储
   * 4. 处理上传完成事件
   * 5. 返回响应结果
   * 
   * @param req HTTP请求对象
   * @param res HTTP响应对象
   * @param context 取消操作上下文
   * @returns 处理后的HTTP响应
   * 
   * 异常处理：
   * - 文件未找到：抛出ERRORS.FILE_NOT_FOUND
   * - 偏移量缺失：抛出ERRORS.MISSING_OFFSET
   * - 内容类型无效：抛出ERRORS.INVALID_CONTENT_TYPE
   * - 文件已过期：抛出ERRORS.FILE_NO_LONGER_EXISTS
   * - 偏移量不匹配：抛出ERRORS.INVALID_OFFSET
   */
  async send(
    req: http.IncomingMessage,
    res: http.ServerResponse,
    context: CancellationContext
  ) {
    try {
      // 从请求中获取文件ID
      const id = this.getFileIdFromRequest(req)
      console.log('id', id)
      if (!id) {
        throw ERRORS.FILE_NOT_FOUND
      }

      // 验证Upload-Offset头是否存在
      if (req.headers['upload-offset'] === undefined) {
        throw ERRORS.MISSING_OFFSET
      }

      // 解析偏移量
      const offset = Number.parseInt(req.headers['upload-offset'] as string, 10)

      // 验证Content-Type头是否存在
      const content_type = req.headers['content-type']
      if (content_type === undefined) {
        throw ERRORS.INVALID_CONTENT_TYPE
      }

      // 触发请求到达事件
      if (this.options.onIncomingRequest) {
        await this.options.onIncomingRequest(req, res, id)
      }

      // 获取配置的最大文件大小
      const maxFileSize = await this.getConfiguredMaxSize(req, id)

      // 获取文件锁
      const lock = await this.acquireLock(req, id, context)

      let upload: Upload
      let newOffset: number
      try {
        // 从存储中获取上传信息
        upload = await this.store.getUpload(id)

        // 检查文件是否已过期
        const now = Date.now()
        const creation = upload.creation_date
          ? new Date(upload.creation_date).getTime()
          : now
        const expiration = creation + this.store.getExpiration()
        if (
          this.store.hasExtension('expiration') &&
          this.store.getExpiration() > 0 &&
          now > expiration
        ) {
          throw ERRORS.FILE_NO_LONGER_EXISTS
        }

        // 验证偏移量是否匹配
        if (upload.offset !== offset) {
          log(
            `[PatchHandler] send: Incorrect offset - ${offset} sent but file is ${upload.offset}`
          )
          throw ERRORS.INVALID_OFFSET
        }

        // 处理上传长度相关头信息
        const upload_length = req.headers['upload-length'] as string | undefined
        if (upload_length !== undefined) {
          const size = Number.parseInt(upload_length, 10)
          // 检查是否支持延迟长度声明
          if (!this.store.hasExtension('creation-defer-length')) {
            throw ERRORS.UNSUPPORTED_CREATION_DEFER_LENGTH_EXTENSION
          }

          // 检查上传长度是否已设置
          if (upload.size !== undefined) {
            throw ERRORS.INVALID_LENGTH
          }

          // 验证长度是否有效
          if (size < upload.offset) {
            throw ERRORS.INVALID_LENGTH
          }

          // 检查是否超过最大文件大小
          if (maxFileSize > 0 && size > maxFileSize) {
            throw ERRORS.ERR_MAX_SIZE_EXCEEDED
          }

          // 声明上传长度
          await this.store.declareUploadLength(id, size)
          upload.size = size
        }

        // 计算最大请求体大小
        const maxBodySize = await this.calculateMaxBodySize(req, upload, maxFileSize)
        // 写入数据到存储
        newOffset = await this.writeToStore(req, upload, maxBodySize, context)
      } finally {
        // 释放文件锁
        await lock.unlock()
      }

      // 更新上传偏移量
      upload.offset = newOffset
      // 触发数据接收完成事件
      this.emit(EVENTS.POST_RECEIVE, req, res, upload)

      // 构建响应数据
      const responseData = {
        status: 204,
        headers: {
          'Upload-Offset': newOffset,
        } as Record<string, string | number>,
        body: '',
      }

      // 处理上传完成事件
      // 文件上传完成后的处理逻辑块
      if (newOffset === upload.size && this.options.onUploadFinish) {
        try {
          // 调用上传完成回调函数，支持异步处理
          // 允许用户自定义上传完成后的处理逻辑
          const resOrObject = await this.options.onUploadFinish(req, res, upload)

          // 兼容性处理：支持两种返回类型
          // 1. 直接返回 http.ServerResponse 对象
          // 2. 返回包含自定义响应信息的对象
          if (
            // 检查是否为标准 ServerResponse 对象
            typeof (resOrObject as http.ServerResponse).write === 'function' &&
            typeof (resOrObject as http.ServerResponse).writeHead === 'function'
          ) {
            // 直接使用返回的服务器响应对象
            res = resOrObject as http.ServerResponse
          } else {
            // 处理自定义响应对象的类型定义
            // 排除 ServerResponse 类型，确保类型安全
            type ExcludeServerResponse<T> = T extends http.ServerResponse ? never : T

            // 将返回对象转换为自定义响应对象
            const obj = resOrObject as ExcludeServerResponse<typeof resOrObject>

            // 更新响应对象
            res = obj.res

            // 可选地更新响应状态码
            if (obj.status_code) responseData.status = obj.status_code

            // 可选地更新响应体
            if (obj.body) responseData.body = obj.body

            // 合并响应头，允许覆盖默认头
            if (obj.headers)
              responseData.headers = Object.assign(obj.headers, responseData.headers)
          }
        } catch (error: any) {
          // 错误处理：记录上传完成回调中的错误
          // 使用日志记录错误信息，并重新抛出异常
          log(`onUploadFinish: ${error.body}`)
          throw error
        }
      }

      // 处理文件过期时间
      if (
        this.store.hasExtension('expiration') &&
        this.store.getExpiration() > 0 &&
        upload.creation_date &&
        (upload.size === undefined || newOffset < upload.size)
      ) {
        const creation = new Date(upload.creation_date)
        const dateString = new Date(
          creation.getTime() + this.store.getExpiration()
        ).toUTCString()
        responseData.headers['Upload-Expires'] = dateString
      }

      // 发送响应
      const writtenRes = this.write(
        res,
        responseData.status,
        responseData.headers,
        responseData.body
      )

      // 触发上传完成事件
      if (newOffset === upload.size) {
        this.emit(EVENTS.POST_FINISH, req, writtenRes, upload)
      }

      return writtenRes
    } catch (e) {
      // 取消操作
      context.abort()
      throw e
    }
  }
}
