// PRUEBA DE PUNTA A PUNTA del cerebrito (etapa 1) contra la BD VIVA:
// un pedido real en el restaurante DEMO se confirma y el cerebrito lo parte
// por puesto y lo "imprime" en dos impresoras virtuales TCP. Limpia todo.
// Env: SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_KEY.
// Correr desde apps/cerebrito con dist/ ya compilado:  npm run prueba
import { createClient } from '@supabase/supabase-js';
import { mkdtempSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { crearImpresoraVirtual } from './impresora_virtual.mjs';
import { iniciarCerebrito } from '../dist/motor.js';

const URL = process.env.SUPABASE_URL || process.env.VITE_SUPABASE_URL;
const ANON = process.env.SUPABASE_ANON_KEY || process.env.VITE_SUPABASE_ANON_KEY;
const SERVICE = process.env.SUPABASE_SERVICE_KEY;
if (!URL || !ANON || !SERVICE) {
  console.log('Faltan SUPABASE_URL / SUPABASE_ANON_KEY / SUPABASE_SERVICE_KEY');
  process.exit(1);
}

let fallos = 0;
const check = (cond, msg) => {
  console.log((cond ? '  OK  ' : '  XX  ') + msg);
  if (!cond) fallos += 1;
};
const espera = (ms) => new Promise((r) => setTimeout(r, ms));
const hasta = async (fn, ms, paso = 500) => {
  const tope = Date.now() + ms;
  while (Date.now() < tope) {
    if (await fn()) return true;
    await espera(paso);
  }
  return await fn();
};
// Compara ignorando saltos de linea (los nombres largos se parten al envolver)
// Y acentos: los tickets van en ASCII puro desde v2.6.5 (las termicas chinas
// en modo GB pintaban caracteres chinos con los bytes altos de CP850).
const plano = (t) => t.normalize('NFD').replace(/[̀-ͯ]/g, '').replace(/\s+/g, ' ');

const svc = createClient(URL, SERVICE, { auth: { persistSession: false } });

const { data: rest } = await svc
  .from('restaurantes')
  .select('id,abierto')
  .eq('slug', 'demo-gustito')
  .maybeSingle();
if (!rest?.id || !rest.abierto) {
  console.log('Demo no disponible');
  process.exit(1);
}
const REST = rest.id;

// --- Puestos del demo (idempotente): Cocina (defecto) + Barra; Bebidas -> Barra. ---
const { data: puestosDemo, error: ePuestos } = await svc
  .from('puestos_impresion')
  .upsert(
    [
      { restaurante_id: REST, nombre: 'Cocina', orden: 1, es_defecto: true },
      { restaurante_id: REST, nombre: 'Barra', orden: 2, es_defecto: false },
    ],
    { onConflict: 'restaurante_id,nombre' },
  )
  .select('id,nombre');
if (ePuestos) {
  console.log('No pude sembrar puestos demo: ' + ePuestos.message);
  process.exit(1);
}
const cocinaId = puestosDemo.find((p) => p.nombre === 'Cocina')?.id;
const barraId = puestosDemo.find((p) => p.nombre === 'Barra')?.id;

// Higiene del sandbox: pedidos viejos que quedaron "en cocina" de pruebas
// anteriores pasarian por el barrido y meterian ruido. Cerrarlos.
await svc
  .from('pedidos')
  .update({ estado: 'listo', listo_en: new Date().toISOString() })
  .eq('restaurante_id', REST)
  .in('estado', ['confirmado', 'preparando'])
  .lt('recibido_en', new Date(Date.now() - 10 * 60_000).toISOString());

const { data: cats } = await svc.from('categorias').select('id,nombre').eq('restaurante_id', REST);
for (const c of cats ?? []) {
  await svc
    .from('categorias')
    .update({ puesto_id: /bebida/i.test(c.nombre) ? barraId : cocinaId })
    .eq('id', c.id);
}

// --- Un producto de comida y una bebida del demo. ---
const { data: prods } = await svc
  .from('productos')
  .select('id,nombre,disponible,categorias(nombre)')
  .eq('restaurante_id', REST)
  .eq('disponible', true);
const bebida = (prods ?? []).find((p) => /bebida/i.test(p.categorias?.nombre ?? ''));
const comida = (prods ?? []).find((p) => !/bebida/i.test(p.categorias?.nombre ?? ''));
if (!bebida || !comida) {
  console.log('El demo no tiene comida + bebida disponibles');
  process.exit(1);
}

const EMAIL = 'cerebrito.test@gustitoxpress.app';
const CLAVE = 'CerebritoTest2026!';
let uid;
let pedidoId;
let deliveryId;
let cuentaLoteId;
const pedidosLote = [];
let motor;
let impCocina;
let impBarra;
let impCaja;
const carpetaTmp = mkdtempSync(join(tmpdir(), 'cerebrito-'));

const limpiar = async () => {
  try {
    motor?.detener();
  } catch {
    /* ya detenido */
  }
  await impCocina?.cerrar();
  await impBarra?.cerrar();
  await impCaja?.cerrar();
  if (pedidoId) await svc.from('pedidos').delete().eq('id', pedidoId);
  if (deliveryId) await svc.from('pedidos').delete().eq('id', deliveryId);
  for (const pid of pedidosLote) await svc.from('pedidos').delete().eq('id', pid);
  if (cuentaLoteId) await svc.from('cuentas_mesa').delete().eq('id', cuentaLoteId);
  if (uid) await svc.auth.admin.deleteUser(uid).catch(() => {});
  rmSync(carpetaTmp, { recursive: true, force: true });
};

try {
  // --- Encargado temporal (el rol con el que entra el cerebrito en la vida real). ---
  const { data: lista } = await svc.auth.admin.listUsers();
  for (const u of lista?.users ?? []) {
    if (u.email === EMAIL) await svc.auth.admin.deleteUser(u.id).catch(() => {});
  }
  const { data: u, error: eU } = await svc.auth.admin.createUser({
    email: EMAIL,
    password: CLAVE,
    email_confirm: true,
  });
  if (eU) throw new Error('createUser: ' + eU.message);
  uid = u.user.id;
  await svc
    .from('perfiles')
    .upsert({ id: uid, rol: 'encargado', restaurante_id: REST, nombre: 'Cerebrito Test' });

  // --- Impresoras virtuales + cerebrito andando. ---
  impCocina = await crearImpresoraVirtual(9310);
  impBarra = await crearImpresoraVirtual(9311);
  impCaja = await crearImpresoraVirtual(9312);
  motor = await iniciarCerebrito(
    {
      supabaseUrl: URL,
      anonKey: ANON,
      correo: EMAIL,
      clave: CLAVE,
      impresoras: [
        { puesto: 'Cocina', tipo: 'tcp', host: '127.0.0.1', puerto: 9310 },
        { puesto: 'Barra', tipo: 'tcp', host: '127.0.0.1', puerto: 9311 },
        { puesto: 'Caja', tipo: 'tcp', host: '127.0.0.1', puerto: 9312 },
      ],
      // Ticket de despacho SOLO para delivery (asi el pedido pickup del primer
      // escenario no lo dispara y cada escenario prueba una cosa).
      despacho: { puesto: 'Caja', tipos: ['delivery'] },
      carpetaEstado: carpetaTmp,
    },
    { log: (m) => console.log('   [motor] ' + m) },
  );

  // --- Pedido real por el camino real (cliente anonimo, como la tienda). ---
  const anon = createClient(URL, ANON, { auth: { persistSession: false } });
  const { data: creado, error: eCrear } = await anon.rpc('crear_pedido', {
    p_restaurante_id: REST,
    p_cliente_nombre: 'Prueba Cerebrito',
    p_cliente_telefono: '04120000001',
    p_tipo_entrega: 'pickup',
    p_metodo_pago: 'efectivo',
    p_items: [
      { producto_id: comida.id, cantidad: 2, nota: 'Elige tu salsa: Tártara · Sin cebolla' },
      { producto_id: bebida.id, cantidad: 1 },
    ],
  });
  check(!eCrear && !!creado?.[0]?.pedido_id, 'crear_pedido funciona' + (eCrear ? ' -> ' + eCrear.message : ''));
  pedidoId = creado?.[0]?.pedido_id;
  const { data: pFila } = await svc.from('pedidos').select('codigo').eq('id', pedidoId).maybeSingle();
  const codigo = pFila?.codigo ?? '';

  // --- Confirmarlo como personal del negocio: eso dispara la impresion. ---
  const enc = createClient(URL, ANON, { auth: { persistSession: false } });
  await enc.auth.signInWithPassword({ email: EMAIL, password: CLAVE });
  const { error: eConf } = await enc.rpc('cambiar_estado_pedido', {
    p_pedido_id: pedidoId,
    p_nuevo_estado: 'confirmado',
  });
  check(!eConf, 'cambiar_estado_pedido a confirmado' + (eConf ? ' -> ' + eConf.message : ''));

  // --- Esperar la impresion (canal en vivo; el barrido de 20s es el respaldo). ---
  const llego = await hasta(() => impCocina.tickets.length >= 1 && impBarra.tickets.length >= 1, 30_000);
  check(llego, 'las DOS impresoras recibieron su comanda (cocina y barra)');

  const tCocina = plano(impCocina.tickets.join('\n'));
  const tBarra = plano(impBarra.tickets.join('\n'));
  check(tCocina.includes('COCINA'), 'la comanda de cocina trae el nombre del puesto');
  check(tCocina.includes(`#${codigo}`) && tBarra.includes(`#${codigo}`), `ambas traen el numero de orden #${codigo}`);
  check(tCocina.includes('PARA RETIRAR'), 'trae el tipo de entrega');
  check(
    /\d{2}\/\d{2}\/\d{4} \d{1,2}:\d{2} (am|pm)/.test(tCocina),
    'trae fecha completa y hora normal con am/pm (nada de hora militar)',
  );
  check(tCocina.includes(plano(`2x ${comida.nombre}`)), 'cocina trae su renglon con cantidad: ' + comida.nombre);
  check(!tCocina.includes(plano(bebida.nombre)), 'cocina NO trae la bebida (ruteo por puesto)');
  check(tBarra.includes(plano(`1x ${bebida.nombre}`)), 'barra trae la bebida: ' + bebida.nombre);
  check(!tBarra.includes(plano(comida.nombre)), 'barra NO trae la comida (ruteo por puesto)');
  check(tCocina.includes('Salsa: Tartara'), 'las opciones elegidas salen legibles (sin acentos: a prueba de impresoras chinas)');
  check(tCocina.includes('>> Sin cebolla'), 'la nota libre del renglon salta a la vista');
  check(impCocina.tickets.join('').includes('[CORTE]'), 'la comanda termina con el corte de papel');

  // MODELO COCINA DE PAPEL: al salir el papel, el pedido pasa solo a "preparando"
  // (el cocinero no toca pantallas; "listo" lo marca quien despacha).
  const avanzo = await hasta(async () => {
    const { data: fila } = await svc.from('pedidos').select('estado').eq('id', pedidoId).maybeSingle();
    return fila?.estado === 'preparando';
  }, 15_000);
  check(avanzo, 'papel impreso = pedido PREPARANDO automatico (el cocinero no toca pantalla)');

  check(impCaja.tickets.length === 0, 'un pickup NO dispara ticket de despacho (config tipos=[delivery])');

  // --- Escenario 2: DELIVERY -> comanda de cocina + ticket de despacho en caja.
  // Ademas: la categoria de bebidas pasa a "No se imprime" (a_cocina=false, como
  // lo hace la pantallita del panel) -> la bebida NO sale en ninguna cocina pero
  // SI en el checklist del despacho. ---
  const { data: catBebidas } = await svc
    .from('categorias')
    .select('id')
    .eq('restaurante_id', REST)
    .ilike('nombre', '%bebida%')
    .maybeSingle();
  if (catBebidas?.id) await svc.from('categorias').update({ a_cocina: false }).eq('id', catBebidas.id);
  const { data: tasaFila } = await svc.from('restaurantes').select('tasa_bs').eq('id', REST).maybeSingle();
  const codigoDeliv = `GD-T${String(Date.now()).slice(-5)}`;
  const { data: pDeliv, error: eDeliv } = await svc
    .from('pedidos')
    .insert({
      restaurante_id: REST,
      codigo: codigoDeliv,
      cliente_nombre: 'Ana Cerebrito',
      cliente_telefono: '04125551234',
      tipo_entrega: 'delivery',
      direccion: 'Urb. Los Samanes, calle 4, casa 12, portón negro',
      estado: 'recibido',
      metodo_pago: 'efectivo',
      subtotal_usd: 13,
      costo_delivery_usd: 2,
      total_usd: 15,
      tasa_bs: tasaFila?.tasa_bs ?? 0,
    })
    .select('id')
    .maybeSingle();
  check(!eDeliv && !!pDeliv?.id, 'pedido delivery creado' + (eDeliv ? ' -> ' + eDeliv.message : ''));
  deliveryId = pDeliv?.id;
  await svc.from('pedido_items').insert([
    {
      pedido_id: deliveryId,
      producto_id: comida.id,
      nombre: comida.nombre,
      precio_usd: 6.5,
      cantidad: 2,
      nota: null,
    },
    {
      // El trigger tg_pedido_item_a_cocina le estampa a_cocina=false (su
      // categoria quedo en "No se imprime").
      pedido_id: deliveryId,
      producto_id: bebida.id,
      nombre: bebida.nombre,
      precio_usd: 2,
      cantidad: 1,
      nota: null,
    },
  ]);
  await svc.from('pedidos').update({ estado: 'confirmado', confirmado_en: new Date().toISOString() }).eq('id', deliveryId);

  const llegoDeliv = await hasta(
    () => impCaja.tickets.length >= 1 && impCocina.tickets.some((t) => plano(t).includes(`#${codigoDeliv}`)),
    30_000,
  );
  check(llegoDeliv, 'el delivery imprimio comanda de cocina Y ticket de despacho');
  const tCaja = plano(impCaja.tickets.join('\n'));
  const tCocinaDeliv = plano(impCocina.tickets.filter((t) => plano(t).includes(`#${codigoDeliv}`)).join('\n'));
  check(tCocinaDeliv.includes('DELIVERY'), 'la comanda de cocina del delivery dice DELIVERY en el encabezado');
  check(
    !tCocinaDeliv.includes('Ana Cerebrito') && !tCocinaDeliv.includes('Samanes'),
    'la cocina NO recibe datos del cliente (nombre/direccion)',
  );
  check(tCaja.includes('DESPACHO') && tCaja.includes(`#${codigoDeliv}`), 'el ticket de despacho salio en la caja con el numero');
  check(tCaja.includes('Cliente: Ana Cerebrito') && tCaja.includes('Telf: 04125551234'), 'trae cliente y telefono');
  check(tCaja.includes('porton negro'), 'trae la direccion completa (en ASCII, sin letras chinas)');
  check(tCaja.includes(plano(`2x ${comida.nombre}`)), 'trae la lista de chequeo para armar la bolsa');
  const barraAntesDeliv = impBarra.tickets.filter((t) => plano(t).includes(`#${codigoDeliv}`)).length;
  check(
    barraAntesDeliv === 0 && !tCocinaDeliv.includes(plano(bebida.nombre)),
    'la bebida "No se imprime" no salio en NINGUNA cocina (a_cocina=false)',
  );
  check(tCaja.includes(plano(`1x ${bebida.nombre}`)), 'pero SI va en el checklist del despacho (la bolsa la lleva)');
  check(tCaja.includes('Total: $15.00') && tCaja.includes('incluye delivery $2.00'), 'trae el total y el delivery');
  check(tCaja.includes('COBRA EN EFECTIVO'), 'grita que hay que cobrar en efectivo');
  check(
    tCaja.includes('ESTE TICKET NO ES FACTURA') && tCaja.includes('Documento no fiscal'),
    'trae la leyenda fiscal: no es factura (SENIAT tranquilo)',
  );
  // Devolver las bebidas a "se imprime" ANTES del escenario de la mesa (alli
  // la bebida debe salir por Barra).
  if (catBebidas?.id) await svc.from('categorias').update({ a_cocina: true }).eq('id', catBebidas.id);

  // ===== UNA MESA = UNA ORDEN: 3 celulares piden por QR, sale UNA comanda =====
  const { data: cuentaMesa } = await svc
    .from('cuentas_mesa')
    .insert({ restaurante_id: REST, mesa: '9', estado: 'abierta' })
    .select('id')
    .maybeSingle();
  cuentaLoteId = cuentaMesa.id;
  const marcaLote = String(Date.now()).slice(-5);
  const gente = [
    { items: [{ producto_id: comida.id, nombre: comida.nombre, precio_usd: 6, cantidad: 1 }] },
    { items: [{ producto_id: comida.id, nombre: comida.nombre, precio_usd: 6, cantidad: 2 }] },
    { items: [{ producto_id: bebida.id, nombre: bebida.nombre, precio_usd: 2, cantidad: 1 }] },
  ];
  for (const [i, g] of gente.entries()) {
    const { data: pl } = await svc
      .from('pedidos')
      .insert({
        restaurante_id: REST,
        codigo: `GD-Q${marcaLote}${i}`,
        cliente_nombre: `Comensal ${i + 1}`,
        cliente_telefono: `0414000010${i}`,
        tipo_entrega: 'mesa',
        mesa: '9',
        cuenta_id: cuentaLoteId,
        estado: 'recibido',
        metodo_pago: 'efectivo',
        subtotal_usd: 6,
        total_usd: 6,
      })
      .select('id')
      .maybeSingle();
    pedidosLote.push(pl.id);
    for (const it of g.items) {
      await svc.from('pedido_items').insert({ pedido_id: pl.id, producto_id: it.producto_id, nombre: it.nombre, precio_usd: it.precio_usd, cantidad: it.cantidad });
    }
  }
  const cocinaAntesLote = impCocina.tickets.length;
  const barraAntesLote = impBarra.tickets.length;
  // El que abrio la mesa envia la ORDEN COMPLETA (RPC anonima con la llave de la cuenta).
  const { data: envio, error: eEnvio } = await anon.rpc('enviar_orden_mesa', { p_cuenta_id: cuentaLoteId });
  check(!eEnvio && envio?.enviados === 3, 'enviar_orden_mesa confirma los 3 borradores de un golpe' + (eEnvio ? ' -> ' + eEnvio.message : ''));
  const loteImpreso = await hasta(
    () => impCocina.tickets.length > cocinaAntesLote && impBarra.tickets.length > barraAntesLote,
    30_000,
  );
  check(loteImpreso, 'la orden de la mesa salio impresa (ventana de lote de 5s)');
  check(
    impCocina.tickets.length === cocinaAntesLote + 1 && impBarra.tickets.length === barraAntesLote + 1,
    'UNA sola comanda por puesto (no 3 papeles): el coje culo evitado',
  );
  const tLote = plano(impCocina.tickets[impCocina.tickets.length - 1]);
  check(tLote.includes('MESA 9') && tLote.includes('#M9-1'), 'la comanda dice MESA 9 y su numero de orden M9-1');
  check(tLote.includes(plano(`3x ${comida.nombre}`)), 'los renglones repetidos se SUMAN (1+2 = 3x)');
  check(tLote.includes('3 personas pidieron'), 'y anota cuantas personas pidieron');
  const lotePreparando = await hasta(async () => {
    const { data: filas } = await svc.from('pedidos').select('estado').in('id', pedidosLote);
    return (filas ?? []).every((f) => f.estado === 'preparando');
  }, 15_000);
  check(lotePreparando, 'los 3 pedidos del lote pasaron a PREPARANDO al salir el papel');

  // --- Antirepetida: pasa un barrido completo y NUESTROS pedidos no deben salir
  // de nuevo. (El barrido SI puede imprimir otros pedidos viejos que hayan
  // quedado confirmados en el demo: ese es su trabajo al arrancar.) ---
  await espera(22_000);
  const veces = (tickets, txt) => tickets.reduce((n, t) => n + (plano(t).includes(txt) ? 1 : 0), 0);
  check(
    veces(impCocina.tickets, `#${codigo}`) === 1 &&
      veces(impBarra.tickets, `#${codigo}`) === 1 &&
      // El delivery deja DOS papeles en caja (DESPACHO + COPIA-NEGOCIO, v2.6.1),
      // pero cada uno UNA sola vez: el ledger evita la repetida.
      veces(impCaja.tickets, `#${codigoDeliv}`) === 2,
    'tras el barrido de respaldo NUESTROS pedidos no se reimprimieron (ledger)',
  );
} catch (e) {
  console.log('  XX  Excepcion: ' + e.message);
  fallos += 1;
} finally {
  // Restaurar la config del demo: bebidas vuelven a imprimirse (a_cocina=true).
  await svc
    .from('categorias')
    .update({ a_cocina: true })
    .eq('restaurante_id', REST)
    .ilike('nombre', '%bebida%');
  await limpiar();
  console.log('\nLimpieza hecha (pedido, usuario, config demo y carpeta temporal restaurados)');
}

console.log(fallos === 0 ? '\nCEREBRITO ETAPA 1 OK' : `\nCEREBRITO: ${fallos} FALLO(S)`);
process.exit(fallos === 0 ? 0 : 1);
