import { Inject, InternalServerErrorException } from '@nestjs/common';
import { CommandHandler, EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { createId } from '@paralleldrive/cuid2';
import { ConflictException, DomainException, LoggerService, ValidationException } from '@modules/shared';
import { UserEntity } from '../../../domain/entities/user.entity';
import { USER_REPOSITORY, type IUserRepository } from '../../../domain/repositories/user.repository';
import { Email } from '../../../domain/value-objects/email.vo';
import { HashedPassword } from '../../../domain/value-objects/hashed-password.vo';
import { Phone } from '../../../domain/value-objects/phone.vo';
import { TokenService, type TokenPair } from '../../../infrastructure/services/token.service';
import { RegisterUserCommand } from './register-user.command';

@CommandHandler(RegisterUserCommand)
export class RegisterUserHandler implements ICommandHandler<RegisterUserCommand> {
  constructor(
    @Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
    private readonly tokenService: TokenService,
    private readonly eventBus: EventBus,
    private readonly logger: LoggerService,
  ) {}

  async execute(command: RegisterUserCommand): Promise<TokenPair> {
    try {
      // Validate phone
      const phoneResult = Phone.create(command.phone);
      if (phoneResult.isErr) {
        throw new ValidationException(phoneResult.unwrapErr());
      }
      const phone = phoneResult.unwrap();

      // Check duplicate phone
      const existingByPhone = await this.userRepo.findByPhone(phone.value);
      if (existingByPhone) {
        throw new ConflictException('Số điện thoại đã được đăng ký');
      }

      // Validate email if provided
      let email: Email | undefined;
      if (command.email) {
        const emailResult = Email.create(command.email);
        if (emailResult.isErr) {
          throw new ValidationException(emailResult.unwrapErr());
        }
        email = emailResult.unwrap();

        const existingByEmail = await this.userRepo.findByEmail(email.value);
        if (existingByEmail) {
          throw new ConflictException('Email đã được đăng ký');
        }
      }

      // Hash password
      const passwordResult = await HashedPassword.fromPlain(command.password);
      if (passwordResult.isErr) {
        throw new ValidationException(passwordResult.unwrapErr());
      }
      const passwordHash = passwordResult.unwrap();

      // Create user entity
      const userId = createId();
      const user = UserEntity.createNew(userId, phone, command.fullName, passwordHash, email);

      // Persist
      await this.userRepo.save(user);

      // Publish domain events
      const events = user.clearDomainEvents();
      for (const event of events) {
        this.eventBus.publish(event);
      }

      // Generate tokens
      return this.tokenService.generateTokenPair({
        sub: user.id,
        phone: user.phone.value,
        role: user.role,
      });
    } catch (error) {
      if (error instanceof DomainException) throw error;
      this.logger.error(
        `Failed to register user: ${error instanceof Error ? error.message : error}`,
        error instanceof Error ? error.stack : undefined,
        this.constructor.name,
      );
      throw new InternalServerErrorException('Không thể đăng ký tài khoản người dùng');
    }
  }
}
