import { Injectable, Logger, BadRequestException } from '@nestjs/common';
import { PrismaService } from '@/infrastructure/persistence/prisma/prisma.service';
import * as crypto from 'crypto';

/**
 * TOTP (Time-based One-Time Password) 服务
 * 实现谷歌验证器兼容的二次验证功能
 */
@Injectable()
export class TotpService {
  private readonly logger = new Logger(TotpService.name);

  // TOTP 配置
  private readonly TOTP_DIGITS = 6; // 验证码位数
  private readonly TOTP_PERIOD = 30; // 验证码有效期 (秒)
  private readonly TOTP_WINDOW = 1; // 允许的时间窗口偏移
  private readonly ISSUER = 'RWADurian'; // 应用名称

  // AES 加密密钥 (生产环境应从环境变量获取)
  private readonly ENCRYPTION_KEY =
    process.env.TOTP_ENCRYPTION_KEY || 'rwa-durian-totp-secret-key-32ch';

  constructor(private readonly prisma: PrismaService) {}

  /**
   * 生成 TOTP 密钥
   */
  private generateSecret(): string {
    // 生成 20 字节随机数，编码为 base32
    const buffer = crypto.randomBytes(20);
    return this.base32Encode(buffer);
  }

  /**
   * Base32 编码
   */
  private base32Encode(buffer: Buffer): string {
    const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    let bits = 0;
    let value = 0;
    let result = '';

    for (let i = 0; i < buffer.length; i++) {
      value = (value << 8) | buffer[i];
      bits += 8;

      while (bits >= 5) {
        result += alphabet[(value >>> (bits - 5)) & 31];
        bits -= 5;
      }
    }

    if (bits > 0) {
      result += alphabet[(value << (5 - bits)) & 31];
    }

    return result;
  }

  /**
   * Base32 解码
   */
  private base32Decode(encoded: string): Buffer {
    const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    const cleanedInput = encoded.toUpperCase().replace(/=+$/, '');

    let bits = 0;
    let value = 0;
    const output: number[] = [];

    for (let i = 0; i < cleanedInput.length; i++) {
      const char = cleanedInput[i];
      const index = alphabet.indexOf(char);
      if (index === -1) continue;

      value = (value << 5) | index;
      bits += 5;

      if (bits >= 8) {
        output.push((value >>> (bits - 8)) & 255);
        bits -= 8;
      }
    }

    return Buffer.from(output);
  }

  /**
   * 生成 TOTP 验证码
   */
  private generateTOTP(secret: string, counter: number): string {
    // 将 counter 转换为 8 字节大端序
    const counterBuffer = Buffer.alloc(8);
    for (let i = 7; i >= 0; i--) {
      counterBuffer[i] = counter & 0xff;
      counter = Math.floor(counter / 256);
    }

    // HMAC-SHA1
    const key = this.base32Decode(secret);
    const hmac = crypto.createHmac('sha1', key);
    hmac.update(counterBuffer);
    const hash = hmac.digest();

    // 动态截断
    const offset = hash[hash.length - 1] & 0x0f;
    const binary =
      ((hash[offset] & 0x7f) << 24) |
      ((hash[offset + 1] & 0xff) << 16) |
      ((hash[offset + 2] & 0xff) << 8) |
      (hash[offset + 3] & 0xff);

    // 取模生成验证码
    const otp = binary % Math.pow(10, this.TOTP_DIGITS);
    return otp.toString().padStart(this.TOTP_DIGITS, '0');
  }

  /**
   * AES 加密
   */
  private encrypt(text: string): string {
    const key = crypto.scryptSync(this.ENCRYPTION_KEY, 'salt', 32);
    const iv = crypto.randomBytes(16);
    const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    return iv.toString('hex') + ':' + encrypted;
  }

  /**
   * AES 解密
   */
  private decrypt(encryptedText: string): string {
    const [ivHex, encrypted] = encryptedText.split(':');
    const key = crypto.scryptSync(this.ENCRYPTION_KEY, 'salt', 32);
    const iv = Buffer.from(ivHex, 'hex');
    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    let decrypted = decipher.update(encrypted, 'hex', 'utf8');
    decrypted += decipher.final('utf8');
    return decrypted;
  }

