// PRUEBA DEL PORTERO DE LA ESTACION (2026-07-29)
//
// Antes de esto, dentro de la computadora del negocio no habia portero: la
// pantalla, la configuracion y /api/anular estaban abiertas para cualquiera que
// se sentara ahi. Anular una factura sin clave y sin dejar quien fue es justo
// lo que un fiscal busca al llegar.
//
// Se comprueba, con el servidor de verdad andando:
//   1. Una estacion recien instalada obliga a ponerle la clave del dueño.
//   2. Sin sesion no se ve ni se toca nada.
//   3. El cajero ve y empuja, pero no configura ni anula.
//   4. El dueño hace todo, y lo que hace queda escrito con su nombre.
//   5. Los programas del negocio entran con su llave, que no es la de nadie.
//   6. Al que anda probando claves se le traba la puerta.
//   7. Cambiar la clave cierra las sesiones abiertas.
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-portero-'));
const PUERTO = 8751;
const base = `http://127.0.0.1:${PUERTO}`;
let servidor;

const CLAVE_DUENO = 'la-del-dueno-2026';
const CLAVE_CAJERO = 'la-del-cajero-2026';

const forma = (datos) => ({
  method: 'POST',
  headers: { 'content-type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams(datos).toString(),
  redirect: 'manual',
});

async function entrar(rol, clave) {
  const r = await fetch(`${base}/entrar`, forma({ rol, clave }));
  const set = r.headers.get('set-cookie');
  return { estado: r.status, galleta: set ? set.split(';')[0] : '' };
}

const con = (galleta, extra = {}) => ({ cookie: galleta, ...extra });

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' },
});

