// PRUEBA DEL BLOQUE 9: LA ESTACION ANDANDO (2026-07-28)
// Levanta el servidor local de verdad y hace lo que va a pasar en el negocio:
// configurar, que Gustito entregue una venta, ver el estado, anular con nota de
// credito y que el fiscal consulte con su clave. Todo contra 127.0.0.1.
//
// Actualizada 2026-07-29: la estacion ya tiene portero, asi que el dueño entra
// con su clave y los programas del negocio entregan la venta con la llave de
// maquina. Quien prueba el portero en si es prueba_portero.mjs.
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { iniciarServidor } from '../dist/servidor.js';

import { Acceso } from '../dist/acceso.js';

let fallos = 0;
const check = (cond, msg) => {
  console.log((cond ? '  OK  ' : '  XX  ') + msg);
  if (!cond) fallos++;
};
const dir = mkdtempSync(join(tmpdir(), 'fiscal-srv-'));
const PUERTO = 8749; // puerto de prueba, no pisa al real
const base = `http://127.0.0.1:${PUERTO}`;
let servidor;

const CLAVE_DUENO = 'clave-del-dueno-2026';
const acceso = new Acceso(join(dir, 'estado', 'acceso.json'));
acceso.fijarClave('dueno', CLAVE_DUENO);
const LLAVE = acceso.llaveMaquina();

let galleta = '';
const conSesion = (extra = {}) => ({ cookie: galleta, ...extra });
const conLlave = (extra = {}) => ({ authorization: `Bearer ${LLAVE}`, ...extra });

