/**
 * Industrial-listing label-density report (TEC-2769 — R5.2.2).
 *
 * Lightweight monitoring query. Answers: do we have enough labelled
 * listing-level data (with `priceUsdM2` populated) to train a listing-level
 * AVM for industrial real estate?
 *
 * Emits:
 *   - Human-readable table to stdout (default).
 *   - JSON blob to stdout when `--json` is passed (for CI / dashboards).
 *
 * Report sections:
 *   - Totals (rows, rows with priceUsdM2, % label coverage).
 *   - Histogram by VietnamRegion.
 *   - Histogram by IndustrialPropertyType.
 *   - Park coverage (parks with ≥1 listing / total parks).
 *   - priceUsdM2 P10/P50/P90 by (region, propertyType).
 *   - Acceptance-gate verdict for TEC-2769 (≥500 rows, NORTH/SOUTH ≥20%,
 *     CENTRAL ≥5%, every propertyType enum ≥10 rows).
 *
 * Usage:
 *     pnpm tsx scripts/report-industrial-label-density.ts
 *     pnpm tsx scripts/report-industrial-label-density.ts --json
 */

import { PrismaPg } from '@prisma/adapter-pg';
import {
  IndustrialPropertyType,
  PrismaClient,
  VietnamRegion,
} from '@prisma/client';
import pg from 'pg';

// ---------------------------------------------------------------------------
// Types.
// ---------------------------------------------------------------------------
type Verdict = 'PASS' | 'FAIL';

interface Percentiles {
  p10: number | null;
  p50: number | null;
  p90: number | null;
  n: number;
}

interface Report {
  generatedAt: string;
  totals: {
    listings: number;
    withPriceUsdM2: number;
    labelCoveragePct: number;
  };
  regionHistogram: Record<string, { rows: number; withPrice: number }>;
  propertyTypeHistogram: Record<string, { rows: number; withPrice: number }>;
  parkCoverage: {
    parksWithListings: number;
    parksTotal: number;
    coveragePct: number;
  };
  priceBuckets: Array<{
    region: VietnamRegion;
    propertyType: IndustrialPropertyType;
    percentiles: Percentiles;
  }>;
  gates: {
    totalAtLeast500: Verdict;
    northAtLeast20pct: Verdict;
    southAtLeast20pct: Verdict;
    centralAtLeast5pct: Verdict;
    everyPropertyTypeAtLeast10: Verdict;
    overall: Verdict;
  };
}

// ---------------------------------------------------------------------------
// Percentile helper. Linear interpolation on sorted array.
// ---------------------------------------------------------------------------
function percentile(sorted: number[], p: number): number | null {
  if (sorted.length === 0) return null;
  if (sorted.length === 1) return sorted[0] ?? null;
  const rank = (p / 100) * (sorted.length - 1);
  const lo = Math.floor(rank);
  const hi = Math.ceil(rank);
  if (lo === hi) return sorted[lo] ?? null;
  const loVal = sorted[lo] ?? 0;
  const hiVal = sorted[hi] ?? 0;
  return loVal + (hiVal - loVal) * (rank - lo);
}

function pct(x: number, total: number): number {
  if (total === 0) return 0;
  return Math.round((x / total) * 1000) / 10;
}

function fmtPrice(n: number | null): string {
  if (n == null) return '   —  ';
  if (n >= 100) return n.toFixed(0).padStart(6);
  return n.toFixed(2).padStart(6);
}

// ---------------------------------------------------------------------------
// Main.
// ---------------------------------------------------------------------------
async function run(): Promise<Report> {
  const pool = new pg.Pool({ connectionString: process.env['DATABASE_URL'] });
  const adapter = new PrismaPg(pool);
  const prisma = new PrismaClient({ adapter });

  try {
    const parksTotal = await prisma.industrialPark.count();

    const listings = await prisma.industrialListing.findMany({
      select: {
        id: true,
        parkId: true,
        propertyType: true,
        priceUsdM2: true,
        park: { select: { region: true } },
      },
    });

    const total = listings.length;
    const withPrice = listings.filter((l) => l.priceUsdM2 != null).length;

    // Region histogram.
    const regionHistogram: Report['regionHistogram'] = {};
    for (const region of Object.values(VietnamRegion)) {
      regionHistogram[region] = { rows: 0, withPrice: 0 };
    }
    for (const l of listings) {
      const r = l.park?.region;
      if (!r) continue;
      const bucket = regionHistogram[r];
      if (!bucket) continue;
      bucket.rows += 1;
      if (l.priceUsdM2 != null) bucket.withPrice += 1;
    }

    // PropertyType histogram.
    const propertyTypeHistogram: Report['propertyTypeHistogram'] = {};
    for (const pt of Object.values(IndustrialPropertyType)) {
      propertyTypeHistogram[pt] = { rows: 0, withPrice: 0 };
    }
    for (const l of listings) {
      const bucket = propertyTypeHistogram[l.propertyType];
      if (!bucket) continue;
      bucket.rows += 1;
      if (l.priceUsdM2 != null) bucket.withPrice += 1;
    }

    // Park coverage.
    const parkIdsWithListings = new Set(listings.map((l) => l.parkId));
    const parkCoverage = {
      parksWithListings: parkIdsWithListings.size,
      parksTotal,
      coveragePct: pct(parkIdsWithListings.size, parksTotal),
    };

    // Price percentiles by (region, propertyType).
    const priceBuckets: Report['priceBuckets'] = [];
    for (const region of Object.values(VietnamRegion)) {
      for (const propertyType of Object.values(IndustrialPropertyType)) {
        const values = listings
          .filter(
            (l) =>
              l.park?.region === region &&
              l.propertyType === propertyType &&
              l.priceUsdM2 != null,
          )
          .map((l) => Number(l.priceUsdM2))
          .sort((a, b) => a - b);
        priceBuckets.push({
          region,
          propertyType,
          percentiles: {
            n: values.length,
            p10: percentile(values, 10),
            p50: percentile(values, 50),
            p90: percentile(values, 90),
          },
        });
      }
    }

    // Acceptance gates.
    const northPct = pct(regionHistogram[VietnamRegion.NORTH]?.rows ?? 0, total);
    const southPct = pct(regionHistogram[VietnamRegion.SOUTH]?.rows ?? 0, total);
    const centralPct = pct(
      regionHistogram[VietnamRegion.CENTRAL]?.rows ?? 0,
      total,
    );
    const everyPropertyTypeAtLeast10: Verdict = Object.values(
      propertyTypeHistogram,
    ).every((b) => b.rows >= 10)
      ? 'PASS'
      : 'FAIL';
    const gates: Report['gates'] = {
      totalAtLeast500: total >= 500 ? 'PASS' : 'FAIL',
      northAtLeast20pct: northPct >= 20 ? 'PASS' : 'FAIL',
      southAtLeast20pct: southPct >= 20 ? 'PASS' : 'FAIL',
      centralAtLeast5pct: centralPct >= 5 ? 'PASS' : 'FAIL',
      everyPropertyTypeAtLeast10,
      overall: 'FAIL',
    };
    gates.overall =
      gates.totalAtLeast500 === 'PASS' &&
      gates.northAtLeast20pct === 'PASS' &&
      gates.southAtLeast20pct === 'PASS' &&
      gates.centralAtLeast5pct === 'PASS' &&
      gates.everyPropertyTypeAtLeast10 === 'PASS'
        ? 'PASS'
        : 'FAIL';

    return {
      generatedAt: new Date().toISOString(),
      totals: {
        listings: total,
        withPriceUsdM2: withPrice,
        labelCoveragePct: pct(withPrice, total),
      },
      regionHistogram,
      propertyTypeHistogram,
      parkCoverage,
      priceBuckets,
      gates,
    };
  } finally {
    await prisma.$disconnect();
    await pool.end();
  }
}

