import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
import { DomainException, LoggerService, UnauthorizedException } from '@modules/shared';
import {
  MFA_CHALLENGE_REPOSITORY,
  type IMfaChallengeRepository,
} from '../../../domain/repositories/mfa-challenge.repository';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
import { MfaService } from '../../../infrastructure/services/mfa.service';
import { TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
import { UseBackupCodeCommand } from './use-backup-code.command';

@CommandHandler(UseBackupCodeCommand)
export class UseBackupCodeHandler implements ICommandHandler<UseBackupCodeCommand> {
  constructor(
    @Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
    @Inject(MFA_CHALLENGE_REPOSITORY) private readonly challengeRepo: IMfaChallengeRepository,
    private readonly mfaService: MfaService,
    private readonly tokenService: TokenService,
    private readonly logger: LoggerService,
  ) {}

  async execute(command: UseBackupCodeCommand): Promise<TokenPair & { remainingBackupCodes: number }> {
    try {
      // Find and validate the challenge
      const challenge = await this.challengeRepo.findById(command.challengeId);
      if (!challenge) {
        throw new UnauthorizedException('Phiên xác thực MFA không tồn tại hoặc đã hết hạn');
      }

      if (challenge.isVerified) {
        throw new UnauthorizedException('Phiên xác thực MFA đã được sử dụng');
      }

      if (challenge.expiresAt < new Date()) {
        throw new UnauthorizedException('Phiên xác thực MFA đã hết hạn');
      }

      if (challenge.attemptCount >= challenge.maxAttempts) {
        throw new UnauthorizedException('Đã vượt quá số lần thử. Vui lòng đăng nhập lại');
      }

      // Look up the user
      const user = await this.userRepo.findById(challenge.userId);
      if (!user || !user.totpEnabled) {
        throw new UnauthorizedException('MFA chưa được thiết lập cho tài khoản này');
      }

      // Verify backup code
      const codeIndex = this.mfaService.verifyBackupCode(
        command.backupCode,
        user.totpBackupCodes,
      );

      if (codeIndex === -1) {
        await this.challengeRepo.incrementAttempts(command.challengeId);
        const remaining = challenge.maxAttempts - challenge.attemptCount - 1;
        throw new UnauthorizedException(
          `Mã backup không hợp lệ. Còn ${remaining} lần thử`,
        );
      }

      // Consume the backup code (remove from array)
      const updatedCodes = user.totpBackupCodes.filter((_, i) => i !== codeIndex);
      await this.userRepo.updateBackupCodes(challenge.userId, updatedCodes);

      // Mark the challenge as verified
      await this.challengeRepo.markVerified(command.challengeId);

      // Generate token pair (login complete)
      const tokens = await this.tokenService.generateTokenPair({
        sub: user.id,
        phone: user.phone.value,
        role: user.role,
      });

      return {
        ...tokens,
        remainingBackupCodes: updatedCodes.length,
      };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to use backup code: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Không thể xác thực bằng mã backup');
    }
  }
}
