// PRUEBA DEL NUCLEO FISCAL (2026-07-28)
// Lo que se prueba es exactamente lo que el SENIAT audita: que el libro no se
// pueda alterar, que la numeracion no tenga saltos, que un error solo se
// corrija con nota de credito, y que si alguien mete la mano en el archivo se
// note. Corre en una carpeta temporal, no toca nada del negocio.
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { LibroFiscal } from '../dist/libro.js';
import { Bitacora } from '../dist/bitacora.js';
import { armarDocumento, armarNotaCredito, desglosar } from '../dist/armador.js';

let fallos = 0;
const check = (cond, msg) => {
  console.log((cond ? '  OK  ' : '  XX  ') + msg);
  if (!cond) fallos++;
};
const cent = (n) => Math.round(n * 100);

const dir = mkdtempSync(join(tmpdir(), 'fiscal-'));
const RUTA = join(dir, 'libro.jsonl');
const RUTA_EV = join(dir, 'bitacora.jsonl');

const EMISOR = {
  rif: 'J-50856106-6',
  razon_social: 'Emprendimiento de prueba',
  domicilio_fiscal: 'Araure, Portuguesa',
  precios_con_iva: true,
};

const venta = (id, cant = 1, precio = 10, alicuota = 16) => ({
  origen_id: id,
  canal: 'mesa',
  cliente: { nombre: 'Consumidor final' },
  renglones: [{ nombre: 'Arepa Dominó', cantidad: cant, precio_usd: precio, alicuota }],
  tasa_bs: 742.23,
  pago: { metodo: 'efectivo' },
});

