// PRUEBA DE LA ANULACION SOLICITADA DESDE EL PORTAL (2026-07-31)
//
// Bloque B: el dueño o el encargado piden la anulacion desde Gustito, la
// solicitud viaja por la cola (Gustito jamas escribe en el libro), y la
// Estacion emite la nota de credito y responde quien la pidio. Tambien cubre
// el hueco real de hoy: venta cancelada YA SELLADA sin su nota de credito.
//
// Contra una nube de mentira (un servidorcito local):
//   1. La venta llega y se sella (el circuito de siempre).
//   2. Llega la solicitud de anulacion y sale la nota de credito, con el
//      motivo y el nombre de quien la pidio en la bitacora.
//   3. Pedirla dos veces NO duplica la nota: responde con la que existe.
//   4. Una venta que no esta en el libro responde su problema, no revienta.
//   5. Venta y anulacion en la MISMA ronda: primero se sella, despues la nota.
import { createServer } from 'node:http';
import { mkdtempSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { randomUUID } from 'node:crypto';
import { EstacionFiscal } from '../dist/estacion.js';
import { guardarConfig } from '../dist/config.js';

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

const dir = mkdtempSync(join(tmpdir(), 'fiscal-anul-'));
const PUERTO = 8761;

// La nube de mentira: entrega ventas y solicitudes, y anota lo que le responden.
const nube = {
  ventas: [],
  anulaciones: [],
  confirmadas: [],
  resultados: [],
};
const falso = createServer(async (req, res) => {
  const trozos = [];
  for await (const t of req) trozos.push(t);
  const p = JSON.parse(Buffer.concat(trozos).toString('utf8') || '{}');
  const responder = (d) => {
    res.writeHead(200, { 'content-type': 'application/json' });
    res.end(JSON.stringify(d));
  };
  if (p.accion === 'latido') {
    return responder({
      ok: true,
      pendientes_en_nube: nube.ventas.length,
      anulaciones_en_nube: nube.anulaciones.length,
      volver_en_segundos: 10,
      serial: null,
    });
  }
  if (p.accion === 'pendientes') return responder(nube.ventas.splice(0));
  if (p.accion === 'confirmar') {
    nube.confirmadas.push(p.datos);
    return responder({ ok: true });
  }
  if (p.accion === 'anulaciones') return responder(nube.anulaciones.splice(0));
  if (p.accion === 'anulacion_resultado') {
    nube.resultados.push(p.datos);
    return responder({ ok: true });
  }
  return responder({ ok: true });
});

const ventaDeNube = (origen_id) => ({
  cola_id: randomUUID(),
  tipo: 'pedido',
  origen_id,
  venta: {
    codigo: 'GX-0001',
    fecha: new Date().toISOString(),
    tipo_entrega: 'pickup',
    mesa: null,
    metodo_pago: 'efectivo',
    tasa_bs: 746.63,
    total_usd: 11.6,
    costo_delivery_usd: 0,
    comprador: { nombre: 'Consumidor Final', rif: null, razon_social: null, direccion: null, correo: null, telefono: null },
    renglones: [{ nombre: 'Arepa pelua', cantidad: 2, precio_usd: 5.8 }],
  },
});

const solicitud = (origen_id, motivo, quien) => ({
  anulacion_id: randomUUID(),
  tipo: 'pedido',
  origen_id,
  motivo,
  pedida_por: quien,
});

const CONFIG = {
  emisor: {
    rif: 'J-50856106-6',
    razon_social: 'Arepa Power de prueba',
    domicilio_fiscal: 'Araure, Portuguesa',
    precios_con_iva: true,
    alicuota_general: 16,
  },
  serie: 'A',
  sucursal: '1',
  equipo: 'FISCAL-01',
  imprenta: { adaptador: 'practica', nombre: 'Practica' },
  respaldo: null,
  usando: 'imprenta',
  hora_url: 'http://127.0.0.1:9/no-existe',
  nube: { url: `http://127.0.0.1:${PUERTO}`, llave: 'efm_de_prueba' },
};

try {
  await new Promise((si) => falso.listen(PUERTO, '127.0.0.1', si));
  guardarConfig(dir, CONFIG);
  const est = await EstacionFiscal.abrir(dir);

  console.log('\n== 1. La venta llega de la nube y se sella ==');
  const idVenta = randomUUID();
  nube.ventas.push(ventaDeNube(idVenta));
  const r1 = await est.sincronizarConNube();
  check(r1.sellada === 1 && r1.problema === undefined, `sellada en la ronda (${r1.sellada})`);
  check(nube.confirmadas.length === 1, 'y confirmada a la nube con su numero');

  console.log('\n== 2. Llega la solicitud y sale la nota de credito ==');
  nube.anulaciones.push(solicitud(idVenta, 'El cliente devolvio el pedido completo', 'Gilberto (dueño)'));
  const r2 = await est.sincronizarConNube();
  check(r2.anulada === 1 && r2.anulacionFallada === 0, `atendida en la ronda (${r2.anulada})`);
  const res2 = nube.resultados.at(-1);
  check(res2?.ok === true && res2?.numero_nota === 1, `respondida con su numero de nota (${res2?.numero_nota})`);
  const libro = readFileSync(join(dir, 'estado', 'libro.jsonl'), 'utf8').trim().split('\n').map((l) => JSON.parse(l));
  const nota = libro.find((a) => a.tipo === 'nota_credito');
  check(!!nota && nota.ref?.tipo === 'factura' && nota.ref?.numero === 1, 'la nota referencia a la factura original');
  check(nota?.documento.total_usd === -11.6, `por el monto completo en negativo (${nota?.documento.total_usd})`);
  check(nota?.documento.motivo === 'El cliente devolvio el pedido completo', 'con el motivo que viajo desde el portal');
  const bitacora = readFileSync(join(dir, 'estado', 'bitacora.jsonl'), 'utf8').trim().split('\n').map((l) => JSON.parse(l));
  const evento = bitacora.find((e) => e.accion === 'nota_credito');
  check(evento?.quien === 'portal: Gilberto (dueño)', `quien la pidio queda en la bitacora (${evento?.quien})`);

  console.log('\n== 3. Pedirla dos veces no duplica la nota ==');
  nube.anulaciones.push(solicitud(idVenta, 'Reintento del portal', 'Gilberto (dueño)'));
  const r3 = await est.sincronizarConNube();
  const res3 = nube.resultados.at(-1);
  check(r3.anulada === 1 && res3?.ok === true && res3?.numero_nota === 1, 'responde con la nota que ya existia');
  const libro3 = readFileSync(join(dir, 'estado', 'libro.jsonl'), 'utf8').trim().split('\n').map((l) => JSON.parse(l));
  check(libro3.filter((a) => a.tipo === 'nota_credito').length === 1, 'y el libro sigue con UNA sola nota');

  console.log('\n== 4. Una venta que no esta en el libro responde su problema ==');
  nube.anulaciones.push(solicitud(randomUUID(), 'Anular algo que no existe', 'Intruso (encargado)'));
  const r4 = await est.sincronizarConNube();
  const res4 = nube.resultados.at(-1);
  check(r4.anulacionFallada === 1 && res4?.ok === false, 'la ronda la reporta fallida');
  check(/no esta en el libro/.test(res4?.error ?? ''), `con el porque (${res4?.error})`);

  console.log('\n== 5. Venta y anulacion en la misma ronda: sella y anula ==');
  const idOtra = randomUUID();
  nube.ventas.push(ventaDeNube(idOtra));
  nube.anulaciones.push(solicitud(idOtra, 'Cancelada de una vez', 'sistema'));
  const r5 = await est.sincronizarConNube();
  check(r5.sellada === 1 && r5.anulada === 1, `primero la factura, despues su nota (${r5.sellada}/${r5.anulada})`);
  const libro5 = readFileSync(join(dir, 'estado', 'libro.jsonl'), 'utf8').trim().split('\n').map((l) => JSON.parse(l));
  check(libro5.filter((a) => a.tipo === 'factura').length === 2, 'dos facturas en el libro');
  check(libro5.filter((a) => a.tipo === 'nota_credito').length === 2, 'y cada anulada con su nota');
} catch (e) {
  console.log('  XX  ' + e.message);
  fallos++;
} finally {
  falso.close();
  rmSync(dir, { recursive: true, force: true });
}

console.log(fallos === 0 ? '\nANULACION DESDE EL PORTAL: TODO VERDE' : `\n${fallos} FALLO(S)`);
process.exitCode = fallos === 0 ? 0 : 1; // salida natural: process.exit() con undici cerrando revienta libuv en Node 24/Windows
