-- =============================================================================
-- AISLAMIENTO ENTRE MESERAS (decision de Gilberto: "cada quien lo suyo").
-- Dueno/admin ven TODO. Un mesero (rol staff) solo ve/toca SUS mesas
-- (cuentas_mesa.mesero_id = el) o una mesa SIN asignar (mesero_id null, para
-- poder tomarla). Entre locales distintos ya estaba blindado (fn_es_staff_de).
-- Se enforced por RLS (lecturas) + triggers de guardia (escrituras, porque
-- crear_pedido/cobrar_* son SECURITY DEFINER y saltan RLS). NO se tocan esas
-- funciones (la otra sesion las esta editando: reserva/personas).
-- =============================================================================

-- Helper central. Un mesero solo su mesa o una libre; dueno/admin todo.
create or replace function public.fn_mesa_de_staff(p_mesero_id uuid, p_restaurante_id uuid)
returns boolean language sql stable security definer set search_path to 'public' as $fn$
  select public.fn_es_dueno_de(p_restaurante_id)
      or public.fn_es_admin()
      or (public.fn_es_staff_de(p_restaurante_id) and (p_mesero_id = auth.uid() or p_mesero_id is null));
$fn$;
comment on function public.fn_mesa_de_staff(uuid,uuid) is 'Aislamiento meseras: dueno/admin ven todo; un mesero solo su mesa (mesero_id=el) o una sin asignar.';

-- ---- RLS: lecturas por mesa propia (o libre) ----
drop policy if exists cuentas_staff on public.cuentas_mesa;
create policy cuentas_staff on public.cuentas_mesa as permissive for all to authenticated
  using (public.fn_mesa_de_staff(mesero_id, restaurante_id))
  with check (public.fn_mesa_de_staff(mesero_id, restaurante_id));

drop policy if exists pedidos_staff_select on public.pedidos;
create policy pedidos_staff_select on public.pedidos as permissive for select to authenticated
  using (public.fn_mesa_de_staff(mesero_id, restaurante_id));

drop policy if exists pedido_items_staff_select on public.pedido_items;
create policy pedido_items_staff_select on public.pedido_items as permissive for select to authenticated
  using (exists (select 1 from public.pedidos p where p.id = pedido_items.pedido_id
                   and public.fn_mesa_de_staff(p.mesero_id, p.restaurante_id)));

drop policy if exists pagos_mesa_staff_select on public.pagos_mesa;
create policy pagos_mesa_staff_select on public.pagos_mesa as permissive for select to authenticated
  using (exists (select 1 from public.cuentas_mesa c where c.id = pagos_mesa.cuenta_id
                   and public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id)));

drop policy if exists comensales_mesa_staff_all on public.comensales_mesa;
create policy comensales_mesa_staff_all on public.comensales_mesa as permissive for all to authenticated
  using (exists (select 1 from public.cuentas_mesa c where c.id = comensales_mesa.cuenta_id
                   and public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id)))
  with check (exists (select 1 from public.cuentas_mesa c where c.id = comensales_mesa.cuenta_id
                   and public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id)));

drop policy if exists llamadas_mesa_staff_select on public.llamadas_mesa;
create policy llamadas_mesa_staff_select on public.llamadas_mesa as permissive for select to authenticated
  using (exists (select 1 from public.cuentas_mesa c where c.id = llamadas_mesa.cuenta_id
                   and public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id)));

drop policy if exists llamadas_mesa_staff_update on public.llamadas_mesa;
create policy llamadas_mesa_staff_update on public.llamadas_mesa as permissive for update to authenticated
  using (exists (select 1 from public.cuentas_mesa c where c.id = llamadas_mesa.cuenta_id
                   and public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id)))
  with check (exists (select 1 from public.cuentas_mesa c where c.id = llamadas_mesa.cuenta_id
                   and public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id)));

-- ---- Triggers de guardia (escrituras): crear_pedido y cobrar_* son SECURITY
-- DEFINER (saltan RLS), asi que el candado va en trigger. Anon (cliente) y
-- dueno pasan; solo un mesero queda atado a sus mesas. ----
create or replace function public.fn_guard_pedido_mesa_mio() returns trigger
language plpgsql security definer set search_path to 'public' as $fn$
begin
  if new.tipo_entrega = 'mesa' and new.cuenta_id is not null and auth.uid() is not null
     and public.fn_es_staff_de(new.restaurante_id) and not public.fn_es_dueno_de(new.restaurante_id) and not public.fn_es_admin()
     and exists (select 1 from public.cuentas_mesa c where c.id = new.cuenta_id and c.mesero_id is not null and c.mesero_id <> auth.uid())
  then
    raise exception 'Esta mesa la atiende otra persona';
  end if;
  return new;
end $fn$;
drop trigger if exists trg_guard_pedido_mesa_mio on public.pedidos;
create trigger trg_guard_pedido_mesa_mio before insert on public.pedidos
  for each row execute function public.fn_guard_pedido_mesa_mio();

create or replace function public.fn_guard_pago_mesa_mio() returns trigger
language plpgsql security definer set search_path to 'public' as $fn$
begin
  if auth.uid() is not null
     and public.fn_es_staff_de(new.restaurante_id) and not public.fn_es_dueno_de(new.restaurante_id) and not public.fn_es_admin()
     and exists (select 1 from public.cuentas_mesa c where c.id = new.cuenta_id and c.mesero_id is not null and c.mesero_id <> auth.uid())
  then
    raise exception 'Esta mesa la atiende otra persona';
  end if;
  return new;
