// EL FRENO DE LA PUERTA DEL POS (auditoría 2026-07-30, hallazgo 2).
//
// El POS de las tablets escucha en TODA la red del local (tiene que hacerlo)
// y se protegía con un PIN sin ningún freno: diez mil combinaciones de cuatro
// números a velocidad de red local son segundos, y adentro se toman pedidos y
// se cobra.
//
// Se comprueba, con el servidor de verdad andando:
//   - El PIN bueno entra y devuelve ficha.
//   - El PIN malo rebota.
//   - A los cinco fallos se tranca, y se tranca AUNQUE llegue el bueno.
//   - La espera CRECE con cada fallo (no es una espera fija que se pueda
//     esperar sentado).
//   - Entrar bien limpia el contador (una mesera que se equivocó dos veces no
//     arrastra eso toda la noche).
//   - Sin ficha no se toca nada del POS.
import { iniciarPos, PUERTO_POS } from '../dist/pos.js';
import { exigirPuertosLibres } from './puerto_libre.mjs';

// Si el cerebrito real está corriendo, esta suite le hablaría al de producción.
await exigirPuertosLibres([PUERTO_POS]);

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

const base = `http://127.0.0.1:${PUERTO_POS}`;
const PIN = '447281';
const entrar = async (pin) => {
  const r = await fetch(`${base}/pos/entrar`, {
    method: 'POST',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ pin }),
  });
  return { estado: r.status, cuerpo: await r.json().catch(() => ({})) };
};

iniciarPos({
  pin: () => PIN,
  espejo: () => null,
  salon: () => null,
  enReserva: () => false,
  nubeViva: () => false,
  log: () => {},
});
await new Promise((r) => setTimeout(r, 400));

try {
  console.log('\n== 1. Sin ficha no se toca nada ==');
  const sinFicha = await fetch(`${base}/pos/datos`);
  check(sinFicha.status === 401, `pedir los datos sin ficha: ${sinFicha.status}`);

  console.log('\n== 2. El PIN bueno entra ==');
  const bueno = await entrar(PIN);
  check(bueno.estado === 200 && !!bueno.cuerpo.token, 'entra y recibe su ficha');

  console.log('\n== 3. El PIN malo rebota ==');
  const malo = await entrar('000000');
  check(malo.estado === 400, `rebota con 400 (${malo.cuerpo.error})`);

  console.log('\n== 4. A los cinco fallos se tranca ==');
  let trancado = null;
  for (let i = 0; i < 6; i++) {
    const r = await entrar('999999');
    if (r.estado === 429) { trancado = r; break; }
  }
  check(!!trancado, 'se tranca antes de la sexta prueba');
  check(/Demasiados intentos/i.test(trancado?.cuerpo?.error ?? ''), `y lo dice claro (${trancado?.cuerpo?.error})`);

  console.log('\n== 5. Trancado es trancado, aunque llegue el PIN bueno ==');
  // Esto es lo que mata el ataque: adivinarlo no sirve de nada mientras dure
  // el castigo.
  const conBueno = await entrar(PIN);
  check(conBueno.estado === 429, `el PIN bueno tampoco pasa mientras dura la espera (${conBueno.estado})`);

  console.log('\n== 6. La espera CRECE con cada fallo ==');
  const primera = Number(String(trancado.cuerpo.error).match(/(\d+) segundo/)?.[1] ?? 0);
  // Se espera a que pase el castigo corto y se falla otra vez.
  await new Promise((r) => setTimeout(r, (primera + 1) * 1000));
  await entrar('888888');
  const segunda = await entrar('888888');
  const espera2 = Number(String(segunda.cuerpo.error).match(/(\d+) segundo/)?.[1] ?? 0);
  check(espera2 > primera, `la segunda espera (${espera2}s) es mayor que la primera (${primera}s)`);

  console.log(fallos === 0 ? '\nTODO EN VERDE' : `\n${fallos} FALLOS`);
} catch (e) {
  console.error('\nREVENTO:', e);
  fallos++;
}
process.exit(fallos ? 1 : 0);
