// PRUEBA DEL RELOJ DEL EQUIPO (2026-07-29)
//
// La hora de un documento fiscal la pone Windows, y a Windows le cambia la hora
// cualquiera que se siente en esa computadora. Dos clics y la venta de hoy sale
// fechada la semana pasada. La Estacion tiene que darse cuenta y plantarse.
//
// Se prueba contra una hora de referencia de mentira (un servidorcito local que
// devuelve la hora que le mandemos), para poder mover el reloj sin tocar el de
// la maquina de nadie.
import { createServer } from 'node:http';
import { mkdtempSync, readFileSync, rmSync, writeFileSync } 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';

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

const dir = mkdtempSync(join(tmpdir(), 'fiscal-reloj-'));
const PUERTO = 8753;

// La hora que devuelve la referencia. Moverla es lo mismo que mover el reloj de
// la computadora, pero al reves: si la referencia se adelanta una hora, es como
// si el equipo se hubiera atrasado una hora.
let desfaseReferenciaMs = 0;
let pedidos = { hora: 0, estacion: 0 };
const falso = createServer((req, res) => {
  if (req.url.startsWith('/api/hora')) {
    pedidos.hora++;
    const ahora = new Date(Date.now() + desfaseReferenciaMs);
    res.writeHead(200, { 'content-type': 'application/json' });
    return res.end(JSON.stringify({ hora: ahora.toISOString(), epoch_ms: ahora.getTime() }));
  }
  // Cualquier otra cosa es la puerta de la nube: se cuenta para comprobar que
  // con el reloj corrido ni se asoma por ahi.
  pedidos.estacion++;
  res.writeHead(200, { 'content-type': 'application/json' });
  res.end(JSON.stringify({ 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',
  hora_url: `http://127.0.0.1:${PUERTO}/api/hora`,
};

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

  console.log('\n== 1. Con la hora en su sitio, se sella normal ==');
  let est = await EstacionFiscal.abrir(dir);
  let r = await est.revisarReloj();
  check(r.bien, `la estacion compara y da el visto bueno (${r.texto})`);
  check(pedidos.hora > 0, 'preguntando la hora de referencia');
  const uno = await est.recibirVenta(venta('r-1'));
  check(uno.sellada === true, `la venta se sella (${uno.serie}-${uno.numero})`);

  console.log('\n== 2. Le mueven la hora al equipo y la estacion se planta ==');
  // Como si alguien atrasara el reloj de Windows 40 minutos.
  desfaseReferenciaMs = 40 * 60_000;
  r = await est.revisarReloj();
  check(!r.bien, `se da cuenta sola (${r.texto})`);
  const dos = await est.recibirVenta(venta('r-2'));
  check(dos.sellada === false, 'y no sella nada mas');
  check(/hora de esta computadora/i.test(dos.problema ?? ''), `diciendo que arreglar: "${(dos.problema ?? '').slice(0, 70)}..."`);
  const anulacion = await est.anular(1, 'a ver si cuela');
  check(anulacion.sellada === false, 'tampoco deja anular con el reloj corrido');

  console.log('\n== 3. La venta no se pierde: queda en el buzon ==');
  const buzon = est.buzon();
  check(buzon.some((b) => b.venta.origen_id === 'r-2'), 'la venta que no se pudo sellar quedo guardada');
  check(est.estado().documentos === 1, 'y el libro sigue con un solo documento, sin basura');

  console.log('\n== 4. Con el reloj corrido ni se hala de la nube ==');
  // Si se halara, cada venta volveria marcada como fallida por algo que no
  // tiene que ver con ella. Mejor que se queden quietas en la cola.
  const antes = pedidos.estacion;
  const conNube = await EstacionFiscal.abrir(dir);
  guardarConfig(dir, { ...CONFIG, nube: { url: `http://127.0.0.1:${PUERTO}`, llave: 'la-que-sea' } });
  const ronda = await (await EstacionFiscal.abrir(dir)).sincronizarConNube();
  check(pedidos.estacion === antes, 'no toco la puerta de la nube');
  check(/hora/i.test(ronda.problema ?? ''), `y dice por que (${(ronda.problema ?? '').slice(0, 40)}...)`);
  check(!!conNube, 'la estacion sigue en pie, no se cae');

  console.log('\n== 5. Se corrige la hora y todo vuelve solo ==');
  desfaseReferenciaMs = 0;
  est = await EstacionFiscal.abrir(dir);
  r = await est.revisarReloj();
  check(r.bien, `la estacion lo confirma (${r.texto})`);
  const tres = await est.recibirVenta(venta('r-2'));
  check(tres.sellada === true, `la venta que habia quedado por fuera ya se sella (${tres.serie}-${tres.numero})`);

  console.log('\n== 6. Sin internet NO se traba el negocio ==');
  // Un local sin señal tiene que poder seguir vendiendo. Lo que no puede es
  // seguir sellando con el reloj que YA se sabe corrido, y eso es lo de arriba.
  guardarConfig(dir, { ...CONFIG, hora_url: 'http://127.0.0.1:9/no-existe' });
  const sinSeñal = await EstacionFiscal.abrir(dir);
  r = await sinSeñal.revisarReloj();
  check(r.bien, `no se traba por no poder comparar (${r.texto})`);
  const cuatro = await sinSeñal.recibirVenta(venta('r-4'));
  check(cuatro.sellada === true, 'y sigue sellando con normalidad');

  console.log('\n== 7. Un local sin internet no se queda trancado para siempre ==');
  // Peor escenario: se detecta el reloj corrido y JUSTO ahi se cae el internet.
  // Sin valvula, ese negocio se queda sin poder facturar y sin manera de
  // destrabarse. Pasadas 6 horas sin poder volver a comparar, la comprobacion
  // vieja deja de trancar (la del libro, que no necesita señal, sigue).
  const rutaReloj = join(dir, 'estado', 'reloj.json');
  writeFileSync(
    rutaReloj,
    JSON.stringify({
      revisado_en: new Date(Date.now() - 7 * 3600_000).toISOString(),
      desfase_seg: 2400,
      corrido: true,
      problema: 'fetch failed',
    }),
  );
  const destrabada = await EstacionFiscal.abrir(dir);
  const seis = await destrabada.recibirVenta(venta('r-6'));
  check(seis.sellada === true, 'con la comprobacion vencida vuelve a sellar');
  check(destrabada.estado().reloj_bien === false, `pero la pantalla lo sigue avisando (${destrabada.estado().reloj})`);
  // Y con la comprobacion fresca, tranca igual que antes.
  writeFileSync(
    rutaReloj,
    JSON.stringify({ revisado_en: new Date().toISOString(), desfase_seg: 2400, corrido: true, problema: null }),
  );
  const trancada = await EstacionFiscal.abrir(dir);
  check((await trancada.recibirVenta(venta('r-7'))).sellada === false, 'y recien comprobada tranca igual que antes');
  rmSync(rutaReloj, { force: true });

  console.log('\n== 8. El libro se defiende solo, sin internet ==');
  // Esta es la comprobacion que no necesita señal: un documento nuevo no puede
  // tener fecha anterior al ultimo ya sellado.
  const rutaLibro = join(dir, 'estado', 'libro.jsonl');
  const lineas = readFileSync(rutaLibro, 'utf8').split('\n').filter((l) => l.trim());
  const ultimo = JSON.parse(lineas[lineas.length - 1]);
  // Se simula que el ultimo documento quedo sellado dentro de dos horas, que es
  // lo que pasaria si alguien adelanta el reloj, sella, y despues lo devuelve.
  ultimo.sellado_en = new Date(Date.now() + 2 * 3600_000).toISOString();
  lineas[lineas.length - 1] = JSON.stringify(ultimo);
  writeFileSync(rutaLibro, lineas.join('\n') + '\n');
  const conLibroAdelantado = await EstacionFiscal.abrir(dir);
  const cinco = await conLibroAdelantado.recibirVenta(venta('r-5'));
  check(cinco.sellada === false, 'no sella un documento con fecha anterior al ultimo');
  check(/ATRAS del ultimo documento/i.test(cinco.problema ?? ''), `y explica que pasa: "${(cinco.problema ?? '').slice(0, 60)}..."`);
} catch (e) {
  console.log('  XX  ' + e.message);
  fallos++;
} finally {
  falso.close();
  rmSync(dir, { recursive: true, force: true });
}

console.log(fallos === 0 ? '\nRELOJ 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
