-- ============================================================
-- Gustito Xpress — FLUJO DE MESA DEL MESERO (fase 2: logica en BD).
--
--   * abrir_mesa: el mesero abre/asigna una mesa (personas + modo).
--   * agregar_comensal: sumar una persona a una cuenta ya abierta.
--   * crear_pedido: ahora acepta p_comensal_id (a que persona es el pedido).
--   * llamar_mesero / atender_llamada / llamadas_activas: el cliente llama,
--     el mesero recibe y baja la llamada.
--   * cuenta_mesa / mis_cuentas_mesa: exponen personas y desglose por persona.
--   * trg_notificar_llamada: avisa al mesero asignado (push, best-effort).
--
-- Se apoya en la fase 1 (comensales_mesa, llamadas_mesa, columnas nuevas).
-- ============================================================

-- ---- abrir_mesa: el mesero abre y se asigna la mesa ----
create or replace function public.abrir_mesa(
  p_mesa text,
  p_comensales int default 1,
  p_modo text default 'unica',
  p_nombres text[] default null
)
returns uuid
language plpgsql security definer set search_path to 'public'
as $function$
declare
  v_rest      uuid := fn_mi_restaurante();
  v_cuenta_id uuid;
  v_mesero_id uuid;
  v_mesero_nombre text;
begin
  if v_rest is null or not (fn_es_staff_de(v_rest) or fn_es_admin()) then
    raise exception 'Sin permiso';
  end if;
  if coalesce(nullif(trim(p_mesa), ''), '') = '' then
    raise exception 'Falta el numero de mesa';
  end if;
  if p_modo not in ('unica','separada') then
    raise exception 'Modo de cuenta invalido';
  end if;

  select pf.id, coalesce(nullif(trim(pf.nombre), ''), 'Mesero')
    into v_mesero_id, v_mesero_nombre
    from public.perfiles pf where pf.id = auth.uid();

  -- Respeta el anti-cruce: una sola cuenta activa por mesa.
  insert into public.cuentas_mesa (restaurante_id, mesa, comensales, modo_cuenta, mesero_id, mesero_nombre)
    values (v_rest, trim(p_mesa), greatest(1, coalesce(p_comensales, 1)), p_modo, v_mesero_id, v_mesero_nombre)
    on conflict (restaurante_id, mesa) where (estado <> 'cerrada') do nothing;

  select id into v_cuenta_id from public.cuentas_mesa
   where restaurante_id = v_rest and mesa = trim(p_mesa) and estado <> 'cerrada' limit 1;

  -- Si la mesa ya estaba abierta (por QR o por otro toque): configura personas
  -- y modo, y toma la mesa si estaba sin mesero (no le pisa el dueño a otro).
  update public.cuentas_mesa
     set comensales  = greatest(1, coalesce(p_comensales, comensales)),
         modo_cuenta  = p_modo,
         mesero_id    = coalesce(mesero_id, v_mesero_id),
         mesero_nombre = coalesce(mesero_nombre, v_mesero_nombre)
   where id = v_cuenta_id;

  -- Registra las personas (solo si no hay ninguna aun, para no duplicar).
  if p_modo = 'separada' and p_nombres is not null
     and not exists (select 1 from public.comensales_mesa where cuenta_id = v_cuenta_id) then
    insert into public.comensales_mesa (cuenta_id, restaurante_id, nombre, orden)
    select v_cuenta_id, v_rest, nullif(trim(n.nombre), ''), n.ord::int
      from unnest(p_nombres) with ordinality as n(nombre, ord)
     where nullif(trim(n.nombre), '') is not null;
  end if;

  return v_cuenta_id;
end;
$function$;
grant execute on function public.abrir_mesa(text, int, text, text[]) to authenticated;

-- ---- agregar_comensal: sumar una persona a una cuenta abierta ----
create or replace function public.agregar_comensal(p_cuenta_id uuid, p_nombre text)
returns uuid
language plpgsql security definer set search_path to 'public'
as $function$
declare
  v_rest uuid;
  v_id   uuid;
  v_ord  int;
begin
  select restaurante_id into v_rest from public.cuentas_mesa where id = p_cuenta_id and estado <> 'cerrada';
  if v_rest is null then raise exception 'La cuenta no esta disponible'; end if;
  if not (fn_es_staff_de(v_rest) or fn_es_admin()) then raise exception 'Sin permiso'; end if;
  if coalesce(nullif(trim(p_nombre), ''), '') = '' then raise exception 'Falta el nombre'; end if;

  select coalesce(max(orden), -1) + 1 into v_ord from public.comensales_mesa where cuenta_id = p_cuenta_id;
  insert into public.comensales_mesa (cuenta_id, restaurante_id, nombre, orden)
    values (p_cuenta_id, v_rest, trim(p_nombre), v_ord)
    returning id into v_id;
  return v_id;
