/**
 * 验证码管理服务
 * 
 * 功能描述：
 * - 生成和管理各种类型的验证码
 * - 使用Redis缓存验证码，支持过期时间
 * - 提供验证码验证和防刷机制
 * 
 * 职责分离：
 * - 验证码生成和存储管理
 * - 验证码验证和尝试次数控制
 * - 频率限制和防刷机制
 * 
 * 支持的验证码类型：
 * - 邮箱验证码
 * - 密码重置验证码
 * - 手机短信验证码
 * 
 * 最近修改：
 * - 2026-01-12: 代码规范优化 - 添加VerificationService类注释，完善职责和方法说明 (修改者: moyin)
 * - 2026-01-07: 代码规范优化 - 清理未使用的导入(ConfigService)和多余空行
 * - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录规范
 * 
 * @author moyin
 * @version 1.0.2
 * @since 2025-12-17
 * @lastModified 2026-01-12
 */

import { Injectable, Logger, BadRequestException, HttpException, HttpStatus, Inject } from '@nestjs/common';
import { IRedisService } from '../../redis/redis.interface';

/**
 * 验证码类型枚举
 */
export enum VerificationCodeType {
  EMAIL_VERIFICATION = 'email_verification',
  PASSWORD_RESET = 'password_reset',
  SMS_VERIFICATION = 'sms_verification',
}

/**
 * 验证码信息接口
 */
export interface VerificationCodeInfo {
  /** 验证码 */
  code: string;
  /** 创建时间 */
  createdAt: number;
  /** 尝试次数 */
  attempts: number;
  /** 最大尝试次数 */
  maxAttempts: number;
}

/**
 * 验证码管理服务
 * 
 * 职责：
 * - 生成和管理各种类型的验证码（邮箱、密码重置、短信）
 * - 提供验证码验证和尝试次数控制机制
 * - 实现防刷机制和频率限制功能
 * - 管理Redis缓存中的验证码存储和过期
 * 
 * 主要方法：
 * - generateCode() - 生成指定类型的验证码
 * - verifyCode() - 验证用户输入的验证码
 * - codeExists() - 检查验证码是否存在
 * - deleteCode() - 删除指定验证码
 * - getCodeTTL() - 获取验证码剩余时间
 * - clearCooldown() - 清除发送冷却时间
 * 
 * 使用场景：
 * - 用户注册时的邮箱验证
 * - 密码重置流程的安全验证
 * - 短信验证码的生成和校验
 * - 防止验证码恶意刷取和暴力破解
 */
@Injectable()
export class VerificationService {
  private readonly logger = new Logger(VerificationService.name);

  // 验证码配置
  private readonly CODE_LENGTH = 6;
  private readonly CODE_EXPIRE_TIME = 5 * 60; // 5分钟
  private readonly MAX_ATTEMPTS = 3; // 最大验证尝试次数
  private readonly RATE_LIMIT_TIME = 60; // 发送频率限制（秒）
  private readonly MAX_SENDS_PER_HOUR = 5; // 每小时最大发送次数

  constructor(
    @Inject('REDIS_SERVICE') private readonly redis: IRedisService,
  ) {}

  /**
   * 生成验证码
   * 
   * @param identifier 标识符（邮箱或手机号）
   * @param type 验证码类型
   * @returns 验证码
   */
  async generateCode(identifier: string, type: VerificationCodeType): Promise<string> {
    // 检查发送频率限制
    await this.checkRateLimit(identifier, type);

    // 生成6位数字验证码
    const code = this.generateRandomCode();

    // 构建Redis键
    const key = this.buildRedisKey(identifier, type);

    // 验证码信息
    const codeInfo: VerificationCodeInfo = {
      code,
      createdAt: Date.now(),
      attempts: 0,
      maxAttempts: this.MAX_ATTEMPTS,
    };

    // 存储到Redis，设置过期时间
    await this.redis.set(key, JSON.stringify(codeInfo), this.CODE_EXPIRE_TIME);

    // 记录发送次数（用于频率限制）
    await this.recordSendAttempt(identifier, type);

    this.logger.log(`验证码已生成: ${identifier} (${type})`);
    return code;
  }

