import { Injectable, type OnModuleInit } from '@nestjs/common';
import { type Client as TypesenseClient } from 'typesense';
import { type CollectionCreateSchema } from 'typesense/lib/Typesense/Collections';
import { TypesenseClientService } from '@modules/search';
import { LoggerService, PrismaService } from '@modules/shared';

export const INDUSTRIAL_PARKS_COLLECTION = 'industrial_parks';
export const INDUSTRIAL_LISTINGS_COLLECTION = 'industrial_listings';

const PARKS_SCHEMA: CollectionCreateSchema = {
  name: INDUSTRIAL_PARKS_COLLECTION,
  fields: [
    { name: 'parkId', type: 'string', facet: false },
    { name: 'name', type: 'string', facet: false },
    { name: 'nameEn', type: 'string', facet: false, optional: true },
    { name: 'slug', type: 'string', facet: false },
    { name: 'developer', type: 'string', facet: true },
    { name: 'operator', type: 'string', facet: true, optional: true },
    { name: 'status', type: 'string', facet: true },
    { name: 'province', type: 'string', facet: true },
    { name: 'region', type: 'string', facet: true },
    { name: 'totalAreaHa', type: 'float', facet: false },
    { name: 'leasableAreaHa', type: 'float', facet: false },
    { name: 'remainingAreaHa', type: 'float', facet: false },
    { name: 'occupancyRate', type: 'float', facet: false },
    { name: 'tenantCount', type: 'int32', facet: false },
    { name: 'landRentUsdM2Year', type: 'float', facet: false, optional: true },
    { name: 'rbfRentUsdM2Month', type: 'float', facet: false, optional: true },
    { name: 'rbwRentUsdM2Month', type: 'float', facet: false, optional: true },
    { name: 'targetIndustries', type: 'string[]', facet: true },
    { name: 'hasReadyBuilt', type: 'bool', facet: true },
    { name: 'location', type: 'geopoint', facet: false },
    { name: 'isVerified', type: 'bool', facet: true },
    { name: 'createdAt', type: 'int64', facet: false },
  ],
  token_separators: ['-', '_'],
  enable_nested_fields: false,
};

const LISTINGS_SCHEMA: CollectionCreateSchema = {
  name: INDUSTRIAL_LISTINGS_COLLECTION,
  fields: [
    { name: 'listingId', type: 'string', facet: false },
    { name: 'title', type: 'string', facet: false },
    { name: 'description', type: 'string', facet: false, optional: true },
    { name: 'parkName', type: 'string', facet: true },
    { name: 'parkId', type: 'string', facet: true },
    { name: 'propertyType', type: 'string', facet: true },
    { name: 'leaseType', type: 'string', facet: true },
    { name: 'province', type: 'string', facet: true },
    { name: 'region', type: 'string', facet: true },
    { name: 'areaM2', type: 'float', facet: false },
    { name: 'priceUsdM2', type: 'float', facet: false, optional: true },
    { name: 'ceilingHeightM', type: 'float', facet: false, optional: true },
    { name: 'floorLoadTonM2', type: 'float', facet: false, optional: true },
    { name: 'targetIndustries', type: 'string[]', facet: true },
    { name: 'location', type: 'geopoint', facet: false },
    { name: 'occupancyRate', type: 'float', facet: false },
    { name: 'status', type: 'string', facet: true },
    { name: 'publishedAt', type: 'int64', facet: false, optional: true },
  ],
  token_separators: ['-', '_'],
  enable_nested_fields: false,
};

interface RawIndustrialPark {
  id: string;
  name: string;
  nameEn: string | null;
  slug: string;
  developer: string;
  operator: string | null;
  status: string;
  province: string;
  region: string;
  totalAreaHa: number;
  leasableAreaHa: number;
  remainingAreaHa: number;
  occupancyRate: number;
  tenantCount: number;
  landRentUsdM2Year: number | null;
  rbfRentUsdM2Month: number | null;
  rbwRentUsdM2Month: number | null;
  targetIndustries: string[] | null;
  isVerified: boolean;
  lat: number;
  lng: number;
  createdAt: Date;
}

interface RawIndustrialListing {
  id: string;
  title: string;
  description: string | null;
  parkId: string;
  parkName: string;
  propertyType: string;
  leaseType: string;
  province: string;
  region: string;
  areaM2: number;
  priceUsdM2: number | null;
  ceilingHeightM: number | null;
  floorLoadTonM2: number | null;
  targetIndustries: string[] | null;
  lat: number;
  lng: number;
  occupancyRate: number;
  status: string;
  publishedAt: Date | null;
}

@Injectable()
export class TypesenseIndustrialService implements OnModuleInit {
  private client: TypesenseClient | null = null;

  constructor(
    private readonly typesenseClient: TypesenseClientService,
    private readonly prisma: PrismaService,
    private readonly logger: LoggerService,
  ) {}

