/**
 * 内容类型检查中间件
 *
 * 功能描述：
 * - 检查POST/PUT请求的Content-Type头
 * - 确保API接口接收正确的数据格式
 * - 提供友好的错误提示信息
 *
 * 职责分离：
 * - Content-Type验证逻辑的实现
 * - 支持类型和排除路径的配置管理
 * - 错误响应的统一格式化处理
 *
 * 主要方法：
 * - use() - 中间件处理入口方法
 * - shouldCheckContentType() - 检查条件判断逻辑
 * - isSupportedContentType() - 类型支持性验证
 * - normalizeContentType() - 类型标准化处理
 *
 * 使用场景：
 * - API接口数据格式验证
 * - 防止错误的请求格式
 * - 提升API接口的健壮性
 *
 * 最近修改：
 * - 2026-01-07: 代码规范优化 - 更新注释规范，完善中间件说明
 *
 * @author moyin
 * @version 1.0.1
 * @since 2025-12-24
 * @lastModified 2026-01-07
 */

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import { Logger } from '@nestjs/common';

/**
 * 不支持的媒体类型响应接口
 */
interface UnsupportedMediaTypeResponse {
  /** 请求是否成功 */
  success: boolean;
  /** 响应消息 */
  message: string;
  /** 错误代码 */
  error_code: string;
  /** 支持的媒体类型 */
  supported_types: string[];
  /** 接收到的媒体类型 */
  received_type?: string;
}

@Injectable()
export class ContentTypeMiddleware implements NestMiddleware {
  private readonly logger = new Logger(ContentTypeMiddleware.name);

  /**
   * 需要检查Content-Type的HTTP方法
   */
  private readonly methodsToCheck = ['POST', 'PUT', 'PATCH'];

  /**
   * 支持的Content-Type列表
   */
  private readonly supportedTypes = [
    'application/json',
    'application/json; charset=utf-8'
  ];

  /**
   * 不需要检查Content-Type的路径（正则表达式）
   */
  private readonly excludePaths = [
    /^\/api-docs/, // Swagger文档
    /^\/health/, // 健康检查
    /^\/admin\/logs\/archive/, // 文件下载
    /\/upload/, // 文件上传
  ];

  /**
   * 中间件处理函数
   *
   * 业务逻辑：
   * 1. 检查是否需要验证Content-Type
   * 2. 获取请求的Content-Type头
   * 3. 验证Content-Type是否支持
   * 4. 记录不支持的请求类型
   *
   * @param req HTTP请求对象
   * @param res HTTP响应对象
   * @param next 下一个中间件函数
   */
  use(req: Request, res: Response, next: NextFunction) {
    // 1. 检查是否需要验证Content-Type
    if (!this.shouldCheckContentType(req)) {
      next();
      return;
    }

    // 2. 获取请求的Content-Type
    const contentType = req.get('Content-Type');

    // 3. 检查Content-Type是否存在
    if (!contentType) {
      this.logger.warn('请求缺少Content-Type头', {
        operation: 'content_type_check',
        method: req.method,
        url: req.url,
        userAgent: req.get('User-Agent'),
        ip: req.ip,
        timestamp: new Date().toISOString()
      });

      const response: UnsupportedMediaTypeResponse = {
        success: false,
        message: '请求缺少Content-Type头，请设置为application/json',
        error_code: 'MISSING_CONTENT_TYPE',
        supported_types: this.supportedTypes,
        received_type: undefined
      };

      res.status(415).json(response);
      return;
    }

    // 4. 验证Content-Type是否支持
    const normalizedContentType = this.normalizeContentType(contentType);

    if (!this.isSupportedContentType(normalizedContentType)) {
      this.logger.warn('不支持的Content-Type', {
        operation: 'content_type_check',
        method: req.method,
        url: req.url,
        contentType: contentType,
        normalizedContentType: normalizedContentType,
        userAgent: req.get('User-Agent'),
        ip: req.ip,
        timestamp: new Date().toISOString()
      });

      const response: UnsupportedMediaTypeResponse = {
        success: false,
        message: `不支持的Content-Type: ${contentType}，请使用application/json`,
        error_code: 'UNSUPPORTED_MEDIA_TYPE',
        supported_types: this.supportedTypes,
        received_type: contentType
      };

      res.status(415).json(response);
      return;
    }

    // 5. Content-Type验证通过，继续处理
    next();
  }

  /**
   * 检查是否需要验证Content-Type
   *
   * @param req HTTP请求对象
   * @returns 是否需要验证
   */
  private shouldCheckContentType(req: Request): boolean {
    // 1. 检查HTTP方法
    if (!this.methodsToCheck.includes(req.method)) {
      return false;
    }

    // 2. 检查是否在排除路径中
    const url = req.url;
    for (const excludePattern of this.excludePaths) {
      if (excludePattern.test(url)) {
        return false;
      }
    }

    // 3. 检查Content-Length，如果为0则不需要验证
    const contentLength = req.get('Content-Length');
    if (contentLength === '0') {
      return false;
    }

    return true;
  }

  /**
   * 标准化Content-Type
   *
   * @param contentType 原始Content-Type
   * @returns 标准化后的Content-Type
   */
  private normalizeContentType(contentType: string): string {
    // 移除空格并转换为小写
    return contentType.toLowerCase().trim();
  }

  /**
   * 检查Content-Type是否支持
   *
   * @param contentType 标准化的Content-Type
   * @returns 是否支持
   */
  private isSupportedContentType(contentType: string): boolean {
    // 检查是否以支持的类型开头
    return this.supportedTypes.some(supportedType =>
      contentType.startsWith(supportedType.toLowerCase())
    );
  }

  /**
   * 获取支持的Content-Type列表
   *
   * @returns 支持的类型列表
   */
  getSupportedTypes(): string[] {
    return [...this.supportedTypes];
  }

  /**
   * 添加支持的Content-Type
   *
   * @param contentType 要添加的Content-Type
   */
  addSupportedType(contentType: string): void {
    if (!this.supportedTypes.includes(contentType)) {
      this.supportedTypes.push(contentType);
    }
  }

  /**
   * 添加排除路径
   *
   * @param pattern 路径正则表达式
   */
  addExcludePath(pattern: RegExp): void {
    this.excludePaths.push(pattern);
  }
}