  /**
   * 设置 TOTP (生成密钥)
   */
  async setupTotp(userId: bigint): Promise<{
    secret: string;
    qrCodeUrl: string;
    manualEntryKey: string;
  }> {
    this.logger.log(`设置 TOTP: userId=${userId}`);

    // 检查是否已有 TOTP 配置
    const existing = await this.prisma.userTotp.findUnique({
      where: { userId },
    });

    if (existing?.isEnabled) {
      throw new BadRequestException('TOTP 已启用，如需重置请先禁用');
    }

    // 生成新密钥
    const secret = this.generateSecret();
    const encryptedSecret = this.encrypt(secret);

    // 获取用户信息用于生成 QR 码
    const user = await this.prisma.userAccount.findUnique({
      where: { userId },
      select: { accountSequence: true, nickname: true },
    });

    const accountName = user?.accountSequence || `user_${userId}`;

    // 生成 otpauth URI
    const otpauthUrl = `otpauth://totp/${this.ISSUER}:${accountName}?secret=${secret}&issuer=${this.ISSUER}&algorithm=SHA1&digits=${this.TOTP_DIGITS}&period=${this.TOTP_PERIOD}`;

    // 保存或更新 TOTP 配置
    if (existing) {
      await this.prisma.userTotp.update({
        where: { userId },
        data: {
          encryptedSecret,
          isEnabled: false,
          isVerified: false,
        },
      });
    } else {
      await this.prisma.userTotp.create({
        data: {
          userId,
          encryptedSecret,
          isEnabled: false,
          isVerified: false,
        },
      });
    }

    this.logger.log(`TOTP 密钥已生成: userId=${userId}`);

    return {
      secret,
      qrCodeUrl: otpauthUrl,
      manualEntryKey: secret,
    };
  }

  /**
   * 验证并启用 TOTP
   */
  async enableTotp(userId: bigint, code: string): Promise<boolean> {
    this.logger.log(`启用 TOTP: userId=${userId}`);

    const totp = await this.prisma.userTotp.findUnique({
      where: { userId },
    });

    if (!totp) {
      throw new BadRequestException('请先设置 TOTP');
    }

    if (totp.isEnabled) {
      throw new BadRequestException('TOTP 已启用');
    }

    // 验证码验证
    const secret = this.decrypt(totp.encryptedSecret);
    const isValid = this.verifyCode(secret, code);

    if (!isValid) {
      throw new BadRequestException('验证码错误');
    }

    // 启用 TOTP
    await this.prisma.userTotp.update({
      where: { userId },
      data: {
        isEnabled: true,
        isVerified: true,
        enabledAt: new Date(),
      },
    });

    this.logger.log(`TOTP 已启用: userId=${userId}`);
    return true;
  }

  /**
   * 禁用 TOTP
   */
  async disableTotp(userId: bigint, code: string): Promise<boolean> {
    this.logger.log(`禁用 TOTP: userId=${userId}`);

    const totp = await this.prisma.userTotp.findUnique({
      where: { userId },
    });

    if (!totp || !totp.isEnabled) {
      throw new BadRequestException('TOTP 未启用');
    }

    // 验证码验证
    const secret = this.decrypt(totp.encryptedSecret);
    const isValid = this.verifyCode(secret, code);

    if (!isValid) {
      throw new BadRequestException('验证码错误');
    }

    // 禁用 TOTP
    await this.prisma.userTotp.update({
      where: { userId },
      data: {
        isEnabled: false,
        enabledAt: null,
      },
    });

    this.logger.log(`TOTP 已禁用: userId=${userId}`);
    return true;
  }

  /**
   * 验证 TOTP 码
   */
  async verifyTotp(userId: bigint, code: string): Promise<boolean> {
    this.logger.log(`验证 TOTP: userId=${userId}`);

    const totp = await this.prisma.userTotp.findUnique({
      where: { userId },
    });

    if (!totp || !totp.isEnabled) {
      // TOTP 未启用，跳过验证
      this.logger.log(`TOTP 未启用，跳过验证: userId=${userId}`);
      return true;
    }

    const secret = this.decrypt(totp.encryptedSecret);
    const isValid = this.verifyCode(secret, code);

    this.logger.log(`TOTP 验证结果: userId=${userId}, valid=${isValid}`);
    return isValid;
  }

  /**
   * 检查用户是否启用了 TOTP
   */
  async isTotpEnabled(userId: bigint): Promise<boolean> {
    const totp = await this.prisma.userTotp.findUnique({
      where: { userId },
      select: { isEnabled: true },
    });

    return totp?.isEnabled ?? false;
  }

  /**
   * 获取用户 TOTP 状态
   */
  async getTotpStatus(userId: bigint): Promise<{
    isEnabled: boolean;
    isSetup: boolean;
    enabledAt: Date | null;
  }> {
    const totp = await this.prisma.userTotp.findUnique({
      where: { userId },
    });

    return {
      isEnabled: totp?.isEnabled ?? false,
      isSetup: !!totp,
      enabledAt: totp?.enabledAt ?? null,
    };
  }

  /**
   * 验证验证码（允许时间窗口偏移）
   */
  private verifyCode(secret: string, code: string): boolean {
    const now = Math.floor(Date.now() / 1000);
    const counter = Math.floor(now / this.TOTP_PERIOD);

    // 检查当前和前后时间窗口
    for (let i = -this.TOTP_WINDOW; i <= this.TOTP_WINDOW; i++) {
      const expectedCode = this.generateTOTP(secret, counter + i);
      if (expectedCode === code) {
        return true;
      }
    }

    return false;
  }
}