end $fn$;
drop trigger if exists trg_guard_pago_mesa_mio on public.pagos_mesa;
create trigger trg_guard_pago_mesa_mio before insert on public.pagos_mesa
  for each row execute function public.fn_guard_pago_mesa_mio();

-- ---- atender_llamada: solo el mesero de la mesa (o de una sin asignar) ----
create or replace function public.atender_llamada(p_llamada_id uuid)
returns void language plpgsql security definer set search_path to 'public' as $fn$
declare v_rest uuid; v_mesero uuid;
begin
  select l.restaurante_id, c.mesero_id into v_rest, v_mesero
    from public.llamadas_mesa l join public.cuentas_mesa c on c.id = l.cuenta_id
   where l.id = p_llamada_id;
  if v_rest is null then raise exception 'Llamada no existe'; end if;
  if not public.fn_mesa_de_staff(v_mesero, v_rest) then raise exception 'Esa llamada es de otra mesa'; 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 $fn$;

-- ---- quitar_item_pedido: solo sobre pedidos de la mesa propia (o libre) ----
create or replace function public.quitar_item_pedido(p_item_id uuid)
returns void language plpgsql security definer set search_path to 'public' as $fn$
declare
  v_pedido uuid; v_rest uuid; v_estado text; v_deliv numeric(10,2); v_pago uuid; v_mesero uuid; v_n int; v_sub numeric(10,2);
begin
  select pi.pedido_id, p.restaurante_id, p.estado, p.costo_delivery_usd, p.pago_mesa_id, p.mesero_id
    into v_pedido, v_rest, v_estado, v_deliv, v_pago, v_mesero
    from public.pedido_items pi join public.pedidos p on p.id = pi.pedido_id
   where pi.id = p_item_id;
  if v_pedido is null then raise exception 'El renglon no existe'; end if;
  if not public.fn_mesa_de_staff(v_mesero, v_rest) then raise exception 'Ese pedido lo atiende otra persona'; end if;
  if v_estado in ('entregado','cancelado') then raise exception 'El pedido ya esta cerrado'; end if;
  if v_pago is not null then raise exception 'Ese pedido ya fue cobrado, no se puede corregir'; end if;
  delete from public.pedido_items where id = p_item_id;
  select count(*), coalesce(sum(precio_usd * cantidad), 0) into v_n, v_sub from public.pedido_items where pedido_id = v_pedido;
  if v_n = 0 then
    update public.pedidos set estado = 'cancelado', cancelado_en = now(), subtotal_usd = 0, total_usd = 0 where id = v_pedido;
  else
    update public.pedidos set subtotal_usd = v_sub, total_usd = v_sub + coalesce(v_deliv, 0) where id = v_pedido;
  end if;
end $fn$;

-- ---- mis_cuentas_mesa: la lista de Mesas de cada mesera queda solo con SUS mesas ----

CREATE OR REPLACE 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 timestamp with time zone, pago_solicitado_en timestamp with time zone, 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, por_revisar_usd numeric, abierta_por 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 and pm.verificado), 0),
    c.comensales, c.modo_cuenta,
    coalesce((select sum(pm.monto_usd) from public.pagos_mesa pm where pm.cuenta_id = c.id and not pm.verificado), 0),
    coalesce(
      (select cm.nombre from public.comensales_mesa cm where cm.cuenta_id = c.id order by cm.orden limit 1),
      (select p.cliente_nombre from public.pedidos p where p.cuenta_id = c.id and p.estado <> 'cancelado' order by p.recibido_en limit 1)
    )
  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 public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id)
  order by c.abierta_en;
$function$
;

-- ---- cuenta_mesa: cerrada a un mesero ajeno (cliente anon y dueno pasan) ----
CREATE OR REPLACE 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, tiempo_estimado_min integer, viene_mesero text)
 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, 'para_llevar', p.para_llevar,
             'cliente', p.cliente_nombre,
             'pagado', (p.pago_mesa_id is not null and coalesce((select pm.verificado from public.pagos_mesa pm where pm.id = p.pago_mesa_id), false)),
             'pago_estado', (case when p.pago_mesa_id is null then null
                                  when coalesce((select pm.verificado from public.pagos_mesa pm where pm.id = p.pago_mesa_id), false) then 'pagado'
                                  else 'por_revisar' end),
             '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 join public.pagos_mesa pm on pm.id = p.pago_mesa_id where p.comensal_id = cm.id and p.estado <> 'cancelado' and pm.verificado), 0),
             'por_revisar_usd', coalesce((select sum(p.total_usd) from public.pedidos p join public.pagos_mesa pm on pm.id = p.pago_mesa_id where p.comensal_id = cm.id and p.estado <> 'cancelado' and not pm.verificado), 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),
         (select max(p.tiempo_estimado_min) from public.pedidos p
           where p.cuenta_id = c.id and p.estado in ('recibido','confirmado','preparando')),
         (select pf.nombre from public.llamadas_mesa l
           join public.perfiles pf on pf.id = l.atendida_por
          where l.cuenta_id = c.id and l.tipo = 'mesero' and l.atendida_en is not null
            and l.atendida_en > now() - interval '5 minutes'
          order by l.atendida_en desc limit 1)
  from public.cuentas_mesa c
  join public.restaurantes r on r.id = c.restaurante_id
  where c.id = p_cuenta_id
    and (auth.uid() is null or public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id));
$function$
;

-- ---- llamadas_activas: cada mesera solo las llamadas de SUS mesas ----
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 timestamp with time zone, 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 public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id)
  order by l.creada_en;
$function$
;
