-- ============================================================
-- Gustito Express — Funciones RPC (operaciones atomicas y acceso publico controlado)
-- ============================================================

-- ------------------------------------------------------------
-- crear_pedido: el comensal (anon) crea su pedido.
-- Los precios se toman de la BD (server-side), NUNCA del cliente -> anti-manipulacion.
-- ------------------------------------------------------------
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,
  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)
language plpgsql security definer set search_path = public
as $$
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;
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') 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;

  v_codigo := 'FF-' || 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
  ) 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
  ) 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;

  insert into public.pagos (pedido_id, metodo, monto_usd, estado)
    values (v_pedido_id, p_metodo_pago, v_subtotal + v_costo_delivery, 'pendiente');

  return query select v_pedido_id, v_codigo;
end;
$$;
grant execute on function public.crear_pedido(uuid,text,text,text,text,jsonb,text,double precision,double precision,text,text,text) to anon, authenticated;

-- ------------------------------------------------------------
-- cambiar_estado_pedido: valida permiso (staff/repartidor) y transicion.
-- ------------------------------------------------------------
create or replace function public.cambiar_estado_pedido(p_pedido_id uuid, p_nuevo_estado text)
returns text language plpgsql security definer set search_path = public
as $$
declare
  v_ped           public.pedidos%rowtype;
  v_es_staff      boolean;
  v_es_repartidor boolean;
  v_permitidos    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;

  v_es_staff := fn_es_staff_de(v_ped.restaurante_id) or fn_es_admin();
  v_es_repartidor := exists (
    select 1 from public.repartidores r
    where r.id = v_ped.repartidor_id and r.perfil_id = auth.uid()
  );
  if not (v_es_staff or v_es_repartidor) then
    raise exception 'Sin permiso para cambiar este pedido';
  end if;

  v_permitidos := case v_ped.estado
    when 'recibido'   then array['confirmado','cancelado']
    when 'confirmado' then array['preparando','cancelado']
    when 'preparando' then array['listo','cancelado']
    when 'listo'      then case when v_ped.tipo_entrega = 'delivery'
                                then array['en_camino','cancelado']
                                else array['entregado','cancelado'] end
    when 'en_camino'  then array['entregado']
    else array[]::text[]
  end;
  if not (p_nuevo_estado = any(v_permitidos)) then
    raise exception 'Transicion invalida: % -> %', v_ped.estado, p_nuevo_estado;
  end if;

  if v_es_repartidor and not v_es_staff and p_nuevo_estado not in ('en_camino','entregado') then
    raise exception 'El repartidor solo despacha y entrega';
  end if;

  update public.pedidos
     set estado        = p_nuevo_estado,
         confirmado_en = case when p_nuevo_estado='confirmado' then now() else confirmado_en end,
         listo_en      = case when p_nuevo_estado='listo'      then now() else listo_en end,
         en_camino_en  = case when p_nuevo_estado='en_camino'  then now() else en_camino_en end,
         entregado_en  = case when p_nuevo_estado='entregado'  then now() else entregado_en end,
         cancelado_en  = case when p_nuevo_estado='cancelado'  then now() else cancelado_en end
   where id = p_pedido_id;

  if p_nuevo_estado = 'entregado' and v_ped.repartidor_id is not null then
    update public.repartidores set estado='disponible' where id = v_ped.repartidor_id;
  end if;

  return p_nuevo_estado;
end;
$$;
grant execute on function public.cambiar_estado_pedido(uuid,text) to authenticated;

-- ------------------------------------------------------------
-- confirmar_pago: staff/admin verifica el comprobante.
-- ------------------------------------------------------------
create or replace function public.confirmar_pago(p_pago_id uuid, p_estado text)
returns text language plpgsql security definer set search_path = public
as $$
declare v_rest uuid;
begin
  if p_estado not in ('pendiente','verificando','confirmado','rechazado') then
    raise exception 'Estado de pago invalido';
  end if;
  select p.restaurante_id into v_rest
    from public.pagos pg join public.pedidos p on p.id = pg.pedido_id
   where pg.id = p_pago_id;
  if not found then raise exception 'Pago 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.pagos
     set estado         = p_estado,
         confirmado_por = auth.uid(),
         confirmado_en  = case when p_estado='confirmado' then now() else confirmado_en end
   where id = p_pago_id;
  return p_estado;
end;
$$;
grant execute on function public.confirmar_pago(uuid,text) to authenticated;

-- ------------------------------------------------------------
-- asignar_repartidor
-- ------------------------------------------------------------
create or replace function public.asignar_repartidor(p_pedido_id uuid, p_repartidor_id uuid)
returns void language plpgsql security definer set search_path = public
as $$
declare v_rest uuid; v_rep_rest uuid;
begin
  select restaurante_id into v_rest 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_rest) or fn_es_admin()) then raise exception 'Sin permiso'; end if;
  select restaurante_id into v_rep_rest from public.repartidores where id = p_repartidor_id;
  if not found or v_rep_rest <> v_rest then raise exception 'Repartidor invalido'; end if;
  update public.pedidos set repartidor_id = p_repartidor_id where id = p_pedido_id;
  update public.repartidores set estado = 'ocupado' where id = p_repartidor_id;
end;
$$;
grant execute on function public.asignar_repartidor(uuid,uuid) to authenticated;

-- ------------------------------------------------------------
-- actualizar_ubicacion_repartidor: persiste un snapshot cada ~15s.
-- El GPS de alta frecuencia va por Realtime Broadcast (no toca la BD).
-- ------------------------------------------------------------
create or replace function public.actualizar_ubicacion_repartidor(p_lat double precision, p_lng double precision)
returns void language plpgsql security definer set search_path = public
as $$
begin
  update public.repartidores
     set ultima_latitud = p_lat, ultima_longitud = p_lng, ultima_ubicacion_en = now()
   where perfil_id = auth.uid();
end;
$$;
grant execute on function public.actualizar_ubicacion_repartidor(double precision,double precision) to authenticated;

-- ------------------------------------------------------------
-- pedido_seguimiento: el comensal (anon) consulta SU pedido por id (uuid no adivinable).
-- Devuelve solo campos seguros -> no expone la tabla completa.
-- ------------------------------------------------------------
create or replace function public.pedido_seguimiento(p_pedido_id uuid)
returns table (
  codigo                  text,
  estado                  text,
  tipo_entrega            text,
  total_usd               numeric,
  tasa_bs                 numeric,
  restaurante_nombre      text,
  restaurante_telefono    text,
  direccion               text,
  direccion_latitud       double precision,
  direccion_longitud      double precision,
  repartidor_latitud      double precision,
  repartidor_longitud     double precision,
  repartidor_ubicacion_en timestamptz,
  recibido_en             timestamptz,
  confirmado_en           timestamptz,
  listo_en                timestamptz,
  en_camino_en            timestamptz,
  entregado_en            timestamptz
)
language sql stable security definer set search_path = public
as $$
  select p.codigo, p.estado, p.tipo_entrega, p.total_usd, p.tasa_bs,
         r.nombre, r.telefono,
         p.direccion, p.direccion_latitud, p.direccion_longitud,
         rep.ultima_latitud, rep.ultima_longitud, rep.ultima_ubicacion_en,
         p.recibido_en, p.confirmado_en, p.listo_en, p.en_camino_en, p.entregado_en
    from public.pedidos p
    join public.restaurantes r on r.id = p.restaurante_id
    left join public.repartidores rep on rep.id = p.repartidor_id
   where p.id = p_pedido_id;
$$;
grant execute on function public.pedido_seguimiento(uuid) to anon, authenticated;
