/**
 * 文件模块：GetHandler.ts
 * 功能描述：负责处理HTTP GET请求，提供文件下载和流式传输功能
 * 使用场景：适用于需要实现文件下载、流媒体播放等功能的Web服务
 */

import stream from 'node:stream'
import { BaseHandler } from './BaseHandler'
import type http from 'node:http'
import type { RouteHandler } from '../types'
import { ERRORS, Upload } from '../utils'
/**
 * GetHandler类
 * 核心功能：处理GET请求，支持自定义路径处理和文件流传输
 * 设计模式：基于策略模式实现路径处理函数的动态注册
 * 使用示例：
 * const handler = new GetHandler()
 * handler.registerPath('/custom', customHandler)
 */
export class GetHandler extends BaseHandler {
  // 使用Map存储路径与处理函数的映射关系，提供O(1)的查找时间复杂度
  paths: Map<string,RouteHandler> = new Map()
  /**
   * 正则表达式用于验证MIME类型是否符合RFC1341规范
   * 支持带参数的MIME类型，如：text/plain; charset=utf-8
   * 时间复杂度：O(n)，n为字符串长度
   * 优化建议：可考虑预编译正则表达式以提高性能
   */
  reMimeType =
    // biome-ignore lint/suspicious/noControlCharactersInRegex: it's fine
    /^(?:application|audio|example|font|haptics|image|message|model|multipart|text|video|x-(?:[0-9A-Za-z!#$%&'*+.^_`|~-]+))\/([0-9A-Za-z!#$%&'*+.^_`|~-]+)((?:[ 	]*;[ 	]*[0-9A-Za-z!#$%&'*+.^_`|~-]+=(?:[0-9A-Za-z!#$%&'*+.^_`|~-]+|"(?:[^"\\]|\.)*"))*)$/
  /**
   * 允许浏览器内联渲染的MIME类型白名单
   * 使用Set数据结构，提供O(1)的查找时间复杂度
   * 优化建议：可根据实际业务需求动态调整白名单
   */
  mimeInlineBrowserWhitelist = new Set([
    'text/plain',
    'image/png',
    'image/jpeg',
    'image/gif',
    'image/bmp',
    'image/webp',
    'audio/wave',
    'audio/wav',
    'audio/x-wav',
    'audio/x-pn-wav',
    'audio/webm',
    'audio/ogg',
    'video/mp4',
    'video/webm',
    'video/ogg',
    'application/ogg',
  ])
  /**
   * 注册路径处理函数
   * 功能描述：将路径与处理函数进行绑定
   * 输入参数：
   * - path: 请求路径
   * - handler: 处理函数
   * 时间复杂度：O(1)
   * 优化建议：可添加路径冲突检测机制
   */
  registerPath(path: string, handler: RouteHandler): void {
    this.paths.set(path, handler)
  }
  /**
   * 发送文件流
   * 功能描述：处理GET请求，返回文件流或执行自定义处理
   * 输入参数：
   * - req: HTTP请求对象
   * - res: HTTP响应对象
   * 返回值：可写流或void
   * 异常处理：抛出FILE_NOT_FOUND错误
   * 时间复杂度：O(n)，n为文件大小
   * 优化建议：可添加流控机制防止内存溢出
   */
  async send(
    req: http.IncomingMessage,
    res: http.ServerResponse
    // biome-ignore lint/suspicious/noConfusingVoidType: it's fine
  ): Promise<stream.Writable | void> {
    // 检查是否注册了自定义路径处理
    if (this.paths.has(req.url as string)) {
      const handler = this.paths.get(req.url as string) as RouteHandler
      return handler(req, res)
    }

    // 检查数据存储是否支持读取操作
    if (!('read' in this.store)) {
      throw ERRORS.FILE_NOT_FOUND
    }

    // 从请求中提取文件ID
    const id = this.getFileIdFromRequest(req)
    if (!id) {
      throw ERRORS.FILE_NOT_FOUND
    }

    // 执行自定义请求处理回调
    if (this.options.onIncomingRequest) {
      await this.options.onIncomingRequest(req, res, id)
    }

    // 获取文件上传状态
    const stats = await this.store.getUpload(id)

    // 验证文件是否完整
    if (!stats || stats.offset !== stats.size) {
      throw ERRORS.FILE_NOT_FOUND
    }

    // 处理内容类型和内容处置头
    const { contentType, contentDisposition } = this.filterContentType(stats)

    // 创建文件读取流
    // @ts-expect-error exists if supported
    const file_stream = await this.store.read(id)
    const headers = {
      'Content-Length': stats.offset,
      'Content-Type': contentType,
      'Content-Disposition': contentDisposition,
    }
    res.writeHead(200, headers)
    // 使用流管道传输数据
    return stream.pipeline(file_stream, res, () => {
      // 忽略流传输错误
    })
  }

  /**
   * 过滤内容类型
   * 功能描述：根据文件类型生成Content-Type和Content-Disposition头
   * 输入参数：
   * - stats: 文件上传状态对象
   * 返回值：包含contentType和contentDisposition的对象
   * 时间复杂度：O(1)
   * 优化建议：可添加更多MIME类型验证规则
   */
  filterContentType(stats: Upload): {
    contentType: string
    contentDisposition: string
  } {
    let contentType: string
    let contentDisposition: string

    // 从元数据中提取文件类型和名称
    const { filetype, filename } = stats.metadata ?? {}

    // 验证文件类型格式
    if (filetype && this.reMimeType.test(filetype)) {
      contentType = filetype

      // 检查是否在白名单中
      if (this.mimeInlineBrowserWhitelist.has(filetype)) {
        contentDisposition = 'inline'
      } else {
        contentDisposition = 'attachment'
      }
    } else {
      // 使用默认类型并强制下载
      contentType = 'application/octet-stream'
      contentDisposition = 'attachment'
    }

    // 添加文件名到内容处置头
    if (filename) {
      contentDisposition += `; filename=${this.quote(filename)}`
    }

    return {
      contentType,
      contentDisposition,
    }
  }

  /**
   * 字符串转义
   * 功能描述：将字符串转换为带引号的字符串字面量
   * 输入参数：
   * - value: 需要转义的字符串
   * 返回值：转义后的字符串
   * 时间复杂度：O(n)，n为字符串长度
   * 优化建议：可考虑使用正则表达式优化替换操作
   */
  quote(value: string) {
    return `"${value.replace(/"/g, '\\"')}"`
  }
}
