// EL INTERRUPTOR FISCAL Y LA COLA DE SELLADO (2026-07-29)
//
// Lo que se prueba, que es exactamente lo que Gilberto pidio:
//   1. Sin vinculo, Gustito no sabe nada de facturacion (sigue siendo el puente).
//   2. Solo el panel de la imprenta lo enciende. El negocio NO tiene boton.
//   3. Encendido, TODA venta cerrada entra sola a la cola, pida o no pida
//      factura el cliente. Ninguna venta queda por fuera.
//   4. El "necesitas factura?" solo captura datos del comprador.
//   5. El dueño no puede zafarse cambiandose el RIF.
//   6. Gilberto baja el breaker y la Estacion deja de poder trabajar.
//
// Corre contra la BD viva y la puerta publicada. 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();
const PUERTA = process.env.PUERTA ?? 'https://sello.gustitoxpress.app';
const USUARIO = process.env.PANEL_USUARIO ?? 'gilberto';
// La clave NO se escribe aca: vive en .panel_clave, que git no sube.
const CLAVE =
  process.env.PANEL_CLAVE ?? readFileSync(new URL('../.panel_clave', 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, "''")}'`;

const RIF = 'V-22606827-5';
const MESAS = ['FX01', 'FX02'];

// La Estacion habla con la nube por la misma puerta que usaria en el negocio.
const estacion = (llave, accion, datos = {}) =>
  fetch(`${PUERTA}/api/estacion`, {
    method: 'POST',
    headers: { authorization: `Bearer ${llave}`, 'content-type': 'application/json' },
    body: JSON.stringify({ accion, rif: RIF, datos }),
  });

let REST = null;

// El panel de Gilberto, con su sesion, igual que cuando lo abre en el navegador.
let galleta = '';
async function api(accion, datos) {
  const r = await fetch(`${PUERTA}/api/admin`, {
    method: 'POST',
    headers: { 'content-type': 'application/json', ...(galleta ? { cookie: galleta } : {}) },
    body: JSON.stringify({ accion, datos: datos || {} }),
  });
  const set = r.headers.get('set-cookie');
  if (set) galleta = set.split(';')[0];
  return { estado: r.status, cuerpo: await r.json().catch(() => ({})) };
}

const limpiar = async () => {
  if (REST) {
    await sql(`delete from fiscal.cola_gustito where restaurante_id = ${lit(REST)};
               delete from fiscal.negocios where restaurante_id = ${lit(REST)};
               delete from public.pedido_items where pedido_id in (
                 select id from public.pedidos where restaurante_id=${lit(REST)} and codigo like 'FXTEST%');
               delete from public.pagos where pedido_id in (
                 select id from public.pedidos where restaurante_id=${lit(REST)} and codigo like 'FXTEST%');
               delete from public.pedidos where restaurante_id=${lit(REST)} and codigo like 'FXTEST%';
               delete from public.cuentas_mesa where restaurante_id=${lit(REST)} and mesa in (${MESAS.map(lit).join(',')});
               update public.restaurantes set rif=null, razon_social=null, domicilio_fiscal=null where id=${lit(REST)};`);
  }
  await sql(`delete from fiscal.estaciones where emisor_id in (select id from fiscal.emisores where rif=${lit(RIF)});
             delete from fiscal.intentos where quien=${lit(RIF)};
             delete from fiscal.llaves where emisor_id in (select id from fiscal.emisores where 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)};`);
};