async function entrar(rol, clave) {
  const r = await fetch(`${base}/entrar`, {
    method: 'POST',
    headers: { 'content-type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({ rol, clave }).toString(),
    redirect: 'manual',
  });
  const set = r.headers.get('set-cookie');
  if (set) galleta = set.split(';')[0];
  return r.status;
}

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

try {
  console.log('\n== 1. Sin configurar: no sella, pero no se cae ==');
  servidor = await iniciarServidor({ base: dir, puerto: PUERTO, log: () => {} });
  check((await entrar('dueno', CLAVE_DUENO)) === 302, 'el dueño entra con su clave');
  const sinCfg = await fetch(`${base}/api/venta`, {
    method: 'POST',
    headers: conLlave({ 'content-type': 'application/json' }),
    body: JSON.stringify(venta('v-0')),
  });
  check(sinCfg.status === 503, `avisa que la estacion no esta lista (HTTP ${sinCfg.status})`);
  const portada = await (await fetch(`${base}/`, { headers: conSesion() })).text();
  check(/no esta lista/i.test(portada), 'y la pantalla dice que falta configurar');

  console.log('\n== 2. Se configura desde la pantalla, sin reiniciar nada ==');
  // Tal como lo haria el dueño: llena el formulario y guarda. La estacion se
  // recarga sola, no hay que reiniciar el programa.
  const guardado = await fetch(`${base}/configurar`, {
    method: 'POST',
    headers: conSesion({ 'content-type': 'application/x-www-form-urlencoded' }),
    body: new URLSearchParams({
      rif: 'j508561066',
      razon_social: 'Arepa Power Restaurant',
      domicilio_fiscal: 'Araure, Portuguesa',
      alicuota_general: '16',
      serie: 'a',
      equipo: 'FISCAL-01',
    }).toString(),
    redirect: 'manual',
  });
  check(guardado.status === 302, `guarda y lleva al estado (HTTP ${guardado.status})`);

  console.log('\n== 3. Un RIF malo no se guarda ==');
  const malo = await fetch(`${base}/configurar`, {
    method: 'POST',
    headers: conSesion({ 'content-type': 'application/x-www-form-urlencoded' }),
    body: new URLSearchParams({
      rif: 'J-12345678-9',
      razon_social: 'Inventado',
      domicilio_fiscal: 'X',
      alicuota_general: '16',
      serie: 'A',
      equipo: 'X',
    }).toString(),
  });
  check(malo.status === 400, `rechaza el RIF inventado (HTTP ${malo.status})`);
  const est1 = await (await fetch(`${base}/api/estado`, { headers: conSesion() })).json();
  check(est1.rif === 'J-50856106-6', `la estacion abre con el RIF del negocio (${est1.rif})`);
  check(est1.libro_integro === true, 'y con el libro integro');

  console.log('\n== 3. Gustito entrega la venta y recibe su numero ==');
  const r1 = await (await fetch(`${base}/api/venta`, {
    method: 'POST',
    headers: conLlave({ 'content-type': 'application/json' }),
    body: JSON.stringify(venta('gx-100')),
  })).json();
  check(r1.sellada === true, 'la venta queda sellada');
  check(r1.numero === 1 && r1.serie === 'A', `con su numero (${r1.serie}-${r1.numero})`);
  check(!!r1.numeroControl, `y el numero de control de la imprenta (${r1.numeroControl})`);
  check(r1.transmision === 'enviado', 'transmitida');

  console.log('\n== 4. El mismo pedido dos veces no factura dos veces ==');
  const r2 = await (await fetch(`${base}/api/venta`, {
    method: 'POST',
    headers: conLlave({ 'content-type': 'application/json' }),
    body: JSON.stringify(venta('gx-100')),
  })).json();
  check(r2.numero === 1, `devuelve el documento que ya existia (${r2.numero})`);

  console.log('\n== 5. Anular es nota de credito ==');
  const nc = await (await fetch(`${base}/api/anular`, {
    method: 'POST',
    headers: conSesion({ 'content-type': 'application/json' }),
    body: JSON.stringify({ numero: 1, motivo: 'Cobro de mas' }),
  })).json();
  check(nc.sellada === true, `sale la nota de credito (${nc.serie}-${nc.numero})`);
  const est2 = await (await fetch(`${base}/api/estado`, { headers: conSesion() })).json();
  check(est2.documentos === 2, `el libro tiene la factura y su nota (${est2.documentos})`);

  console.log('\n== 6. La puerta del fiscal pide clave ==');
  const sinClave = await fetch(`${base}/api/fiscal/documentos?clave=loquesea`);
  check(sinClave.status === 401, `sin la clave correcta no entra (HTTP ${sinClave.status})`);
  acceso.fijarClave('auditor', 'clave-del-seniat-2026');
  const conClave = await fetch(`${base}/api/fiscal/documentos?clave=clave-del-seniat-2026`);
  check(conClave.status === 200, 'con la clave del SENIAT entra');
  const datos = await conClave.json();
  check(datos.totales.documentos === 2, `ve los 2 documentos (${datos.totales.documentos})`);
  check(datos.integridad.integro === true, 'y puede comprobar el libro');

  console.log('\n== 7. El fiscal se lleva su reporte ==');
  const csv = await (await fetch(`${base}/api/fiscal/csv?clave=clave-del-seniat-2026`)).text();
  check(csv.split('\n').length === 3, `el archivo trae encabezado y 2 filas (${csv.split('\n').length})`);
  check(csv.startsWith('fecha;tipo;serie'), 'con sus columnas');

  console.log('\n== 7b. El registro de eventos y la API para sistemas del SENIAT ==');
  const evs = await (await fetch(`${base}/api/fiscal/eventos?clave=clave-del-seniat-2026`)).json();
  check(Array.isArray(evs.eventos) && evs.eventos.length > 0, `los eventos salen (${evs.eventos.length})`);
  check(evs.integridad.integro === true, 'con su cadena comprobada');
  const evCsv = await (await fetch(`${base}/api/fiscal/eventos_csv?clave=clave-del-seniat-2026`)).text();
  check(evCsv.startsWith('seq;fecha;quien'), 'el CSV de eventos sale con sus columnas');
  const bearer = await fetch(`${base}/api/fiscal/documentos`, { headers: { authorization: 'Bearer clave-del-seniat-2026' } });
  check(bearer.status === 200, 'la misma clave entra como Bearer (interfaz de programacion)');
  const bearerMalo = await fetch(`${base}/api/fiscal/documentos`, { headers: { authorization: 'Bearer clave-mala' } });
  check(bearerMalo.status === 401, 'y un Bearer con clave mala rebota');
  const puntual = await (await fetch(`${base}/api/fiscal/documento?tipo=factura&numero=1&clave=clave-del-seniat-2026`)).json();
  check(puntual.numero === 1 && !!puntual.documento, 'el documento puntual sale con su detalle completo');
  const puntualMalo = await fetch(`${base}/api/fiscal/documento?tipo=factura&numero=999&clave=clave-del-seniat-2026`);
  check(puntualMalo.status === 404, 'y uno que no existe responde 404');

  console.log('\n== 8. El dueño se lleva SUS documentos, sin prestar la clave del SENIAT ==');
  const hoyISO = new Date().toISOString().slice(0, 10);
  const rango = `desde=2000-01-01&hasta=${hoyISO}`;
  const mio = await fetch(`${base}/api/documentos.csv?${rango}`, { headers: conSesion() });
  check(mio.status === 200, 'el dueño baja el archivo con su propia sesion');
  check((mio.headers.get('content-type') ?? '').startsWith('text/csv'), 'y sale como archivo de Excel');
  const textoMio = await mio.text();
  check(textoMio.startsWith('fecha;tipo;serie;numero;control'), 'con las mismas columnas que se lleva el fiscal');
  check(textoMio.split('\n').length > 1, 'y con documentos adentro');
  const sinSesion = await fetch(`${base}/api/documentos.csv?${rango}`);
  check(sinSesion.status === 401, 'sin sesion no se baja nada');
  // El cajero ve la pantalla, pero no se lleva el libro del periodo.
  acceso.fijarClave('cajero', 'clave-del-cajero-2026');
  const galletaDueno = galleta;
  check((await entrar('cajero', 'clave-del-cajero-2026')) === 302, 'el cajero entra con la suya');
  check((await fetch(`${base}/documentos`, { headers: conSesion() })).status === 200, 'el cajero SI ve la pantalla de documentos');
  const cajeroBaja = await fetch(`${base}/api/documentos.csv?${rango}`, { headers: conSesion() });
  check(cajeroBaja.status === 403, 'pero NO se lleva el archivo del periodo');
  const pantallaCajero = await (await fetch(`${base}/documentos`, { headers: conSesion() })).text();
  check(!pantallaCajero.includes('Bajar el archivo'), 'y ni siquiera le aparece el boton');
  galleta = galletaDueno;
  const pantallaDueno = await (await fetch(`${base}/documentos`, { headers: conSesion() })).text();
  check(pantallaDueno.includes('Bajar el archivo'), 'al dueño si le aparece');
  check(pantallaDueno.includes('No le entregues la clave de consulta del SENIAT'), 'con el aviso de no prestar la clave del fiscal');
  // Art. 3: quien se lleva algo queda anotado, y con su nombre, no como "fiscal".
  const rastro = await (await fetch(`${base}/api/fiscal/eventos?clave=clave-del-seniat-2026`)).json();
  check(
    rastro.eventos.some((e) => e.accion === 'exportacion' && e.quien === 'Dueño'),
    'y queda anotado que fue el Dueño quien exporto',
  );

  console.log('\n== 8b. La version y el retiro, cada uno con su puerta ==');
  const estV = await (await fetch(`${base}/api/estado`, { headers: conSesion() })).json();
  check(estV.version && estV.version.actual === 'dev', `el estado dice la version de la instalacion (${estV.version?.actual})`);
  const aplicarSinSesion = await fetch(`${base}/actualizar`, { method: 'POST', redirect: 'manual' });
  check(aplicarSinSesion.status !== 200, `aplicar version sin sesion rebota (HTTP ${aplicarSinSesion.status})`);
  const aplicarDueno = await fetch(`${base}/actualizar`, { method: 'POST', headers: conSesion(), redirect: 'manual' });
  check(aplicarDueno.status === 400, `sin version nueva no hay nada que aplicar (HTTP ${aplicarDueno.status})`);
  const retirarSinSesion = await fetch(`${base}/desinstalar`, { method: 'POST', redirect: 'manual' });
  check(retirarSinSesion.status !== 200, `retirar sin sesion rebota (HTTP ${retirarSinSesion.status})`);
  const retirarDev = await fetch(`${base}/desinstalar`, { method: 'POST', headers: conSesion(), redirect: 'manual' });
  check(retirarDev.status === 400, `retirar solo aplica en el programa instalado (HTTP ${retirarDev.status} en desarrollo)`);

  console.log('\n== 9. Y no hay puerta de atras ==');
  const inventada = await fetch(`${base}/api/documentos`, { headers: conSesion() });
  check(inventada.status === 404, 'una direccion que no existe responde 404');
  const fiscalSinClave = await fetch(`${base}/api/fiscal/csv`);
  check(fiscalSinClave.status === 401, 'y a lo del fiscal no se entra sin clave');
  const ventaSinLlave = await fetch(`${base}/api/venta`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(venta('gx-999')),
  });
  check(ventaSinLlave.status === 401, 'ni se entrega una venta sin la llave de la estacion');

  console.log('\n== 10. El modelo de factura: muestra, guardado y nada se pisa ==');
  const formModelo = (extra) => new URLSearchParams({ mensaje_pie: 'Gracias por su compra', logo_url: '', color: '', ...extra }).toString();
  const muestra = await fetch(`${base}/modelo-factura`, {
    method: 'POST',
    headers: conSesion({ 'content-type': 'application/x-www-form-urlencoded' }),
    body: formModelo({ accion: 'probar' }),
  });
  const muestraHtml = await muestra.text();
  check(muestra.status === 200 && /Gracias por su compra/.test(muestraHtml), 'la muestra enseña el papel con el mensaje');
  check(/sin guardar/i.test(muestraHtml), 'y avisa que todavia no se guardo');
  check(/N\. de Control/.test(muestraHtml), 'con los datos obligatorios del art. 7 en su sitio');
  let pantalla = await (await fetch(`${base}/configurar`, { headers: conSesion() })).text();
  check(!/value="Gracias por su compra"/.test(pantalla), 'probar NO guarda nada');

  const colorMalo = await fetch(`${base}/modelo-factura`, {
    method: 'POST',
    headers: conSesion({ 'content-type': 'application/x-www-form-urlencoded' }),
    body: formModelo({ accion: 'guardar', color: 'dorado' }),
  });
  check(colorMalo.status === 400, `un color que no es #RRGGBB rebota (HTTP ${colorMalo.status})`);

  const guardaModelo = await fetch(`${base}/modelo-factura`, {
    method: 'POST',
    headers: conSesion({ 'content-type': 'application/x-www-form-urlencoded' }),
    body: formModelo({ accion: 'guardar', color: '#C9A227' }),
    redirect: 'manual',
  });
  check(guardaModelo.status === 302, `el modelo bueno se guarda (HTTP ${guardaModelo.status})`);
  pantalla = await (await fetch(`${base}/configurar`, { headers: conSesion() })).text();
  check(/value="Gracias por su compra"/.test(pantalla), 'y la pantalla lo muestra guardado');

  // La clase de bug que ya nos mordio tres veces: re-guardar los datos del
  // negocio NO puede borrar el modelo (ni el resto de lo que esa pantalla no toca).
  const reGuardar = await fetch(`${base}/configurar`, {
    method: 'POST',
    headers: conSesion({ 'content-type': 'application/x-www-form-urlencoded' }),
    body: new URLSearchParams({
      rif: 'j508561066',
      razon_social: 'Arepa Power Restaurant',
      domicilio_fiscal: 'Araure, Portuguesa',
      alicuota_general: '16',
      serie: 'a',
      equipo: 'FISCAL-01',
    }).toString(),
    redirect: 'manual',
  });
  check(reGuardar.status === 302, 're-guardar los datos del negocio pasa');
  pantalla = await (await fetch(`${base}/configurar`, { headers: conSesion() })).text();
  check(/value="Gracias por su compra"/.test(pantalla), 'y el modelo de factura sigue ahi: re-guardar no pisa lo que no toca');
} catch (e) {
  console.log('  XX  ' + e.message);
  fallos++;
} finally {
  if (servidor) servidor.cerrar();
  rmSync(dir, { recursive: true, force: true });
}

console.log(fallos === 0 ? '\nBLOQUE 9 (LA ESTACION ANDANDO): 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
