// PRUEBA DEL LADO IMPRENTA DIGITAL (2026-07-28)
// Lo que se juega aca: repetir un numero de control es CAUSAL DE REVOCATORIA
// (Providencia 000102, art. 34). Asi que se prueba de verdad, mandando
// documentos al mismo tiempo y viendo si alguno se repite.
// Corre contra el esquema fiscal de la BD viva y limpia lo que crea.
import { readFileSync } from 'node:fs';

const REF = 'szzhhlevtrcambcoytiz';
const PAT = readFileSync(new URL('../.sb_token', import.meta.url), 'utf8').trim();

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

/** Corre SQL contra la BD viva. Cada llamada es su propia conexion. */
async function sql(query) {
  const r = await fetch(`https://api.supabase.com/v1/projects/${REF}/database/query`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${PAT}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ query }),
  });
  const txt = await r.text();
  if (!r.ok) throw new Error(txt.slice(0, 200));
  return JSON.parse(txt);
}

// RIF DE PRUEBAS, JAMAS el de un cliente real de la imprenta: la limpieza de
// este test borra el emisor COMPLETO. El 2026-07-31 una corrida con
// J-50856106-6 (ya dado de alta real como EF-00006-0) barrio el alta del
// piloto: llaves, respaldo del libro y estacion. Ademas, limpiar() ahora
// ABORTA si el RIF tiene libro respaldado en la nube.
const RIF = 'V-22606827-5';
const lit = (s) => `'${String(s).replace(/'/g, "''")}'`;

const TOKEN_FALSO = 'token-inventado-que-no-existe';

const limpiar = async () => {
  const real = await sql(`select count(*)::int as n from fiscal.respaldo r join fiscal.emisores e on e.id = r.emisor_id where e.rif = ${lit(RIF)}`);
  if (real[0]?.n > 0) throw new Error(`FRENO: el emisor ${RIF} tiene ${real[0].n} lineas de libro respaldadas: es un cliente REAL, este test no lo toca`);
  // Tambien el token falso: si no, queda de una corrida para otra y el conteo
  // de intentos sale mal en la siguiente (una prueba tiene que poder correrse
  // mil veces y dar lo mismo).
  await sql(`delete from fiscal.consultas_qr where token = ${lit(TOKEN_FALSO)};
             delete from fiscal.consultas_qr where token in (select token from fiscal.documentos d join fiscal.emisores e on e.id=d.emisor_id where e.rif=${lit(RIF)});
             delete from fiscal.documentos where emisor_id in (select id from fiscal.emisores where rif=${lit(RIF)});
             delete from fiscal.series where emisor_id in (select id from fiscal.emisores where rif=${lit(RIF)});
             delete from fiscal.emisores where rif=${lit(RIF)};`);
};

const recibir = (hash, numero) =>
  sql(`select * from fiscal.recibir_documento(${lit(RIF)}, 'A', 'factura', ${numero}, ${lit(hash)},
       now(), 100.00, 16.00, 116.00, '{"prueba":true}'::jsonb)`);

