import { Injectable } from '@nestjs/common';
import type { IndustrialLeaseType, IndustrialListingStatus, IndustrialPropertyType, Prisma } from '@prisma/client';
import { PrismaService } from '@modules/shared';
import { IndustrialListingEntity } from '../../domain/entities/industrial-listing.entity';
import type {
  IIndustrialListingRepository,
  IndustrialListingSearchParams,
  PaginatedResult,
  IndustrialListingListItem,
  IndustrialListingDetailData,
} from '../../domain/repositories/industrial-listing.repository';

@Injectable()
export class PrismaIndustrialListingRepository implements IIndustrialListingRepository {
  constructor(private readonly prisma: PrismaService) {}

  async findById(id: string): Promise<IndustrialListingEntity | null> {
    const rows = await this.prisma.$queryRaw<RawListing[]>`
      SELECT * FROM "IndustrialListing" WHERE id = ${id} LIMIT 1
    `;
    return rows[0] ? this.toDomain(rows[0]) : null;
  }

  async findDetailById(id: string): Promise<IndustrialListingDetailData | null> {
    const rows = await this.prisma.$queryRaw<RawListingDetail[]>`
      SELECT l.*, p.name as "parkName", p.slug as "parkSlug"
      FROM "IndustrialListing" l
      JOIN "IndustrialPark" p ON p.id = l."parkId"
      WHERE l.id = ${id}
      LIMIT 1
    `;
    return rows[0] ? this.toDetail(rows[0]) : null;
  }

  async save(entity: IndustrialListingEntity): Promise<void> {
    await this.prisma.$executeRaw`
      INSERT INTO "IndustrialListing" (
        id, "parkId", "agentId", "sellerId", "propertyType", "leaseType", status,
        title, description, "areaM2", "ceilingHeightM", "floorLoadTonM2",
        "columnSpacingM", "dockCount", "craneCapacityTon", "hasMezzanine",
        "hasOfficeArea", "officeAreaM2", "priceUsdM2", "pricingUnit",
        "totalLeasePrice", "managementFee", "depositMonths", "minLeaseYears",
        "maxLeaseYears", "leaseExpiry", "availableFrom", "powerCapacityKva",
        "waterSupplyM3Day", media, "viewCount", "inquiryCount",
        "publishedAt", "createdAt", "updatedAt"
      ) VALUES (
        ${entity.id}, ${entity.parkId}, ${entity.agentId}, ${entity.sellerId},
        ${entity.propertyType}::"IndustrialPropertyType",
        ${entity.leaseType}::"IndustrialLeaseType",
        ${entity.status}::"IndustrialListingStatus",
        ${entity.title}, ${entity.description}, ${entity.areaM2},
        ${entity.ceilingHeightM}, ${entity.floorLoadTonM2},
        ${entity.columnSpacingM}, ${entity.dockCount}, ${entity.craneCapacityTon},
        ${entity.hasMezzanine}, ${entity.hasOfficeArea}, ${entity.officeAreaM2},
        ${entity.priceUsdM2}, ${entity.pricingUnit}, ${entity.totalLeasePrice},
        ${entity.managementFee}, ${entity.depositMonths}, ${entity.minLeaseYears},
        ${entity.maxLeaseYears}, ${entity.leaseExpiry}, ${entity.availableFrom},
        ${entity.powerCapacityKva}, ${entity.waterSupplyM3Day},
        ${entity.media ? JSON.stringify(entity.media) : null}::jsonb,
        ${entity.viewCount}, ${entity.inquiryCount},
        ${entity.publishedAt}, ${entity.createdAt}, ${entity.updatedAt}
      )
    `;
  }

