/**
 * Backfill de `fecha_emision` (y opcionalmente `fecha_cert_sat`) para CFDIs
 * sincronizados antes del fix de zona horaria. El parser convertía la fecha
 * del XML ("2025-12-31T18:37:51") asumiéndola como hora local de la máquina
 * y la guardaba en UTC ("2026-01-01T00:37:51Z"), corriendo 6 horas y a veces
 * sacando el CFDI de su mes/año correcto.
 *
 * Re-parsea la fecha literal del XML (atributo `Fecha=""` del Comprobante y
 * `FechaTimbrado=""` del TimbreFiscalDigital) y lo guarda como UTC-literal
 * (forzando 'Z' al string del XML).
 *
 * Solo aplica a CFDIs con `xml_original IS NOT NULL`. Idempotente.
 *
 * Uso:
 *   pnpm --filter @horux/api exec tsx scripts/backfill-fechas-tz.ts        # ejecuta
 *   pnpm --filter @horux/api exec tsx scripts/backfill-fechas-tz.ts --dry  # reporta
 */
import { prisma, tenantDb } from '../src/config/database.js';

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

function parseLiteral(str: string | null | undefined): Date | null {
  if (!str) return null;
  const s = String(str).trim();
  if (!s) return null;
  const hasTz = /[Zz]|[+-]\d{2}:?\d{2}$/.test(s);
  return new Date(hasTz ? s : s + 'Z');
}

function extractFechaFromXml(xml: string): string | null {
  // Atributo Fecha del root <cfdi:Comprobante Fecha="...">
  const m = xml.match(/<cfdi:Comprobante\b[^>]*\bFecha="([^"]+)"/);
  return m ? m[1] : null;
}

function extractFechaTimbradoFromXml(xml: string): string | null {
  const m = xml.match(/<tfd:TimbreFiscalDigital\b[^>]*\bFechaTimbrado="([^"]+)"/);
  return m ? m[1] : null;
}

interface PerTenantResult {
  tenantId: string;
  rfc: string;
  databaseName: string;
  scanned: number;
  updatedFechaEmision: number;
  updatedFechaCert: number;
  noChange: number;
  noXmlMatch: number;
  error?: string;
}

async function backfillTenant(tenantId: string, rfc: string, databaseName: string): Promise<PerTenantResult> {
  const result: PerTenantResult = {
    tenantId, rfc, databaseName,
    scanned: 0, updatedFechaEmision: 0, updatedFechaCert: 0, noChange: 0, noXmlMatch: 0,
  };
  const pool = await tenantDb.getPool(tenantId, databaseName);

  const { rows } = await pool.query<{
    id: number;
    uuid: string;
    fecha_emision: Date;
    fecha_cert_sat: Date | null;
    xml_original: string;
  }>(
    `SELECT id, uuid, fecha_emision, fecha_cert_sat, xml_original
       FROM cfdis
      WHERE xml_original IS NOT 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) {
      const fechaXml = extractFechaFromXml(row.xml_original);
      const fechaTimbradoXml = extractFechaTimbradoFromXml(row.xml_original);

      if (!fechaXml) {
        result.noXmlMatch++;
        continue;
      }

      const nuevaFecha = parseLiteral(fechaXml);
      const nuevaFechaCert = fechaTimbradoXml ? parseLiteral(fechaTimbradoXml) : null;

      if (!nuevaFecha) {
        result.noXmlMatch++;
        continue;
      }

      const fechaEmisionActual = row.fecha_emision?.toISOString();
      const fechaCertActual = row.fecha_cert_sat?.toISOString();
      const fechaEmisionNueva = nuevaFecha.toISOString();
      const fechaCertNueva = nuevaFechaCert?.toISOString();

      let updatedThis = false;
      if (fechaEmisionActual !== fechaEmisionNueva) {
        await client.query(
          `UPDATE cfdis SET fecha_emision = $2 WHERE id = $1`,
          [row.id, nuevaFecha],
        );
        result.updatedFechaEmision++;
        updatedThis = true;
      }
      if (nuevaFechaCert && fechaCertActual !== fechaCertNueva) {
        await client.query(
          `UPDATE cfdis SET fecha_cert_sat = $2 WHERE id = $1`,
          [row.id, nuevaFechaCert],
        );
        result.updatedFechaCert++;
        updatedThis = true;
      }
      if (!updatedThis) result.noChange++;
    }

    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 fechas (fecha_emision + fecha_cert_sat) ${DRY_RUN ? '(DRY RUN)' : ''} ===\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.scanned === 0) console.log(`sin XMLs (skip)`);
      else console.log(
        `scan=${r.scanned} upd_emision=${r.updatedFechaEmision} upd_cert=${r.updatedFechaCert} ` +
          `sin_cambio=${r.noChange} sin_match=${r.noXmlMatch}${DRY_RUN ? ' (rolled back)' : ''}`,
      );
    } catch (err: any) {
      console.log(`FATAL: ${err?.message || err}`);
    }
  }

  const totalScan = results.reduce((s, r) => s + r.scanned, 0);
  const totalUpdEm = results.reduce((s, r) => s + r.updatedFechaEmision, 0);
  const totalUpdCert = results.reduce((s, r) => s + r.updatedFechaCert, 0);
  const tFail = results.filter(r => r.error).length;

  console.log(`\n=== Resumen ===`);
  console.log(`  Tenants procesados:      ${results.length}`);
  console.log(`  CFDIs escaneados:        ${totalScan}`);
  console.log(`  fecha_emision actualizada: ${totalUpdEm}`);
  console.log(`  fecha_cert_sat actualizada: ${totalUpdCert}`);
  if (tFail > 0) console.log(`  Tenants con error:       ${tFail}`);

  await prisma.$disconnect();
  process.exit(tFail > 0 ? 1 : 0);
}
main().catch(async e => { console.error(e); await prisma.$disconnect().catch(() => {}); process.exit(1); });
