-- =============================================================================
-- RESERVA "voy a comer aqui": un cuarto tipo_entrega junto a delivery/pickup/mesa.
--
-- El cliente pide igual que en pickup (elige platos, paga con el metodo que sea)
-- pero en vez de "paso a buscarlo" dice cuantas personas van a comer EN EL LOCAL.
-- El pedido entra a cocina exactamente igual que un pickup (recibido -> confirmado
-- -> preparando -> listo -> entregado, misma maquina de estados: no lleva
-- en_camino). La diferencia real es que en algun momento (no necesariamente
-- cuando esta "listo"; el mesero puede sentarlos apenas lleguen) el mesero lo
-- convierte en una cuenta de mesa real via sentar_reserva(), reusando TODO el
-- mecanismo que ya existe para mesas: comensales, pago por persona, llamar al
-- mesero, etc. Desde ese momento, si el cliente escanea el QR fisico de esa
-- mesa, cuenta_mesa_activa ya la encuentra (cero cambios de frontend cliente
-- para ese cruce: es la misma consulta que usa cualquier mesa).
-- =============================================================================

alter table public.pedidos drop constraint if exists pedidos_tipo_entrega_check;
alter table public.pedidos add constraint pedidos_tipo_entrega_check
  check (tipo_entrega = any (array['delivery','pickup','mesa','reserva']));

-- Cantidad de personas de la reserva. NULL para delivery/pickup/mesa (mesa ya
-- guarda esto en cuentas_mesa.comensales).
alter table public.pedidos add column if not exists personas int;
alter table public.pedidos add constraint pedidos_personas_check
  check (personas is null or personas between 1 and 50);

-- -----------------------------------------------------------------------------
-- crear_pedido: agrega p_personas y la rama 'reserva'.
-- OJO: agregar un parametro nuevo con CREATE OR REPLACE crea un OVERLOAD (firma
-- de 14 params conviviendo con la de 15), no lo reemplaza, y PostgREST se va a
-- HTTP 300 (multiple choices). Hay que DROP la firma vieja primero.
-- -----------------------------------------------------------------------------
DROP FUNCTION IF EXISTS public.crear_pedido(uuid,text,text,text,text,jsonb,text,double precision,double precision,text,text,text,uuid,boolean);

CREATE OR REPLACE 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::text, p_direccion_lat double precision DEFAULT NULL::double precision, p_direccion_lng double precision DEFAULT NULL::double precision, p_nota text DEFAULT NULL::text, p_mesa text DEFAULT NULL::text, p_cedula text DEFAULT NULL::text, p_comensal_id uuid DEFAULT NULL::uuid, p_para_llevar boolean DEFAULT false, p_personas int DEFAULT NULL::int)
 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_extra          numeric(10,2) := 0;
  v_subtotal       numeric(10,2) := 0;
  v_costo_delivery numeric(10,2) := 0;
  v_pedido_id      uuid;
  v_codigo         text;
  v_num            int;
  v_cuenta_id      uuid := null;
  v_firma          text := null;
  v_dup_id         uuid;
  v_dup_codigo     text;
  v_dup_cuenta     uuid;
  v_mesero_id      uuid := null;
  v_mesero_nombre  text := null;
  v_comensal_id    uuid := null;
  v_estado         text := 'recibido';
  -- Solo tiene sentido marcar "para llevar" en pedidos de mesa.
  v_para_llevar    boolean := coalesce(p_para_llevar, false) and p_tipo_entrega = 'mesa';
  v_personas       int := 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','reserva') 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 auth.uid() is not null then
    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()
       and pf.restaurante_id = p_restaurante_id
       and pf.rol in ('dueno','staff');
  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;

  if p_tipo_entrega = 'reserva' then
    v_personas := greatest(1, coalesce(p_personas, 1));
  end if;

  -- El anti-duplicado (doble toque / reintento) aplica igual a reserva: se pide
  -- remoto, sin cuenta_mesa todavia, mismo riesgo de doble-envio que pickup.
  if p_tipo_entrega in ('delivery','pickup','reserva') then
    v_firma := md5(coalesce(lower(trim(p_cliente_telefono)), '') || '|' || coalesce(p_items::text, ''));
    select p.id, p.codigo, p.cuenta_id into v_dup_id, v_dup_codigo, v_dup_cuenta
      from public.pedidos p
     where p.restaurante_id = p_restaurante_id
       and p.firma = v_firma
       and p.recibido_en > now() - interval '3 minutes'
     order by p.recibido_en desc
     limit 1;
    if found then
      return query select v_dup_id, v_dup_codigo, v_dup_cuenta;
      return;
    end if;
  end if;

  if p_tipo_entrega = 'mesa' then
    -- El cliente ya esta en la mesa: el pedido entra DIRECTO a cocina.
    v_estado := 'confirmado';
    if coalesce(nullif(p_mesa, ''), '') = '' then
      raise exception 'Falta el numero de mesa';
    end if;
    insert into public.cuentas_mesa (restaurante_id, mesa, mesero_id, mesero_nombre)
      values (p_restaurante_id, p_mesa, v_mesero_id, v_mesero_nombre)
      on conflict (restaurante_id, mesa) where (estado <> 'cerrada') do nothing;
    select cm.id into v_cuenta_id from public.cuentas_mesa cm
      where cm.restaurante_id = p_restaurante_id and cm.mesa = p_mesa and cm.estado <> 'cerrada'
      limit 1;
    if v_mesero_id is not null and v_cuenta_id is not null then
      update public.cuentas_mesa
         set mesero_id = v_mesero_id, mesero_nombre = v_mesero_nombre
       where id = v_cuenta_id and mesero_id is null;
    end if;
    if p_comensal_id is not null then
      perform 1 from public.comensales_mesa cm where cm.id = p_comensal_id and cm.cuenta_id = v_cuenta_id;
      if not found then raise exception 'La persona no pertenece a esta mesa'; end if;
      v_comensal_id := p_comensal_id;
    end if;
  end if;
  -- 'reserva' NO crea cuenta_mesa aqui: no hay mesa fisica todavia. Queda
  -- cuenta_id/mesa en null hasta que el mesero llame a sentar_reserva().

  update public.restaurantes set contador_pedidos = contador_pedidos + 1
   where id = p_restaurante_id
   returning contador_pedidos into v_num;
  v_codigo := coalesce(nullif(v_rest.prefijo, ''), 'GX') || '-' || lpad(v_num::text, 4, '0');

  insert into public.pedidos (
    restaurante_id, codigo, cliente_nombre, cliente_telefono, cliente_cedula, tipo_entrega, mesa,
    direccion, direccion_latitud, direccion_longitud, estado, confirmado_en,
    subtotal_usd, costo_delivery_usd, total_usd, tasa_bs, metodo_pago, nota, cuenta_id, firma,
    mesero_id, mesero_nombre, comensal_id, para_llevar, personas
  ) 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, v_estado, case when v_estado = 'confirmado' then now() else null end,
    0, v_costo_delivery, 0, v_rest.tasa_bs, p_metodo_pago, p_nota, v_cuenta_id, v_firma,
    v_mesero_id, v_mesero_nombre, v_comensal_id, v_para_llevar, v_personas
  ) 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;
    select coalesce(sum((op->>'precio_extra')::numeric), 0) into v_extra
      from jsonb_array_elements(coalesce(v_prod.opciones, '[]'::jsonb)) as grupo,
           jsonb_array_elements(coalesce(grupo->'opciones', '[]'::jsonb)) as op
     where jsonb_exists(coalesce(v_item->'opciones', '[]'::jsonb), op->>'nombre');
    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_extra, v_cant, nullif(v_item->>'nota', ''));
    v_subtotal := v_subtotal + (v_prod.precio_usd + v_extra) * v_cant;
  end loop;

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

  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$;

