import { Injectable } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { LoggerService, PrismaService } from '@modules/shared';
import {
  type ISearchRepository,
  type ListingDocument,
  type SearchParams,
  type SearchResult,
} from '../../domain/repositories/search.repository';
import { buildSearchConditions, buildOrderClause } from './search-query-builder';
import { type RawListingRow, mapRowToListingDocument } from './search-result-mapper';

/**
 * PostgreSQL-backed search repository used as a fallback when Typesense
 * is unavailable.
 *
 * Capabilities:
 * - Full-text search via PostgreSQL `to_tsvector` / `plainto_tsquery`
 * - Geo radius filtering via PostGIS `ST_DWithin`
 * - Faceted filters (property type, transaction type, price range, area, etc.)
 *
 * Limitations compared to Typesense:
 * - No relevance-ranked highlighting
 * - Slower for large result sets
 * - Vietnamese language support depends on PG config (defaults to 'simple')
 */
@Injectable()
export class PostgresSearchRepository implements ISearchRepository {
  constructor(
    private readonly prisma: PrismaService,
    private readonly logger: LoggerService,
  ) {}

  /**
   * Search listings using PostgreSQL full-text search + PostGIS.
   */
  async search(params: SearchParams): Promise<SearchResult> {
    const startMs = Date.now();
    const page = params.page ?? 1;
    const perPage = params.perPage ?? 20;
    const offset = (page - 1) * perPage;

    const conditions = buildSearchConditions(params);
    const whereClause = Prisma.sql`WHERE ${Prisma.join(conditions, ' AND ')}`;
    const orderClause = buildOrderClause(params);

    const countResult = await this.prisma.$queryRaw<[{ count: bigint }]>(
      Prisma.sql`
        SELECT COUNT(*) as count
        FROM "Listing" l JOIN "Property" p ON l."propertyId" = p."id"
        ${whereClause}
      `,
    );
    const totalFound = Number(countResult[0]?.count ?? 0);

    const rows = await this.prisma.$queryRaw<RawListingRow[]>(
      Prisma.sql`
        SELECT
          l."id" AS "listingId", l."propertyId", p."title", p."description",
          p."propertyType", l."transactionType", l."priceVND", l."pricePerM2",
          p."areaM2", p."bedrooms", p."bathrooms", p."floors", p."direction",
          p."address", p."ward", p."district", p."city",
          ST_Y(p."location"::geometry) AS "lat",
          ST_X(p."location"::geometry) AS "lng",
          l."agentId", l."sellerId", l."status", l."publishedAt",
          l."viewCount", l."saveCount", p."projectName", p."legalStatus", p."amenities"
        FROM "Listing" l JOIN "Property" p ON l."propertyId" = p."id"
        ${whereClause}
        ${orderClause}
        LIMIT ${perPage} OFFSET ${offset}
      `,
    );

    return {
      hits: rows.map(mapRowToListingDocument),
      totalFound,
      page,
      perPage,
      totalPages: Math.ceil(totalFound / perPage),
      searchTimeMs: Date.now() - startMs,
    };
  }

  // ── Indexing operations are no-ops for the PG fallback ───────────────

  async indexDocument(_doc: ListingDocument): Promise<void> {
    // Data already lives in PostgreSQL — nothing to do.
  }

  async indexDocuments(_docs: ListingDocument[]): Promise<void> {
    // Data already lives in PostgreSQL — nothing to do.
  }

  async removeDocument(_id: string): Promise<void> {
    // No separate index to clean up.
  }

  async ensureCollection(): Promise<void> {
    // PostgreSQL tables/indexes are managed by Prisma migrations.
  }

  async dropCollection(): Promise<void> {
    // Not applicable for PostgreSQL fallback.
  }
}