  /**
   * 验证验证码
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   * @param inputCode 用户输入的验证码
   * @returns 验证结果
   */
  async verifyCode(identifier: string, type: VerificationCodeType, inputCode: string): Promise<boolean> {
    const key = this.buildRedisKey(identifier, type);
    
    // 从Redis获取验证码信息
    const codeInfoStr = await this.redis.get(key);
    
    if (!codeInfoStr) {
      this.logger.warn(`验证码不存在或已过期: ${identifier} (${type})`);
      throw new BadRequestException('验证码不存在或已过期');
    }

    let codeInfo: VerificationCodeInfo;
    try {
      codeInfo = JSON.parse(codeInfoStr);
    } catch (error) {
      this.logger.error(`验证码数据解析失败: ${identifier} (${type})`, error);
      await this.redis.del(key);
      throw new BadRequestException('验证码数据异常，请重新获取');
    }

    // 检查尝试次数
    if (codeInfo.attempts >= codeInfo.maxAttempts) {
      this.logger.warn(`验证码尝试次数已达上限: ${identifier} (${type}), 尝试次数: ${codeInfo.attempts}`);
      await this.redis.del(key);
      throw new BadRequestException('验证码尝试次数过多，请重新获取');
    }

    // 获取当前TTL
    const currentTTL = await this.redis.ttl(key);
    this.logger.debug(`验证码当前TTL: ${identifier} (${type}), TTL: ${currentTTL}秒`);

    // 验证验证码
    if (codeInfo.code !== inputCode) {
      // 增加尝试次数
      codeInfo.attempts++;
      
      // 保持原有的TTL，不重置过期时间
      if (currentTTL > 0) {
        // 使用剩余的TTL时间
        await this.redis.set(key, JSON.stringify(codeInfo), currentTTL);
        this.logger.warn(`验证码验证失败: ${identifier} (${type}) - 剩余尝试次数: ${codeInfo.maxAttempts - codeInfo.attempts}, 剩余时间: ${currentTTL}秒`);
      } else if (currentTTL === -1) {
        // 永不过期的情况，保持永不过期
        await this.redis.set(key, JSON.stringify(codeInfo));
        this.logger.warn(`验证码验证失败: ${identifier} (${type}) - 剩余尝试次数: ${codeInfo.maxAttempts - codeInfo.attempts}, 永不过期`);
      } else {
        // TTL为-2表示键不存在，这种情况理论上不应该发生
        this.logger.error(`验证码TTL异常: ${identifier} (${type}), TTL: ${currentTTL}`);
        throw new BadRequestException('验证码状态异常，请重新获取');
      }
      
      throw new BadRequestException(`验证码错误，剩余尝试次数: ${codeInfo.maxAttempts - codeInfo.attempts}`);
    }

    // 验证成功，删除验证码
    await this.redis.del(key);
    this.logger.log(`验证码验证成功: ${identifier} (${type})`);
    return true;
  }

  /**
   * 检查验证码是否存在
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   * @returns 是否存在
   */
  async codeExists(identifier: string, type: VerificationCodeType): Promise<boolean> {
    const key = this.buildRedisKey(identifier, type);
    return await this.redis.exists(key);
  }

  /**
   * 删除验证码
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   */
  async deleteCode(identifier: string, type: VerificationCodeType): Promise<void> {
    const key = this.buildRedisKey(identifier, type);
    await this.redis.del(key);
    this.logger.log(`验证码已删除: ${identifier} (${type})`);
  }

  /**
   * 获取验证码剩余时间
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   * @returns 剩余时间（秒），-1表示不存在
   */
  async getCodeTTL(identifier: string, type: VerificationCodeType): Promise<number> {
    const key = this.buildRedisKey(identifier, type);
    return await this.redis.ttl(key);
  }

  /**
   * 检查发送频率限制
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   */
  private async checkRateLimit(identifier: string, type: VerificationCodeType): Promise<void> {
    // 检查是否在冷却时间内
    const cooldownKey = this.buildCooldownKey(identifier, type);
    const cooldownExists = await this.redis.exists(cooldownKey);
    
    if (cooldownExists) {
      const ttl = await this.redis.ttl(cooldownKey);
      throw new HttpException(`请等待 ${ttl} 秒后再试`, HttpStatus.TOO_MANY_REQUESTS);
    }

    // 检查每小时发送次数限制
    const hourlyKey = this.buildHourlyKey(identifier, type);
    const hourlyCount = await this.redis.get(hourlyKey);
    
    if (hourlyCount && parseInt(hourlyCount) >= this.MAX_SENDS_PER_HOUR) {
      throw new HttpException('每小时发送次数已达上限，请稍后再试', HttpStatus.TOO_MANY_REQUESTS);
    }
  }

