/**
 * 频率限制守卫
 * 
 * 功能描述：
 * - 实现API接口的频率限制功能
 * - 基于IP地址进行限制
 * - 支持自定义限制规则
 * 
 * 职责分离：
 * - 频率限制逻辑的核心实现
 * - 请求记录的内存存储和管理
 * - 限制检查和异常处理
 * 
 * 主要方法：
 * - canActivate() - 守卫检查入口方法
 * - checkThrottle() - 频率限制核心检查逻辑
 * - generateKey() - 限制键生成算法
 * - cleanupExpiredRecords() - 过期记录清理机制
 * 
 * 使用场景：
 * - 防止API滥用
 * - 登录暴力破解防护
 * - 验证码发送频率控制
 * 
 * 最近修改：
 * - 2026-01-07: 代码规范优化 - 更新注释规范，完善守卫说明
 * 
 * @author moyin
 * @version 1.0.1
 * @since 2025-12-24
 * @lastModified 2026-01-07
 */

import {
  Injectable,
  CanActivate,
  ExecutionContext,
  HttpException,
  HttpStatus,
  Logger,
  OnModuleDestroy
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { THROTTLE_KEY, ThrottleConfig } from './throttle.decorator';

/**
 * 频率限制记录接口
 */
interface ThrottleRecord {
  /** 请求次数 */
  count: number;
  /** 窗口开始时间 */
  windowStart: number;
  /** 最后请求时间 */
  lastRequest: number;
}

/**
 * 频率限制响应接口
 */
interface ThrottleResponse {
  /** 请求是否成功 */
  success: boolean;
  /** 响应消息 */
  message: string;
  /** 错误代码 */
  error_code: string;
  /** 限制信息 */
  throttle_info: {
    /** 限制次数 */
    limit: number;
    /** 时间窗口（秒） */
    window_seconds: number;
    /** 当前请求次数 */
    current_requests: number;
    /** 重置时间 */
    reset_time: string;
  };
}

@Injectable()
export class ThrottleGuard implements CanActivate, OnModuleDestroy {
  private readonly logger = new Logger(ThrottleGuard.name);
  
  /**
   * 存储频率限制记录
   * Key: IP地址 + 路径
   * Value: 限制记录
   */
  private readonly records = new Map<string, ThrottleRecord>();

  /**
   * 清理过期记录的间隔（毫秒）
   */
  private readonly CLEANUP_INTERVAL = 60000; // 1分钟

  /**
   * 清理任务的定时器ID
   */
  private cleanupTimer?: NodeJS.Timeout;

  constructor(private readonly reflector: Reflector) {
    // 启动定期清理任务
    this.startCleanupTask();
  }

  /**
   * 组件销毁时的清理方法
   */
  onModuleDestroy() {
    if (this.cleanupTimer) {
      clearInterval(this.cleanupTimer);
      this.cleanupTimer = undefined;
    }
  }

  /**
   * 守卫检查函数
   * 
   * 业务逻辑：
   * 1. 从装饰器元数据获取频率限制配置
   * 2. 提取请求信息（IP、路径、方法等）
   * 3. 生成唯一的限制键标识
   * 4. 检查当前请求是否超过频率限制
   * 5. 记录被限制的请求日志
   * 6. 抛出频率限制异常或允许请求通过
   * 
   * @param context 执行上下文
   * @returns 是否允许通过
   * @throws HttpException 当请求频率超过限制时抛出429状态码
   * 
   * @example
   * ```typescript
   * // 守卫会自动应用到使用@Throttle装饰器的方法上
   * // 无需手动调用此方法
   * ```
   */
  async canActivate(context: ExecutionContext): Promise<boolean> {
    // 1. 获取频率限制配置
    const throttleConfig = this.getThrottleConfig(context);
    
    if (!throttleConfig) {
      // 没有配置频率限制，直接通过
      return true;
    }

    // 2. 获取请求信息
    const request = context.switchToHttp().getRequest<Request>();
    const key = this.generateKey(request, throttleConfig);
    
    // 3. 检查频率限制
    const isAllowed = this.checkThrottle(key, throttleConfig);
    
    if (!isAllowed) {
      // 4. 记录被限制的请求
      this.logger.warn('请求被频率限制', {
        operation: 'throttle_limit',
        method: request.method,
        url: request.url,
        ip: request.ip,
        userAgent: request.get('User-Agent'),
        limit: throttleConfig.limit,
        ttl: throttleConfig.ttl,
        timestamp: new Date().toISOString()
      });

      // 5. 抛出频率限制异常
      const record = this.records.get(key);
      const resetTime = new Date(record!.windowStart + throttleConfig.ttl * 1000);
      
      const response: ThrottleResponse = {
        success: false,
        message: throttleConfig.message || '请求过于频繁，请稍后再试',
        error_code: 'TOO_MANY_REQUESTS',
        throttle_info: {
          limit: throttleConfig.limit,
          window_seconds: throttleConfig.ttl,
          current_requests: record!.count,
          reset_time: resetTime.toISOString()
        }
      };

      throw new HttpException(response, HttpStatus.TOO_MANY_REQUESTS);
    }

    return true;
  }

  /**
   * 获取频率限制配置
   * 
   * @param context 执行上下文
   * @returns 频率限制配置或null
   */
  private getThrottleConfig(context: ExecutionContext): ThrottleConfig | null {
    // 从方法装饰器获取配置
    const methodConfig = this.reflector.get<ThrottleConfig>(
      THROTTLE_KEY,
      context.getHandler()
    );

    if (methodConfig) {
      return methodConfig;
    }

    // 从类装饰器获取配置
    const classConfig = this.reflector.get<ThrottleConfig>(
      THROTTLE_KEY,
      context.getClass()
    );

    return classConfig || null;
  }

  /**
   * 生成限制键
   * 
   * @param request 请求对象
   * @param config 频率限制配置
   * @returns 限制键
   */
  private generateKey(request: Request, config: ThrottleConfig): string {
    const ip = request.ip || 'unknown';
    const path = request.route?.path || request.url;
    const method = request.method;
    
    // 根据限制类型生成不同的键
    if (config.type === 'user') {
      // 基于用户的限制（需要从JWT中获取用户ID）
      const userId = this.extractUserId(request);
      return `user:${userId}:${method}:${path}`;
    } else if (config.type === 'email') {
      // 基于邮箱的限制（从请求体中获取邮箱）
      const email = this.extractEmail(request);
      return `email:${email}:${method}:${path}`;
    } else {
      // 基于IP的限制（默认）
      return `ip:${ip}:${method}:${path}`;
    }
  }

  /**
   * 检查频率限制
   * 
   * @param key 限制键
   * @param config 频率限制配置
   * @returns 是否允许通过
   */
  private checkThrottle(key: string, config: ThrottleConfig): boolean {
    const now = Date.now();
    const windowMs = config.ttl * 1000;
    
    let record = this.records.get(key);
    
    if (!record) {
      // 第一次请求
      this.records.set(key, {
        count: 1,
        windowStart: now,
        lastRequest: now
      });
      return true;
    }

    // 检查是否需要重置窗口
    if (now - record.windowStart >= windowMs) {
      // 重置窗口
      record.count = 1;
      record.windowStart = now;
      record.lastRequest = now;
      return true;
    }

    // 在当前窗口内
    if (record.count >= config.limit) {
      // 超过限制
      return false;
    }

    // 增加计数
    record.count++;
    record.lastRequest = now;
    return true;
  }

  /**
   * 从请求中提取用户ID
   * 
   * @param request 请求对象
   * @returns 用户ID
   */
  private extractUserId(request: Request): string {
    // 这里应该从JWT token中提取用户ID
    // 简化实现，使用IP作为fallback
    const authHeader = request.get('Authorization');
    if (authHeader && authHeader.startsWith('Bearer ')) {
      try {
        // 这里应该解析JWT token获取用户ID
        // 简化实现，返回token的hash
        const token = authHeader.substring(7);
        return Buffer.from(token).toString('base64').substring(0, 10);
      } catch (error) {
        // JWT解析失败，使用IP
        return request.ip || 'unknown';
      }
    }
    
    return request.ip || 'unknown';
  }

  /**
   * 从请求中提取邮箱地址
   * 
   * @param request 请求对象
   * @returns 邮箱地址
   */
  private extractEmail(request: Request): string {
    try {
      // 从请求体中获取邮箱
      const body = request.body;
      
      // 优先从email字段获取
      if (body && body.email) {
        return body.email.toLowerCase(); // 统一转换为小写
      }
      
      // 从identifier字段获取（登录接口使用这个字段）
      if (body && body.identifier) {
        // 检查identifier是否是邮箱格式
        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
        if (emailRegex.test(body.identifier)) {
          return body.identifier.toLowerCase();
        }
        // 如果不是邮箱格式，可能是用户名，也用作标识符
        return body.identifier.toLowerCase();
      }
      
      // 检查其他可能的字段
      if (body && body.username) {
        return body.username.toLowerCase();
      }
      
      // 如果都没有找到，使用IP作为fallback
      return request.ip || 'unknown';
    } catch (error) {
      // 解析失败，使用IP作为fallback
      return request.ip || 'unknown';
    }
  }

  /**
   * 启动清理任务
   */
  private startCleanupTask(): void {
    this.cleanupTimer = setInterval(() => {
      this.cleanupExpiredRecords();
    }, this.CLEANUP_INTERVAL);
  }

  /**
   * 清理过期记录
   */
  private cleanupExpiredRecords(): void {
    const now = Date.now();
    const MAX_AGE = 3600000; // 1小时
    
    for (const [key, record] of this.records.entries()) {
      if (now - record.lastRequest > MAX_AGE) {
        this.records.delete(key);
      }
    }
  }

  /**
   * 获取当前记录统计
   * 
   * @returns 记录统计信息
   */
  getStats() {
    return {
      totalRecords: this.records.size,
      records: Array.from(this.records.entries()).map(([key, record]) => ({
        key,
        count: record.count,
        windowStart: new Date(record.windowStart).toISOString(),
        lastRequest: new Date(record.lastRequest).toISOString()
      }))
    };
  }

  /**
   * 清除所有记录
   */
  clearAllRecords(): void {
    this.records.clear();
  }

  /**
   * 清除指定键的记录
   * 
   * @param key 限制键
   */
  clearRecord(key: string): void {
    this.records.delete(key);
  }
}