end;
$function$;
grant execute on function public.agregar_comensal(uuid, text) to authenticated;

-- ---- crear_pedido: ahora acepta p_comensal_id (a que persona es el pedido) ----
-- Se agrega un parametro: DROP de la firma vieja + CREATE (evita overload
-- ambiguo en PostgREST, HTTP 300). El cuerpo es el vivo + estampado de comensal.
drop function if exists public.crear_pedido(uuid, text, text, text, text, jsonb, text, double precision, double precision, text, text, text);
create or replace function public.crear_pedido(
  p_restaurante_id uuid, p_cliente_nombre text, p_cliente_telefono text, p_tipo_entrega text,
  p_metodo_pago text, p_items jsonb, p_direccion text default null,
  p_direccion_lat double precision default null, p_direccion_lng double precision default null,
  p_nota text default null, p_mesa text default null, p_cedula text default null,
  p_comensal_id uuid default null
)
returns table(pedido_id uuid, codigo text, cuenta_id uuid)
language plpgsql security definer set search_path to 'public'
as $function$
declare
  v_rest           public.restaurantes%rowtype;
  v_item           jsonb;
  v_prod           public.productos%rowtype;
  v_cant           int;
  v_extra          numeric(10,2) := 0;
  v_subtotal       numeric(10,2) := 0;
  v_costo_delivery numeric(10,2) := 0;
  v_pedido_id      uuid;
  v_codigo         text;
  v_num            int;
  v_cuenta_id      uuid := null;
  v_firma          text := null;
  v_dup_id         uuid;
  v_dup_codigo     text;
  v_dup_cuenta     uuid;
  v_mesero_id      uuid := null;
  v_mesero_nombre  text := null;
  v_comensal_id    uuid := null;
