import { Injectable } from '@nestjs/common';
import type { Prisma } from '@prisma/client';
import { PrismaService } from '@modules/shared';
import { IndustrialParkEntity } from '../../domain/entities/industrial-park.entity';
import type {
  IIndustrialParkRepository,
  IndustrialParkSearchParams,
  PaginatedResult,
  IndustrialParkListItem,
  IndustrialParkDetailData,
  IndustrialParkStatsData,
  IndustrialMarketData,
} from '../../domain/repositories/industrial-park.repository';

@Injectable()
export class PrismaIndustrialParkRepository implements IIndustrialParkRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findById(id: string): Promise<IndustrialParkEntity | null> {
    const row = await this.prisma.$queryRaw<RawPark[]>`
      SELECT *, ST_Y(location::geometry) as lat, ST_X(location::geometry) as lng
      FROM "IndustrialPark" WHERE id = ${id} LIMIT 1
    `;
    return row[0] ? this.toDomain(row[0]) : null;
  }

  async findBySlug(slug: string): Promise<IndustrialParkEntity | null> {
    const row = await this.prisma.$queryRaw<RawPark[]>`
      SELECT *, ST_Y(location::geometry) as lat, ST_X(location::geometry) as lng
      FROM "IndustrialPark" WHERE slug = ${slug} LIMIT 1
    `;
    return row[0] ? this.toDomain(row[0]) : null;
  }

  async findDetailBySlug(slug: string): Promise<IndustrialParkDetailData | null> {
    const rows = await this.prisma.$queryRaw<RawParkDetail[]>`
      SELECT p.*,
        ST_Y(p.location::geometry) as lat,
        ST_X(p.location::geometry) as lng,
        COUNT(l.id)::int as "listingCount"
      FROM "IndustrialPark" p
      LEFT JOIN "IndustrialListing" l ON l."parkId" = p.id AND l.status = 'ACTIVE'
      WHERE p.slug = ${slug}
      GROUP BY p.id
      LIMIT 1
    `;
    return rows[0] ? this.toDetail(rows[0]) : null;
  }

  async findDetailById(id: string): Promise<IndustrialParkDetailData | null> {
    const rows = await this.prisma.$queryRaw<RawParkDetail[]>`
      SELECT p.*,
        ST_Y(p.location::geometry) as lat,
        ST_X(p.location::geometry) as lng,
        COUNT(l.id)::int as "listingCount"
      FROM "IndustrialPark" p
      LEFT JOIN "IndustrialListing" l ON l."parkId" = p.id AND l.status = 'ACTIVE'
      WHERE p.id = ${id}
      GROUP BY p.id
      LIMIT 1
    `;
    return rows[0] ? this.toDetail(rows[0]) : null;
  }

  async save(entity: IndustrialParkEntity): Promise<void> {
    await this.prisma.$executeRaw`
      INSERT INTO "IndustrialPark" (
        id, name, "nameEn", slug, developer, operator, status, location,
        address, district, province, region, "totalAreaHa", "leasableAreaHa",
        "occupancyRate", "remainingAreaHa", "tenantCount", "establishedYear",
        "landRentUsdM2Year", "rbfRentUsdM2Month", "rbwRentUsdM2Month",
        "managementFeeUsd", infrastructure, connectivity, incentives,
        "targetIndustries", "existingTenants", certifications, media, documents,
        description, "descriptionEn", "isVerified", "ownerId", "createdAt", "updatedAt"
      ) VALUES (
        ${entity.id}, ${entity.name}, ${entity.nameEn}, ${entity.slug},
        ${entity.developer}, ${entity.operator}, ${entity.status}::"IndustrialParkStatus",
        ST_SetSRID(ST_MakePoint(${entity.longitude}, ${entity.latitude}), 4326),
        ${entity.address}, ${entity.district}, ${entity.province}, ${entity.region}::"VietnamRegion",
        ${entity.totalAreaHa}, ${entity.leasableAreaHa}, ${entity.occupancyRate},
        ${entity.remainingAreaHa}, ${entity.tenantCount}, ${entity.establishedYear},
        ${entity.landRentUsdM2Year}, ${entity.rbfRentUsdM2Month}, ${entity.rbwRentUsdM2Month},
        ${entity.managementFeeUsd},
        ${entity.infrastructure ? JSON.stringify(entity.infrastructure) : null}::jsonb,
        ${entity.connectivity ? JSON.stringify(entity.connectivity) : null}::jsonb,
        ${entity.incentives ? JSON.stringify(entity.incentives) : null}::jsonb,
        ${entity.targetIndustries}::text[],
        ${entity.existingTenants ? JSON.stringify(entity.existingTenants) : null}::jsonb,
        ${entity.certifications ? JSON.stringify(entity.certifications) : null}::jsonb,
        ${entity.media ? JSON.stringify(entity.media) : null}::jsonb,
        ${entity.documents ? JSON.stringify(entity.documents) : null}::jsonb,
        ${entity.description}, ${entity.descriptionEn},
        ${entity.isVerified}, ${entity.ownerId}, ${entity.createdAt}, ${entity.updatedAt}
      )
    `;
  }

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

  async update(entity: IndustrialParkEntity): Promise<void> {
    await this.prisma.$executeRaw`
      UPDATE "IndustrialPark" SET
        name = ${entity.name}, "nameEn" = ${entity.nameEn},
        developer = ${entity.developer}, operator = ${entity.operator},
        status = ${entity.status}::"IndustrialParkStatus",
        "occupancyRate" = ${entity.occupancyRate},
        "remainingAreaHa" = ${entity.remainingAreaHa},
        "tenantCount" = ${entity.tenantCount},
        "landRentUsdM2Year" = ${entity.landRentUsdM2Year},
        "rbfRentUsdM2Month" = ${entity.rbfRentUsdM2Month},
        "rbwRentUsdM2Month" = ${entity.rbwRentUsdM2Month},
        "managementFeeUsd" = ${entity.managementFeeUsd},
        infrastructure = ${entity.infrastructure ? JSON.stringify(entity.infrastructure) : null}::jsonb,
        connectivity = ${entity.connectivity ? JSON.stringify(entity.connectivity) : null}::jsonb,
        incentives = ${entity.incentives ? JSON.stringify(entity.incentives) : null}::jsonb,
        "targetIndustries" = ${entity.targetIndustries}::text[],
        description = ${entity.description}, "descriptionEn" = ${entity.descriptionEn},
        "isVerified" = ${entity.isVerified},
        "ownerId" = ${entity.ownerId},
        "updatedAt" = ${entity.updatedAt}
      WHERE id = ${entity.id}
    `;
  }

  async search(params: IndustrialParkSearchParams): Promise<PaginatedResult<IndustrialParkListItem>> {
    const page = params.page ?? 1;
    const limit = params.limit ?? 20;
    const offset = (page - 1) * limit;

    const conditions: string[] = ['1=1'];
    const values: unknown[] = [];
    let paramIndex = 1;

    if (params.province) {
      conditions.push(`province = $${paramIndex++}`);
      values.push(params.province);
    }
    if (params.region) {
      conditions.push(`region = $${paramIndex++}::"VietnamRegion"`);
      values.push(params.region);
    }
    if (params.status) {
      conditions.push(`status = $${paramIndex++}::"IndustrialParkStatus"`);
      values.push(params.status);
    }
    if (params.minAreaHa != null) {
      conditions.push(`"remainingAreaHa" >= $${paramIndex++}`);
      values.push(params.minAreaHa);
    }
    if (params.maxRentUsdM2 != null) {
      conditions.push(`"landRentUsdM2Year" <= $${paramIndex++}`);
      values.push(params.maxRentUsdM2);
    }
    if (params.targetIndustry) {
      conditions.push(`$${paramIndex++} = ANY("targetIndustries")`);
      values.push(params.targetIndustry);
    }
    if (params.ownerId) {
      conditions.push(`"ownerId" = $${paramIndex++}`);
      values.push(params.ownerId);
    }
    if (params.query) {
      conditions.push(`(name ILIKE $${paramIndex} OR "nameEn" ILIKE $${paramIndex} OR developer ILIKE $${paramIndex} OR province ILIKE $${paramIndex})`);
      values.push(`%${params.query}%`);
      paramIndex++;
    }

    const where = conditions.join(' AND ');

    const countResult = await this.prisma.$queryRawUnsafe<[{ count: bigint }]>(
      `SELECT COUNT(*)::bigint as count FROM "IndustrialPark" WHERE ${where}`,
      ...values,
    );
    const total = Number(countResult[0].count);

    const rows = await this.prisma.$queryRawUnsafe<RawPark[]>(
      `SELECT *, ST_Y(location::geometry) as lat, ST_X(location::geometry) as lng
       FROM "IndustrialPark" WHERE ${where}
       ORDER BY "occupancyRate" DESC, "createdAt" DESC
       LIMIT $${paramIndex++} OFFSET $${paramIndex}`,
      ...values, limit, offset,
    );

    return {
      data: rows.map((r) => this.toListItem(r)),
      total,
      page,
      limit,
      totalPages: Math.ceil(total / limit),
    };
  }

  async compareParks(ids: string[]): Promise<IndustrialParkDetailData[]> {
    const rows = await this.prisma.$queryRaw<RawParkDetail[]>`
      SELECT p.*,
        ST_Y(p.location::geometry) as lat,
        ST_X(p.location::geometry) as lng,
        COUNT(l.id)::int as "listingCount"
      FROM "IndustrialPark" p
      LEFT JOIN "IndustrialListing" l ON l."parkId" = p.id AND l.status = 'ACTIVE'
      WHERE p.id = ANY(${ids}::text[])
      GROUP BY p.id
    `;
    return rows.map((r) => this.toDetail(r));
  }

  async getStats(): Promise<IndustrialParkStatsData> {
    const [summary] = await this.prisma.$queryRaw<[{ totalParks: bigint; totalAreaHa: number; avgOccupancy: number; totalTenants: bigint }]>`
      SELECT COUNT(*)::bigint as "totalParks",
        COALESCE(SUM("totalAreaHa"), 0) as "totalAreaHa",
        COALESCE(AVG("occupancyRate"), 0) as "avgOccupancy",
        COALESCE(SUM("tenantCount"), 0)::bigint as "totalTenants"
      FROM "IndustrialPark"
    `;

    const byRegion = await this.prisma.$queryRaw<{ region: string; count: bigint; avgOccupancy: number }[]>`
      SELECT region::text, COUNT(*)::bigint as count, AVG("occupancyRate") as "avgOccupancy"
      FROM "IndustrialPark" GROUP BY region ORDER BY count DESC
    `;

    const byStatus = await this.prisma.$queryRaw<{ status: string; count: bigint }[]>`
      SELECT status::text, COUNT(*)::bigint as count
      FROM "IndustrialPark" GROUP BY status ORDER BY count DESC
    `;

    const topProvinces = await this.prisma.$queryRaw<{ province: string; count: bigint; avgRent: number | null }[]>`
      SELECT province, COUNT(*)::bigint as count, AVG("landRentUsdM2Year") as "avgRent"
      FROM "IndustrialPark" GROUP BY province ORDER BY count DESC LIMIT 10
    `;

    return {
      totalParks: Number(summary.totalParks),
      totalAreaHa: summary.totalAreaHa,
      avgOccupancyRate: summary.avgOccupancy,
      totalTenants: Number(summary.totalTenants),
      byRegion: byRegion.map((r) => ({ region: r.region, count: Number(r.count), avgOccupancy: r.avgOccupancy })),
      byStatus: byStatus.map((r) => ({ status: r.status, count: Number(r.count) })),
      topProvinces: topProvinces.map((r) => ({ province: r.province, count: Number(r.count), avgRent: r.avgRent })),
    };
  }

  async getMarketData(): Promise<IndustrialMarketData> {
    const [overall] = await this.prisma.$queryRaw<[{ totalParks: bigint; avgOccupancy: number; avgLandRent: string | null; avgRbfRent: string | null }]>`
      SELECT COUNT(*)::bigint as "totalParks",
        AVG("occupancyRate") as "avgOccupancy",
        AVG("landRentUsdM2Year") as "avgLandRent",
        AVG("rbfRentUsdM2Month") as "avgRbfRent"
      FROM "IndustrialPark" WHERE status = 'OPERATIONAL' OR status = 'FULL'
    `;

    const rentByRegion = await this.prisma.$queryRaw<{ region: string; avgLandRent: string | null; avgRbfRent: string | null; parkCount: bigint }[]>`
      SELECT region::text, AVG("landRentUsdM2Year") as "avgLandRent",
        AVG("rbfRentUsdM2Month") as "avgRbfRent", COUNT(*)::bigint as "parkCount"
      FROM "IndustrialPark" WHERE status IN ('OPERATIONAL', 'FULL')
      GROUP BY region ORDER BY "avgLandRent" DESC NULLS LAST
    `;

    const rentByProvince = await this.prisma.$queryRaw<{ province: string; avgLandRent: string | null; avgRbfRent: string | null; parkCount: bigint }[]>`
      SELECT province, AVG("landRentUsdM2Year") as "avgLandRent",
        AVG("rbfRentUsdM2Month") as "avgRbfRent", COUNT(*)::bigint as "parkCount"
      FROM "IndustrialPark" WHERE status IN ('OPERATIONAL', 'FULL')
      GROUP BY province ORDER BY "avgLandRent" DESC NULLS LAST
    `;

    return {
      totalParks: Number(overall.totalParks),
      avgOccupancyRate: overall.avgOccupancy,
      avgLandRentUsdM2: overall.avgLandRent,
      avgRbfRentUsdM2: overall.avgRbfRent,
      rentByRegion: rentByRegion.map((r) => ({ ...r, parkCount: Number(r.parkCount) })),
      rentByProvince: rentByProvince.map((r) => ({ ...r, parkCount: Number(r.parkCount) })),
    };
  }

  private toDomain(row: RawPark): IndustrialParkEntity {
    return new IndustrialParkEntity(
      row.id,
      {
        name: row.name,
        nameEn: row.nameEn,
        slug: row.slug,
        developer: row.developer,
        operator: row.operator,
        status: row.status,
        latitude: Number(row.lat),
        longitude: Number(row.lng),
        address: row.address,
        district: row.district,
        province: row.province,
        region: row.region,
        totalAreaHa: row.totalAreaHa,
        leasableAreaHa: row.leasableAreaHa,
        occupancyRate: row.occupancyRate,
        remainingAreaHa: row.remainingAreaHa,
        tenantCount: row.tenantCount,
        establishedYear: row.establishedYear,
        landRentUsdM2Year: row.landRentUsdM2Year != null ? parseFloat(row.landRentUsdM2Year) : null,
        rbfRentUsdM2Month: row.rbfRentUsdM2Month != null ? parseFloat(row.rbfRentUsdM2Month) : null,
        rbwRentUsdM2Month: row.rbwRentUsdM2Month != null ? parseFloat(row.rbwRentUsdM2Month) : null,
        managementFeeUsd: row.managementFeeUsd != null ? parseFloat(row.managementFeeUsd) : null,
        infrastructure: row.infrastructure as Record<string, unknown> | null,
        connectivity: row.connectivity as Record<string, unknown> | null,
        incentives: row.incentives as Record<string, unknown> | null,
        targetIndustries: row.targetIndustries ?? [],
        existingTenants: row.existingTenants as Record<string, unknown>[] | null,
        certifications: row.certifications as string[] | null,
        media: row.media as Record<string, unknown>[] | null,
        documents: row.documents as Record<string, unknown>[] | null,
        description: row.description,
        descriptionEn: row.descriptionEn,
        isVerified: row.isVerified,
        ownerId: row.ownerId,
      },
      row.createdAt,
      row.updatedAt,
    );
  }

  private toListItem(row: RawPark): IndustrialParkListItem {
    return {
      id: row.id,
      name: row.name,
      nameEn: row.nameEn,
      slug: row.slug,
      developer: row.developer,
      status: row.status,
      province: row.province,
      region: row.region,
      totalAreaHa: row.totalAreaHa,
      occupancyRate: row.occupancyRate,
      remainingAreaHa: row.remainingAreaHa,
      tenantCount: row.tenantCount,
      landRentUsdM2Year: row.landRentUsdM2Year,
      rbfRentUsdM2Month: row.rbfRentUsdM2Month,
      rbwRentUsdM2Month: row.rbwRentUsdM2Month,
      targetIndustries: row.targetIndustries ?? [],
      latitude: Number(row.lat),
      longitude: Number(row.lng),
      ownerId: row.ownerId,
    };
  }

  private toDetail(row: RawParkDetail): IndustrialParkDetailData {
    return {
      id: row.id,
      name: row.name,
      nameEn: row.nameEn,
      slug: row.slug,
      developer: row.developer,
      operator: row.operator,
      status: row.status,
      latitude: Number(row.lat),
      longitude: Number(row.lng),
      address: row.address,
      district: row.district,
      province: row.province,
      region: row.region,
      totalAreaHa: row.totalAreaHa,
      leasableAreaHa: row.leasableAreaHa,
      occupancyRate: row.occupancyRate,
      remainingAreaHa: row.remainingAreaHa,
      tenantCount: row.tenantCount,
      establishedYear: row.establishedYear,
      landRentUsdM2Year: row.landRentUsdM2Year,
      rbfRentUsdM2Month: row.rbfRentUsdM2Month,
      rbwRentUsdM2Month: row.rbwRentUsdM2Month,
      managementFeeUsd: row.managementFeeUsd,
      infrastructure: row.infrastructure as Record<string, unknown> | null,
      connectivity: row.connectivity as Record<string, unknown> | null,
      incentives: row.incentives as Record<string, unknown> | null,
      targetIndustries: row.targetIndustries ?? [],
      existingTenants: row.existingTenants as Record<string, unknown>[] | null,
      certifications: row.certifications as string[] | null,
      media: row.media as Record<string, unknown>[] | null,
      documents: row.documents as Record<string, unknown>[] | null,
      description: row.description,
      descriptionEn: row.descriptionEn,
      isVerified: row.isVerified,
      ownerId: row.ownerId,
      listingCount: row.listingCount ?? 0,
      createdAt: row.createdAt,
      updatedAt: row.updatedAt,
    };
  }
}

