-- ============================================================
-- Gustito Express — CUENTAS SEPARADAS (fase 1: cobro por partes del mesero).
--
-- Modelo: sigue habiendo UNA cuenta activa por mesa (el anti-cruce no se
-- toca). Lo nuevo es que la cuenta se puede COBRAR POR PARTES: cada cobro
-- es una fila en pagos_mesa (quien cobro, metodo, referencia) amarrada a
-- pedidos concretos via pedidos.pago_mesa_id (un pedido solo puede
-- cobrarse UNA vez: es una columna, no una lista). El saldo se calcula en
-- BD y "Cobrar y liberar" pasa a significar "cobrar el resto".
--
-- De paso se corrige un hueco preexistente: los totales de cuenta sumaban
-- tambien los pedidos CANCELADOS (se podia cobrar de mas). Ahora todos los
-- totales excluyen cancelados.
-- ============================================================

-- ---- Tabla de cobros parciales ----
create table if not exists public.pagos_mesa (
  id             uuid primary key default gen_random_uuid(),
  cuenta_id      uuid not null references public.cuentas_mesa(id) on delete cascade,
  restaurante_id uuid not null references public.restaurantes(id) on delete cascade,
  monto_usd      numeric(10,2) not null check (monto_usd > 0),
  metodo         text not null check (metodo in ('pago_movil','efectivo','transferencia','zelle','punto_venta')),
  referencia     text,
  nota           text,
  mesero_id      uuid references public.perfiles(id) on delete set null,
  mesero_nombre  text,
  creado_en      timestamptz not null default now()
);
create index if not exists idx_pagos_mesa_cuenta on public.pagos_mesa (cuenta_id);
create index if not exists idx_pagos_mesa_rest on public.pagos_mesa (restaurante_id, creado_en);
alter table public.pagos_mesa enable row level security;

-- Staff VE los cobros de su restaurante; solo el dueño ANULA (borra) un
-- cobro equivocado (los pedidos vuelven a "sin pagar" por el FK de abajo).
-- NO hay policy de insert/update: se escribe SOLO via RPC cobrar_parte_mesa.
create policy pagos_mesa_staff_select on public.pagos_mesa
  for select to authenticated
  using (fn_es_staff_de(restaurante_id) or fn_es_admin());
create policy pagos_mesa_dueno_delete on public.pagos_mesa
  for delete to authenticated
  using (fn_es_dueno_de(restaurante_id) or fn_es_admin());

-- Cada pedido puede pertenecer a UN cobro (anti doble-cobro estructural).
alter table public.pedidos add column if not exists pago_mesa_id uuid references public.pagos_mesa(id) on delete set null;
create index if not exists idx_pedidos_pago_mesa on public.pedidos (pago_mesa_id) where (pago_mesa_id is not null);

-- ---- RPC: cobrar una parte de la cuenta (solo personal del restaurante) ----
create or replace function public.cobrar_parte_mesa(
  p_cuenta_id uuid,
  p_pedido_ids uuid[],
  p_metodo text,
  p_referencia text default null,
  p_nota text default null
)
returns table(pago_id uuid, monto_usd numeric, saldo_usd numeric)
language plpgsql security definer set search_path to 'public'
as $function$
declare
  v_cta            public.cuentas_mesa%rowtype;
  v_esperados      int;
  v_n              int;
  v_monto          numeric(10,2);
  v_pago           uuid;
  v_mesero_id      uuid := null;
  v_mesero_nombre  text := null;
  v_total          numeric(10,2);
  v_pagado         numeric(10,2);