function renderHuman(report: Report): string {
  const out: string[] = [];
  const push = (s = ''): void => {
    out.push(s);
  };

  push('');
  push('━'.repeat(72));
  push(' Industrial listing — label density report');
  push(` generated: ${report.generatedAt}`);
  push('━'.repeat(72));

  push('');
  push(' TOTALS');
  push(`   listings:              ${report.totals.listings}`);
  push(
    `   with priceUsdM2:       ${report.totals.withPriceUsdM2} ` +
      `(${report.totals.labelCoveragePct}%)`,
  );

  push('');
  push(' BY REGION');
  push('   region   | rows | withPrice');
  push('   ---------+------+----------');
  for (const [region, b] of Object.entries(report.regionHistogram)) {
    push(
      `   ${region.padEnd(8)} | ${String(b.rows).padStart(4)} | ${String(
        b.withPrice,
      ).padStart(8)}`,
    );
  }

  push('');
  push(' BY PROPERTY TYPE');
  push('   type                | rows | withPrice');
  push('   --------------------+------+----------');
  for (const [pt, b] of Object.entries(report.propertyTypeHistogram)) {
    push(
      `   ${pt.padEnd(19)} | ${String(b.rows).padStart(4)} | ${String(
        b.withPrice,
      ).padStart(8)}`,
    );
  }

  push('');
  push(' PARK COVERAGE');
  push(
    `   ${report.parkCoverage.parksWithListings} / ${report.parkCoverage.parksTotal} ` +
      `parks have ≥1 listing (${report.parkCoverage.coveragePct}%)`,
  );

  push('');
  push(' priceUsdM2 P10/P50/P90 (USD/m², USD/m²/mo, or USD/m²/yr by type)');
  push('   region   | type                |   n |    P10 |    P50 |    P90');
  push('   ---------+---------------------+-----+--------+--------+-------');
  for (const bucket of report.priceBuckets) {
    if (bucket.percentiles.n === 0) continue;
    push(
      `   ${bucket.region.padEnd(8)} | ${bucket.propertyType.padEnd(19)} | ` +
        `${String(bucket.percentiles.n).padStart(3)} | ${fmtPrice(
          bucket.percentiles.p10,
        )} | ${fmtPrice(bucket.percentiles.p50)} | ${fmtPrice(
          bucket.percentiles.p90,
        )}`,
    );
  }

  push('');
  push(' ACCEPTANCE GATES (TEC-2769)');
  push(`   total ≥ 500 rows:                 ${report.gates.totalAtLeast500}`);
  push(`   NORTH ≥ 20% of rows:              ${report.gates.northAtLeast20pct}`);
  push(`   SOUTH ≥ 20% of rows:              ${report.gates.southAtLeast20pct}`);
  push(`   CENTRAL ≥ 5% of rows:             ${report.gates.centralAtLeast5pct}`);
  push(
    `   every propertyType ≥ 10 rows:     ${report.gates.everyPropertyTypeAtLeast10}`,
  );
  push(`   OVERALL:                          ${report.gates.overall}`);
  push('');

  return out.join('\n');
}

async function main(): Promise<void> {
  const asJson = process.argv.includes('--json');
  try {
    const report = await run();
    if (asJson) {
      console.log(JSON.stringify(report, null, 2));
    } else {
      console.log(renderHuman(report));
    }
    process.exit(report.gates.overall === 'PASS' ? 0 : 1);
  } catch (err) {
    console.error('Label-density report failed:', err);
    process.exit(2);
  }
}

if (require.main === module) {
  void main();
}