begin
  select * into v_rest from public.restaurantes where id = p_restaurante_id;
  if not found or not v_rest.activo or not v_rest.publicado then
    raise exception 'Restaurante no disponible';
  end if;
  if not v_rest.abierto then
    raise exception 'El restaurante esta cerrado en este momento';
  end if;
  if p_tipo_entrega not in ('delivery','pickup','mesa') then
    raise exception 'Tipo de entrega invalido';
  end if;
  if p_metodo_pago not in ('pago_movil','efectivo','transferencia','zelle','punto_venta') then
    raise exception 'Metodo de pago invalido';
  end if;
  if jsonb_array_length(coalesce(p_items, '[]'::jsonb)) = 0 then
    raise exception 'El carrito esta vacio';
  end if;

  if auth.uid() is not null then
    select pf.id, coalesce(nullif(trim(pf.nombre), ''), 'Mesero')
      into v_mesero_id, v_mesero_nombre
      from public.perfiles pf
     where pf.id = auth.uid()
       and pf.restaurante_id = p_restaurante_id
       and pf.rol in ('dueno','staff');
  end if;

  if p_tipo_entrega = 'delivery' then
    v_costo_delivery := coalesce(v_rest.costo_delivery_usd, 0);
    if coalesce(length(trim(p_direccion)), 0) < 4 then
      raise exception 'Para delivery necesitas indicar la direccion';
    end if;
  end if;

  if p_tipo_entrega in ('delivery','pickup') then
    v_firma := md5(coalesce(lower(trim(p_cliente_telefono)), '') || '|' || coalesce(p_items::text, ''));
    select p.id, p.codigo, p.cuenta_id into v_dup_id, v_dup_codigo, v_dup_cuenta
      from public.pedidos p
     where p.restaurante_id = p_restaurante_id
       and p.firma = v_firma
       and p.recibido_en > now() - interval '3 minutes'
     order by p.recibido_en desc
     limit 1;
    if found then
      return query select v_dup_id, v_dup_codigo, v_dup_cuenta;
      return;
    end if;
  end if;

  if p_tipo_entrega = 'mesa' then
    if coalesce(nullif(p_mesa, ''), '') = '' then
      raise exception 'Falta el numero de mesa';
    end if;
    insert into public.cuentas_mesa (restaurante_id, mesa, mesero_id, mesero_nombre)
      values (p_restaurante_id, p_mesa, v_mesero_id, v_mesero_nombre)
      on conflict (restaurante_id, mesa) where (estado <> 'cerrada') do nothing;
    select cm.id into v_cuenta_id from public.cuentas_mesa cm
      where cm.restaurante_id = p_restaurante_id and cm.mesa = p_mesa and cm.estado <> 'cerrada'
      limit 1;
    if v_mesero_id is not null and v_cuenta_id is not null then
      update public.cuentas_mesa
         set mesero_id = v_mesero_id, mesero_nombre = v_mesero_nombre
       where id = v_cuenta_id and mesero_id is null;
    end if;
    -- La persona (comensal) debe ser DE ESTA cuenta; si no, se rechaza.
    if p_comensal_id is not null then
      perform 1 from public.comensales_mesa cm where cm.id = p_comensal_id and cm.cuenta_id = v_cuenta_id;
      if not found then raise exception 'La persona no pertenece a esta mesa'; end if;
      v_comensal_id := p_comensal_id;
    end if;
  end if;

  update public.restaurantes set contador_pedidos = contador_pedidos + 1
   where id = p_restaurante_id
   returning contador_pedidos into v_num;
  v_codigo := coalesce(nullif(v_rest.prefijo, ''), 'GX') || '-' || lpad(v_num::text, 4, '0');

  insert into public.pedidos (
    restaurante_id, codigo, cliente_nombre, cliente_telefono, cliente_cedula, tipo_entrega, mesa,
    direccion, direccion_latitud, direccion_longitud, estado,
    subtotal_usd, costo_delivery_usd, total_usd, tasa_bs, metodo_pago, nota, cuenta_id, firma,
    mesero_id, mesero_nombre, comensal_id
  ) values (
    p_restaurante_id, v_codigo, p_cliente_nombre, p_cliente_telefono, nullif(p_cedula, ''), p_tipo_entrega, nullif(p_mesa, ''),
    p_direccion, p_direccion_lat, p_direccion_lng, 'recibido',
    0, v_costo_delivery, 0, v_rest.tasa_bs, p_metodo_pago, p_nota, v_cuenta_id, v_firma,
    v_mesero_id, v_mesero_nombre, v_comensal_id
  ) returning id into v_pedido_id;

  for v_item in select * from jsonb_array_elements(p_items)
  loop
    v_cant := greatest(1, coalesce((v_item->>'cantidad')::int, 1));
    select * into v_prod from public.productos
      where id = (v_item->>'producto_id')::uuid
        and restaurante_id = p_restaurante_id;
    if not found then
      raise exception 'Un producto no pertenece a este restaurante';
    end if;
    if not v_prod.disponible then
      raise exception 'Producto agotado: %', v_prod.nombre;
    end if;
    select coalesce(sum((op->>'precio_extra')::numeric), 0) into v_extra
      from jsonb_array_elements(coalesce(v_prod.opciones, '[]'::jsonb)) as grupo,
           jsonb_array_elements(coalesce(grupo->'opciones', '[]'::jsonb)) as op
     where jsonb_exists(coalesce(v_item->'opciones', '[]'::jsonb), op->>'nombre');
    insert into public.pedido_items (pedido_id, producto_id, nombre, precio_usd, cantidad, nota)
      values (v_pedido_id, v_prod.id, v_prod.nombre, v_prod.precio_usd + v_extra, v_cant, nullif(v_item->>'nota', ''));
    v_subtotal := v_subtotal + (v_prod.precio_usd + v_extra) * v_cant;
  end loop;

  update public.pedidos
     set subtotal_usd = v_subtotal,
         total_usd    = v_subtotal + v_costo_delivery
   where id = v_pedido_id;

  if p_tipo_entrega <> 'mesa' then
    insert into public.pagos (pedido_id, metodo, monto_usd, estado)
      values (v_pedido_id, p_metodo_pago, v_subtotal + v_costo_delivery, 'pendiente');
  end if;

  return query select v_pedido_id, v_codigo, v_cuenta_id;
end;
$function$;
grant execute on function public.crear_pedido(uuid, text, text, text, text, jsonb, text, double precision, double precision, text, text, text, uuid) to anon, authenticated;

-- ---- llamar_mesero: el cliente llama al mesero o pide la cuenta ----
create or replace function public.llamar_mesero(
  p_cuenta_id uuid, p_tipo text default 'mesero', p_nota text default null
)
returns table(llamada_id uuid, ya_estaba boolean)
language plpgsql security definer set search_path to 'public'
as $function$
declare
  v_rest uuid;
  v_mesa text;
  v_id   uuid;
