import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
import {
  CachePrefix,
  CacheService,
  ConflictException,
  DomainException,
  LoggerService,
  NotFoundException,
  RedisService,
  ValidationException,
} from '@modules/shared';
import { EmailChangedEvent } from '../../../domain/events/email-changed.event';
import { type IUserRepository, USER_REPOSITORY } from '../../../domain/repositories/user.repository';
import { Email } from '../../../domain/value-objects/email.vo';
import { EMAIL_CHANGE_OTP_PREFIX } from '../update-profile/update-profile.handler';
import { VerifyEmailChangeCommand } from './verify-email-change.command';

export interface VerifyEmailChangeResultDto {
  id: string;
  email: string;
  updatedAt: Date;
}

@CommandHandler(VerifyEmailChangeCommand)
export class VerifyEmailChangeHandler implements ICommandHandler<VerifyEmailChangeCommand> {
  constructor(
    @Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
    private readonly redis: RedisService,
    private readonly cache: CacheService,
    private readonly eventBus: EventBus,
    private readonly logger: LoggerService,
  ) {}

  async execute(command: VerifyEmailChangeCommand): Promise<VerifyEmailChangeResultDto> {
    try {
      const redisKey = `${EMAIL_CHANGE_OTP_PREFIX}:${command.userId}`;
      const raw = await this.redis.get(redisKey);

      if (!raw) {
        throw new ValidationException(
          'Mã xác thực đã hết hạn hoặc không tồn tại. Vui lòng yêu cầu đổi email lại.',
        );
      }

      const { newEmail, code } = JSON.parse(raw) as { newEmail: string; code: string };

      if (code !== command.code) {
        throw new ValidationException('Mã xác thực không đúng');
      }

      const user = await this.userRepo.findById(command.userId);
      if (!user) {
        throw new NotFoundException('Người dùng', command.userId);
      }

      // Re-check email uniqueness (may have been taken since the request)
      const existingUser = await this.userRepo.findByEmail(newEmail);
      if (existingUser && existingUser.id !== command.userId) {
        await this.redis.del(redisKey);
        throw new ConflictException('Email đã được sử dụng bởi tài khoản khác');
      }

      const emailVo = Email.create(newEmail).unwrap();
      const oldEmail = user.email?.value ?? null;
      user.updateProfile(undefined, undefined, emailVo);
      await this.userRepo.update(user);

      // Clean up OTP and invalidate profile cache
      await this.redis.del(redisKey);
      await this.cache.invalidate(
        CacheService.buildKey(CachePrefix.USER_PROFILE, command.userId),
      );

      // Emit event for audit log
      this.eventBus.publish(new EmailChangedEvent(command.userId, oldEmail, emailVo.value));

      return {
        id: user.id,
        email: emailVo.value,
        updatedAt: user.updatedAt,
      };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to verify email change: ${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 đổi email');
    }
  }
}