// Raw query result types
interface RawPark {
  id: string;
  name: string;
  nameEn: string | null;
  slug: string;
  developer: string;
  operator: string | null;
  status: 'PLANNING' | 'UNDER_CONSTRUCTION' | 'OPERATIONAL' | 'FULL';
  lat: number;
  lng: number;
  address: string;
  district: string;
  province: string;
  region: 'NORTH' | 'CENTRAL' | 'SOUTH';
  totalAreaHa: number;
  leasableAreaHa: number;
  occupancyRate: number;
  remainingAreaHa: number;
  tenantCount: number;
  establishedYear: number | null;
  landRentUsdM2Year: string | null;
  rbfRentUsdM2Month: string | null;
  rbwRentUsdM2Month: string | null;
  managementFeeUsd: string | null;
  infrastructure: Prisma.JsonValue;
  connectivity: Prisma.JsonValue;
  incentives: Prisma.JsonValue;
  targetIndustries: string[] | null;
  existingTenants: Prisma.JsonValue;
  certifications: Prisma.JsonValue;
  media: Prisma.JsonValue;
  documents: Prisma.JsonValue;
  description: string | null;
  descriptionEn: string | null;
  isVerified: boolean;
  ownerId: string | null;
  createdAt: Date;
  updatedAt: Date;
}

interface RawParkDetail extends RawPark {
  listingCount: number;
}
