// PRUEBA DEL RESPALDO QUE SALE DEL EQUIPO (2026-07-29)
//
// Hasta ahora, si se quemaba la laptop del negocio se quemaba el libro fiscal
// completo, que es la defensa del contribuyente. El respaldo cifrado existia
// pero se quedaba en la misma computadora que se puede quemar.
//
// Se prueba, contra una imprenta de mentira (un servidorcito local que guarda lo
// que le mandan):
//   1. El libro sale del equipo solo, cifrado, y solo lo que falte.
//   2. Lo que sale es ilegible para el que lo guarda.
//   3. El dia malo: maquina nueva, se baja el respaldo y el libro vuelve entero.
//   4. Con la frase mala no se abre, y no se toca nada de la instalacion.
//   5. Un libro con documentos NO se pisa jamas.
import { createServer } from 'node:http';
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { EstacionFiscal } from '../dist/estacion.js';
import { guardarConfig } from '../dist/config.js';
import { fraseDeRecuperacion } from '../dist/respaldo.js';

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

const dir = mkdtempSync(join(tmpdir(), 'fiscal-resp-'));
const nueva = mkdtempSync(join(tmpdir(), 'fiscal-resp-nueva-'));
const PUERTO = 8754;

// La imprenta de mentira: guarda lineas por RIF y no sabe abrirlas.
const deposito = new Map();
const falso = createServer(async (req, res) => {
  const trozos = [];
  for await (const t of req) trozos.push(t);
  const p = JSON.parse(Buffer.concat(trozos).toString('utf8') || '{}');
  const llave = (req.headers.authorization ?? '').replace(/^Bearer\s+/i, '');
  const responder = (d) => {
    res.writeHead(llave ? 200 : 401, { 'content-type': 'application/json' });
    res.end(JSON.stringify(d));
  };
  const mias = deposito.get(p.rif) ?? new Map();
  deposito.set(p.rif, mias);
  if (p.accion === 'respaldo_hasta') {
    const seqs = [...mias.keys()];
    return responder({ ultimo_seq: seqs.length ? Math.max(...seqs) : 0, lineas: mias.size });
  }
  if (p.accion === 'respaldo_guardar') {
    let guardadas = 0;
    for (const l of p.datos.lineas ?? []) {
      if (!mias.has(l.seq)) {
        mias.set(l.seq, l);
        guardadas++;
      }
    }
    const seqs = [...mias.keys()];
    return responder({ guardadas, ultimo_seq: seqs.length ? Math.max(...seqs) : 0 });
  }
  if (p.accion === 'respaldo_bajar') {
    const desde = p.datos.desde ?? 0;
    const lineas = [...mias.values()].filter((l) => l.seq > desde).sort((a, b) => a.seq - b.seq).slice(0, p.datos.limite ?? 200);
    return responder({ lineas });
  }
  return responder({ pendientes_en_nube: 0, volver_en_segundos: 60 });
});

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

const CONFIG = {
  emisor: {
    rif: 'J-50856106-6',
    razon_social: 'Arepa Power de prueba',
    domicilio_fiscal: 'Araure, Portuguesa',
    precios_con_iva: true,
    alicuota_general: 16,
  },
  serie: 'A',
  sucursal: '1',
  equipo: 'FISCAL-01',
  imprenta: { adaptador: 'practica', nombre: 'Practica' },
  respaldo: null,
  usando: 'imprenta',
  nube: { url: `http://127.0.0.1:${PUERTO}`, llave: 'la-llave-del-negocio' },
  hora_url: 'http://127.0.0.1:9/no-existe', // sin referencia de hora: no debe estorbar
};