  async update(entity: IndustrialListingEntity): Promise<void> {
    await this.prisma.$executeRaw`
      UPDATE "IndustrialListing" SET
        "agentId" = ${entity.agentId},
        "propertyType" = ${entity.propertyType}::"IndustrialPropertyType",
        "leaseType" = ${entity.leaseType}::"IndustrialLeaseType",
        status = ${entity.status}::"IndustrialListingStatus",
        title = ${entity.title},
        description = ${entity.description},
        "areaM2" = ${entity.areaM2},
        "ceilingHeightM" = ${entity.ceilingHeightM},
        "floorLoadTonM2" = ${entity.floorLoadTonM2},
        "columnSpacingM" = ${entity.columnSpacingM},
        "dockCount" = ${entity.dockCount},
        "craneCapacityTon" = ${entity.craneCapacityTon},
        "hasMezzanine" = ${entity.hasMezzanine},
        "hasOfficeArea" = ${entity.hasOfficeArea},
        "officeAreaM2" = ${entity.officeAreaM2},
        "priceUsdM2" = ${entity.priceUsdM2},
        "pricingUnit" = ${entity.pricingUnit},
        "totalLeasePrice" = ${entity.totalLeasePrice},
        "managementFee" = ${entity.managementFee},
        "depositMonths" = ${entity.depositMonths},
        "minLeaseYears" = ${entity.minLeaseYears},
        "maxLeaseYears" = ${entity.maxLeaseYears},
        "leaseExpiry" = ${entity.leaseExpiry},
        "availableFrom" = ${entity.availableFrom},
        "powerCapacityKva" = ${entity.powerCapacityKva},
        "waterSupplyM3Day" = ${entity.waterSupplyM3Day},
        media = ${entity.media ? JSON.stringify(entity.media) : null}::jsonb,
        "updatedAt" = ${entity.updatedAt}
      WHERE id = ${entity.id}
    `;
  }