try {
  const rest = await uno(`select id, nombre from public.restaurantes where slug='demo-gustito'`);
  if (!rest) throw new Error('No existe el restaurante demo');
  REST = rest.id;
  await limpiar();

  console.log('\n== 1. Sin vinculo, Gustito no sabe de facturacion ==');
  const apagado = (await uno(`select public.fn_estado_fiscal(${lit(REST)}) as e`)).e;
  check(apagado.activa === false, 'la facturacion esta apagada');
  check(apagado.motivo === 'sin_vinculo', `y dice por que (${apagado.motivo})`);

  // Una venta cerrada estando apagado NO debe encolar nada.
  await sql(`insert into public.pedidos (restaurante_id, codigo, cliente_nombre, cliente_telefono,
               tipo_entrega, estado, entregado_en, subtotal_usd, total_usd, tasa_bs, metodo_pago)
             values (${lit(REST)}, 'FXTEST-APAGADO', 'Cliente', '0', 'pickup', 'entregado', now(), 5, 5, 40, 'efectivo')`);
  const colaApagada = await uno(`select count(*)::int as n from fiscal.cola_gustito where restaurante_id=${lit(REST)}`);
  check(colaApagada.n === 0, 'y una venta cerrada no entra a ninguna cola fiscal');

  console.log('\n== 2. El negocio NO puede encenderla por su cuenta ==');
  // Lo unico que un dueño podria tocar por su lado son sus datos fiscales.
  await sql(`update public.restaurantes set rif=${lit(RIF)} where id=${lit(REST)}`);
  const conRif = (await uno(`select public.fn_estado_fiscal(${lit(REST)}) as e`)).e;
  check(conRif.activa === false, 'aunque se ponga un RIF, sigue apagada (falta el vinculo del proveedor)');

  console.log('\n== 3. Gilberto la enciende desde su panel ==');
  await sql(`select fiscal.guardar_cliente(${lit(RIF)}, 'Restaurante de prueba', 'Araure, Portuguesa',
              null, null, null, null, 'Gustito Xpress', 'A', false, null)`);
  await sql(`select fiscal.vincular_negocio(${lit(REST)}, ${lit(RIF)}, 'A', 'gilberto', 'prueba')`);
  const sinComprobante = (await uno(`select public.fn_estado_fiscal(${lit(REST)}) as e`)).e;
  check(sinComprobante.activa === false && sinComprobante.motivo === 'sin_comprobante_rif',
    'vinculado pero sin comprobante de RIF todavia no emite (art. 29)');

  await sql(`update fiscal.emisores set rif_comprobado=true where rif=${lit(RIF)}`);
  const sinLlave = (await uno(`select public.fn_estado_fiscal(${lit(REST)}) as e`)).e;
  check(sinLlave.motivo === 'sin_llave', 'y sin llave entregada tampoco');

  const llave = (await uno(`select fiscal.crear_llave(${lit(RIF)}, 'estacion del negocio') as k`)).k;
  const encendido = (await uno(`select public.fn_estado_fiscal(${lit(REST)}) as e`)).e;
  check(encendido.activa === true, 'con todo en regla, queda habilitado');
  check(encendido.rif === RIF && !!encendido.razon_social, `y trae el encabezado del emisor (${encendido.razon_social})`);

  console.log('\n== 4. El dueño no puede zafarse cambiando su RIF ==');
  let bloqueado = false;
  try {
    await sql(`update public.restaurantes set rif='J-50856106-6' where id=${lit(REST)}`);
  } catch (e) {
    bloqueado = /proveedor autorizado/i.test(String(e));
  }
  check(bloqueado, 'cambiarse los datos fiscales estando habilitado se rechaza');

  console.log('\n== 5. TODA venta cerrada entra sola a la cola ==');
  // Venta de retiro en tienda, sin que nadie pida factura.
  const p1 = await uno(`insert into public.pedidos (restaurante_id, codigo, cliente_nombre, cliente_telefono,
                          tipo_entrega, estado, entregado_en, subtotal_usd, total_usd, tasa_bs, metodo_pago, origen)
                        values (${lit(REST)}, 'FXTEST-1', 'Quien sea', '04120000000', 'pickup', 'entregado', now(),
                                12, 12, 40, 'efectivo', 'caja') returning id`);
  await sql(`insert into public.pedido_items (pedido_id, nombre, precio_usd, cantidad)
             values (${lit(p1.id)}, 'Arepa de pernil', 6, 2)`);
  const enCola = await uno(`select count(*)::int as n from fiscal.cola_gustito
                             where restaurante_id=${lit(REST)} and tipo='pedido' and origen_id=${lit(p1.id)}`);
  check(enCola.n === 1, 'la venta sin factura pedida entra igual (va como Consumidor Final)');

  // Una mesa que se cierra.
  const cta = await uno(`insert into public.cuentas_mesa (restaurante_id, mesa, estado, cerrada_en, metodo_pago)
                         values (${lit(REST)}, ${lit(MESAS[0])}, 'abierta', null, 'efectivo') returning id`);
  const p2 = await uno(`insert into public.pedidos (restaurante_id, codigo, cliente_nombre, cliente_telefono,
                          tipo_entrega, mesa, estado, entregado_en, subtotal_usd, total_usd, tasa_bs, metodo_pago, cuenta_id)
                        values (${lit(REST)}, 'FXTEST-2', 'Mesa', '0', 'mesa', ${lit(MESAS[0])}, 'entregado', now(),
                                20, 20, 40, 'efectivo', ${lit(cta.id)}) returning id`);
  await sql(`insert into public.pedido_items (pedido_id, nombre, precio_usd, cantidad)
             values (${lit(p2.id)}, 'Parrilla', 10, 2)`);
  const mesaSinCerrar = await uno(`select count(*)::int as n from fiscal.cola_gustito
                                    where restaurante_id=${lit(REST)} and tipo='cuenta'`);
  check(mesaSinCerrar.n === 0, 'la mesa abierta todavia no se factura');
  check((await uno(`select count(*)::int as n from fiscal.cola_gustito
                     where tipo='pedido' and origen_id=${lit(p2.id)}`)).n === 0,
    'y el pedido de mesa no se factura suelto (va dentro de la cuenta)');

  await sql(`update public.cuentas_mesa set estado='cerrada', cerrada_en=now() where id=${lit(cta.id)}`);
  check((await uno(`select count(*)::int as n from fiscal.cola_gustito
                     where tipo='cuenta' and origen_id=${lit(cta.id)}`)).n === 1,
    'al cerrar la mesa, entra UN documento con todo lo consumido');

  console.log('\n== 6. El "necesitas factura?" solo captura datos ==');
  let rifMalo = false;
  try {
    await sql(`select public.guardar_datos_fiscales('cuenta', ${lit(cta.id)}, 'J-12345678-9', 'Inventada', null, null)`);
  } catch (e) {
    rifMalo = /no es valido/i.test(String(e));
  }
  check(rifMalo, 'un RIF con digito malo se rechaza');
  await sql(`select public.guardar_datos_fiscales('cuenta', ${lit(cta.id)}, 'J-50856106-6',
              'Empresa del cliente', 'Acarigua', 'cliente@ejemplo.com')`);
  const conDatos = await uno(`select cliente_rif, cliente_razon_social from public.cuentas_mesa where id=${lit(cta.id)}`);
  check(conDatos.cliente_rif === 'J-50856106-6', `los datos quedan guardados (${conDatos.cliente_razon_social})`);

  console.log('\n== 7. La Estacion hala las ventas y las sella ==');
  const rHalar = await estacion(llave, 'pendientes', { limite: 10 });
  const pendientes = await rHalar.json();
  check(rHalar.status === 200, `la puerta responde (HTTP ${rHalar.status})`);
  check(Array.isArray(pendientes) && pendientes.length === 2, `hala las 2 ventas cerradas (${pendientes.length})`);

  const conCuenta = pendientes.find((p) => p.tipo === 'cuenta');
  check(conCuenta?.venta?.total_usd == 20, `la mesa viene con su total (${conCuenta?.venta?.total_usd})`);
  check(conCuenta?.venta?.comprador?.rif === 'J-50856106-6', 'y con el RIF del comprador que lo pidio');
  check(Array.isArray(conCuenta?.venta?.renglones) && conCuenta.venta.renglones.length === 1,
    'con el detalle de lo que se consumio');
  const sinRif = pendientes.find((p) => p.tipo === 'pedido');
  check(sinRif?.venta?.comprador?.rif === null, 'la que no pidio factura viene sin RIF (Consumidor Final)');
  check(sinRif?.venta?.origen === 'caja', 'y sabe que fue venta de caja');

  const sinImpuestos = JSON.stringify(pendientes);
  check(!/iva|alicuota|base_imponible/i.test(sinImpuestos),
    'FRONTERA: la nube no manda ni una linea de impuestos');

  console.log('\n== 8. La Estacion confirma y la cola se vacia ==');
  const conf = await estacion(llave, 'confirmar', {
    cola_id: conCuenta.cola_id, numero_documento: 1, numero_control: 1, token: 'abc123',
  });
  check(conf.status === 200, `confirma el sellado (HTTP ${conf.status})`);
  const yaSellada = await uno(`select estado, numero_control from fiscal.cola_gustito where id=${lit(conCuenta.cola_id)}`);
  check(yaSellada.estado === 'sellado', `queda marcada como sellada (control ${yaSellada.numero_control})`);

  let noSeEdita = false;
  try {
    await sql(`select public.guardar_datos_fiscales('cuenta', ${lit(cta.id)}, 'V-22606827-5', 'Otro', null, null)`);
  } catch (e) {
    noSeEdita = /nota de credito/i.test(String(e));
  }
  check(noSeEdita, 'y ya no se le cambian los datos al comprador (eso es nota de credito)');

  console.log('\n== 9. Si algo falla, la venta vuelve a la cola ==');
  await estacion(llave, 'error', { cola_id: sinRif.cola_id, error: 'la impresora no respondio' });
  const conError = await uno(`select estado, intentos, ultimo_error, proximo_intento_en > now() as esperando
    from fiscal.cola_gustito where id=${lit(sinRif.cola_id)}`);
  check(conError.estado === 'pendiente' && conError.intentos >= 1,
    `vuelve a pendiente con su motivo (${conError.ultimo_error})`);
  // No vuelve a entregarse de una: espera antes del proximo intento. Sin eso, un
  // fallo repetido hace que la misma venta se entregue cada 10 segundos, que fue
  // un bug real (ver scripts/test_cola_no_martilla.mjs).
  check(conError.esperando === true, 'y espera un rato antes de volver a intentarlo');

  console.log('\n== 10. El latido: Gilberto ve si la Estacion esta viva ==');
  // Se adelanta el reloj de la espera para comprobar el conteo del latido.
  await sql(`update fiscal.cola_gustito set proximo_intento_en = now() - interval '1 second'
              where id=${lit(sinRif.cola_id)}`);
  const lat = await estacion(llave, 'latido', {
    equipo: 'caja-1', version: '1.0.0', pendientes: 0, ultimo_documento: 1, libro_integro: true,
  });
  const datosLat = await lat.json();
  check(lat.status === 200, `la estacion reporta (HTTP ${lat.status})`);
  check(datosLat.pendientes_en_nube === 1, `y le dicen cuanto le falta (${datosLat.pendientes_en_nube})`);
  const vista = await uno(`select * from fiscal.estaciones_vivas() where rif=${lit(RIF)}`);
  check(!!vista && vista.minutos_sin_reportar === 0, `en el panel se ve viva (${vista?.equipo}, v${vista?.version})`);

  console.log('\n== 11. Gilberto baja el breaker ==');
  await sql(`select fiscal.activar_cliente(${lit(RIF)}, false)`);
  const cortado = await estacion(llave, 'pendientes', {});
  check(cortado.status === 401, `la Estacion ya no puede halar (HTTP ${cortado.status})`);
  const apagadoPorPago = (await uno(`select public.fn_estado_fiscal(${lit(REST)}) as e`)).e;
  check(apagadoPorPago.activa === false && apagadoPorPago.motivo === 'suspendido',
    'y el negocio queda suspendido');

  // Y una venta nueva ya no entra a la cola: nada queda a medias.
  await sql(`insert into public.pedidos (restaurante_id, codigo, cliente_nombre, cliente_telefono,
               tipo_entrega, estado, entregado_en, subtotal_usd, total_usd, tasa_bs, metodo_pago)
             values (${lit(REST)}, 'FXTEST-3', 'Cliente', '0', 'pickup', 'entregado', now(), 7, 7, 40, 'efectivo')`);
  const trasBreaker = await uno(`select count(*)::int as n from fiscal.cola_gustito
                                  where restaurante_id=${lit(REST)} and estado='pendiente'`);
  check(trasBreaker.n === 1, 'las ventas nuevas dejan de encolarse (queda solo la de antes)');

  console.log('\n== 12. Le paga y todo vuelve solo ==');
  await sql(`select fiscal.activar_cliente(${lit(RIF)}, true)`);
  const revivido = await estacion(llave, 'pendientes', {});
  check(revivido.status === 200, `la Estacion vuelve a trabajar (HTTP ${revivido.status})`);
  const rescate = await estacion(llave, 'rescatar', { horas: 1 });
  const nRescate = await rescate.json();
  check(nRescate.rescatadas >= 1, `y rescata la venta que quedo por fuera (${nRescate.rescatadas})`);

  console.log('\n== 13. Gilberto lo ve y lo maneja desde su panel ==');
  await api('entrar', { usuario: USUARIO, clave: CLAVE });
  const busqueda = await api('buscar_negocio', { texto: 'demo' });
  check(busqueda.estado === 200 && busqueda.cuerpo.datos.some((n) => n.id === REST),
    'consigue el negocio buscandolo por nombre');
  check(busqueda.cuerpo.datos.find((n) => n.id === REST)?.ya_vinculado === true,
    'y le avisa que ya esta habilitado');

  const negocios = (await api('negocios_gustito')).cuerpo.datos;
  const mio = negocios.find((n) => n.restaurante_id === REST);
  check(mio?.activa === true, `sale en la lista de habilitados (${mio?.nombre})`);

  const ests = (await api('estaciones')).cuerpo.datos;
  check(ests.some((e) => e.rif === RIF), 'la estacion del negocio aparece con su latido');

  const cola = (await api('cola')).cuerpo.datos;
  check(Array.isArray(cola), `la cola por revisar se puede consultar (${cola.length} en espera)`);

  await api('desvincular_negocio', { restaurante_id: REST });
  const trasQuitar = (await uno(`select public.fn_estado_fiscal(${lit(REST)}) as e`)).e;
  check(trasQuitar.activa === false && trasQuitar.motivo === 'sin_vinculo',
    'y al quitarle la facturacion, el negocio vuelve a ser solo Gustito');
} catch (e) {
  console.log('  XX  ' + String(e).slice(0, 300));
  fallos++;
} finally {
  await limpiar();
}

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