import 'dotenv/config';
import { PrismaClient } from '@prisma/client';
import { XMLParser } from 'fast-xml-parser';

/**
 * Vincula notas de crédito huérfanas con su factura, consultando el DE a SIFEN.
 *
 * Una NC se asocia a su factura por el CDC que lleva en `gCamDEAsoc`. Si la NC se
 * importó cuando la factura todavía no existía en el sistema, esa búsqueda no encuentra
 * nada y la NC queda con `factura_cab_id` en NULL: existe, pero suelta. Es justo lo que
 * pasó con las NC de DOBA de 2025, cargadas meses antes de recuperar las facturas.
 *
 * Nada en la base guarda a qué factura apuntaba (`documentos_asociados` viene vacío),
 * así que el vínculo hay que traerlo de nuevo de SIFEN.
 *
 * Uso:
 *   pnpm vincular:nc -- --empresa=<uuid> --dry
 *   pnpm vincular:nc -- --empresa=<uuid>
 */

const prisma = new PrismaClient();

function arg(nombre: string): string | undefined {
  const m = process.argv.slice(2).find((a) => a.startsWith(`--${nombre}=`));
  return m?.split('=').slice(1).join('=');
}
const DRY = process.argv.includes('--dry');

async function consultarLote(base: string, ruc: string, cdcs: string[]): Promise<any[]> {
  const res = await fetch(`${base}/api/consultar-cdc`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ ruc, incluir_xml: true, documentos: cdcs.map((cdc) => ({ cdc })) }),
  });
  const json: any = await res.json().catch(() => null);
  if (!res.ok || json?.status === 'error') throw new Error(json?.message ?? `HTTP ${res.status}`);
  return Array.isArray(json?.response) ? json.response : [];
}

/** Devuelve el CDC de la factura que la NC referencia en gCamDEAsoc. */
function cdcReferenciado(xml: string): string | null {
  const parsed = new XMLParser({ ignoreAttributes: false, parseTagValue: false, trimValues: true }).parse(xml);
  const de = (parsed?.rDE ?? parsed)?.DE;
  const asoc = de?.gCamDEAsoc;
  const lista = Array.isArray(asoc) ? asoc : asoc ? [asoc] : [];
  for (const a of lista) {
    const ref = String(a?.dCdCDERef ?? '').trim();
    if (ref) return ref;
  }
  return null;
}

async function main() {
  const empresaId = arg('empresa');
  if (!empresaId) throw new Error('Falta --empresa=<uuid>');

  const base = (process.env.MIDDLEWARE_SIFEN_URL ?? '').replace(/\/+$/, '');
  if (!base) throw new Error('MIDDLEWARE_SIFEN_URL no configurado');

  const empresa = await prisma.empresas.findUnique({ where: { id: empresaId }, select: { ruc: true } });
  if (!empresa?.ruc) throw new Error('Empresa no encontrada o sin RUC');

  const huerfanas = await prisma.nota_credito_cab.findMany({
    where: { empresa_id: empresaId, factura_cab_id: null, cdc: { not: null } },
    select: { id: true, cdc: true, cliente_id: true },
  });
  console.log(`NC sin factura vinculada: ${huerfanas.length}`);
  if (huerfanas.length === 0) return;
  console.log(`Modo: ${DRY ? 'DRY-RUN' : 'REAL'}\n`);

  const porCdc = new Map(huerfanas.map((n) => [String(n.cdc), n]));
  const cdcs = [...porCdc.keys()];
  const LOTE = 25;

  let vinculadas = 0;
  let sinRef = 0;
  let facturaAusente = 0;
  const fallidos: string[] = [];

  for (let i = 0; i < cdcs.length; i += LOTE) {
    const lote = cdcs.slice(i, i + LOTE);
    process.stdout.write(`Lote ${Math.floor(i / LOTE) + 1} (${lote.length})... `);

    let resp: any[];
    try {
      resp = await consultarLote(base, empresa.ruc, lote);
    } catch (e: any) {
      console.log(`ERROR: ${e.message}`);
      fallidos.push(...lote);
      continue;
    }

    let n = 0;
    for (const item of resp) {
      const nc = porCdc.get(String(item?.cdc));
      if (!nc || !item?.xml_documento) { fallidos.push(String(item?.cdc)); continue; }

      let ref: string | null = null;
      try {
        ref = cdcReferenciado(String(item.xml_documento));
      } catch { /* XML ilegible: cae en sinRef */ }
      if (!ref) { sinRef++; continue; }

      const factura = await prisma.factura_cab.findFirst({
        where: { empresa_id: empresaId, cdc: ref },
        select: { id: true, cliente_id: true },
      });
      // La factura referenciada puede ser de un período que todavía no se recuperó.
      if (!factura) { facturaAusente++; continue; }

      if (!DRY) {
        await prisma.nota_credito_cab.update({
          where: { id: nc.id },
          data: {
            factura_cab_id: factura.id,
            // Solo se completa si falta: no se pisa un cliente ya asignado.
            ...(nc.cliente_id ? {} : { cliente_id: factura.cliente_id }),
          },
        });
      }
      vinculadas++;
      n++;
    }
    console.log(`ok — ${n} vinculadas`);
  }

  console.log('\n─── Resumen ───');
  console.log(`Vinculadas          : ${vinculadas}${DRY ? ' (simulado)' : ''}`);
  console.log(`Sin CDC referenciado: ${sinRef}`);
  console.log(`Factura no está     : ${facturaAusente}`);
  console.log(`Fallidos en SIFEN   : ${fallidos.length}`);
}

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