import { ConflictException, Inject } from '@nestjs/common';
import { CommandHandler, type ICommandHandler } from '@nestjs/cqrs';
import { createId } from '@paralleldrive/cuid2';
import { PrismaService, ValidationException } from '@modules/shared';
import { USER_REPOSITORY, type IUserRepository } from '../../../../auth/domain/repositories/user.repository';
import { UserEntity } from '../../../../auth/domain/entities/user.entity';
import { Email } from '../../../../auth/domain/value-objects/email.vo';
import { HashedPassword } from '../../../../auth/domain/value-objects/hashed-password.vo';
import { Phone } from '../../../../auth/domain/value-objects/phone.vo';
import { ProvisionDeveloperCommand } from './provision-developer.command';

export interface ProvisionDeveloperResult {
  userId: string;
  phone: string;
  email: string | null;
  fullName: string;
  linkedProjectIds: string[];
}

@CommandHandler(ProvisionDeveloperCommand)
export class ProvisionDeveloperHandler
  implements ICommandHandler<ProvisionDeveloperCommand, ProvisionDeveloperResult>
{
  constructor(
    @Inject(USER_REPOSITORY) private readonly userRepo: IUserRepository,
    private readonly prisma: PrismaService,
  ) {}

  async execute(cmd: ProvisionDeveloperCommand): Promise<ProvisionDeveloperResult> {
    // Validate + hash auth fields.
    const phoneResult = Phone.create(cmd.phone);
    if (phoneResult.isErr) throw new ValidationException(phoneResult.unwrapErr());
    const phone = phoneResult.unwrap();

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

    const passwordResult = await HashedPassword.fromPlain(cmd.password);
    if (passwordResult.isErr) throw new ValidationException(passwordResult.unwrapErr());
    const passwordHash = passwordResult.unwrap();

    // Uniqueness.
    if (await this.userRepo.findByPhone(phone.value)) {
      throw new ConflictException('Số điện thoại đã được đăng ký');
    }
    if (email && (await this.userRepo.findByEmail(email.value))) {
      throw new ConflictException('Email đã được đăng ký');
    }

    // Pre-validate project ownership before creating the user — avoids
    // orphaning a user if any target project is already owned by someone else.
    if (cmd.projectIds.length > 0) {
      const rows = await this.prisma.projectDevelopment.findMany({
        where: { id: { in: cmd.projectIds } },
        select: { id: true, ownerId: true, name: true },
      });
      const byId = new Map(rows.map((r) => [r.id, r]));
      const missing = cmd.projectIds.filter((id) => !byId.has(id));
      if (missing.length > 0) {
        throw new ValidationException(
          `Không tìm thấy dự án: ${missing.join(', ')}`,
        );
      }
      const occupied = rows.filter((r) => r.ownerId && r.ownerId !== null);
      if (occupied.length > 0) {
        throw new ConflictException(
          `Các dự án đã có CĐT khác quản lý: ${occupied.map((r) => r.name).join(', ')}`,
        );
      }
    }

    // Create the user (role=DEVELOPER).
    const user = UserEntity.createNew(
      createId(),
      phone,
      cmd.fullName,
      passwordHash,
      email,
      'DEVELOPER',
    );
    await this.userRepo.save(user);

    // Link projects.
    if (cmd.projectIds.length > 0) {
      await this.prisma.projectDevelopment.updateMany({
        where: { id: { in: cmd.projectIds } },
        data: { ownerId: user.id },
      });
    }

    return {
      userId: user.id,
      phone: user.phone.value,
      email: user.email?.value ?? null,
      fullName: user.fullName,
      linkedProjectIds: cmd.projectIds,
    };
  }
}
