// LA IMPRENTA Y SUS RANGOS DE NUMERACION (2026-07-30).
//
// Hueco que encontro Gilberto: "tenemos elegante para asignar al usuario final
// pero la parte de nosotros donde queda". El sistema no tenia ni datos de la
// imprenta ni rangos, y el numero de control se entregaba con un contador sin
// techo. Modelo de dos niveles: el SENIAT asigna rangos a la imprenta, la
// imprenta reparte sub-rangos a cada cliente.
//
// Lo que se comprueba contra la base viva:
//   - Los datos de la autorizacion se cargan y no se puede marcar autorizada
//     sin providencia.
//   - Dos rangos de la imprenta no se pueden solapar.
//   - Un sub-rango tiene que caber dentro del rango autorizado.
//   - Dos clientes no pueden tener el mismo numero prometido.
//   - Al entregar rangos seguidos, arrancan donde termino el anterior.
//   - El numero de control sale DEL RANGO, en orden.
//   - Cuando se agota, REVIENTA (mejor no emitir que emitir invalido).
//   - Sin rangos configurados, el sistema sigue numerando como hoy (para no
//     tumbar a Arepa Power, que factura bajo el paraguas de un tercero).
//
// Limpia todo 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++;
};

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, 300));
  return JSON.parse(t);
}
const uno = async (q) => (await sql(q))[0];
const lit = (s) => `'${String(s).replace(/'/g, "''")}'`;
/** Corre algo y devuelve el mensaje de error, o null si paso. */
async function falla(q) {
  try { await sql(q); return null; } catch (e) {
    const m = String(e.message).match(/"message":"([^"]+)"/);
    return m ? m[1] : String(e.message).slice(0, 140);
  }
}

// Numeros bien lejos de cualquier cosa real, para no chocar con datos vivos.
const BASE = 900000000;
let RIF_A = null, RIF_B = null;

const limpiar = async () => {
  const rifs = [RIF_A, RIF_B].filter(Boolean).map(lit).join(',');
  if (rifs) {
    await sql(`delete from fiscal.rangos_emisor where emisor_id in (select id from fiscal.emisores where rif in (${rifs}));
               delete from fiscal.series 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.rangos_emisor where rango_imprenta_id in (select id from fiscal.rangos_imprenta where desde >= ${BASE});
             delete from fiscal.rangos_imprenta where desde >= ${BASE};`);
};

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;
}

