// EL SERIAL DE LA ESTACION (2026-07-30) — Modulo 1: la nube.
//
// Lo que se comprueba contra la base viva:
//   - El alta de un cliente le asigna su serial solo (EF-NNNNN-D), valido y
//     consecutivo; dos altas jamas repiten.
//   - El digito verificador rebota seriales manoseados o inventados.
//   - La verificacion publica: ACTIVA con razon social y RIF completos,
//     SUSPENDIDA cuando se le baja el breaker, no registrada, basura.
//   - El freno por direccion (10 por hora): la consulta 11 rebota.
//   - El latido devuelve el serial (la Estacion lo aprende sola).
//   - El envoltorio publico NO es llamable por anon ni authenticated.
//
// Limpia todo lo que crea y deja el correlativo donde estaba.
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++;
};

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 t = await r.text();
  if (!r.ok) throw new Error(t.slice(0, 400));
  return JSON.parse(t);
}
const uno = async (q) => (await sql(q))[0];
const lit = (s) => `'${String(s).replace(/'/g, "''")}'`;

// RIF sinteticos VALIDOS para las altas de prueba (se calcula el digito real).
async function rifDePrueba(base) {
  const r = await uno(`select x::text as dv from generate_series(0,9) x where public.fn_rif_valido('${base}' || x) limit 1`);
  return base + r.dv;
}

let RIF_A = null;
let RIF_B = null;
const limpiar = async () => {
  const rifs = [RIF_A, RIF_B].filter(Boolean).map(lit).join(',');
  if (rifs) {
    await sql(`delete from fiscal.estaciones where emisor_id in (select id from fiscal.emisores where rif in (${rifs}));
               delete from fiscal.llaves where emisor_id in (select id from fiscal.emisores where rif in (${rifs}));
               delete from fiscal.emisores where rif in (${rifs});`);
  }
  await sql(`delete from fiscal.consultas_serial where desde like 'prueba-%';
             select setval('fiscal.serial_correlativo',
               greatest(coalesce((select max(substr(serial, 4, 5)::int) from fiscal.emisores), 0), 1),
               coalesce((select max(substr(serial, 4, 5)::int) from fiscal.emisores), 0) > 0);`);
};

try {
  RIF_A = await rifDePrueba('J-30900001-');
  RIF_B = await rifDePrueba('J-30900002-');
  await limpiar();

  console.log('\n== 1. El alta asigna el serial solo, consecutivo y sin repetir ==');
  await sql(`select fiscal.guardar_cliente(${lit(RIF_A)}, 'Serial Prueba Uno C.A.', 'Acarigua', null, null, null, null, 'propio', 'A', true, 'prueba serial')`);
  const a = await uno(`select serial from fiscal.emisores where rif = ${lit(RIF_A)}`);
  check(/^EF-\d{5}-\d$/.test(a.serial), `el primero nace con serial (${a.serial})`);
  const va = await uno(`select fiscal.fn_serial_valido(${lit(a.serial)}) as v`);
  check(va.v === true, 'y el serial pasa su propio verificador');
  await sql(`select fiscal.guardar_cliente(${lit(RIF_B)}, 'Serial Prueba Dos C.A.', 'Araure', null, null, null, null, 'propio', 'A', true, 'prueba serial')`);
  const b = await uno(`select serial from fiscal.emisores where rif = ${lit(RIF_B)}`);
  const nA = Number(a.serial.slice(3, 8));
  const nB = Number(b.serial.slice(3, 8));
  check(nB === nA + 1, `el segundo es consecutivo (${b.serial})`);
  const rep = await uno(`select count(distinct serial) = count(*) as ok from fiscal.emisores`);
  check(rep.ok === true, 'ningun serial repetido en toda la tabla');

  console.log('\n== 2. Re-guardar el mismo cliente NO le cambia el serial ==');
  await sql(`select fiscal.guardar_cliente(${lit(RIF_A)}, 'Serial Prueba Uno RENOMBRADA', 'Acarigua', null, null, null, null, 'propio', 'A', true, 'prueba serial')`);
  const a2 = await uno(`select serial from fiscal.emisores where rif = ${lit(RIF_A)}`);
  check(a2.serial === a.serial, `el serial es del cliente para siempre (${a2.serial})`);

  console.log('\n== 3. La verificacion publica ==');
  const act = (await uno(`select fiscal.verificar_serial(${lit(a.serial)}, 'prueba-vista') as r`)).r;
  check(act.registrada === true && act.estado === 'activa', 'registrada y ACTIVA');
  check(act.razon_social === 'Serial Prueba Uno RENOMBRADA' && act.rif === RIF_A, 'con razon social y RIF completos');
  await sql(`update fiscal.emisores set activo = false where rif = ${lit(RIF_A)}`);
  const sus = (await uno(`select fiscal.verificar_serial(${lit(a.serial)}, 'prueba-vista') as r`)).r;
  check(sus.registrada === true && sus.estado === 'suspendida', 'con el breaker abajo dice SUSPENDIDA');
  await sql(`update fiscal.emisores set activo = true where rif = ${lit(RIF_A)}`);
  const fantasma = (await uno(`select fiscal.fn_armar_serial(99998) as s`)).s;
  const nr = (await uno(`select fiscal.verificar_serial(${lit(fantasma)}, 'prueba-vista') as r`)).r;
  check(nr.registrada === false && nr.motivo === 'no_registrada', 'un serial valido pero ajeno: no registrada');
  const bas = (await uno(`select fiscal.verificar_serial('EF-00001-0', 'prueba-vista') as r`)).r;
  check(bas.registrada === false && bas.motivo === 'serial_invalido', 'un serial manoseado ni llega a la tabla');

  console.log('\n== 4. El freno por direccion (el serial es correlativo: sin esto me enumeran la cartera) ==');
  let trancado = false;
  for (let i = 0; i < 12; i++) {
    try {
      await sql(`select fiscal.verificar_serial(${lit(a.serial)}, 'prueba-freno')`);
    } catch (e) {
      trancado = /Demasiadas consultas/i.test(String(e.message));
      break;
    }
  }
  check(trancado, 'a la consulta 11 desde la misma direccion, se tranca');

  console.log('\n== 5. El latido trae el serial (la Estacion lo aprende sola) ==');
  const llave = (await uno(`select fiscal.crear_llave(${lit(RIF_A)}, 'prueba serial') as l`)).l;
  const lat = (await uno(`select fiscal.latido(${lit(llave)}, ${lit(RIF_A)}, '{"nombre":"Estacion Prueba","equipo":"TEST-SERIAL"}'::jsonb) as r`)).r;
  check(lat.serial === a.serial, `el latido responde con el serial (${lat.serial})`);

  console.log('\n== 6. El envoltorio publico no es de anon ni de authenticated ==');
  const acl = await uno(`select
    has_function_privilege('anon', 'public.fn_verificar_estacion(text, text)', 'execute') as anon_puede,
    has_function_privilege('authenticated', 'public.fn_verificar_estacion(text, text)', 'execute') as auth_puede`);
  check(acl.anon_puede === false && acl.auth_puede === false, 'solo la Function del sello (service role) lo llama');

  await limpiar();
  console.log(fallos === 0 ? '\nTODO EN VERDE' : `\n${fallos} FALLOS`);
  process.exit(fallos ? 1 : 0);
} catch (e) {
  console.error('\nREVENTO:', String(e.message).slice(0, 500));
  try { await limpiar(); } catch { /* mejor esfuerzo */ }
  process.exit(1);
}
