import { Injectable } from '@nestjs/common';
import { type Review as PrismaReview } from '@prisma/client';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports -- NestJS DI requires value imports for emitDecoratorMetadata
import { PrismaService } from '@modules/shared';
import { ReviewEntity } from '../../domain/entities/review.entity';
import { type ReviewItemData, type ReviewStatsData } from '../../domain/repositories/review-read.dto';
import { type IReviewRepository, type PaginatedResult } from '../../domain/repositories/review.repository';
import { Rating } from '../../domain/value-objects/rating.vo';

@Injectable()
export class PrismaReviewRepository implements IReviewRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findById(id: string): Promise<ReviewEntity | null> {
    const review = await this.prisma.review.findUnique({ where: { id } });
    return review ? this.toDomain(review) : null;
  }

  async findByUserAndTarget(
    userId: string,
    targetType: string,
    targetId: string,
  ): Promise<ReviewEntity | null> {
    const review = await this.prisma.review.findFirst({
      where: { userId, targetType, targetId },
    });
    return review ? this.toDomain(review) : null;
  }

  async save(entity: ReviewEntity): Promise<void> {
    await this.prisma.review.create({
      data: {
        id: entity.id,
        userId: entity.userId,
        targetType: entity.targetType,
        targetId: entity.targetId,
        rating: entity.rating.value,
        comment: entity.comment,
      },
    });
  }

  async delete(id: string): Promise<void> {
    await this.prisma.review.delete({ where: { id } });
  }

  async findByTarget(
    targetType: string,
    targetId: string,
    page: number,
    limit: number,
  ): Promise<PaginatedResult<ReviewItemData>> {
    const take = Math.min(limit, 100);
    const skip = (page - 1) * take;
    const where = { targetType, targetId };

    const [data, total] = await Promise.all([
      this.prisma.review.findMany({
        where,
        skip,
        take,
        orderBy: { createdAt: 'desc' },
        include: { user: { select: { id: true, fullName: true } } },
      }),
      this.prisma.review.count({ where }),
    ]);

    return {
      data: data.map((r) => ({
        id: r.id,
        userId: r.userId,
        userName: r.user.fullName,
        targetType: r.targetType,
        targetId: r.targetId,
        rating: r.rating,
        comment: r.comment,
        createdAt: r.createdAt.toISOString(),
      })),
      total,
      page,
      limit: take,
      totalPages: Math.ceil(total / take),
    };
  }

  async findByUserId(
    userId: string,
    page: number,
    limit: number,
  ): Promise<PaginatedResult<ReviewItemData>> {
    const take = Math.min(limit, 100);
    const skip = (page - 1) * take;
    const where = { userId };

    const [data, total] = await Promise.all([
      this.prisma.review.findMany({
        where,
        skip,
        take,
        orderBy: { createdAt: 'desc' },
        include: { user: { select: { id: true, fullName: true } } },
      }),
      this.prisma.review.count({ where }),
    ]);

    return {
      data: data.map((r) => ({
        id: r.id,
        userId: r.userId,
        userName: r.user.fullName,
        targetType: r.targetType,
        targetId: r.targetId,
        rating: r.rating,
        comment: r.comment,
        createdAt: r.createdAt.toISOString(),
      })),
      total,
      page,
      limit: take,
      totalPages: Math.ceil(total / take),
    };
  }

  async getStats(targetType: string, targetId: string): Promise<ReviewStatsData> {
    const reviews = await this.prisma.review.findMany({
      where: { targetType, targetId },
      select: { rating: true },
    });

    const totalReviews = reviews.length;
    const distribution: Record<number, number> = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 };
    let sum = 0;

    for (const r of reviews) {
      sum += r.rating;
      distribution[r.rating] = (distribution[r.rating] ?? 0) + 1;
    }

    return {
      targetType,
      targetId,
      averageRating: totalReviews > 0 ? Math.round((sum / totalReviews) * 10) / 10 : 0,
      totalReviews,
      distribution,
    };
  }

  private toDomain(raw: PrismaReview): ReviewEntity {
    const rating = Rating.create(raw.rating).unwrap();
    return new ReviewEntity(
      raw.id,
      {
        userId: raw.userId,
        targetType: raw.targetType,
        targetId: raw.targetId,
        rating,
        comment: raw.comment,
      },
      raw.createdAt,
    );
  }
}