try {
  console.log('\n== 1. El precio de la carta no cambia: el IVA sale hacia atras ==');
  const d = desglosar(10, 16, true);
  check(cent(d.base) + cent(d.iva) === 1000, `base ${d.base} + IVA ${d.iva} = 10,00 exacto`);
  check(cent(d.base) === 862, `la base de un plato de 10,00 al 16% es 8,62 (dio ${d.base})`);

  console.log('\n== 2. El documento se arma con su desglose por alicuota ==');
  const doc = armarDocumento(
    {
      ...venta('v1'),
      renglones: [
        { nombre: 'Arepa', cantidad: 2, precio_usd: 5, alicuota: 16 },
        { nombre: 'Jugo natural', cantidad: 1, precio_usd: 3, alicuota: 8 },
      ],
      otros_cargos: [{ nombre: 'Delivery', monto_usd: 2, alicuota: 16 }],
    },
    EMISOR,
  );
  check(cent(doc.base_usd) + cent(doc.iva_usd) === cent(doc.total_usd), `base + IVA = total (${doc.total_usd})`);
  check(cent(doc.total_usd) === 1500, `el cliente paga lo mismo que suma la carta (${doc.total_usd})`);
  check(doc.por_alicuota.length === 2, `separa las dos alicuotas (${doc.por_alicuota.map((a) => a.alicuota).join(' y ')})`);
  check(doc.total_bs != null && cent(doc.total_bs) === cent(15 * 742.23), 'y lo expresa tambien en bolivares');

  console.log('\n== 3. Numeracion ciega: automatica, sin saltos, sin huecos ==');
  const libro = new LibroFiscal({ ruta: RUTA, serie: 'A', equipo: 'PRUEBA-01' });
  const a1 = libro.asentar('factura', armarDocumento(venta('v-001'), EMISOR));
  const a2 = libro.asentar('factura', armarDocumento(venta('v-002'), EMISOR));
  const a3 = libro.asentar('factura', armarDocumento(venta('v-003'), EMISOR));
  check(a1.numero === 1 && a2.numero === 2 && a3.numero === 3, 'las facturas van 1, 2, 3');
  check(a1.control === 1 && a3.control === 3, 'el numero de control tambien es consecutivo');
  check(typeof libro.asentar === 'function' && libro.editar === undefined && libro.borrar === undefined, 'el libro NO expone editar ni borrar');

  console.log('\n== 4. La misma venta no se factura dos veces ==');
  const repetida = libro.asentar('factura', armarDocumento(venta('v-002'), EMISOR));
  check(repetida.numero === a2.numero, `un reintento devuelve la factura que ya existia (${repetida.numero})`);
  check(libro.resumen().total === 3, `y no agrega otra al libro (${libro.resumen().total} asientos)`);

  console.log('\n== 5. Un error se corrige con nota de credito, no borrando ==');
  const nc = libro.corregir({ tipo: 'factura', numero: 2 }, armarNotaCredito(a2.documento, 'Al cliente le cobraron de mas'));
  check(nc.tipo === 'nota_credito' && nc.numero === 1, 'la nota lleva su propia numeracion (nota 1)');
  check(nc.ref?.numero === 2, 'y apunta a la factura que corrige');
  check(cent(nc.documento.total_usd) === -cent(a2.documento.total_usd), 'con los montos en negativo');
  const original = libro.buscar('factura', 2);
  check(cent(original.documento.total_usd) === cent(a2.documento.total_usd), 'la factura original queda intacta');
  check(nc.control === 4, `el numero de control siguio corrido (${nc.control})`);

  console.log('\n== 6. La cadena detecta si alguien mete la mano ==');
  check(libro.verificar().integro, 'con el libro sano, la verificacion pasa');
  // Simulamos a alguien editando el archivo por fuera para bajar una venta.
  const lineas = readFileSync(RUTA, 'utf8').split('\n').filter(Boolean);
  const tocada = JSON.parse(lineas[0]);
  tocada.documento.total_usd = 1;
  lineas[0] = JSON.stringify(tocada);
  writeFileSync(RUTA, lineas.join('\n') + '\n');
  const libroTocado = new LibroFiscal({ ruta: RUTA, serie: 'A', equipo: 'PRUEBA-01' });
  const v = libroTocado.verificar();
  check(!v.integro, `la verificacion falla: ${v.problema}`);
  check(v.linea === 1, `y señala la linea exacta (${v.linea})`);

  console.log('\n== 7. Al reabrir, el correlativo sigue donde iba ==');
  const RUTA2 = join(dir, 'libro2.jsonl');
  const l1 = new LibroFiscal({ ruta: RUTA2, serie: 'A', equipo: 'PRUEBA-01' });
  l1.asentar('factura', armarDocumento(venta('x-1'), EMISOR));
  l1.asentar('factura', armarDocumento(venta('x-2'), EMISOR));
  const l2 = new LibroFiscal({ ruta: RUTA2, serie: 'A', equipo: 'PRUEBA-01' });
  const sig = l2.asentar('factura', armarDocumento(venta('x-3'), EMISOR));
  check(sig.numero === 3 && sig.control === 3, `sigue en 3 tras cerrar y abrir (${sig.numero})`);
  check(l2.verificar().integro, 'y la cadena sigue sana');

  console.log('\n== 8. Lo que pide un fiscal: documentos por rango de fechas ==');
  const hoy = new Date();
  const desde = new Date(hoy.getTime() - 60000);
  const hasta = new Date(hoy.getTime() + 60000);
  check(l2.entre(desde, hasta).length === 3, `saca los 3 documentos del rango (${l2.entre(desde, hasta).length})`);
  check(l2.entre(new Date('2020-01-01'), new Date('2020-12-31')).length === 0, 'y no trae nada de un rango vacio');

  console.log('\n== 9. Bitacora: quien, que, cuando y desde donde ==');
  const bit = new Bitacora(RUTA_EV);
  bit.anotar('mesera.ana', '192.168.1.20', 'documento_sellado', { numero: 1 });
  bit.anotar('dueno.diego', '192.168.1.10', 'consulta_fiscal', { rango: 'julio' });
  check(bit.todos().length === 2, 'quedan los dos eventos');
  check(bit.verificar().integro, 'la bitacora tambien va encadenada');
  const ev = readFileSync(RUTA_EV, 'utf8').split('\n').filter(Boolean);
  const evTocado = JSON.parse(ev[0]);
  evTocado.quien = 'otro';
  ev[0] = JSON.stringify(evTocado);
  writeFileSync(RUTA_EV, ev.join('\n') + '\n');
  const bit2 = new Bitacora(RUTA_EV);
  check(!bit2.verificar().integro, 'y si le cambian el autor a un evento, se nota');
} catch (e) {
  console.log('  XX  ' + e.message);
  fallos++;
} finally {
  rmSync(dir, { recursive: true, force: true });
}

console.log(fallos === 0 ? '\nNUCLEO FISCAL: TODO VERDE' : `\n${fallos} FALLO(S)`);
process.exitCode = fallos === 0 ? 0 : 1; // salida natural: process.exit() con undici cerrando revienta libuv en Node 24/Windows
