import { Inject, Logger } from '@nestjs/common';
import { CommandHandler, type EventBus, type ICommandHandler } from '@nestjs/cqrs';
import { createId } from '@paralleldrive/cuid2';
import { ConflictException, ValidationException } from '@modules/shared/domain/domain-exception';
import { ReviewEntity } from '../../../domain/entities/review.entity';
import { REVIEW_REPOSITORY, type IReviewRepository } from '../../../domain/repositories/review.repository';
import { Rating } from '../../../domain/value-objects/rating.vo';
import { CreateReviewCommand } from './create-review.command';

export interface CreateReviewResult {
  id: string;
  rating: number;
  targetType: string;
  targetId: string;
  createdAt: string;
}

@CommandHandler(CreateReviewCommand)
export class CreateReviewHandler implements ICommandHandler<CreateReviewCommand> {
  private readonly logger = new Logger(CreateReviewHandler.name);

  constructor(
    @Inject(REVIEW_REPOSITORY) private readonly reviewRepo: IReviewRepository,
    private readonly eventBus: EventBus,
  ) {}

  async execute(command: CreateReviewCommand): Promise<CreateReviewResult> {
    // Validate rating value object
    const ratingResult = Rating.create(command.rating);
    if (ratingResult.isErr) {
      throw new ValidationException(ratingResult.unwrapErr());
    }
    const rating = ratingResult.unwrap();

    // Prevent self-review
    if (command.userId === command.targetId && command.targetType === 'user') {
      throw new ValidationException('Không thể tự đánh giá bản thân');
    }

    // One review per user per target
    const existing = await this.reviewRepo.findByUserAndTarget(
      command.userId,
      command.targetType,
      command.targetId,
    );
    if (existing) {
      throw new ConflictException('Bạn đã đánh giá mục tiêu này rồi');
    }

    const id = createId();
    const review = ReviewEntity.createNew(
      id,
      command.userId,
      command.targetType,
      command.targetId,
      rating,
      command.comment,
    );

    await this.reviewRepo.save(review);

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

    this.logger.log(`Review ${id} created by user ${command.userId} for ${command.targetType}:${command.targetId}`);

    return {
      id,
      rating: rating.value,
      targetType: command.targetType,
      targetId: command.targetId,
      createdAt: review.createdAt.toISOString(),
    };
  }
}