-- -----------------------------------------------------------------------------
-- sentar_reserva: el mesero convierte un pedido 'reserva' en una mesa real.
-- Mismo patron anti-cruce que abrir_mesa (on conflict where estado<>'cerrada').
-- -----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sentar_reserva(p_pedido_id uuid, p_mesa text)
 RETURNS uuid
 LANGUAGE plpgsql
 SECURITY DEFINER
 SET search_path TO 'public'
AS $function$
declare
  v_ped       public.pedidos%rowtype;
  v_cuenta_id uuid;
  v_mesero_id uuid;
  v_mesero_nombre text;
begin
  select * into v_ped from public.pedidos where id = p_pedido_id;
  if not found then
    raise exception 'Pedido no existe';
  end if;
  if not (fn_es_staff_de(v_ped.restaurante_id) or fn_es_admin()) then
    raise exception 'Sin permiso';
  end if;
  if v_ped.tipo_entrega <> 'reserva' then
    raise exception 'Este pedido no es una reserva';
  end if;
  if v_ped.cuenta_id is not null then
    raise exception 'Esta reserva ya tiene mesa asignada';
  end if;
  if v_ped.estado = 'cancelado' then
    raise exception 'Esta reserva esta cancelada';
  end if;
  if coalesce(nullif(trim(p_mesa), ''), '') = '' then
    raise exception 'Falta el numero de mesa';
  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();

  insert into public.cuentas_mesa (restaurante_id, mesa, comensales, mesero_id, mesero_nombre)
    values (v_ped.restaurante_id, trim(p_mesa), greatest(1, coalesce(v_ped.personas, 1)), v_mesero_id, v_mesero_nombre)
    on conflict (restaurante_id, mesa) where (estado <> 'cerrada') do nothing;

  select id into v_cuenta_id from public.cuentas_mesa
   where restaurante_id = v_ped.restaurante_id and mesa = trim(p_mesa) and estado <> 'cerrada' limit 1;

  -- Registra al cliente de la reserva como primer comensal, si la mesa no
  -- tenia ninguno todavia (no duplicar si ya la abrieron de otra forma).
  if not exists (select 1 from public.comensales_mesa where cuenta_id = v_cuenta_id) then
    insert into public.comensales_mesa (cuenta_id, restaurante_id, nombre, orden)
    values (v_cuenta_id, v_ped.restaurante_id, nullif(trim(v_ped.cliente_nombre), ''), 1);
  end if;

  update public.pedidos
     set cuenta_id = v_cuenta_id,
         mesa      = trim(p_mesa)
   where id = p_pedido_id;

  return v_cuenta_id;
end;
$function$;

comment on function public.sentar_reserva(uuid, text) is
  'El mesero convierte un pedido tipo_entrega=reserva en una cuenta de mesa real: crea/reusa cuentas_mesa, registra al cliente como comensal y liga el pedido a esa cuenta.';