begin
  -- Lock de la cuenta: dos cobros simultaneos sobre la misma mesa se
  -- serializan aqui (el segundo espera y revalida).
  select * into v_cta from public.cuentas_mesa where id = p_cuenta_id for update;
  if not found or v_cta.estado = 'cerrada' then
    raise exception 'La cuenta no esta disponible';
  end if;
  if not (fn_es_staff_de(v_cta.restaurante_id) or fn_es_admin()) then
    raise exception 'Sin permiso';
  end if;
  if p_metodo not in ('pago_movil','efectivo','transferencia','zelle','punto_venta') then
    raise exception 'Metodo de pago invalido';
  end if;

  select count(distinct u) into v_esperados from unnest(coalesce(p_pedido_ids, '{}'::uuid[])) u;
  if v_esperados = 0 then
    raise exception 'Elige al menos un pedido para cobrar';
  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();

  -- Lock + validacion atomica: los pedidos deben ser DE ESTA cuenta,
  -- estar SIN cobrar y no estar cancelados. Si alguno no cumple, nada se cobra.
  select count(*), coalesce(sum(s.total_usd), 0) into v_n, v_monto
    from (
      select p.total_usd from public.pedidos p
       where p.id = any(p_pedido_ids)
         and p.cuenta_id = p_cuenta_id
         and p.pago_mesa_id is null
         and p.estado <> 'cancelado'
       for update
    ) s;
  if v_n <> v_esperados then
    raise exception 'Algun pedido ya fue cobrado, esta cancelado o no es de esta cuenta';
  end if;
  if v_monto <= 0 then
    raise exception 'Nada que cobrar';
  end if;

  insert into public.pagos_mesa (cuenta_id, restaurante_id, monto_usd, metodo, referencia, nota, mesero_id, mesero_nombre)
    values (p_cuenta_id, v_cta.restaurante_id, v_monto, p_metodo, nullif(trim(coalesce(p_referencia,'')), ''), nullif(trim(coalesce(p_nota,'')), ''), v_mesero_id, v_mesero_nombre)
    returning id into v_pago;

  update public.pedidos set pago_mesa_id = v_pago where id = any(p_pedido_ids);

  select coalesce(sum(p.total_usd), 0) into v_total
    from public.pedidos p where p.cuenta_id = p_cuenta_id and p.estado <> 'cancelado';
  select coalesce(sum(pm.monto_usd), 0) into v_pagado
    from public.pagos_mesa pm where pm.cuenta_id = p_cuenta_id;

  return query select v_pago, v_monto, greatest(v_total - v_pagado, 0::numeric);
end;
$function$;
grant execute on function public.cobrar_parte_mesa(uuid, uuid[], text, text, text) to authenticated;

-- ---- Totales SIN cancelados + desglose por persona ----

-- cuenta_mesa_activa: mismo retorno, suma corregida.
create or replace function public.cuenta_mesa_activa(p_restaurante_id uuid, p_mesa text)
returns table(id uuid, mesa text, estado text, total_usd numeric, mesero_nombre text)
language sql stable security definer set search_path to 'public'
as $$
  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),
         c.mesero_nombre
  from public.cuentas_mesa c
  where c.restaurante_id = p_restaurante_id
    and c.mesa = p_mesa
    and c.estado <> 'cerrada'
  limit 1;
$$;
grant execute on function public.cuenta_mesa_activa(uuid, text) to anon, authenticated;

-- cuenta_mesa (pantalla del cliente): cada pedido trae quien lo pidio y si
-- ya esta pagado; el total excluye cancelados. Mismo tipo de retorno.
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
)
language sql stable security definer set search_path to 'public'
as $$
  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(
             'codigo', p.codigo, 'estado', p.estado, 'total_usd', p.total_usd,
             'cliente', p.cliente_nombre, 'pagado', (p.pago_mesa_id is not null),
             '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
  from public.cuentas_mesa c
  join public.restaurantes r on r.id = c.restaurante_id
  where c.id = p_cuenta_id;
$$;
grant execute on function public.cuenta_mesa(uuid) to anon, authenticated;

-- mis_cuentas_mesa (panel): + pagado_usd (cobros parciales acumulados).
-- Cambia el tipo de retorno -> DROP previo.
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
)
language sql stable security definer set search_path to 'public'
as $$
  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)
  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;
$$;
grant execute on function public.mis_cuentas_mesa(uuid) to anon, authenticated;

-- ---- cerrar_negocio: tambien limpia referencias de los cobros parciales ----
create or replace function public.cerrar_negocio()
returns void
language plpgsql security definer set search_path to 'public'
as $function$
declare v_rest uuid := fn_mi_restaurante();
begin
  if v_rest is null or not (fn_es_dueno_de(v_rest) or fn_es_admin()) then
    raise exception 'Sin permiso';
  end if;
  update public.restaurantes set abierto = false where id = v_rest;
  -- ocultar datos personales (se conserva el pedido y sus totales)
  update public.pedidos
     set cliente_nombre     = 'Cliente',
         cliente_telefono   = '',
         cliente_cedula     = null,
         direccion          = null,
         direccion_latitud  = null,
         direccion_longitud = null,
         nota               = null
   where restaurante_id = v_rest
     and cliente_telefono <> '';            -- no re-procesar pedidos ya ocultados
  update public.pagos
     set referencia = null, comprobante_url = null
   where pedido_id in (select id from public.pedidos where restaurante_id = v_rest);
  update public.pagos_mesa
     set referencia = null
   where restaurante_id = v_rest and referencia is not null;
  update public.repartidores set estado = 'disponible' where restaurante_id = v_rest and estado = 'ocupado';
end;
$function$;