try {
  await new Promise((listo) => falso.listen(PUERTO, '127.0.0.1', listo));
  guardarConfig(dir, CONFIG);

  console.log('\n== 1. El libro sale del equipo, cifrado ==');
  let est = await EstacionFiscal.abrir(dir);
  await est.recibirVenta(venta('b-1'));
  await est.recibirVenta(venta('b-2'));
  let r = await est.subirRespaldo();
  check(r.subidas === 3, `salieron los 2 documentos mas la cabecera (${r.subidas} lineas)`);
  check(est.estadoRespaldo().al_dia, 'y la estacion sabe que esta al dia');

  console.log('\n== 2. Lo que sale es ilegible para el que lo guarda ==');
  const guardadas = [...deposito.get('J-50856106-6').values()];
  const enElDeposito = JSON.stringify(guardadas);
  check(!/Arepa/.test(enElDeposito), 'el deposito no tiene ni el nombre de lo que se vendio');
  check(!/50856106/.test(enElDeposito.replace(/"seq":\d+/g, '')), 'ni datos del negocio en claro');
  check(guardadas.every((l) => l.seq === 0 || (l.iv && l.tag && l.datos)), 'cada linea va con su sello propio');

  console.log('\n== 3. Solo sale lo que falta ==');
  r = await est.subirRespaldo();
  check(r.subidas === 0, 'mandar de nuevo no sube nada repetido');
  await est.recibirVenta(venta('b-3'));
  r = await est.subirRespaldo();
  check(r.subidas === 1, 'una venta nueva manda una sola linea');

  console.log('\n== 4. El dia malo: maquina nueva y el libro vuelve ==');
  const frase = fraseDeRecuperacion(dir);
  check(frase.split('-').length === 6, `la frase se genero sola (${frase.split('-')[0]}-...-${frase.split('-')[5]})`);
  guardarConfig(nueva, CONFIG);
  const enBlanco = await EstacionFiscal.abrir(nueva);
  check(enBlanco.documentos().length === 0, 'la estacion nueva arranca vacia');
  const traido = await enBlanco.traerRespaldoDeLaNube(frase);
  check(traido.documentos === 3, `bajo y abrio los 3 documentos (${traido.documentos})`);
  const reabierta = await EstacionFiscal.abrir(nueva);
  check(reabierta.estado().documentos === 3, 'el libro quedo levantado en la maquina nueva');
  check(reabierta.estado().libro_integro === true, 'y la cadena de hashes cuadra completa');
  const viejo = readFileSync(join(dir, 'estado', 'libro.jsonl'), 'utf8');
  const nuevoLibro = readFileSync(join(nueva, 'estado', 'libro.jsonl'), 'utf8');
  check(viejo.trim() === nuevoLibro.trim(), 'y es exactamente el mismo libro, linea por linea');

  console.log('\n== 5. Con la frase mala no se abre nada ==');
  const otra = mkdtempSync(join(tmpdir(), 'fiscal-resp-mala-'));
  guardarConfig(otra, CONFIG);
  const conFraseMala = await EstacionFiscal.abrir(otra);
  let problema = '';
  try {
    await conFraseMala.traerRespaldoDeLaNube('arepa-arepa-arepa-arepa-arepa-arepa');
  } catch (e) {
    problema = e.message;
  }
  check(/frase de recuperacion no corresponde/i.test(problema), `avisa que la frase no es (${problema.slice(0, 50)}...)`);
  check((await EstacionFiscal.abrir(otra)).documentos().length === 0, 'y la instalacion queda como estaba, sin libro a medias');
  rmSync(otra, { recursive: true, force: true });

  console.log('\n== 6. Un libro con documentos no se pisa nunca ==');
  problema = '';
  try {
    await est.traerRespaldoDeLaNube(frase);
  } catch (e) {
    problema = e.message;
  }
  check(/ya tiene libro/i.test(problema), `se niega en redondo (${problema.slice(0, 45)}...)`);
  check(est.estado().documentos === 3, 'y el libro de esa estacion sigue completo');

  console.log('\n== 7. Al sellar, el respaldo sale SOLO en segundos ==');
  // La ventana de perdida: antes el respaldo salia cada 10 minutos, o sea que
  // un rayo en ese rato se llevaba hasta 10 minutos de ventas. Ahora el
  // sellado mismo programa la subida (agrupando rafagas), sin que nadie llame
  // subirRespaldo a mano.
  const antesAuto = deposito.get('J-50856106-6').size;
  await est.recibirVenta(venta('b-auto'));
  await new Promise((s) => setTimeout(s, 4_500));
  const despuesAuto = deposito.get('J-50856106-6').size;
  check(despuesAuto === antesAuto + 1, `la venta subio sola, sin esperar los 10 minutos (${antesAuto} -> ${despuesAuto})`);
  check(est.estadoRespaldo().al_dia, 'y la estacion se sabe al dia');

  console.log('\n== 8. Sin internet no se cae, solo lo anota ==');
  guardarConfig(dir, { ...CONFIG, nube: { url: 'http://127.0.0.1:9', llave: 'x' } });
  const sinSeñal = await EstacionFiscal.abrir(dir);
  const conProblema = await sinSeñal.subirRespaldo();
  check(conProblema.subidas === 0 && !!conProblema.problema, `lo anota y sigue (${(conProblema.problema ?? '').slice(0, 40)}...)`);
  check((await sinSeñal.recibirVenta(venta('b-4'))).sellada === true, 'y el negocio sigue vendiendo igual');
} catch (e) {
  console.log('  XX  ' + e.message);
  fallos++;
} finally {
  falso.close();
  rmSync(dir, { recursive: true, force: true });
  rmSync(nueva, { recursive: true, force: true });
}

console.log(fallos === 0 ? '\nRESPALDO FUERA DEL EQUIPO: 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