try {
  console.log('\n== 1. Una estacion recien instalada exige ponerle clave ==');
  servidor = await iniciarServidor({ base: dir, puerto: PUERTO, log: () => {} });
  const primera = await (await fetch(`${base}/`)).text();
  check(/todavia no tiene clave/i.test(primera), 'la primera pantalla pide la clave del dueño');
  const cortita = await fetch(`${base}/primer-uso`, forma({ clave: '123', repetir: '123' }));
  check(cortita.status === 400, `una clave de tres letras no se acepta (HTTP ${cortita.status})`);
  const distintas = await fetch(`${base}/primer-uso`, forma({ clave: CLAVE_DUENO, repetir: 'otra-cosa-2026' }));
  check(distintas.status === 400, 'y si las dos no coinciden, tampoco');
  const puesta = await fetch(`${base}/primer-uso`, forma({ clave: CLAVE_DUENO, repetir: CLAVE_DUENO }));
  check(puesta.status === 302, 'con la clave buena, queda puesta y entra de una vez');
  const galletaDueno0 = (puesta.headers.get('set-cookie') ?? '').split(';')[0];
  check(galletaDueno0.startsWith('gxf_estacion='), 'con su sesion');
  const yaNo = await (await fetch(`${base}/entrar`)).text();
  check(/name="clave"/.test(yaNo) && !/primer-uso/.test(yaNo), 'y desde ahi la puerta es la normal, no la de primer uso');

  console.log('\n== 2. Sin sesion no se ve ni se toca nada ==');
  const raiz = await fetch(`${base}/`, { redirect: 'manual' });
  check(raiz.status === 302 && raiz.headers.get('location') === '/entrar', `la pantalla manda a entrar (HTTP ${raiz.status})`);
  const cfg = await fetch(`${base}/configurar`, { redirect: 'manual' });
  check(cfg.status === 302, 'la configuracion tambien');
  const anularSuelto = await fetch(`${base}/api/anular`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ numero: 1, motivo: 'a ver si cuela' }),
  });
  check(anularSuelto.status === 401, `y anular sin clave rebota (HTTP ${anularSuelto.status})`);

  console.log('\n== 3. El dueño configura y el negocio queda listo ==');
  const dueno = await entrar('dueno', CLAVE_DUENO);
  check(dueno.estado === 302, 'el dueño entra');
  const guardo = await fetch(`${base}/configurar`, {
    ...forma({
      rif: 'j508561066',
      razon_social: 'Arepa Power Restaurant',
      domicilio_fiscal: 'Araure, Portuguesa',
      alicuota_general: '16',
      serie: 'A',
      equipo: 'FISCAL-01',
    }),
    headers: con(dueno.galleta, { 'content-type': 'application/x-www-form-urlencoded' }),
  });
  check(guardo.status === 302, 'y configura la estacion');

  console.log('\n== 4. Los programas del negocio entran con su llave ==');
  const llave = new Acceso(join(dir, 'estado', 'acceso.json')).llaveMaquina();
  check(llave.startsWith('efm_'), `hay una llave de maquina (${llave.slice(0, 8)}...)`);
  const sinLlave = await fetch(`${base}/api/venta`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify(venta('p-1')),
  });
  check(sinLlave.status === 401, `sin llave no se entrega una venta (HTTP ${sinLlave.status})`);
  const llaveMala = await fetch(`${base}/api/venta`, {
    method: 'POST',
    headers: { 'content-type': 'application/json', authorization: 'Bearer efm_' + 'a'.repeat(48) },
    body: JSON.stringify(venta('p-1')),
  });
  check(llaveMala.status === 401, 'con una llave inventada tampoco');
  const conLlave = await fetch(`${base}/api/venta`, {
    method: 'POST',
    headers: { 'content-type': 'application/json', authorization: `Bearer ${llave}` },
    body: JSON.stringify(venta('p-1')),
  });
  const sellada = await conLlave.json();
  check(sellada.sellada === true, `con la llave buena la venta se sella (${sellada.serie}-${sellada.numero})`);
  // Y la sesion de una persona NO sirve para entregar ventas: son puertas
  // distintas a proposito.
  const conSesionNoVale = await fetch(`${base}/api/venta`, {
    method: 'POST',
    headers: con(dueno.galleta, { 'content-type': 'application/json' }),
    body: JSON.stringify(venta('p-2')),
  });
  check(conSesionNoVale.status === 401, 'la sesion del dueño no sirve para esa puerta');

  console.log('\n== 5. El cajero ve, pero no configura ni anula ==');
  const puestaCajero = await fetch(`${base}/claves`, {
    ...forma({ que: 'cajero', clave: CLAVE_CAJERO }),
    headers: con(dueno.galleta, { 'content-type': 'application/x-www-form-urlencoded' }),
  });
  check(puestaCajero.status === 302, 'el dueño le pone la clave al cajero');
  check(!puestaCajero.headers.get('set-cookie'), 'y ponersela al cajero no lo saca a el de su sesion');
  const cajero = await entrar('cajero', CLAVE_CAJERO);
  check(cajero.estado === 302, 'el cajero entra con la suya');
  const veEstado = await fetch(`${base}/api/estado`, { headers: con(cajero.galleta) });
  check(veEstado.status === 200, `ve el estado de la estacion (HTTP ${veEstado.status})`);
  const empuja = await fetch(`${base}/api/reintentar`, { method: 'POST', headers: con(cajero.galleta) });
  check(empuja.status === 200, 'y puede empujar lo que quedo por transmitir');
  const cajeroConfig = await fetch(`${base}/configurar`, { headers: con(cajero.galleta) });
  check(cajeroConfig.status === 403, `pero no entra a la configuracion (HTTP ${cajeroConfig.status})`);
  const cajeroAnula = await fetch(`${base}/api/anular`, {
    method: 'POST',
    headers: con(cajero.galleta, { 'content-type': 'application/json' }),
    body: JSON.stringify({ numero: 1, motivo: 'me equivoque' }),
  });
  check(cajeroAnula.status === 403, `ni puede anular (HTTP ${cajeroAnula.status})`);
  const cajeroClaves = await fetch(`${base}/claves`, { headers: con(cajero.galleta) });
  check(cajeroClaves.status === 403, 'ni tocar las claves');
  const cajeroMov = await fetch(`${base}/movimientos`, { headers: con(cajero.galleta) });
  check(cajeroMov.status === 403, 'ni leer la bitacora');
  const pantallaCajero = await (await fetch(`${base}/`, { headers: con(cajero.galleta) })).text();
  check(!/Anular con nota de credito/.test(pantallaCajero), 'y en su pantalla ni siquiera aparece el boton de anular');
  check(/class="quien"><b>Cajero<\/b>/.test(pantallaCajero), 'la pantalla le dice como entro (barra superior)');

  console.log('\n== 6. El dueño anula, y queda escrito quien fue ==');
  const anulada = await (await fetch(`${base}/api/anular`, {
    method: 'POST',
    headers: con(dueno.galleta, { 'content-type': 'application/json' }),
    body: JSON.stringify({ numero: 1, motivo: 'Cobro de mas' }),
  })).json();
  check(anulada.sellada === true, `sale la nota de credito (${anulada.serie}-${anulada.numero})`);
  const movimientos = await (await fetch(`${base}/movimientos`, { headers: con(dueno.galleta) })).text();
  check(/Cada linea esta encadenada/.test(movimientos), 'el dueño abre la bitacora');
  check(/<td>Dueño<\/td><td>nota_credito<\/td>/.test(movimientos), 'y ahi esta la anulacion, con el nombre de quien la pidio');
  check(/<td>Dueño<\/td><td>configuracion_guardada<\/td>/.test(movimientos), 'tambien quien configuro la estacion');
  check(/<td>Dueño<\/td><td>clave_cambiada<\/td>/.test(movimientos), 'y quien cambio una clave');

  console.log('\n== 7. Al que anda probando claves se le traba la puerta ==');
  let ultimo = 0;
  for (let i = 0; i < 6; i++) ultimo = (await entrar('cajero', 'probando' + i)).estado;
  check(ultimo === 401, `los intentos con clave mala rebotan (HTTP ${ultimo})`);
  const trabado = await fetch(`${base}/entrar`, forma({ clave: CLAVE_CAJERO }));
  const textoTrabado = await trabado.text();
  check(/Demasiados intentos/i.test(textoTrabado), 'y a la sexta se traba, aunque llegue la clave buena');
  const duenoTrabado = await fetch(`${base}/entrar`, forma({ clave: CLAVE_DUENO }));
  check(/Demasiados intentos/i.test(await duenoTrabado.text()), 'la puerta es UNA: trancada para todos mientras dure el castigo');

  console.log('\n== 8. Cambiar la clave cierra sesiones y destranca la puerta ==');
  const galletaVieja = cajero.galleta;
  await fetch(`${base}/claves`, {
    ...forma({ que: 'cajero', clave: 'otra-del-cajero-2026' }),
    headers: con(dueno.galleta, { 'content-type': 'application/x-www-form-urlencoded' }),
  });
  const conVieja = await fetch(`${base}/api/estado`, { headers: con(galletaVieja) });
  check(conVieja.status === 401, `la sesion vieja del cajero ya no vale (HTTP ${conVieja.status})`);
  const conNueva = await entrar('cajero', 'otra-del-cajero-2026');
  check(conNueva.estado === 302, 'y con la clave nueva entra otra vez (cambiarla destranco la puerta)');

  console.log('\n== 9. Salir es salir ==');
  const salida = await fetch(`${base}/salir`, { method: 'POST', headers: con(conNueva.galleta), redirect: 'manual' });
  check(salida.status === 302, 'la pantalla de salir manda a la puerta');
  const despues = await fetch(`${base}/api/estado`, { headers: con(conNueva.galleta) });
  check(despues.status === 401, 'y esa sesion ya no sirve');
} catch (e) {
  console.log('  XX  ' + e.message);
  fallos++;
} finally {
  if (servidor) servidor.cerrar();
  rmSync(dir, { recursive: true, force: true });
}

console.log(fallos === 0 ? '\nPORTERO DE LA ESTACION: 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