try {
  await limpiar();

  console.log('\n== 1. Solo emite quien esta autorizado ==');
  let rechazado = false;
  try {
    await recibir('hash-x', 1);
  } catch (e) {
    rechazado = /no esta autorizado/i.test(e);
  }
  check(rechazado, 'un RIF que no esta registrado NO puede emitir');

  console.log('\n== 2. El emisor se registra con RIF valido ==');
  let rifMalo = false;
  try {
    await sql(`insert into fiscal.emisores (rif, razon_social, domicilio_fiscal, llave_hash)
               values ('J-12345678-9', 'Inventado', 'X', 'x')`);
  } catch (e) {
    rifMalo = /emisores_rif_valido|violates check/i.test(e);
  }
  check(rifMalo, 'un RIF inventado no se puede registrar como emisor');
  await sql(`insert into fiscal.emisores (rif, razon_social, domicilio_fiscal, llave_hash)
             values (${lit(RIF)}, 'Emprendimiento de prueba', 'Araure', 'llave-hasheada')`);
  check(true, 'el emisor con RIF real queda registrado');

  console.log('\n== 3. Numeros de control consecutivos, sin huecos ==');
  const r1 = (await recibir('hash-1', 1))[0];
  const r2 = (await recibir('hash-2', 2))[0];
  const r3 = (await recibir('hash-3', 3))[0];
  check(Number(r1.numero_control) === 1 && Number(r2.numero_control) === 2 && Number(r3.numero_control) === 3,
    `van 1, 2, 3 (${r1.numero_control}, ${r2.numero_control}, ${r3.numero_control})`);

  console.log('\n== 4. LO CRITICO: 20 documentos AL MISMO TIEMPO ==');
  const simultaneos = await Promise.all(
    Array.from({ length: 20 }, (_, i) => recibir(`hash-sim-${i}`, 100 + i)),
  );
  const controles = simultaneos.map((x) => Number(x[0].numero_control));
  const unicos = new Set(controles);
  check(unicos.size === 20, `los 20 recibieron numeros DISTINTOS (${unicos.size} unicos de 20)`);
  const ordenados = [...controles].sort((a, b) => a - b);
  const seguidos = ordenados.every((n, i) => i === 0 || n === ordenados[i - 1] + 1);
  check(seguidos, `y son consecutivos, sin huecos (del ${ordenados[0]} al ${ordenados[ordenados.length - 1]})`);

  console.log('\n== 5. Reintento: el mismo documento NO se numera dos veces ==');
  const otra = (await recibir('hash-1', 1))[0];
  check(otra.ya_estaba === true, 'avisa que ese documento ya estaba');
  check(Number(otra.numero_control) === Number(r1.numero_control), `y devuelve el numero que ya tenia (${otra.numero_control})`);
  check(otra.token === r1.token, 'con el mismo token, para que el QR impreso siga sirviendo');
  const total = (await sql(`select count(*)::int as n from fiscal.documentos d join fiscal.emisores e on e.id=d.emisor_id where e.rif=${lit(RIF)}`))[0].n;
  check(total === 23, `no se duplico nada en la tabla (${total} documentos)`);

  console.log('\n== 6. El token del QR es opaco ==');
  check(r1.token.length === 32, `es largo: 128 bits, imposible de adivinar (${r1.token.length} caracteres)`);
  // Lo que hay que probar no es que "no contenga el numero" (en 48 caracteres
  // hexadecimales cualquier digito aparece), sino que de un token NO se pueda
  // deducir el siguiente. Si fueran correlativos o derivados del numero de
  // control, los tokens del lote compartirian el arranque.
  const tokensLote = simultaneos.map((x) => x[0].token);
  check(new Set(tokensLote).size === tokensLote.length, 'los 20 tokens son distintos entre si');
  check(new Set(tokensLote.map((t) => t.slice(0, 8))).size === tokensLote.length, 'y ni siquiera comparten el arranque: no hay como adivinar el siguiente');
  check(/^[0-9a-f]+$/.test(r1.token), 'formato parejo, sin nada del documento adentro');

  console.log('\n== 7. El enrutador valida sin destapar el consumo ==');
  const v = (await sql(`select * from fiscal.validar_token(${lit(r1.token)}, '190.0.0.1')`))[0];
  check(v.valido === true, 'con un token real, responde que la factura existe');
  check(v.rif_emisor === RIF && Number(v.numero_control) === 1, `y dice quien la emitio y su numero de control (${v.numero_control})`);
  const campos = Object.keys(v);
  check(!campos.includes('documento') && !campos.includes('token'), `NO devuelve el detalle de lo consumido (campos: ${campos.join(', ')})`);

  console.log('\n== 8. Token inventado: no valida, y queda el intento anotado ==');
  const falso = (await sql(`select * from fiscal.validar_token(${lit(TOKEN_FALSO)}, '190.0.0.2')`))[0];
  check(falso.valido === false, 'un token que no existe no valida nada');
  const intentos = (await sql(`select count(*)::int as n from fiscal.consultas_qr where token=${lit(TOKEN_FALSO)}`))[0].n;
  check(intentos === 1, 'y el intento queda registrado (sirve para frenar a quien ande probando)');

  console.log('\n== 9. A este esquema no se llega desde el navegador ==');
  const permisos = await sql(`select has_schema_privilege('anon', 'fiscal', 'USAGE') as anon_entra,
                                     has_schema_privilege('authenticated', 'fiscal', 'USAGE') as auth_entra`);
  check(permisos[0].anon_entra === false, 'un desconocido no tiene acceso al esquema fiscal');
  check(permisos[0].auth_entra === false, 'ni siquiera un usuario logueado de Gustito');
} catch (e) {
  console.log('  XX  ' + String(e).slice(0, 200));
  fallos++;
} finally {
  await limpiar();
}

console.log(fallos === 0 ? '\nIMPRENTA DIGITAL: TODO VERDE' : `\n${fallos} FALLO(S)`);
process.exit(fallos === 0 ? 0 : 1);
