-- ============================================================
-- Gustito Express — Cuenta abierta por mesa (comer en el local).
-- Regla de oro: AISLAMIENTO TOTAL entre mesas. Un indice unico parcial
-- garantiza a lo sumo UNA cuenta activa por (restaurante, mesa).
-- ============================================================

create table if not exists public.cuentas_mesa (
  id                 uuid primary key default gen_random_uuid(),
  restaurante_id     uuid not null references public.restaurantes(id) on delete cascade,
  mesa               text not null,
  estado             text not null default 'abierta' check (estado in ('abierta','por_pagar','cerrada')),
  metodo_pago        text check (metodo_pago in ('pago_movil','efectivo','transferencia','zelle','punto_venta')),
  referencia         text,
  comprobante_url    text,
  abierta_en         timestamptz not null default now(),
  pago_solicitado_en timestamptz,
  cerrada_en         timestamptz
);
alter table public.cuentas_mesa enable row level security;

-- AISLAMIENTO: a lo sumo UNA cuenta activa (abierta/por_pagar) por mesa de un negocio.
create unique index if not exists uq_cuenta_mesa_activa
  on public.cuentas_mesa (restaurante_id, mesa) where (estado <> 'cerrada');
create index if not exists idx_cuentas_mesa_rest on public.cuentas_mesa (restaurante_id, estado);

drop policy if exists cuentas_staff on public.cuentas_mesa;
create policy cuentas_staff on public.cuentas_mesa for all to authenticated
  using (fn_es_staff_de(restaurante_id) or fn_es_admin())
  with check (fn_es_staff_de(restaurante_id) or fn_es_admin());

alter table public.pedidos add column if not exists cuenta_id uuid references public.cuentas_mesa(id) on delete set null;
create index if not exists idx_pedidos_cuenta on public.pedidos (cuenta_id);

-- ---- crear_pedido: + punto_venta, + cuenta de mesa, prefijo GX, devuelve cuenta_id ----
drop function if exists public.crear_pedido(uuid, text, text, text, text, jsonb, text, double precision, double precision, text, text, text);
create 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
)
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_subtotal       numeric(10,2) := 0;
  v_costo_delivery numeric(10,2) := 0;
  v_pedido_id      uuid;
  v_codigo         text;
  v_cuenta_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 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;

  -- Mesa: abrir la cuenta si no hay una activa (aislada por restaurante+mesa) y engancharla.
  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)
      values (p_restaurante_id, p_mesa)
      on conflict (restaurante_id, mesa) where (estado <> 'cerrada') do nothing;
    select id into v_cuenta_id from public.cuentas_mesa
      where restaurante_id = p_restaurante_id and mesa = p_mesa and estado <> 'cerrada'
      limit 1;
  end if;

  v_codigo := 'GX-' || lpad(nextval('public.pedido_codigo_seq')::text, 5, '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
  ) 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
  ) 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;
    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_cant, nullif(v_item->>'nota', ''));
    v_subtotal := v_subtotal + v_prod.precio_usd * v_cant;
  end loop;

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

  -- En mesa el pago es a nivel de cuenta (al final). En delivery/pickup, pago por pedido.
  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) to anon, authenticated;

-- ---- cuenta_mesa: el cliente ve SU cuenta (solo la de su id). Aislada. ----
create or replace function public.cuenta_mesa(p_cuenta_id uuid)
returns table(
  id uuid, mesa text, estado text, restaurante_nombre text, restaurante_color text,
  tasa_bs numeric, total_usd numeric, metodo_pago text, pedidos jsonb
)
language sql stable security definer set search_path to 'public'
as $$
  select c.id, c.mesa, c.estado, r.nombre, r.color_primario, r.tasa_bs,
    coalesce((select sum(p.total_usd) from public.pedidos p where p.cuenta_id = c.id), 0),
    c.metodo_pago,
    coalesce((
      select jsonb_agg(jsonb_build_object(
        'codigo', p.codigo, 'estado', p.estado, 'total_usd', p.total_usd,
        '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)
  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;

-- ---- pedir_cuenta_mesa: el cliente pide pagar (elige metodo, sube comprobante si aplica) ----
create or replace function public.pedir_cuenta_mesa(
  p_cuenta_id uuid, p_metodo text, p_referencia text default null, p_comprobante_url text default null
)
returns void language plpgsql security definer set search_path to 'public'
as $$
begin
  if p_metodo not in ('pago_movil','efectivo','transferencia','zelle','punto_venta') then
    raise exception 'Metodo invalido';
  end if;
  update public.cuentas_mesa
     set estado = 'por_pagar',
         metodo_pago = p_metodo,
         referencia = nullif(p_referencia, ''),
         comprobante_url = nullif(p_comprobante_url, ''),
         pago_solicitado_en = now()
   where id = p_cuenta_id and estado in ('abierta', 'por_pagar');
end;
$$;
grant execute on function public.pedir_cuenta_mesa(uuid, text, text, text) to anon, authenticated;

-- ---- cerrar_cuenta_mesa: el staff confirma el pago y libera la mesa ----
create or replace function public.cerrar_cuenta_mesa(p_cuenta_id uuid)
returns void language plpgsql security definer set search_path to 'public'
as $$
declare v_rest uuid;
begin
  select restaurante_id into v_rest from public.cuentas_mesa where id = p_cuenta_id;
  if v_rest is null then raise exception 'Cuenta 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.cuentas_mesa set estado = 'cerrada', cerrada_en = now() where id = p_cuenta_id;
end;
$$;
grant execute on function public.cerrar_cuenta_mesa(uuid) to authenticated;

-- ---- mis_cuentas_mesa: el staff ve las cuentas activas de su negocio (vista 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 timestamptz, pago_solicitado_en timestamptz, metodo_pago text,
  referencia text, comprobante_url text, n_pedidos int
)
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), 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)
  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 authenticated;
