/**
 * Backfill de `saldo_pendiente_mxn` para CFDIs I PPD vigentes. Computa el
 * saldo con la fórmula centralizada en `utils/saldo.ts` (pagos P + NC no-07
 * + anticipo aplicado si es I/07) y lo persiste.
 *
 * Idempotente: corrido varias veces produce el mismo resultado. Safe para
 * repetir después de un sync SAT masivo o si se sospecha drift.
 *
 * Uso:
 *   pnpm --filter @horux/api exec tsx scripts/backfill-saldo-pendiente.ts        # ejecuta
 *   pnpm --filter @horux/api exec tsx scripts/backfill-saldo-pendiente.ts --dry  # reporta sin escribir
 */
import { prisma, tenantDb } from '../src/config/database.js';
import { saldoComputadoExpr } from '../src/utils/saldo.js';

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

interface PerTenantResult {
  tenantId: string;
  rfc: string;
  databaseName: string;
  iPpdsVigentes: number;
  actualizadas: number;
  saldoTotalAntes: number;
  saldoTotalDespues: number;
  error?: string;
}

async function backfillTenant(
  tenantId: string,
  rfc: string,
  databaseName: string,
): Promise<PerTenantResult> {
  const result: PerTenantResult = {
    tenantId,
    rfc,
    databaseName,
    iPpdsVigentes: 0,
    actualizadas: 0,
    saldoTotalAntes: 0,
    saldoTotalDespues: 0,
  };

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

  const { rows: count } = await pool.query<{ n: number; suma: string }>(
    `SELECT COUNT(*)::int AS n, COALESCE(SUM(COALESCE(saldo_pendiente_mxn, total_mxn)), 0) AS suma
       FROM cfdis
      WHERE tipo_comprobante = 'I' AND metodo_pago = 'PPD'
        AND status NOT IN ('Cancelado', '0')`,
  );
  result.iPpdsVigentes = count[0]?.n || 0;
  result.saldoTotalAntes = Number(count[0]?.suma || 0);
  if (result.iPpdsVigentes === 0) return result;

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

    // UPDATE masivo con la fórmula centralizada (misma que hooks y reporte).
    const expr = saldoComputadoExpr('c');
    const { rowCount } = await client.query(
      `UPDATE cfdis c
          SET saldo_pendiente_mxn = ${expr}
        WHERE c.tipo_comprobante = 'I'
          AND c.metodo_pago = 'PPD'
          AND c.status NOT IN ('Cancelado', '0')`,
    );
    result.actualizadas = rowCount ?? 0;

    const { rows: cntDespues } = await client.query<{ suma: string }>(
      `SELECT COALESCE(SUM(COALESCE(saldo_pendiente_mxn, total_mxn)), 0) AS suma
         FROM cfdis
        WHERE tipo_comprobante = 'I' AND metodo_pago = 'PPD'
          AND status NOT IN ('Cancelado', '0')`,
    );
    result.saldoTotalDespues = Number(cntDespues[0]?.suma || 0);

    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;
}

function fmt(n: number): string {
  return n.toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
}

async function main() {
  console.log(`=== Backfill saldo_pendiente_mxn ${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}] ... `);
    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.iPpdsVigentes === 0) {
        console.log(`sin I PPD vigentes (skip)`);
      } else {
        const delta = r.saldoTotalDespues - r.saldoTotalAntes;
        console.log(
          `I_PPD=${r.iPpdsVigentes} upd=${r.actualizadas} ` +
            `antes=${fmt(r.saldoTotalAntes)} despues=${fmt(r.saldoTotalDespues)} ` +
            `Δ=${delta >= 0 ? '+' : ''}${fmt(delta)}${DRY_RUN ? ' (rolled back)' : ''}`,
        );
      }
    } catch (err: any) {
      console.log(`FATAL: ${err?.message || err}`);
      results.push({
        tenantId: t.id,
        rfc: t.rfc,
        databaseName: t.databaseName,
        iPpdsVigentes: 0,
        actualizadas: 0,
        saldoTotalAntes: 0,
        saldoTotalDespues: 0,
        error: err?.message || String(err),
      });
    }
  }

  const totalI = results.reduce((s, r) => s + r.iPpdsVigentes, 0);
  const totalAntes = results.reduce((s, r) => s + r.saldoTotalAntes, 0);
  const totalDespues = results.reduce((s, r) => s + r.saldoTotalDespues, 0);
  const tenantsFailed = results.filter(r => r.error).length;

  console.log(`\n=== Resumen ===`);
  console.log(`  Tenants procesados:      ${results.length}`);
  console.log(`  I PPD vigentes total:    ${totalI}`);
  console.log(`  Saldo total antes:       ${fmt(totalAntes)}`);
  console.log(`  Saldo total después:     ${fmt(totalDespues)}${DRY_RUN ? ' (rolled back)' : ''}`);
  console.log(`  Delta (recuperado):      ${fmt(totalAntes - totalDespues)} (saldo que ya no está pendiente)`);
  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);
});