  /**
   * 记录发送尝试
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   */
  private async recordSendAttempt(identifier: string, type: VerificationCodeType): Promise<void> {
    // 设置冷却时间
    const cooldownKey = this.buildCooldownKey(identifier, type);
    await this.redis.set(cooldownKey, '1', this.RATE_LIMIT_TIME);

    // 记录每小时发送次数
    const hourlyKey = this.buildHourlyKey(identifier, type);
    const current = await this.redis.get(hourlyKey);
    
    if (current) {
      const newCount = (parseInt(current) + 1).toString();
      await this.redis.set(hourlyKey, newCount, 3600);
    } else {
      await this.redis.set(hourlyKey, '1', 3600); // 1小时过期
    }
  }

  /**
   * 生成随机验证码
   * 
   * @returns 验证码
   */
  private generateRandomCode(): string {
    return Math.floor(Math.random() * Math.pow(10, this.CODE_LENGTH))
      .toString()
      .padStart(this.CODE_LENGTH, '0');
  }

  /**
   * 构建Redis键
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   * @returns Redis键
   */
  private buildRedisKey(identifier: string, type: VerificationCodeType): string {
    return `verification_code:${type}:${identifier}`;
  }

  /**
   * 构建冷却时间Redis键
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   * @returns Redis键
   */
  private buildCooldownKey(identifier: string, type: VerificationCodeType): string {
    return `verification_cooldown:${type}:${identifier}`;
  }

  /**
   * 构建每小时限制Redis键
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   * @returns Redis键
   */
  private buildHourlyKey(identifier: string, type: VerificationCodeType): string {
    const hour = new Date().getHours();
    const date = new Date().toDateString();
    return `verification_hourly:${type}:${identifier}:${date}:${hour}`;
  }

  /**
   * 清除验证码冷却时间
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   */
  async clearCooldown(identifier: string, type: VerificationCodeType): Promise<void> {
    const cooldownKey = this.buildCooldownKey(identifier, type);
    await this.redis.del(cooldownKey);
    this.logger.log(`验证码冷却时间已清除: ${identifier} (${type})`);
  }

  /**
   * 清理过期的验证码（可选的定时任务）
   */
  async cleanupExpiredCodes(): Promise<void> {
    // Redis会自动清理过期的键，这里可以添加额外的清理逻辑
    this.logger.log('验证码清理任务执行完成');
  }

  /**
   * 获取验证码统计信息
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   * @returns 统计信息
   */
  async getCodeStats(identifier: string, type: VerificationCodeType): Promise<{
    exists: boolean;
    ttl: number;
    attempts?: number;
    maxAttempts?: number;
    code?: string;
    createdAt?: number;
  }> {
    const key = this.buildRedisKey(identifier, type);
    const exists = await this.redis.exists(key);
    const ttl = await this.redis.ttl(key);

    if (!exists) {
      this.logger.debug(`验证码不存在: ${identifier} (${type})`);
      return { exists: false, ttl: -2 };
    }

    const codeInfoStr = await this.redis.get(key);
    let codeInfo: VerificationCodeInfo;
    
    try {
      codeInfo = JSON.parse(codeInfoStr || '{}');
    } catch (error) {
      this.logger.error('验证码信息解析失败', error);
      codeInfo = {} as VerificationCodeInfo;
    }

    this.logger.debug(`验证码统计: ${identifier} (${type}), TTL: ${ttl}, 尝试次数: ${codeInfo.attempts}/${codeInfo.maxAttempts}`);

    return {
      exists: true,
      ttl,
      attempts: codeInfo.attempts,
      maxAttempts: codeInfo.maxAttempts,
      code: codeInfo.code, // 仅用于调试，生产环境应该移除
      createdAt: codeInfo.createdAt,
    };
  }

  /**
   * 调试方法：获取验证码详细信息
   * 仅用于开发和调试，生产环境应该移除或限制访问
   * 
   * @param identifier 标识符
   * @param type 验证码类型
   * @returns 详细信息
   */
  async debugCodeInfo(identifier: string, type: VerificationCodeType): Promise<any> {
    const key = this.buildRedisKey(identifier, type);
    
    const [exists, ttl, rawData] = await Promise.all([
      this.redis.exists(key),
      this.redis.ttl(key),
      this.redis.get(key)
    ]);

    const result = {
      key,
      exists,
      ttl,
      rawData,
      parsedData: null as any,
      currentTime: Date.now(),
      timeFormatted: new Date().toISOString()
    };

    if (rawData) {
      try {
        result.parsedData = JSON.parse(rawData);
      } catch (error) {
        result.parsedData = { error: 'JSON解析失败', raw: rawData };
      }
    }

    this.logger.debug(`调试验证码信息: ${JSON.stringify(result, null, 2)}`);
    return result;
  }
}