import { Injectable } from '@nestjs/common';
import { type Listing as PrismaListing, type ListingStatus } from '@prisma/client';
import { PrismaService } from '@modules/shared';
import { ListingEntity, type ListingProps } from '../../domain/entities/listing.entity';
import { type ListingDetailData, type ListingSearchItem, type ListingSellerItem, type ListingSimilarItem } from '../../domain/repositories/listing-read.dto';
import { type IListingRepository, type ListingSearchParams, type PaginatedResult } from '../../domain/repositories/listing.repository';
import { Price } from '../../domain/value-objects/price.vo';
import { findByIdWithProperty, searchListings, findBySellerIdQuery, findSimilarListingsQuery } from './listing-read.queries';

@Injectable()
export class PrismaListingRepository implements IListingRepository {
  constructor(private readonly prisma: PrismaService) {}

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

  async findByIdWithProperty(id: string): Promise<ListingDetailData | null> {
    return findByIdWithProperty(this.prisma, id);
  }

  async save(entity: ListingEntity): Promise<void> {
    await this.prisma.listing.create({
      data: {
        id: entity.id,
        propertyId: entity.propertyId,
        agentId: entity.agentId,
        sellerId: entity.sellerId,
        transactionType: entity.transactionType,
        status: entity.status,
        priceVND: entity.price.amountVND,
        pricePerM2: entity.pricePerM2,
        rentPriceMonthly: entity.rentPriceMonthly,
        commissionPct: entity.commissionPct,
        aiPriceEstimate: entity.aiPriceEstimate,
        aiConfidence: entity.aiConfidence,
        moderationScore: entity.moderationScore,
        moderationNotes: entity.moderationNotes,
        viewCount: entity.viewCount,
        saveCount: entity.saveCount,
        inquiryCount: entity.inquiryCount,
        featuredUntil: entity.featuredUntil,
        expiresAt: entity.expiresAt,
        publishedAt: entity.publishedAt,
      },
    });
  }

  async delete(id: string): Promise<void> {
    // Listing has reverse relations without `onDelete: Cascade` in the schema
    // (Inquiry, SavedListing, PriceHistory, Order, Transaction). Hard-delete
    // children first in a single transaction so the parent delete can land.
    // Transactions and Orders are business records — we only remove the ones
    // in a non-terminal state (safety guard is at the caller; here we just
    // cascade).
    await this.prisma.$transaction([
      this.prisma.savedListing.deleteMany({ where: { listingId: id } }),
      this.prisma.inquiry.deleteMany({ where: { listingId: id } }),
      this.prisma.priceHistory.deleteMany({ where: { listingId: id } }),
      this.prisma.order.deleteMany({ where: { listingId: id } }),
      this.prisma.transaction.deleteMany({ where: { listingId: id } }),
      this.prisma.listing.delete({ where: { id } }),
    ]);
  }

  async update(entity: ListingEntity): Promise<void> {
    await this.prisma.listing.update({
      where: { id: entity.id },
      data: {
        status: entity.status,
        priceVND: entity.price.amountVND,
        pricePerM2: entity.pricePerM2,
        rentPriceMonthly: entity.rentPriceMonthly,
        commissionPct: entity.commissionPct,
        moderationScore: entity.moderationScore,
        moderationNotes: entity.moderationNotes,
        viewCount: entity.viewCount,
        saveCount: entity.saveCount,
        inquiryCount: entity.inquiryCount,
        featuredUntil: entity.featuredUntil,
        expiresAt: entity.expiresAt,
        publishedAt: entity.publishedAt,
      },
    });
  }

  async search(params: ListingSearchParams): Promise<PaginatedResult<ListingSearchItem>> {
    return searchListings(this.prisma, params);
  }

  async findByStatus(status: ListingStatus, page: number, limit: number): Promise<PaginatedResult<ListingSearchItem>> {
    return this.search({ status, page, limit });
  }

  async findBySellerId(sellerId: string, page: number, limit: number): Promise<PaginatedResult<ListingSellerItem>> {
    return findBySellerIdQuery(this.prisma, sellerId, page, limit);
  }

  async findSimilar(id: string, limit: number): Promise<ListingSimilarItem[]> {
    return findSimilarListingsQuery(this.prisma, id, limit);
  }

  private toDomain(raw: PrismaListing): ListingEntity {
    const price = Price.create(raw.priceVND).unwrap();

    const props: ListingProps = {
      propertyId: raw.propertyId,
      agentId: raw.agentId,
      sellerId: raw.sellerId,
      transactionType: raw.transactionType,
      status: raw.status,
      price,
      pricePerM2: raw.pricePerM2,
      rentPriceMonthly: raw.rentPriceMonthly,
      commissionPct: raw.commissionPct,
      aiPriceEstimate: raw.aiPriceEstimate,
      aiConfidence: raw.aiConfidence,
      moderationScore: raw.moderationScore,
      moderationNotes: raw.moderationNotes,
      viewCount: raw.viewCount,
      saveCount: raw.saveCount,
      inquiryCount: raw.inquiryCount,
      featuredUntil: raw.featuredUntil,
      expiresAt: raw.expiresAt,
      publishedAt: raw.publishedAt,
    };

    return new ListingEntity(raw.id, props, raw.createdAt, raw.updatedAt);
  }
}