  async onModuleInit(): Promise<void> {
    try {
      this.client = this.typesenseClient.getClient();
      await this.ensureCollections();
      await this.syncParks();
      await this.syncListings();
    } catch (err) {
      this.logger.warn(`Typesense industrial init failed (non-fatal): ${err}`, 'TypesenseIndustrial');
    }
  }

  private async ensureCollections(): Promise<void> {
    if (!this.client) return;

    for (const schema of [PARKS_SCHEMA, LISTINGS_SCHEMA]) {
      try {
        await this.client.collections(schema.name).retrieve();
        this.logger.log(`Collection "${schema.name}" exists`, 'TypesenseIndustrial');
      } catch {
        await this.client.collections().create(schema);
        this.logger.log(`Collection "${schema.name}" created`, 'TypesenseIndustrial');
      }
    }
  }

  async syncParks(): Promise<void> {
    if (!this.client) return;

    const parks = await this.prisma.$queryRaw<RawIndustrialPark[]>`
      SELECT id, name, "nameEn", slug, developer, operator, status::text,
        province, region::text, "totalAreaHa", "leasableAreaHa", "remainingAreaHa",
        "occupancyRate", "tenantCount", "landRentUsdM2Year", "rbfRentUsdM2Month",
        "rbwRentUsdM2Month", "targetIndustries", "isVerified",
        ST_Y(location::geometry) as lat, ST_X(location::geometry) as lng,
        "createdAt"
      FROM "IndustrialPark"
    `;

    if (parks.length === 0) return;

    const docs = parks.map((p) => ({
      id: p.id,
      parkId: p.id,
      name: p.name,
      nameEn: p.nameEn ?? undefined,
      slug: p.slug,
      developer: p.developer,
      operator: p.operator ?? undefined,
      status: p.status.toLowerCase(),
      province: p.province,
      region: p.region.toLowerCase(),
      totalAreaHa: p.totalAreaHa,
      leasableAreaHa: p.leasableAreaHa,
      remainingAreaHa: p.remainingAreaHa,
      occupancyRate: p.occupancyRate,
      tenantCount: p.tenantCount,
      landRentUsdM2Year: p.landRentUsdM2Year ?? undefined,
      rbfRentUsdM2Month: p.rbfRentUsdM2Month ?? undefined,
      rbwRentUsdM2Month: p.rbwRentUsdM2Month ?? undefined,
      targetIndustries: p.targetIndustries ?? [],
      hasReadyBuilt: (p.rbfRentUsdM2Month ?? 0) > 0 || (p.rbwRentUsdM2Month ?? 0) > 0,
      location: [Number(p.lat), Number(p.lng)],
      isVerified: p.isVerified,
      createdAt: Math.floor(p.createdAt.getTime() / 1000),
    }));

    try {
      const jsonl = docs.map((d) => JSON.stringify(d)).join('\n');
      await this.client.collections(INDUSTRIAL_PARKS_COLLECTION).documents().import(jsonl, { action: 'upsert' });
      this.logger.log(`Synced ${docs.length} parks to Typesense`, 'TypesenseIndustrial');
    } catch (err) {
      this.logger.warn(`Park sync error: ${err}`, 'TypesenseIndustrial');
    }
  }

  async syncListings(): Promise<void> {
    if (!this.client) return;

    const listings = await this.prisma.$queryRaw<RawIndustrialListing[]>`
      SELECT l.id, l.title, l.description, l."parkId", p.name as "parkName",
        l."propertyType"::text, l."leaseType"::text, p.province, p.region::text,
        l."areaM2", l."priceUsdM2", l."ceilingHeightM", l."floorLoadTonM2",
        p."targetIndustries",
        ST_Y(p.location::geometry) as lat, ST_X(p.location::geometry) as lng,
        p."occupancyRate", l.status::text, l."publishedAt"
      FROM "IndustrialListing" l
      JOIN "IndustrialPark" p ON p.id = l."parkId"
      WHERE l.status != 'EXPIRED'
    `;

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

    const docs = listings.map((l) => ({
      id: l.id,
      listingId: l.id,
      title: l.title,
      description: l.description ?? undefined,
      parkName: l.parkName,
      parkId: l.parkId,
      propertyType: l.propertyType.toLowerCase(),
      leaseType: l.leaseType.toLowerCase(),
      province: l.province,
      region: l.region.toLowerCase(),
      areaM2: l.areaM2,
      priceUsdM2: l.priceUsdM2 ?? undefined,
      ceilingHeightM: l.ceilingHeightM ?? undefined,
      floorLoadTonM2: l.floorLoadTonM2 ?? undefined,
      targetIndustries: l.targetIndustries ?? [],
      location: [Number(l.lat), Number(l.lng)],
      occupancyRate: l.occupancyRate,
      status: l.status.toLowerCase(),
      publishedAt: l.publishedAt ? Math.floor(l.publishedAt.getTime() / 1000) : undefined,
    }));

    try {
      const jsonl = docs.map((d) => JSON.stringify(d)).join('\n');
      await this.client.collections(INDUSTRIAL_LISTINGS_COLLECTION).documents().import(jsonl, { action: 'upsert' });
      this.logger.log(`Synced ${docs.length} listings to Typesense`, 'TypesenseIndustrial');
    } catch (err) {
      this.logger.warn(`Listing sync error: ${err}`, 'TypesenseIndustrial');
    }
  }