begin
  if p_tipo not in ('mesero','cuenta') then raise exception 'Tipo de llamada invalido'; end if;
  select restaurante_id, mesa into v_rest, v_mesa
    from public.cuentas_mesa where id = p_cuenta_id and estado <> 'cerrada';
  if v_rest is null then raise exception 'La cuenta no esta disponible'; end if;

  insert into public.llamadas_mesa (cuenta_id, restaurante_id, mesa, tipo, nota)
    values (p_cuenta_id, v_rest, v_mesa, p_tipo, nullif(trim(coalesce(p_nota,'')), ''))
    on conflict (cuenta_id, tipo) where (atendida_en is null) do nothing
    returning id into v_id;

  if v_id is not null then
    return query select v_id, false;
  else
    select id into v_id from public.llamadas_mesa
      where cuenta_id = p_cuenta_id and tipo = p_tipo and atendida_en is null limit 1;
    return query select v_id, true;
  end if;
end;
$function$;
grant execute on function public.llamar_mesero(uuid, text, text) to anon, authenticated;

-- ---- atender_llamada: el mesero baja la llamada ----
create or replace function public.atender_llamada(p_llamada_id uuid)
returns void
language plpgsql security definer set search_path to 'public'
as $function$
declare v_rest uuid;
begin
  select restaurante_id into v_rest from public.llamadas_mesa where id = p_llamada_id;
  if v_rest is null then raise exception 'Llamada no existe'; end if;
  if not (fn_es_staff_de(v_rest) or fn_es_admin()) then raise exception 'Sin permiso'; end if;
  update public.llamadas_mesa
     set atendida_en = now(), atendida_por = auth.uid()
   where id = p_llamada_id and atendida_en is null;
end;
$function$;
grant execute on function public.atender_llamada(uuid) to authenticated;

-- ---- llamadas_activas: llamadas pendientes del restaurante (para el panel) ----
create or replace function public.llamadas_activas(p_restaurante_id uuid)
returns table(id uuid, cuenta_id uuid, mesa text, tipo text, nota text, creada_en timestamptz, mesero_id uuid, mesero_nombre text)
language sql stable security definer set search_path to 'public'
as $function$
  select l.id, l.cuenta_id, l.mesa, l.tipo, l.nota, l.creada_en, c.mesero_id, c.mesero_nombre
  from public.llamadas_mesa l
  join public.cuentas_mesa c on c.id = l.cuenta_id
  where l.restaurante_id = p_restaurante_id
    and l.atendida_en is null
    and (fn_es_staff_de(l.restaurante_id) or fn_es_admin())
  order by l.creada_en;
$function$;
grant execute on function public.llamadas_activas(uuid) to authenticated;

-- ---- cuenta_mesa (cliente): + personas, modo y desglose por persona ----
drop function if exists public.cuenta_mesa(uuid);
create function public.cuenta_mesa(p_cuenta_id uuid)
returns table(
  id uuid, mesa text, estado text, restaurante_nombre text, restaurante_slug text,
  restaurante_color text, restaurante_telefono text, tasa_bs numeric, total_usd numeric,
  metodo_pago text, metodos_pago jsonb, promedio_min integer, pedidos jsonb, mesero_nombre text,
  modo_cuenta text, comensales jsonb, llamadas jsonb
)
language sql stable security definer set search_path to 'public'
as $function$
  select c.id, c.mesa, c.estado,
         r.nombre, r.slug, r.color_primario, r.telefono,
         r.tasa_bs,
         coalesce((select sum(p.total_usd) from public.pedidos p where p.cuenta_id = c.id and p.estado <> 'cancelado'), 0),
         c.metodo_pago, r.metodos_pago,
         coalesce(r.sala_promedio_min, 10),
         coalesce((
           select jsonb_agg(jsonb_build_object(
             'id', p.id, 'codigo', p.codigo, 'estado', p.estado, 'total_usd', p.total_usd,
             'cliente', p.cliente_nombre, 'pagado', (p.pago_mesa_id is not null),
             'comensal_id', p.comensal_id,
             'comensal', (select cm.nombre from public.comensales_mesa cm where cm.id = p.comensal_id),
             'items', coalesce((select jsonb_agg(jsonb_build_object(
                         'nombre', it.nombre, 'cantidad', it.cantidad, 'precio_usd', it.precio_usd, 'nota', it.nota))
                       from public.pedido_items it where it.pedido_id = p.id), '[]'::jsonb)
           ) order by p.recibido_en)
           from public.pedidos p where p.cuenta_id = c.id
         ), '[]'::jsonb),
         c.mesero_nombre,
         c.modo_cuenta,
         coalesce((
           select jsonb_agg(jsonb_build_object(
             'id', cm.id, 'nombre', cm.nombre, 'orden', cm.orden,
             'total_usd', coalesce((select sum(p.total_usd) from public.pedidos p where p.comensal_id = cm.id and p.estado <> 'cancelado'), 0),
             'pagado_usd', coalesce((select sum(p.total_usd) from public.pedidos p where p.comensal_id = cm.id and p.estado <> 'cancelado' and p.pago_mesa_id is not null), 0)
           ) order by cm.orden)
           from public.comensales_mesa cm where cm.cuenta_id = c.id
         ), '[]'::jsonb),
         coalesce((select jsonb_agg(l.tipo) from public.llamadas_mesa l where l.cuenta_id = c.id and l.atendida_en is null), '[]'::jsonb)
  from public.cuentas_mesa c
  join public.restaurantes r on r.id = c.restaurante_id
  where c.id = p_cuenta_id;
