-- Marcar como PERDIDA (no cobrada) la parte de una cuenta de mesa que el
-- negocio decide no perseguir mas (ej: 2 de 7 comensales se fueron sin pagar).
-- Gilberto: "que quede bien claro que es una perdida" -> metodo propio
-- 'perdida', separado de lo realmente cobrado en TODOS los reportes que
-- suman pagos_mesa (si no, una perdida se veria como venta/rendimiento real).

alter table public.pagos_mesa drop constraint pagos_mesa_metodo_check;
alter table public.pagos_mesa add constraint pagos_mesa_metodo_check
  check (metodo = any (array['pago_movil','efectivo','transferencia','zelle','punto_venta','perdida']));

-- Solo dueno/admin/encargado (mismo nivel que "Anular" un cobro): un mesero
-- raso no puede condonar deuda por su cuenta. Motivo obligatorio para auditar.
create or replace function public.marcar_perdida_mesa(
  p_cuenta_id uuid,
  p_pedido_ids uuid[],
  p_motivo text
)
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_quien_id     uuid;
  v_quien_nombre text;
  v_total        numeric(10,2);
  v_pagado       numeric(10,2);
begin
  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_dueno_de(v_cta.restaurante_id) or fn_es_admin() or fn_es_encargado_de(v_cta.restaurante_id)) then
    raise exception 'Solo el dueño o encargado puede marcar una parte como pérdida';
  end if;
  if coalesce(nullif(trim(p_motivo), ''), '') = '' then
    raise exception 'Escribe el motivo de la pérdida';
  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'; end if;

  select pf.id, coalesce(nullif(trim(pf.nombre), ''), 'Dueño')
    into v_quien_id, v_quien_nombre from public.perfiles pf where pf.id = auth.uid();

  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 'Algún pedido ya fue cobrado, está cancelado o no es de esta cuenta';
  end if;
  if v_monto <= 0 then raise exception 'Nada que marcar'; end if;

  insert into public.pagos_mesa (cuenta_id, restaurante_id, monto_usd, metodo, nota, mesero_id, mesero_nombre, verificado)
    values (p_cuenta_id, v_cta.restaurante_id, v_monto, 'perdida', trim(p_motivo), v_quien_id, v_quien_nombre, true)
    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 and pm.verificado;

  return query select v_pago, v_monto, greatest(v_total - v_pagado, 0::numeric);
end;
$function$;

-- rendimiento_meseros: una perdida NO es venta del mesero (no se cobro nada).
drop function public.rendimiento_meseros(uuid, timestamptz);
create or replace function public.rendimiento_meseros(p_restaurante_id uuid, p_desde timestamp with time zone default null)
returns table(mesero_id uuid, nombre text, mesas bigint, pedidos bigint, ventas_usd numeric, propinas_usd numeric, perdidas_usd numeric)
language plpgsql
security definer
set search_path to 'public'
as $function$
begin
  if not (fn_es_dueno_de(p_restaurante_id) or fn_es_admin()) then
    raise exception 'Sin permiso';
  end if;
  return query
  with mesas_m as (
    select c.id, c.mesero_id
      from public.cuentas_mesa c
     where c.restaurante_id = p_restaurante_id
       and c.mesero_id is not null
       and (p_desde is null or c.abierta_en >= p_desde)
  )
  select
    pf.id,
    coalesce(nullif(trim(pf.nombre), ''), 'Mesero'),
    (select count(*) from mesas_m m where m.mesero_id = pf.id),
    (select count(*) from public.pedidos pd join mesas_m m on m.id = pd.cuenta_id
       where m.mesero_id = pf.id and pd.estado <> 'cancelado'),
    (select coalesce(sum(pg.monto_usd), 0) from public.pagos_mesa pg join mesas_m m on m.id = pg.cuenta_id
       where m.mesero_id = pf.id and pg.metodo <> 'perdida'),
    (select coalesce(sum(pg.propina_usd), 0) from public.pagos_mesa pg join mesas_m m on m.id = pg.cuenta_id
       where m.mesero_id = pf.id),
    (select coalesce(sum(pg.monto_usd), 0) from public.pagos_mesa pg join mesas_m m on m.id = pg.cuenta_id
       where m.mesero_id = pf.id and pg.metodo = 'perdida')
  from public.perfiles pf
  where pf.restaurante_id = p_restaurante_id
    and pf.rol = 'staff'
  order by 5 desc, 2;
end;
$function$;

-- historial_mesas: separa lo COBRADO de verdad de lo marcado como perdida.
drop function public.historial_mesas(uuid, integer);
create or replace function public.historial_mesas(p_restaurante_id uuid, p_dias integer default 1)
returns table(id uuid, mesa text, abierta_en timestamptz, cerrada_en timestamptz, total_usd numeric,
              cobrado_usd numeric, perdida_usd numeric, n_pedidos integer, mesero_nombre text,
              comensales integer, modo_cuenta text, metodo_pago text, tasa_bs numeric)
language sql
stable security definer
set search_path to 'public'
as $function$
  select c.id, c.mesa, c.abierta_en, c.cerrada_en,
    coalesce((select sum(p.total_usd) from public.pedidos p where p.cuenta_id = c.id and p.estado <> 'cancelado'), 0),
    coalesce((select sum(pm.monto_usd) from public.pagos_mesa pm where pm.cuenta_id = c.id and pm.metodo <> 'perdida'), 0),
    coalesce((select sum(pm.monto_usd) from public.pagos_mesa pm where pm.cuenta_id = c.id and pm.metodo = 'perdida'), 0),
    (select count(*)::int from public.pedidos p where p.cuenta_id = c.id and p.estado <> 'cancelado'),
    c.mesero_nombre, c.comensales, c.modo_cuenta, c.metodo_pago, r.tasa_bs
  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 c.cerrada_en >= now() - make_interval(days => greatest(1, coalesce(p_dias, 1)))
    and (fn_es_staff_de(c.restaurante_id) or fn_es_admin())
  order by c.cerrada_en desc;
$function$;

-- mis_cuentas_mesa: idem, "pagado" no debe incluir lo marcado como perdida
-- (si no, el panel diria "ya pagado" de plata que nunca entro).
drop function public.mis_cuentas_mesa(uuid);
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 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, por_revisar_usd numeric, abierta_por text, todo_entregado boolean, perdida_usd numeric)
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 and pm.metodo <> 'perdida'), 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)
    ),
    (
      exists (select 1 from public.pedidos p where p.cuenta_id = c.id and p.estado = 'entregado')
      and not exists (select 1 from public.pedidos p where p.cuenta_id = c.id and p.estado not in ('entregado', 'cancelado'))
    ),
    coalesce((select sum(pm.monto_usd) from public.pagos_mesa pm where pm.cuenta_id = c.id and pm.metodo = 'perdida'), 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 public.fn_mesa_de_staff(c.mesero_id, c.restaurante_id)
  order by c.abierta_en;
$function$;