  async indexListing(listingId: string): Promise<void> {
    if (!this.client) return;

    const [listing] = await this.prisma.$queryRaw<RawIndustrialListing[]>`
      SELECT l.id, l.title, l.description, l."parkId", p.name as "parkName",
        l."propertyType"::text, l."leaseType"::text, p.province, p.region::text,
        l."areaM2", l."priceUsdM2", l."ceilingHeightM", l."floorLoadTonM2",
        p."targetIndustries",
        ST_Y(p.location::geometry) as lat, ST_X(p.location::geometry) as lng,
        p."occupancyRate", l.status::text, l."publishedAt"
      FROM "IndustrialListing" l
      JOIN "IndustrialPark" p ON p.id = l."parkId"
      WHERE l.id = ${listingId}
    `;

    if (!listing) return;

    const doc = {
      id: listing.id,
      listingId: listing.id,
      title: listing.title,
      description: listing.description ?? undefined,
      parkName: listing.parkName,
      parkId: listing.parkId,
      propertyType: listing.propertyType.toLowerCase(),
      leaseType: listing.leaseType.toLowerCase(),
      province: listing.province,
      region: listing.region.toLowerCase(),
      areaM2: listing.areaM2,
      priceUsdM2: listing.priceUsdM2 ?? undefined,
      ceilingHeightM: listing.ceilingHeightM ?? undefined,
      floorLoadTonM2: listing.floorLoadTonM2 ?? undefined,
      targetIndustries: listing.targetIndustries ?? [],
      location: [Number(listing.lat), Number(listing.lng)],
      occupancyRate: listing.occupancyRate,
      status: listing.status.toLowerCase(),
      publishedAt: listing.publishedAt ? Math.floor(listing.publishedAt.getTime() / 1000) : undefined,
    };

    await this.client.collections(INDUSTRIAL_LISTINGS_COLLECTION).documents().upsert(doc);
  }

  async deleteListing(listingId: string): Promise<void> {
    if (!this.client) return;
    try {
      await this.client.collections(INDUSTRIAL_LISTINGS_COLLECTION).documents(listingId).delete();
    } catch {
      // Document may not exist in Typesense
    }
  }

  async indexPark(parkId: string): Promise<void> {
    if (!this.client) return;

    const [park] = await this.prisma.$queryRaw<RawIndustrialPark[]>`
      SELECT id, name, "nameEn", slug, developer, operator, status::text,
        province, region::text, "totalAreaHa", "leasableAreaHa", "remainingAreaHa",
        "occupancyRate", "tenantCount", "landRentUsdM2Year", "rbfRentUsdM2Month",
        "rbwRentUsdM2Month", "targetIndustries", "isVerified",
        ST_Y(location::geometry) as lat, ST_X(location::geometry) as lng,
        "createdAt"
      FROM "IndustrialPark" WHERE id = ${parkId}
    `;

    if (!park) return;

    const doc = {
      id: park.id,
      parkId: park.id,
      name: park.name,
      nameEn: park.nameEn ?? undefined,
      slug: park.slug,
      developer: park.developer,
      operator: park.operator ?? undefined,
      status: park.status.toLowerCase(),
      province: park.province,
      region: park.region.toLowerCase(),
      totalAreaHa: park.totalAreaHa,
      leasableAreaHa: park.leasableAreaHa,
      remainingAreaHa: park.remainingAreaHa,
      occupancyRate: park.occupancyRate,
      tenantCount: park.tenantCount,
      landRentUsdM2Year: park.landRentUsdM2Year ?? undefined,
      rbfRentUsdM2Month: park.rbfRentUsdM2Month ?? undefined,
      rbwRentUsdM2Month: park.rbwRentUsdM2Month ?? undefined,
      targetIndustries: park.targetIndustries ?? [],
      hasReadyBuilt: (park.rbfRentUsdM2Month ?? 0) > 0 || (park.rbwRentUsdM2Month ?? 0) > 0,
      location: [Number(park.lat), Number(park.lng)],
      isVerified: park.isVerified,
      createdAt: Math.floor(park.createdAt.getTime() / 1000),
    };

    await this.client.collections(INDUSTRIAL_PARKS_COLLECTION).documents().upsert(doc);
  }
}