$function$;
grant execute on function public.cuenta_mesa(uuid) to anon, authenticated;

-- ---- mis_cuentas_mesa (panel): + comensales y modo ----
drop function if exists public.mis_cuentas_mesa(uuid);
create function public.mis_cuentas_mesa(p_restaurante_id uuid)
returns table(
  id uuid, mesa text, estado text, total_usd numeric, tasa_bs numeric,
  abierta_en timestamptz, pago_solicitado_en timestamptz, metodo_pago text,
  referencia text, comprobante_url text, n_pedidos integer,
  mesero_id uuid, mesero_nombre text, pagado_usd numeric,
  comensales integer, modo_cuenta text
)
language sql stable security definer set search_path to 'public'
as $function$
  select c.id, c.mesa, c.estado,
    coalesce((select sum(p.total_usd) from public.pedidos p where p.cuenta_id = c.id and p.estado <> 'cancelado'), 0),
    r.tasa_bs, c.abierta_en, c.pago_solicitado_en, c.metodo_pago, c.referencia, c.comprobante_url,
    (select count(*)::int from public.pedidos p where p.cuenta_id = c.id and p.estado <> 'cancelado'),
    c.mesero_id, c.mesero_nombre,
    coalesce((select sum(pm.monto_usd) from public.pagos_mesa pm where pm.cuenta_id = c.id), 0),
    c.comensales, c.modo_cuenta
  from public.cuentas_mesa c
  join public.restaurantes r on r.id = c.restaurante_id
  where c.restaurante_id = p_restaurante_id
    and c.estado <> 'cerrada'
    and (fn_es_staff_de(c.restaurante_id) or fn_es_admin())
  order by c.abierta_en;
$function$;
grant execute on function public.mis_cuentas_mesa(uuid) to anon, authenticated;

-- ---- trigger de aviso: al mesero asignado cuando entra una llamada ----
-- Mismo patron que tg_notificar_push: lee secretos de Vault y llama a la
-- Edge Function enviar-push por pg_net. Best-effort (nunca rompe el insert).
create or replace function public.tg_notificar_llamada()
returns trigger
language plpgsql security definer set search_path to 'public'
as $function$
declare
  v_secret text;
  v_anon   text;
  v_url    text := 'https://szzhhlevtrcambcoytiz.supabase.co/functions/v1/enviar-push';
begin
  begin
    select decrypted_secret into v_secret from vault.decrypted_secrets where name = 'push_secret' limit 1;
    select decrypted_secret into v_anon   from vault.decrypted_secrets where name = 'anon_key'    limit 1;
    perform net.http_post(
      url := v_url,
      headers := jsonb_build_object(
        'Content-Type', 'application/json',
        'apikey', coalesce(v_anon, ''),
        'x-push-secret', coalesce(v_secret, '')
      ),
      body := jsonb_build_object('evento', 'mesero_llamada', 'cuenta_id', new.cuenta_id, 'tipo', new.tipo, 'llamada_id', new.id)
    );
  exception when others then
    null;
  end;
  return null;
end;
$function$;

drop trigger if exists trg_notificar_llamada on public.llamadas_mesa;
create trigger trg_notificar_llamada
  after insert on public.llamadas_mesa
  for each row execute function public.tg_notificar_llamada();
