/**
 * Backfill de cfdis.codigo_postal_receptor desde xml_original para CFDIs
 * pre-migracion 044.
 *
 * Criterio: WHERE xml_original IS NOT NULL AND codigo_postal_receptor IS NULL.
 * Re-usa `parseXml()` para mantener la logica de extraccion identica al sync.
 *
 * Uso:
 *   pnpm --filter @horux/api exec tsx scripts/backfill-cp-receptor.ts        # ejecuta
 *   pnpm --filter @horux/api exec tsx scripts/backfill-cp-receptor.ts --dry  # reporta sin escribir
 */
import { prisma, tenantDb } from '../src/config/database.js';
import { parseXml } from '../src/services/sat/sat-parser.service.js';

const DRY_RUN = process.argv.includes('--dry') || process.argv.includes('--dry-run');

interface PerTenantResult {
  tenantId: string;
  rfc: string;
  databaseName: string;
  scanned: number;
  parsedOk: number;
  parseFailed: number;
  withCp: number;
  updated: number;
  error?: string;
}

async function backfillTenant(
  tenantId: string,
  rfc: string,
  databaseName: string,
): Promise<PerTenantResult> {
  const result: PerTenantResult = {
    tenantId,
    rfc,
    databaseName,
    scanned: 0,
    parsedOk: 0,
    parseFailed: 0,
    withCp: 0,
    updated: 0,
  };

  const pool = await tenantDb.getPool(tenantId, databaseName);

  const { rows } = await pool.query<{
    id: number;
    uuid: string;
    type: string;
    xml_original: string | null;
  }>(
    `SELECT id, uuid, type, xml_original
     FROM cfdis
     WHERE xml_original IS NOT NULL
       AND codigo_postal_receptor IS NULL
     ORDER BY id`,
  );

  result.scanned = rows.length;
  if (rows.length === 0) return result;

  const client = await pool.connect();
  try {
    await client.query('BEGIN');

    for (const row of rows) {
      if (!row.xml_original) continue;

      const downloadType = row.type === 'EMITIDO' ? 'emitidos' : 'recibidos';
      let parsed;
      try {
        parsed = parseXml(row.xml_original, downloadType);
      } catch {
        result.parseFailed++;
        continue;
      }

      if (!parsed) {
        result.parseFailed++;
        continue;
      }
      result.parsedOk++;

      if (!parsed.codigoPostalReceptor) continue;

      result.withCp++;

      await client.query(
        `UPDATE cfdis
         SET codigo_postal_receptor = $2,
             actualizado_en = NOW()
         WHERE id = $1`,
        [row.id, parsed.codigoPostalReceptor],
      );
      result.updated++;
    }

    if (DRY_RUN) {
      await client.query('ROLLBACK');
    } else {
      await client.query('COMMIT');
    }
  } catch (err: any) {
    await client.query('ROLLBACK').catch(() => {});
    result.error = err?.message || String(err);
  } finally {
    client.release();
  }

  return result;
}

async function main() {
  console.log(`=== Backfill codigo_postal_receptor ${DRY_RUN ? '(DRY RUN — no writes)' : ''} ===\n`);

  const tenants = await prisma.tenant.findMany({
    where: { active: true },
    select: { id: true, rfc: true, databaseName: true },
    orderBy: { rfc: 'asc' },
  });

  console.log(`Tenants activos: ${tenants.length}\n`);

  const results: PerTenantResult[] = [];
  for (const t of tenants) {
    process.stdout.write(`[${t.rfc}] (${t.databaseName}) ... `);
    try {
      const r = await backfillTenant(t.id, t.rfc, t.databaseName);
      results.push(r);
      if (r.error) {
        console.log(`ERROR: ${r.error}`);
      } else if (r.scanned === 0) {
        console.log(`sin CFDIs candidatos (skip)`);
      } else {
        console.log(
          `scan=${r.scanned} parsed=${r.parsedOk} fail=${r.parseFailed} conCP=${r.withCp} upd=${r.updated}`,
        );
      }
    } catch (err: any) {
      console.log(`FATAL: ${err?.message || err}`);
      results.push({
        tenantId: t.id,
        rfc: t.rfc,
        databaseName: t.databaseName,
        scanned: 0,
        parsedOk: 0,
        parseFailed: 0,
        withCp: 0,
        updated: 0,
        error: err?.message || String(err),
      });
    }
  }

  const totalScanned = results.reduce((s, r) => s + r.scanned, 0);
  const totalUpdated = results.reduce((s, r) => s + r.updated, 0);
  const totalParseFailed = results.reduce((s, r) => s + r.parseFailed, 0);
  const tenantsTouched = results.filter(r => r.updated > 0).length;
  const tenantsFailed = results.filter(r => r.error).length;

  console.log(`\n=== Resumen ===`);
  console.log(`  Tenants procesados:   ${results.length}`);
  console.log(`  Tenants con backfill: ${tenantsTouched}`);
  console.log(`  CFDIs escaneados:     ${totalScanned}`);
  console.log(`  CFDIs actualizados:   ${totalUpdated}${DRY_RUN ? ' (rolled back)' : ''}`);
  if (totalParseFailed > 0) console.log(`  CFDIs parse fallo:    ${totalParseFailed}`);
  if (tenantsFailed > 0) console.log(`  Tenants con error:    ${tenantsFailed}`);

  await prisma.$disconnect();
  process.exit(tenantsFailed > 0 ? 1 : 0);
}

main().catch(async (err) => {
  console.error('Fatal:', err);
  await prisma.$disconnect().catch(() => {});
  process.exit(1);
});
