import { createHmac, randomBytes } from 'crypto';
import { Injectable, Logger } from '@nestjs/common';
import { generateSecret, generateURI, verify } from 'otplib';
import * as QRCode from 'qrcode';

const TOTP_ISSUER = 'GoodGo Platform';
const BACKUP_CODE_COUNT = 10;
const BACKUP_CODE_LENGTH = 8;
const TOTP_EPOCH_TOLERANCE = 30; // 1-step clock skew (30 seconds)

export interface MfaSetupResult {
  secret: string;
  otpauthUrl: string;
  qrCodeDataUrl: string;
}

export interface BackupCodeResult {
  codes: string[];
  count: number;
}

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

  /**
   * Generate a new TOTP secret and QR code for setup.
   */
  async generateSetup(userIdentifier: string): Promise<MfaSetupResult> {
    const secret = generateSecret();
    const otpauthUrl = generateURI({
      issuer: TOTP_ISSUER,
      label: userIdentifier,
      secret,
      algorithm: 'sha1',
      digits: 6,
      period: 30,
    });
    const qrCodeDataUrl = await QRCode.toDataURL(otpauthUrl);

    return { secret, otpauthUrl, qrCodeDataUrl };
  }

  /**
   * Verify a TOTP code against a secret.
   * Returns true if valid within the configured window.
   */
  async verifyTotp(token: string, secret: string): Promise<boolean> {
    try {
      const result = await verify({
        secret,
        token,
        epochTolerance: TOTP_EPOCH_TOLERANCE,
      });
      return result.valid;
    } catch (error) {
      this.logger.warn(
        `TOTP verification error: ${error instanceof Error ? error.message : error}`,
      );
      return false;
    }
  }

  /**
   * Generate backup codes.
   * Returns plaintext codes (to show to user) and hashed versions (to store).
   */
  generateBackupCodes(): { plainCodes: string[]; hashedCodes: string[] } {
    const plainCodes: string[] = [];
    const hashedCodes: string[] = [];

    for (let i = 0; i < BACKUP_CODE_COUNT; i++) {
      const code = this.generateReadableCode(BACKUP_CODE_LENGTH);
      plainCodes.push(code);
      hashedCodes.push(this.hashBackupCode(code));
    }

    return { plainCodes, hashedCodes };
  }

  /**
   * Verify a backup code against a list of hashed codes.
   * Returns the index of the matching code, or -1 if not found.
   */
  verifyBackupCode(code: string, hashedCodes: string[]): number {
    const normalizedCode = code.replace(/[\s-]/g, '').toUpperCase();
    const hashedInput = this.hashBackupCode(normalizedCode);

    for (let i = 0; i < hashedCodes.length; i++) {
      if (hashedCodes[i] === hashedInput) {
        return i;
      }
    }
    return -1;
  }

  /**
   * Generate a human-readable alphanumeric code (excluding ambiguous characters).
   */
  private generateReadableCode(length: number): string {
    const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // No 0, O, I, 1
    const bytes = randomBytes(length);
    let result = '';
    for (let i = 0; i < length; i++) {
      result += chars[bytes[i]! % chars.length];
    }
    return result;
  }

  /**
   * Hash a backup code using HMAC-SHA256.
   * Uses a fixed key derived from the app secret for consistent hashing.
   */
  private hashBackupCode(code: string): string {
    const secret = process.env['MFA_BACKUP_CODE_SECRET'] || process.env['JWT_SECRET'] || 'goodgo-mfa-backup-default';
    return createHmac('sha256', secret).update(code).digest('hex');
  }
}
