-- Una venta que la Estacion no logra confirmar ya no se entrega otra vez en
-- cada ronda: la nube cuenta el intento y espacia el siguiente (30 s, 1 min,
-- 2 min... tope 30 min), y a los 10 intentos la aparta para revision.
-- Cazado en caliente: la Estacion resello la misma venta cada 10 segundos
-- durante 20 minutos, sin nadie trabajando.

alter table fiscal.cola_gustito
  add column if not exists proximo_intento_en timestamptz not null default now();

comment on column fiscal.cola_gustito.proximo_intento_en is
  'Cuando se le puede volver a entregar esta venta a la Estacion. Sube sola con cada intento fallido para que una venta trabada no martille la cola.';

create index if not exists idx_cola_entregable
  on fiscal.cola_gustito (emisor_id, proximo_intento_en)
  where estado = 'pendiente';

CREATE OR REPLACE FUNCTION fiscal.ventas_pendientes(p_llave text, p_rif text, p_limite integer DEFAULT 50)
 RETURNS jsonb
 LANGUAGE plpgsql
 SECURITY DEFINER
 SET search_path TO 'fiscal', 'public'
AS $function$
declare
  v_emisor uuid;
  v_res jsonb;
begin
  v_emisor := fiscal.autenticar(p_llave, p_rif, 'estacion: pendientes');

  -- Cada entrega cuenta como un intento y programa la siguiente mas lejos
  -- (30 s, 1 min, 2 min... tope 30 min). Sin esto, una venta que la Estacion
  -- no logra confirmar se entrega otra vez en cada ronda, para siempre: paso
  -- de verdad, 10 segundos por vuelta, y con volumen se lleva por delante la
  -- cola de todos los demas negocios.
  with tomadas as (
    update fiscal.cola_gustito c
       set tomado_en = now(),
           intentos = c.intentos + 1,
           proximo_intento_en = now() + least(interval '30 minutes',
                                              interval '30 seconds' * power(2, least(c.intentos, 6))),
           estado = case when c.intentos + 1 >= 10 then 'error' else c.estado end,
           ultimo_error = case when c.intentos + 1 >= 10
             then 'entregada 10 veces sin que la Estacion confirmara el sellado'
             else c.ultimo_error end
     where c.id in (
       select id from fiscal.cola_gustito
        where emisor_id = v_emisor and estado = 'pendiente'
          and proximo_intento_en <= now()
        order by creado_en
        limit greatest(1, least(coalesce(p_limite, 50), 200))
        for update skip locked
     )
    returning c.*
  )
  select coalesce(jsonb_agg(jsonb_build_object(
    'cola_id', t.id,
    'tipo', t.tipo,
    'origen_id', t.origen_id,
    'venta', case t.tipo
      when 'pedido' then (
        select jsonb_build_object(
          'codigo', p.codigo,
          'fecha', coalesce(p.entregado_en, p.recibido_en),
          'tipo_entrega', p.tipo_entrega,
          'mesa', p.mesa,
          'metodo_pago', p.metodo_pago,
          'origen', p.origen,
          'tasa_bs', p.tasa_bs,
          'total_usd', p.total_usd,
          'costo_delivery_usd', p.costo_delivery_usd,
          'comprador', jsonb_build_object(
            'nombre', p.cliente_nombre,
            'rif', p.cliente_rif,
            'razon_social', p.cliente_razon_social,
            'direccion', p.cliente_direccion_fiscal,
            'correo', p.cliente_correo,
            'telefono', p.cliente_telefono
          ),
          'renglones', coalesce((
            select jsonb_agg(jsonb_build_object(
              'nombre', it.nombre, 'cantidad', it.cantidad, 'precio_usd', it.precio_usd))
              from public.pedido_items it where it.pedido_id = p.id), '[]'::jsonb)
        ) from public.pedidos p where p.id = t.origen_id
      )
      else (
        select jsonb_build_object(
          'codigo', 'MESA-' || coalesce(cm.mesa, '?'),
          'fecha', coalesce(cm.cerrada_en, cm.abierta_en),
          'tipo_entrega', 'mesa',
          'mesa', cm.mesa,
          'metodo_pago', cm.metodo_pago,
          'tasa_bs', (select max(p.tasa_bs) from public.pedidos p where p.cuenta_id = cm.id),
          'total_usd', coalesce((select sum(p.total_usd) from public.pedidos p
                                  where p.cuenta_id = cm.id and p.estado <> 'cancelado'), 0),
          'costo_delivery_usd', 0,
          'comprador', jsonb_build_object(
            'nombre', coalesce(cm.cliente_razon_social, 'Consumidor Final'),
            'rif', cm.cliente_rif,
            'razon_social', cm.cliente_razon_social,
            'direccion', cm.cliente_direccion_fiscal,
            'correo', cm.cliente_correo,
            'telefono', null
          ),
          -- La propina va aparte: es servicio voluntario, no forma parte de la
          -- base imponible. La Estacion decide que hace con ella.
          'propina_usd', coalesce((select sum(pm.propina_usd) from public.pagos_mesa pm
                                    where pm.cuenta_id = cm.id), 0),
          'renglones', coalesce((
            select jsonb_agg(jsonb_build_object(
              'nombre', it.nombre, 'cantidad', it.cantidad, 'precio_usd', it.precio_usd))
              from public.pedido_items it
              join public.pedidos p on p.id = it.pedido_id
             where p.cuenta_id = cm.id and p.estado <> 'cancelado'), '[]'::jsonb)
        ) from public.cuentas_mesa cm where cm.id = t.origen_id
      )
    end
  ) order by t.creado_en), '[]'::jsonb) into v_res
  from tomadas t;

  return v_res;