try {
  RIF_A = await rifDePrueba('J-30900011-');
  RIF_B = await rifDePrueba('J-30900012-');
  await limpiar();
  // Se guarda como estaba la imprenta, para devolverla al final.
  const antes = await uno(`select coalesce((select to_jsonb(i) from fiscal.imprenta i where id), 'null'::jsonb) as i`);

  console.log('\n== 1. Los datos de la imprenta (lo que no existia) ==');
  const sinProv = await falla(`select fiscal.guardar_imprenta('J-50856106-6', 'Prueba Imprenta', 'Araure', null, null, null, true)`);
  check(!!sinProv && /Providencia/i.test(sinProv), 'no deja marcarla autorizada sin numero de Providencia');
  await sql(`select fiscal.guardar_imprenta('J-50856106-6', 'Prueba Imprenta', 'Araure', 'SNAT/INTI/GR/PRUEBA-001', '2026-07-30', null, true)`);
  const r1 = (await uno(`select fiscal.resumen_numeracion() as r`)).r;
  check(r1.autorizada === true && r1.providencia === 'SNAT/INTI/GR/PRUEBA-001', `queda cargada su autorizacion (${r1.providencia})`);

  console.log('\n== 2. Los rangos que asigna el SENIAT ==');
  await sql(`select fiscal.cargar_rango_imprenta(${BASE}, ${BASE + 999}, 'OFICIO-PRUEBA')`);
  const r2 = (await uno(`select fiscal.resumen_numeracion() as r`)).r;
  check(Number(r2.autorizados) >= 1000, `el rango quedo cargado (${r2.autorizados} numeros autorizados)`);
  const solape = await falla(`select fiscal.cargar_rango_imprenta(${BASE + 500}, ${BASE + 1500}, 'SOLAPADO')`);
  check(!!solape, 'dos rangos de la imprenta NO se pueden solapar (seria numerar dos veces igual)');

  console.log('\n== 3. Repartir sub-rangos a los clientes ==');
  await sql(`select fiscal.guardar_cliente(${lit(RIF_A)}, 'Cliente Rango Uno', 'Acarigua', null,null,null,null,'propio','A',true,'prueba rangos')`);
  await sql(`select fiscal.guardar_cliente(${lit(RIF_B)}, 'Cliente Rango Dos', 'Araure', null,null,null,null,'propio','A',true,'prueba rangos')`);
  const e1 = (await uno(`select fiscal.entregar_rango(${lit(RIF_A)}, 10) as r`)).r;
  check(Number(e1.desde) === BASE && Number(e1.hasta) === BASE + 9, `al primero le tocan ${e1.desde} al ${e1.hasta}`);
  const e2 = (await uno(`select fiscal.entregar_rango(${lit(RIF_B)}, 5) as r`)).r;
  check(Number(e2.desde) === BASE + 10, `el segundo arranca donde termino el primero (${e2.desde}), sin huecos ni pisadas`);
  const noCabe = await falla(`select fiscal.entregar_rango(${lit(RIF_A)}, 5000)`);
  check(!!noCabe && /alcanza/i.test(noCabe), 'no se puede repartir mas de lo autorizado');

  console.log('\n== 4. El numero de control sale DEL RANGO ==');
  const idA = (await uno(`select id from fiscal.emisores where rif = ${lit(RIF_A)}`)).id;
  const c1 = (await uno(`select fiscal.asignar_control(${lit(idA)}, 'A') as c`)).c;
  const c2 = (await uno(`select fiscal.asignar_control(${lit(idA)}, 'A') as c`)).c;
  check(Number(c1) === BASE && Number(c2) === BASE + 1, `entrega ${c1} y luego ${c2}, en orden y dentro del rango`);
  const idB = (await uno(`select id from fiscal.emisores where rif = ${lit(RIF_B)}`)).id;
  const cb = (await uno(`select fiscal.asignar_control(${lit(idB)}, 'A') as cb`)).cb;
  check(Number(cb) === BASE + 10, `el otro cliente toma del SUYO (${cb}), sin cruzarse`);

  console.log('\n== 5. Cuando se acaba, REVIENTA (no emite fuera de rango) ==');
  // Se gastan los 8 que le quedan al primero.
  await sql(`update fiscal.rangos_emisor set proximo = hasta where emisor_id = ${lit(idA)}`);
  const ultimo = (await uno(`select fiscal.asignar_control(${lit(idA)}, 'A') as c`)).c;
  check(Number(ultimo) === BASE + 9, `entrega el ultimo numero del rango (${ultimo})`);
  const agotado = await falla(`select fiscal.asignar_control(${lit(idA)}, 'A')`);
  check(!!agotado && /agotaron/i.test(agotado), 'y al siguiente se planta: pide rango nuevo en vez de emitir invalido');
  check(/imprenta debe asignarle/i.test(agotado ?? ''), 'diciendo claro que la imprenta tiene que asignarle mas');

  console.log('\n== 6. El panel ve quien esta por quedarse sin numeros ==');
  const lista = await sql(`select * from fiscal.rangos_de_clientes() where desde >= ${BASE}`);
  const primero = lista.find((x) => x.rif === RIF_A);
  check(Number(primero.quedan) === 0, `al primero le quedan ${primero.quedan} numeros`);
  check(Number(primero.gastado_pct) === 100, 'y su rango aparece gastado al 100%');
  check(lista[0].rif === RIF_A, 'la lista pone de primero al que menos le queda');

  console.log('\n== 7. Sin rangos configurados, todo sigue como hoy ==');
  // Es el caso de Arepa Power mientras la imprenta opera bajo un tercero.
  await sql(`delete from fiscal.rangos_emisor where emisor_id = ${lit(idB)}`);
  const libre1 = (await uno(`select fiscal.asignar_control(${lit(idB)}, 'A') as c`)).c;
  const libre2 = (await uno(`select fiscal.asignar_control(${lit(idB)}, 'A') as c`)).c;
  check(Number(libre2) === Number(libre1) + 1, `numera libre y consecutivo (${libre1}, ${libre2}), sin tumbar a nadie`);

  console.log('\n== 8. El resumen cuadra ==');
  const r8 = (await uno(`select fiscal.resumen_numeracion() as r`)).r;
  check(Number(r8.autorizados) - Number(r8.repartidos) === Number(r8.por_repartir), 'autorizados menos repartidos es lo que queda por repartir');

  // Devolver la imprenta a como estaba (esta prueba corre contra la base viva).
  if (antes.i === null) {
    await sql('delete from fiscal.imprenta');
  } else {
    const i = antes.i;
    await sql(`select fiscal.guardar_imprenta(${lit(i.rif)}, ${lit(i.razon_social)}, ${lit(i.domicilio_fiscal)},
      ${i.providencia ? lit(i.providencia) : 'null'}, ${i.providencia_fecha ? lit(i.providencia_fecha) : 'null'},
      ${i.vigencia_hasta ? lit(i.vigencia_hasta) : 'null'}, ${i.autorizada})`);
  }
  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, 400));
  try { await limpiar(); } catch { /* mejor esfuerzo */ }
  process.exit(1);
}
