import { Inject, Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { LoggerService, PrismaService } from '@modules/shared';
import { Address } from '@modules/listings/domain/value-objects/address.vo';
import {
  SEARCH_REPOSITORY,
  type ISearchRepository,
  type ListingDocument,
} from '../../domain/repositories/search.repository';

/** Maps featuredPackage to a tier weight for sort boost: higher = more prominent */
function featuredTierWeight(pkg: string | null | undefined): number {
  switch (pkg) {
    case '30_days': return 3;
    case '7_days': return 2;
    case '3_days': return 1;
    default: return 1; // fallback for legacy rows with no package
  }
}

@Injectable()
export class ListingIndexerService {
  constructor(
    private readonly prisma: PrismaService,
    @Inject(SEARCH_REPOSITORY) private readonly searchRepo: ISearchRepository,
    private readonly logger: LoggerService,
  ) {}

  async indexListing(listingId: string): Promise<void> {
    const doc = await this.fetchListingDocumentById(listingId);

    if (!doc || doc.status !== 'ACTIVE') {
      this.logger.warn(`Listing ${listingId} not found or not active, skipping index`, 'ListingIndexer');
      return;
    }

    await this.searchRepo.indexDocument(doc);
    this.logger.log(`Indexed listing ${listingId}`, 'ListingIndexer');
  }

  async removeListing(listingId: string): Promise<void> {
    await this.searchRepo.removeDocument(listingId);
    this.logger.log(`Removed listing ${listingId} from index`, 'ListingIndexer');
  }

  async reindexAll(): Promise<{ indexed: number; total: number }> {
    this.logger.log('Starting full reindex...', 'ListingIndexer');

    await this.searchRepo.dropCollection();
    await this.searchRepo.ensureCollection();

    const batchSize = 100;
    let offset = 0;
    let indexed = 0;

    while (true) {
      const rows = await this.fetchListingsWithCoords(batchSize, offset);
      if (rows.length === 0) break;

      await this.searchRepo.indexDocuments(rows);
      indexed += rows.length;
      offset += batchSize;

      this.logger.log(`Reindex progress: ${indexed} documents`, 'ListingIndexer');
    }

    this.logger.log(`Full reindex complete: ${indexed} documents`, 'ListingIndexer');
    return { indexed, total: indexed };
  }

  private async fetchListingsWithCoords(
    limit: number,
    offset: number,
  ): Promise<ListingDocument[]> {
    const listings = await this.prisma.listing.findMany({
      where: { status: 'ACTIVE' },
      orderBy: { publishedAt: { sort: 'desc', nulls: 'last' } },
      skip: offset,
      take: limit,
      include: { property: true },
    });

    if (listings.length === 0) return [];

    const propertyIds = listings.map((l) => l.property.id);
    const coords = await this.prisma.$queryRaw<
      Array<{ id: string; lat: number | null; lng: number | null }>
    >(
      Prisma.sql`SELECT "id", ST_Y("location"::geometry) AS lat, ST_X("location"::geometry) AS lng FROM "Property" WHERE "id" IN (${Prisma.join(propertyIds)})`
    );
    const coordMap = new Map(coords.map((c) => [c.id, c]));

    return listings.map((l) => {
      const p = l.property;
      const c = coordMap.get(p.id);
      return {
        id: l.id,
        listingId: l.id,
        propertyId: p.id,
        title: p.title,
        description: p.description,
        propertyType: p.propertyType,
        transactionType: l.transactionType,
        priceVND: Number(l.priceVND),
        pricePerM2: l.pricePerM2,
        areaM2: p.areaM2,
        bedrooms: p.bedrooms,
        bathrooms: p.bathrooms,
        floors: p.floors,
        direction: p.direction,
        address: p.address,
        ward: p.ward,
        district: p.district,
        city: p.city,
        location: [c?.lat ?? 0, c?.lng ?? 0] as [number, number],
        agentId: l.agentId,
        sellerId: l.sellerId,
        status: l.status,
        publishedAt: l.publishedAt ? Math.floor(l.publishedAt.getTime() / 1000) : 0,
        viewCount: l.viewCount,
        saveCount: l.saveCount,
        projectName: p.projectName,
        legalStatus: p.legalStatus,
        amenities: Array.isArray(p.amenities) ? (p.amenities as string[]) : [],
        isFeatured: l.featuredUntil && l.featuredUntil > new Date()
          ? featuredTierWeight(l.featuredPackage as string | null)
          : 0,

        // Vietnamese diacritic-normalized fields
        titleNormalized: Address.normalize(p.title),
        descriptionNormalized: Address.normalize(p.description),
        addressNormalized: Address.normalize(p.address),
        wardNormalized: Address.normalize(p.ward),
        districtNormalized: Address.normalize(p.district),
        cityNormalized: Address.normalize(p.city),
        projectNameNormalized: p.projectName ? Address.normalize(p.projectName) : null,
      };
    });
  }

  async fetchListingDocumentById(listingId: string): Promise<ListingDocument | null> {
    const listing = await this.prisma.listing.findUnique({
      where: { id: listingId },
      include: { property: true },
    });

    if (!listing) return null;

    const p = listing.property;
    const coords = await this.prisma.$queryRaw<
      Array<{ lat: number | null; lng: number | null }>
    >(
      Prisma.sql`SELECT ST_Y("location"::geometry) AS lat, ST_X("location"::geometry) AS lng FROM "Property" WHERE "id" = ${p.id}`
    );
    const c = coords[0];

    return {
      id: listing.id,
      listingId: listing.id,
      propertyId: p.id,
      title: p.title,
      description: p.description,
      propertyType: p.propertyType,
      transactionType: listing.transactionType,
      priceVND: Number(listing.priceVND),
      pricePerM2: listing.pricePerM2,
      areaM2: p.areaM2,
      bedrooms: p.bedrooms,
      bathrooms: p.bathrooms,
      floors: p.floors,
      direction: p.direction,
      address: p.address,
      ward: p.ward,
      district: p.district,
      city: p.city,
      location: [c?.lat ?? 0, c?.lng ?? 0],
      agentId: listing.agentId,
      sellerId: listing.sellerId,
      status: listing.status,
      publishedAt: listing.publishedAt ? Math.floor(listing.publishedAt.getTime() / 1000) : 0,
      viewCount: listing.viewCount,
      saveCount: listing.saveCount,
      projectName: p.projectName,
      legalStatus: p.legalStatus,
      amenities: Array.isArray(p.amenities) ? (p.amenities as string[]) : [],
      isFeatured: listing.featuredUntil && listing.featuredUntil > new Date()
        ? featuredTierWeight(listing.featuredPackage as string | null)
        : 0,

      // Vietnamese diacritic-normalized fields
      titleNormalized: Address.normalize(p.title),
      descriptionNormalized: Address.normalize(p.description),
      addressNormalized: Address.normalize(p.address),
      wardNormalized: Address.normalize(p.ward),
      districtNormalized: Address.normalize(p.district),
      cityNormalized: Address.normalize(p.city),
      projectNameNormalized: p.projectName ? Address.normalize(p.projectName) : null,
    };
  }

}
