/**
 * Backfill de cfdis.contribuyente_id para los despachos.
 *
 * Asocia CFDIs huérfanos (contribuyente_id NULL) con el contribuyente cuyo RFC
 * coincide con rfc_emisor (si type='EMITIDO') o rfc_receptor (si type='RECIBIDO').
 *
 * Causa raíz: retry path de sat.service.ts construía SyncContext sin
 * contribuyenteId (bug fixed 2026-04-20).
 *
 * Idempotente: solo actualiza filas con contribuyente_id IS NULL y match único
 * por RFC. Si no hay contribuyentes en el tenant (Horux360 clásico), no-op.
 *
 * Uso:
 *   pnpm --filter @horux/api exec tsx scripts/backfill-cfdi-contribuyente.ts          # ejecuta
 *   pnpm --filter @horux/api exec tsx scripts/backfill-cfdi-contribuyente.ts --dry    # reporta sin escribir
 */
import { prisma, tenantDb } from '../src/config/database.js';

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

interface PerTenantResult {
  tenantId: string;
  rfc: string;
  databaseName: string;
  contribuyentesCount: number;
  updated: number;
  perContribuyente: Array<{ rfc: string; entidadId: string; rows: number }>;
  error?: string;
}

async function backfillTenant(
  tenantId: string,
  rfc: string,
  databaseName: string,
): Promise<PerTenantResult> {
  const result: PerTenantResult = {
    tenantId,
    rfc,
    databaseName,
    contribuyentesCount: 0,
    updated: 0,
    perContribuyente: [],
  };

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

  const { rows: contribs } = await pool.query<{ entidad_id: string; rfc: string }>(
    `SELECT entidad_id, rfc FROM contribuyentes`,
  );
  result.contribuyentesCount = contribs.length;
  if (contribs.length === 0) return result;

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

    const sql = `
      UPDATE cfdis c
      SET contribuyente_id = cnt.entidad_id
      FROM contribuyentes cnt
      WHERE c.contribuyente_id IS NULL
        AND (
          (c.type = 'EMITIDO'  AND cnt.rfc = c.rfc_emisor) OR
          (c.type = 'RECIBIDO' AND cnt.rfc = c.rfc_receptor)
        )
      RETURNING cnt.entidad_id as "entidadId", cnt.rfc as "rfcContrib"
    `;

    const { rows: updated } = await client.query<{ entidadId: string; rfcContrib: string }>(sql);
    result.updated = updated.length;

    const byContrib = new Map<string, { rfc: string; rows: number }>();
    for (const row of updated) {
      const cur = byContrib.get(row.entidadId);
      if (cur) cur.rows += 1;
      else byContrib.set(row.entidadId, { rfc: row.rfcContrib, rows: 1 });
    }
    result.perContribuyente = Array.from(byContrib.entries()).map(([entidadId, v]) => ({
      entidadId,
      rfc: v.rfc,
      rows: v.rows,
    }));

    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 cfdis.contribuyente_id ${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.contribuyentesCount === 0) {
        console.log(`sin contribuyentes (skip)`);
      } else {
        console.log(`${r.contribuyentesCount} contribs, ${r.updated} CFDIs backfill`);
        for (const pc of r.perContribuyente) {
          console.log(`    ${pc.rfc}: ${pc.rows}`);
        }
      }
    } catch (err: any) {
      console.log(`FATAL: ${err?.message || err}`);
      results.push({
        tenantId: t.id,
        rfc: t.rfc,
        databaseName: t.databaseName,
        contribuyentesCount: 0,
        updated: 0,
        perContribuyente: [],
        error: err?.message || String(err),
      });
    }
  }

  const totalUpdated = results.reduce((s, r) => s + r.updated, 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 actualizados:  ${totalUpdated}${DRY_RUN ? ' (rolled back)' : ''}`);
  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);
});