end;
$function$
;

CREATE OR REPLACE FUNCTION fiscal.latido(p_llave text, p_rif text, p_datos jsonb DEFAULT '{}'::jsonb)
 RETURNS jsonb
 LANGUAGE plpgsql
 SECURITY DEFINER
 SET search_path TO 'fiscal', 'public'
AS $function$
declare
  v_emisor uuid;
  v_serial text;
  v_pend int;
  v_anul int;
  v_ultima timestamptz;
  v_espera int;
begin
  v_emisor := fiscal.autenticar(p_llave, p_rif, 'estacion: latido');
  select serial into v_serial from fiscal.emisores where id = v_emisor;

  insert into fiscal.estaciones (emisor_id, nombre, version, equipo, ultimo_latido,
                                 pendientes, ultimo_documento, libro_integro)
    values (v_emisor,
            coalesce(nullif(p_datos->>'nombre', ''), 'Estacion Fiscal'),
            p_datos->>'version',
            coalesce(nullif(p_datos->>'equipo', ''), 'unico'),
            now(),
            coalesce((p_datos->>'pendientes')::int, 0),
            nullif(p_datos->>'ultimo_documento', '')::bigint,
            (p_datos->>'libro_integro')::boolean)
    on conflict (emisor_id, equipo) do update set
      nombre = excluded.nombre,
      version = excluded.version,
      ultimo_latido = now(),
      pendientes = excluded.pendientes,
      ultimo_documento = excluded.ultimo_documento,
      libro_integro = excluded.libro_integro;

  select count(*), max(creado_en) into v_pend, v_ultima
    from fiscal.cola_gustito
   where emisor_id = v_emisor and estado = 'pendiente'
     and proximo_intento_en <= now();

  select count(*) into v_anul
    from fiscal.anulaciones_gustito
   where emisor_id = v_emisor and estado = 'pendiente';

  -- El ritmo lo marca el movimiento del negocio, no el reloj.
  v_espera := case
    when v_pend > 0 or v_anul > 0 then 10                                -- hay trabajo: ya mismo
    when v_ultima > now() - interval '15 minutes' then 20                -- vendio hace nada: atento
    when v_ultima > now() - interval '2 hours' then 60                   -- venia vendiendo: tranquilo
    else 300                                                             -- cerrado o sin movimiento
  end;

  return jsonb_build_object(
    'ok', true,
    'pendientes_en_nube', v_pend,
    'anulaciones_en_nube', v_anul,
    'habilitado', true,
    'serial', v_serial,
    'volver_en_segundos', v_espera
  );
end;
$function$
;

CREATE OR REPLACE FUNCTION fiscal.reportar_error(p_llave text, p_rif text, p_cola_id uuid, p_error text)
 RETURNS jsonb
 LANGUAGE plpgsql
 SECURITY DEFINER
 SET search_path TO 'fiscal', 'public'
AS $function$
declare
  v_emisor uuid;
  v_intentos int;
begin
  v_emisor := fiscal.autenticar(p_llave, p_rif, 'estacion: error');
  update fiscal.cola_gustito
     set intentos = intentos + 1,
         ultimo_error = left(coalesce(p_error, 'sin detalle'), 500),
         tomado_en = null,
         proximo_intento_en = now() + least(interval '30 minutes',
                                            interval '30 seconds' * power(2, least(intentos, 6))),
         estado = case when intentos + 1 >= 5 then 'error' else 'pendiente' end
   where id = p_cola_id and emisor_id = v_emisor
  returning intentos into v_intentos;
  if not found then raise exception 'Esa venta no es de este emisor'; end if;
  return jsonb_build_object('ok', true, 'intentos', v_intentos);
end;
$function$
;