  async search(params: IndustrialListingSearchParams): Promise<PaginatedResult<IndustrialListingListItem>> {
    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.parkId) {
      conditions.push(`l."parkId" = $${paramIndex++}`);
      values.push(params.parkId);
    }
    if (params.propertyType) {
      conditions.push(`l."propertyType" = $${paramIndex++}::"IndustrialPropertyType"`);
      values.push(params.propertyType);
    }
    if (params.leaseType) {
      conditions.push(`l."leaseType" = $${paramIndex++}::"IndustrialLeaseType"`);
      values.push(params.leaseType);
    }
    if (params.status) {
      conditions.push(`l.status = $${paramIndex++}::"IndustrialListingStatus"`);
      values.push(params.status);
    }
    if (params.minAreaM2 != null) {
      conditions.push(`l."areaM2" >= $${paramIndex++}`);
      values.push(params.minAreaM2);
    }
    if (params.maxAreaM2 != null) {
      conditions.push(`l."areaM2" <= $${paramIndex++}`);
      values.push(params.maxAreaM2);
    }
    if (params.minPriceUsdM2 != null) {
      conditions.push(`l."priceUsdM2" >= $${paramIndex++}`);
      values.push(params.minPriceUsdM2);
    }
    if (params.maxPriceUsdM2 != null) {
      conditions.push(`l."priceUsdM2" <= $${paramIndex++}`);
      values.push(params.maxPriceUsdM2);
    }
    if (params.query) {
      conditions.push(`(l.title ILIKE $${paramIndex} OR l.description 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 "IndustrialListing" l WHERE ${where}`,
      ...values,
    );
    const total = Number(countResult[0].count);

    const rows = await this.prisma.$queryRawUnsafe<RawListingListItem[]>(
      `SELECT l.id, l."parkId", p.name as "parkName", l."propertyType"::text,
              l."leaseType"::text, l.status::text, l.title, l."areaM2",
              l."priceUsdM2", l."pricingUnit", l."ceilingHeightM",
              l."hasMezzanine", l."hasOfficeArea", l."publishedAt", l."createdAt"
       FROM "IndustrialListing" l
       JOIN "IndustrialPark" p ON p.id = l."parkId"
       WHERE ${where}
       ORDER BY l."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),
    };
  }

  private toDomain(row: RawListing): IndustrialListingEntity {
    return new IndustrialListingEntity(
      row.id,
      {
        parkId: row.parkId,
        agentId: row.agentId,
        sellerId: row.sellerId,
        propertyType: row.propertyType,
        leaseType: row.leaseType,
        status: row.status,
        title: row.title,
        description: row.description,
        areaM2: row.areaM2,
        ceilingHeightM: row.ceilingHeightM,
        floorLoadTonM2: row.floorLoadTonM2,
        columnSpacingM: row.columnSpacingM,
        dockCount: row.dockCount,
        craneCapacityTon: row.craneCapacityTon,
        hasMezzanine: row.hasMezzanine,
        hasOfficeArea: row.hasOfficeArea,
        officeAreaM2: row.officeAreaM2,
        priceUsdM2: row.priceUsdM2 != null ? parseFloat(row.priceUsdM2 as unknown as string) : null,
        pricingUnit: row.pricingUnit,
        totalLeasePrice: row.totalLeasePrice != null ? parseFloat(row.totalLeasePrice as unknown as string) : null,
        managementFee: row.managementFee != null ? parseFloat(row.managementFee as unknown as string) : null,
        depositMonths: row.depositMonths,
        minLeaseYears: row.minLeaseYears,
        maxLeaseYears: row.maxLeaseYears,
        leaseExpiry: row.leaseExpiry,
        availableFrom: row.availableFrom,
        powerCapacityKva: row.powerCapacityKva,
        waterSupplyM3Day: row.waterSupplyM3Day,
        media: row.media as Record<string, unknown>[] | null,
        viewCount: row.viewCount,
        inquiryCount: row.inquiryCount,
        publishedAt: row.publishedAt,
      },
      row.createdAt,
      row.updatedAt,
    );
  }

  private toListItem(row: RawListingListItem): IndustrialListingListItem {
    return {
      id: row.id,
      parkId: row.parkId,
      parkName: row.parkName,
      propertyType: row.propertyType as IndustrialPropertyType,
      leaseType: row.leaseType as IndustrialLeaseType,
      status: row.status as IndustrialListingStatus,
      title: row.title,
      areaM2: row.areaM2,
      priceUsdM2: row.priceUsdM2,
      pricingUnit: row.pricingUnit,
      ceilingHeightM: row.ceilingHeightM,
      hasMezzanine: row.hasMezzanine,
      hasOfficeArea: row.hasOfficeArea,
      publishedAt: row.publishedAt,
      createdAt: row.createdAt,
    };
  }

  private toDetail(row: RawListingDetail): IndustrialListingDetailData {
    return {
      id: row.id,
      parkId: row.parkId,
      parkName: row.parkName,
      parkSlug: row.parkSlug,
      agentId: row.agentId,
      sellerId: row.sellerId,
      propertyType: row.propertyType,
      leaseType: row.leaseType,
      status: row.status,
      title: row.title,
      description: row.description,
      areaM2: row.areaM2,
      ceilingHeightM: row.ceilingHeightM,
      floorLoadTonM2: row.floorLoadTonM2,
      columnSpacingM: row.columnSpacingM,
      dockCount: row.dockCount,
      craneCapacityTon: row.craneCapacityTon,
      hasMezzanine: row.hasMezzanine,
      hasOfficeArea: row.hasOfficeArea,
      officeAreaM2: row.officeAreaM2,
      priceUsdM2: row.priceUsdM2,
      pricingUnit: row.pricingUnit,
      totalLeasePrice: row.totalLeasePrice,
      managementFee: row.managementFee,
      depositMonths: row.depositMonths,
      minLeaseYears: row.minLeaseYears,
      maxLeaseYears: row.maxLeaseYears,
      leaseExpiry: row.leaseExpiry,
      availableFrom: row.availableFrom,
      powerCapacityKva: row.powerCapacityKva,
      waterSupplyM3Day: row.waterSupplyM3Day,
      media: row.media as Record<string, unknown>[] | null,
      viewCount: row.viewCount,
      inquiryCount: row.inquiryCount,
      publishedAt: row.publishedAt,
      createdAt: row.createdAt,
      updatedAt: row.updatedAt,
    };
  }
}

interface RawListing {
  id: string;
  parkId: string;
  agentId: string | null;
  sellerId: string;
  propertyType: IndustrialPropertyType;
  leaseType: IndustrialLeaseType;
  status: IndustrialListingStatus;
  title: string;
  description: string | null;
  areaM2: number;
  ceilingHeightM: number | null;
  floorLoadTonM2: number | null;
  columnSpacingM: number | null;
  dockCount: number | null;
  craneCapacityTon: number | null;
  hasMezzanine: boolean;
  hasOfficeArea: boolean;
  officeAreaM2: number | null;
  priceUsdM2: string | null;
  pricingUnit: string | null;
  totalLeasePrice: string | null;
  managementFee: string | null;
  depositMonths: number | null;
  minLeaseYears: number | null;
  maxLeaseYears: number | null;
  leaseExpiry: Date | null;
  availableFrom: Date | null;
  powerCapacityKva: number | null;
  waterSupplyM3Day: number | null;
  media: Prisma.JsonValue;
  viewCount: number;
  inquiryCount: number;
  publishedAt: Date | null;
  createdAt: Date;
  updatedAt: Date;
}

interface RawListingListItem {
  id: string;
  parkId: string;
  parkName: string;
  propertyType: string;
  leaseType: string;
  status: string;
  title: string;
  areaM2: number;
  priceUsdM2: string | null;
  pricingUnit: string | null;
  ceilingHeightM: number | null;
  hasMezzanine: boolean;
  hasOfficeArea: boolean;
  publishedAt: Date | null;
  createdAt: Date;
}

interface RawListingDetail extends RawListing {
  parkName: string;
  parkSlug: string;
}
