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 { PhoneChangedEvent } from '../../../domain/events/phone-changed.event';
import { type IUserRepository, USER_REPOSITORY } from '../../../domain/repositories/user.repository';
import { Phone } from '../../../domain/value-objects/phone.vo';
import { PHONE_CHANGE_OTP_PREFIX } from '../update-profile/update-profile.handler';
import { VerifyPhoneChangeCommand } from './verify-phone-change.command';

export interface VerifyPhoneChangeResultDto {
  id: string;
  phoneNumber: string;
  updatedAt: Date;
}

@CommandHandler(VerifyPhoneChangeCommand)
export class VerifyPhoneChangeHandler implements ICommandHandler<VerifyPhoneChangeCommand> {
  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: VerifyPhoneChangeCommand): Promise<VerifyPhoneChangeResultDto> {
    try {
      const redisKey = `${PHONE_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 số điện thoại lại.',
        );
      }

      const { newPhone, code } = JSON.parse(raw) as { newPhone: 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 phone uniqueness (may have been taken since the request)
      const existingUser = await this.userRepo.findByPhone(newPhone);
      if (existingUser && existingUser.id !== command.userId) {
        await this.redis.del(redisKey);
        throw new ConflictException('Số điện thoại đã được sử dụng bởi tài khoản khác');
      }

      const phoneVo = Phone.create(newPhone).unwrap();
      const oldPhone = user.phone.value;
      user.updatePhone(phoneVo);
      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 PhoneChangedEvent(command.userId, oldPhone, phoneVo.value));

      return {
        id: user.id,
        phoneNumber: phoneVo.value,
        updatedAt: user.updatedAt,
      };
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to verify phone 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 số điện thoại');
    }
  }
}
