// PRUEBA DE CARGA (conservadora) del Broadcast del panel, contra el restaurante
// DEMO. Simula N dispositivos de personal escuchando el canal y una rafaga de M
// pedidos concurrentes (como si fuera el pico de un domingo), y mide cuanto
// tarda cada dispositivo en recibir cada aviso.
//
// A PROPOSITO se queda chico: esto corre contra el MISMO proyecto de Supabase
// donde Arepa Power opera en vivo, y el cupo de conexiones realtime del plan
// free (~200) es del proyecto ENTERO, no por restaurante. N_LISTENERS=20 deja
// margen de sobra sin competir con los dispositivos reales de Arepa Power.
// Para la prueba a escala completa (~50 locales), subir el plan primero.
//
// Env: SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_KEY.
import { createClient } from '@supabase/supabase-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);
}

const N_LISTENERS = Number(process.env.N_LISTENERS) || 20; // "dispositivos" del panel/cocina
const M_PEDIDOS = Number(process.env.M_PEDIDOS) || 30; // rafaga de pedidos concurrentes
const espera = (ms) => new Promise((r) => setTimeout(r, ms));
const svc = createClient(URL, SERVICE, { auth: { persistSession: false } });

let fallos = 0;
const check = (cond, msg) => {
  console.log((cond ? '  OK  ' : '  XX  ') + msg);
  if (!cond) fallos++;
};
const pct = (arr, p) => {
  if (!arr.length) return 0;
  const s = [...arr].sort((a, b) => a - b);
  return s[Math.min(s.length - 1, Math.floor((p / 100) * s.length))];
};

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;
const CANAL = `rest-bcast-${REST}`;
const { data: prods } = await svc.from('productos').select('id').eq('restaurante_id', REST).eq('disponible', true).limit(1);
const PROD_ID = prods?.[0]?.id;
if (!PROD_ID) {
  console.log('Demo sin productos disponibles');
  process.exit(1);
}

const EMAIL = 'staff.test.carga@gustitoxpress.app';
const CLAVE = 'CargaTest2026!';
let staffUid;
const pedidoIds = [];

const limpiar = async () => {
  if (pedidoIds.length) await svc.from('pedidos').delete().in('id', pedidoIds);
  if (staffUid) await svc.auth.admin.deleteUser(staffUid).catch(() => {});
};

try {
  await limpiar();
  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);
  staffUid = u.user.id;
  await svc.from('perfiles').upsert({ id: staffUid, rol: 'staff', restaurante_id: REST, nombre: 'Staff Carga Test' });

  console.log(`Preparando ${N_LISTENERS} "dispositivos" escuchando ${CANAL}...\n`);

  // --- N listeners: cada uno su propio cliente/socket, autenticados como el
  // mismo staff (lo que importa aqui es el numero de CONEXIONES, no de cuentas). ---
  const recibidos = Array.from({ length: N_LISTENERS }, () => new Map()); // pedido_id -> [{accion, recibidoEn}]
  const listeners = [];
  for (let i = 0; i < N_LISTENERS; i++) {
    const cli = createClient(URL, ANON, { auth: { persistSession: false } });
    const { data: sesion, error: eSes } = await cli.auth.signInWithPassword({ email: EMAIL, password: CLAVE });
    if (eSes) throw new Error(`listener ${i} signIn: ${eSes.message}`);
    await cli.realtime.setAuth(sesion.session.access_token);
    const canal = cli.channel(CANAL, { config: { private: true } });
    canal.on('broadcast', { event: 'cambio' }, ({ payload }) => {
      const arr = recibidos[i].get(payload.id) ?? [];
      arr.push({ accion: payload.accion, recibidoEn: Date.now() });
      recibidos[i].set(payload.id, arr);
    });
    const estado = await new Promise((resolve) => {
      canal.subscribe((s) => {
        if (s === 'SUBSCRIBED' || s === 'CHANNEL_ERROR' || s === 'TIMED_OUT') resolve(s);
      });
    });
    if (estado !== 'SUBSCRIBED') throw new Error(`listener ${i} no pudo suscribirse: ${estado}`);
    listeners.push({ cli, canal });
  }
  check(listeners.length === N_LISTENERS, `los ${N_LISTENERS} dispositivos quedaron conectados al canal`);

  // --- Rafaga: M pedidos concurrentes, telefono distinto cada uno (para no
  // pisar el anti-duplicado, que agrupa por telefono+items en 3 minutos). ---
  console.log(`\nDisparando ${M_PEDIDOS} pedidos concurrentes...\n`);
  const anon = createClient(URL, ANON, { auth: { persistSession: false } });
  const enviadoEn = new Map(); // pedido_id -> timestamp del disparo
  const resultados = await Promise.all(
    Array.from({ length: M_PEDIDOS }, async (_, i) => {
      const t0 = Date.now();
      const { data, error } = await anon.rpc('crear_pedido', {
        p_restaurante_id: REST,
        p_cliente_nombre: `Carga Test ${i}`,
        p_cliente_telefono: `0412000${String(i).padStart(4, '0')}`,
        p_tipo_entrega: 'pickup',
        p_metodo_pago: 'efectivo',
        p_items: [{ producto_id: PROD_ID, cantidad: 1 }],
      });
      if (error) return { i, error };
      const id = data?.[0]?.pedido_id;
      if (id) {
        pedidoIds.push(id);
        enviadoEn.set(id, t0);
      }
      return { i, id };
    }),
  );
  const fallidos = resultados.filter((r) => r.error);
  check(fallidos.length === 0, `los ${M_PEDIDOS} pedidos se crearon sin error` + (fallidos.length ? ` (${fallidos.length} fallaron, ej: ${fallidos[0].error.message})` : ''));

  // Margen para que la rafaga de broadcasts termine de viajar por el socket.
  await espera(6000);

  // --- Analisis: completitud y latencia de entrega (solo el evento INSERT,
  // que es el aviso real de "pedido nuevo" que le importa a cocina/panel). ---
  let entregasEsperadas = 0;
  let entregasOk = 0;
  const latencias = [];
  for (const id of pedidoIds) {
    const t0 = enviadoEn.get(id);
    for (let i = 0; i < N_LISTENERS; i++) {
      entregasEsperadas++;
      const eventos = recibidos[i].get(id) ?? [];
      const insert = eventos.find((e) => e.accion === 'INSERT');
      if (insert) {
        entregasOk++;
        latencias.push(insert.recibidoEn - t0);
      }
    }
  }
  const completitud = entregasEsperadas ? (entregasOk / entregasEsperadas) * 100 : 0;
  check(completitud === 100, `completitud de entrega: ${entregasOk}/${entregasEsperadas} (${completitud.toFixed(1)}%)`);

  latencias.sort((a, b) => a - b);
  const p50 = pct(latencias, 50);
  const p95 = pct(latencias, 95);
  const max = latencias[latencias.length - 1] ?? 0;
  console.log(`\nLatencia de entrega (disparo -> el dispositivo lo recibe): p50=${p50}ms  p95=${p95}ms  max=${max}ms`);
  check(p95 < 3000, `p95 de latencia bajo 3s (${p95}ms) con ${N_LISTENERS} dispositivos x ${M_PEDIDOS} pedidos a la vez`);

  for (const { cli, canal } of listeners) {
    await cli.removeChannel(canal);
  }
} catch (e) {
  console.log('  XX  Excepción: ' + e.message);
  fallos++;
} finally {
  await limpiar();
  console.log('\nLimpieza hecha (pedidos y usuario de prueba fuera)');
}

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