-- =============================================================================
-- P1: rate limiting de escrituras ANONIMAS (cliente sin login) por RECURSO.
-- El rate limit por IP va en el edge (Cloudflare WAF); aca ponemos topes por
-- recurso (por local / por cuenta) como red de seguridad contra flood/abuso de
-- las RPC anon (crear_pedido, abrir_mesa_cliente, pedir_cuenta_mesa,
-- pagar_parte_cliente, llamar_mesero, registrar_comprobante).
-- Se hace por TRIGGERS anon-only (auth.uid() is null): el staff/dueno nunca se
-- topa. NO se toca ninguna RPC (cero riesgo con la otra sesion).
-- Limites generosos: un local/cliente real jamas los alcanza; frenan bots.
-- =============================================================================

create table if not exists public.rate_limits (
  clave       text primary key,
  conteo      int not null default 0,
  ventana_fin timestamptz not null
);
create index if not exists idx_rate_limits_ventana on public.rate_limits(ventana_fin);
alter table public.rate_limits enable row level security;  -- sin policies: nadie la lee/escribe directo (solo el helper SECDEF)

-- Cuenta un golpe en la ventana actual; revienta si pasa el tope.
create or replace function public.fn_rate_limit(p_clave text, p_max int, p_ventana_seg int)
returns void
language plpgsql
security definer
set search_path to 'public'
as $$
declare
  v_bucket bigint := floor(extract(epoch from clock_timestamp()) / p_ventana_seg);
  v_key    text   := p_clave || ':' || v_bucket;
  v_conteo int;
begin
  insert into public.rate_limits (clave, conteo, ventana_fin)
    values (v_key, 1, to_timestamp((v_bucket + 1) * p_ventana_seg))
  on conflict (clave) do update set conteo = public.rate_limits.conteo + 1
  returning conteo into v_conteo;
  if v_conteo > p_max then
    raise exception 'Demasiadas solicitudes seguidas. Espera un momento e intenta de nuevo.'
      using errcode = '54000';
  end if;
end $$;

-- ── Triggers anon-only por tabla ──
-- pedidos: tope por LOCAL (flood de pedidos falsos).
create or replace function public.fn_rl_pedidos() returns trigger
language plpgsql security definer set search_path to 'public' as $$
begin
  if (select auth.uid()) is null then
    perform public.fn_rate_limit('ped:' || new.restaurante_id::text, 40, 60);
  end if;
  return new;
end $$;
drop trigger if exists trg_rl_pedidos on public.pedidos;
create trigger trg_rl_pedidos before insert on public.pedidos for each row execute function public.fn_rl_pedidos();

-- cuentas_mesa INSERT: abrir mesas por LOCAL (mesas fantasma).
create or replace function public.fn_rl_cuentas_ins() returns trigger
language plpgsql security definer set search_path to 'public' as $$
begin
  if (select auth.uid()) is null then
    perform public.fn_rate_limit('cta:' || new.restaurante_id::text, 30, 60);
  end if;
  return new;
end $$;
drop trigger if exists trg_rl_cuentas_ins on public.cuentas_mesa;
create trigger trg_rl_cuentas_ins before insert on public.cuentas_mesa for each row execute function public.fn_rl_cuentas_ins();

-- cuentas_mesa UPDATE: pedir la cuenta / re-tocar mesa por CUENTA (un cliente).
create or replace function public.fn_rl_cuentas_upd() returns trigger
language plpgsql security definer set search_path to 'public' as $$
begin
  if (select auth.uid()) is null then
    perform public.fn_rate_limit('ctaU:' || new.id::text, 15, 60);
  end if;
  return new;
end $$;
drop trigger if exists trg_rl_cuentas_upd on public.cuentas_mesa;
create trigger trg_rl_cuentas_upd before update on public.cuentas_mesa for each row execute function public.fn_rl_cuentas_upd();

-- pagos_mesa INSERT: pagar parte por CUENTA.
create or replace function public.fn_rl_pagos_mesa() returns trigger
language plpgsql security definer set search_path to 'public' as $$
begin
  if (select auth.uid()) is null then
    perform public.fn_rate_limit('pm:' || new.cuenta_id::text, 12, 60);
  end if;
  return new;
end $$;
drop trigger if exists trg_rl_pagos_mesa on public.pagos_mesa;
create trigger trg_rl_pagos_mesa before insert on public.pagos_mesa for each row execute function public.fn_rl_pagos_mesa();

-- llamadas_mesa INSERT: llamar al mesero por CUENTA.
create or replace function public.fn_rl_llamadas() returns trigger
language plpgsql security definer set search_path to 'public' as $$
begin
  if (select auth.uid()) is null then
    perform public.fn_rate_limit('ll:' || new.cuenta_id::text, 12, 60);
  end if;
  return new;
end $$;
drop trigger if exists trg_rl_llamadas on public.llamadas_mesa;
create trigger trg_rl_llamadas before insert on public.llamadas_mesa for each row execute function public.fn_rl_llamadas();

-- pagos UPDATE: registrar comprobante (delivery/pickup) por PEDIDO.
create or replace function public.fn_rl_pagos() returns trigger
language plpgsql security definer set search_path to 'public' as $$
begin
  if (select auth.uid()) is null then
    perform public.fn_rate_limit('pg:' || new.pedido_id::text, 12, 60);
  end if;
  return new;
end $$;
drop trigger if exists trg_rl_pagos on public.pagos;
create trigger trg_rl_pagos before update on public.pagos for each row execute function public.fn_rl_pagos();

-- Limpieza de ventanas vencidas cada 15 min (evita crecer sin control).
select cron.schedule('limpiar-rate-limits', '*/15 * * * *', $$delete from public.rate_limits where ventana_fin < now()$$);
