-- ═══════════════════════════════════════════════════════════════════════════
-- Agendador Online — acesso público (anon) + função de slots disponíveis
-- ═══════════════════════════════════════════════════════════════════════════

-- ── 1. Geração automática de slug ──────────────────────────────────────────
-- Cria slug único de 8 chars quando o profissional ativa sem link_personalizado
CREATE OR REPLACE FUNCTION public.agendador_gerar_slug()
RETURNS trigger LANGUAGE plpgsql AS $$
DECLARE
  v_slug text;
  v_exists boolean;
BEGIN
  -- só gera se ativou e não tem slug ainda
  IF NEW.ativo = true AND (NEW.link_slug IS NULL OR NEW.link_slug = '') THEN
    LOOP
      v_slug := lower(substring(replace(gen_random_uuid()::text, '-', ''), 1, 8));
      SELECT EXISTS (
        SELECT 1 FROM public.agendador_configuracoes
        WHERE link_slug = v_slug AND owner_id <> NEW.owner_id
      ) INTO v_exists;
      EXIT WHEN NOT v_exists;
    END LOOP;
    NEW.link_slug := v_slug;
  END IF;
  RETURN NEW;
END;
$$;

DROP TRIGGER IF EXISTS agendador_slug_trigger ON public.agendador_configuracoes;
CREATE TRIGGER agendador_slug_trigger
  BEFORE INSERT OR UPDATE ON public.agendador_configuracoes
  FOR EACH ROW EXECUTE FUNCTION public.agendador_gerar_slug();

-- ── 2. Políticas públicas (anon) ────────────────────────────────────────────

-- Leitura pública da config pelo slug (só ativo)
DROP POLICY IF EXISTS "agendador_cfg_public_read" ON public.agendador_configuracoes;
CREATE POLICY "agendador_cfg_public_read" ON public.agendador_configuracoes
  FOR SELECT TO anon
  USING (ativo = true AND link_slug IS NOT NULL);

-- Inserção pública de solicitações (qualquer pessoa pode solicitar)
DROP POLICY IF EXISTS "agendador_sol_public_insert" ON public.agendador_solicitacoes;
CREATE POLICY "agendador_sol_public_insert" ON public.agendador_solicitacoes
  FOR INSERT TO anon
  WITH CHECK (true);

-- Leitura da própria solicitação (pelo paciente logado)
DROP POLICY IF EXISTS "agendador_sol_patient_read" ON public.agendador_solicitacoes;
CREATE POLICY "agendador_sol_patient_read" ON public.agendador_solicitacoes
  FOR SELECT TO authenticated
  USING (auth.uid() = user_id OR auth.uid() = owner_id);

-- ── 3. Função: retorna slots disponíveis para uma data ──────────────────────
-- Roda como SECURITY DEFINER (acessa agenda_regras e agenda_eventos sem RLS)
CREATE OR REPLACE FUNCTION public.agendador_slots_disponiveis(
  p_slug        text,
  p_data        date
)
RETURNS TABLE (hora time, disponivel boolean)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
  v_owner_id    uuid;
  v_duracao     int;
  v_reserva     int;
  v_antecedencia int;
  v_dia_semana  int;         -- 0=dom..6=sab (JS) → convertemos
  v_db_dow      int;         -- 0=dom..6=sab no Postgres (extract dow)
  v_inicio      time;
  v_fim         time;
  v_slot        time;
  v_slot_fim    time;
  v_agora       timestamptz;
BEGIN
  -- carrega config do agendador
  SELECT
    c.owner_id,
    c.duracao_sessao_min,
    c.reserva_horas,
    c.antecedencia_minima_horas
  INTO v_owner_id, v_duracao, v_reserva, v_antecedencia
  FROM public.agendador_configuracoes c
  WHERE c.link_slug = p_slug AND c.ativo = true
  LIMIT 1;

  IF v_owner_id IS NULL THEN
    RETURN;
  END IF;

  v_agora      := now();
  v_db_dow     := extract(dow from p_data::timestamp)::int;  -- 0=dom..6=sab

  -- regra semanal para o dia da semana
  SELECT hora_inicio, hora_fim
  INTO v_inicio, v_fim
  FROM public.agenda_regras_semanais
  WHERE owner_id = v_owner_id
    AND dia_semana = v_db_dow
    AND ativo = true
  LIMIT 1;

  IF v_inicio IS NULL THEN
    RETURN;  -- profissional não atende nesse dia
  END IF;

  -- itera slots de v_duracao em v_duracao dentro da jornada
  v_slot := v_inicio;
  WHILE v_slot + (v_duracao || ' minutes')::interval <= v_fim LOOP
    v_slot_fim := v_slot + (v_duracao || ' minutes')::interval;

    -- bloco temporário para verificar conflitos
    DECLARE
      v_ocupado boolean := false;
      v_slot_ts timestamptz;
    BEGIN
      -- antecedência mínima (compara em horário de Brasília)
      v_slot_ts := (p_data::text || ' ' || v_slot::text)::timestamp AT TIME ZONE 'America/Sao_Paulo';

      IF v_slot_ts < v_agora + (v_antecedencia || ' hours')::interval THEN
        v_ocupado := true;
      END IF;

      -- conflito com eventos existentes na agenda
      IF NOT v_ocupado THEN
        SELECT EXISTS (
          SELECT 1 FROM public.agenda_eventos
          WHERE owner_id = v_owner_id
            AND status::text NOT IN ('cancelado', 'faltou')
            AND inicio_em AT TIME ZONE 'America/Sao_Paulo' >= p_data::timestamp
            AND inicio_em AT TIME ZONE 'America/Sao_Paulo' <  p_data::timestamp + interval '1 day'
            AND (inicio_em AT TIME ZONE 'America/Sao_Paulo')::time < v_slot_fim
            AND (fim_em   AT TIME ZONE 'America/Sao_Paulo')::time > v_slot
        ) INTO v_ocupado;
      END IF;

      -- conflito com solicitações pendentes (reservadas)
      IF NOT v_ocupado THEN
        SELECT EXISTS (
          SELECT 1 FROM public.agendador_solicitacoes
          WHERE owner_id = v_owner_id
            AND status = 'pendente'
            AND data_solicitada = p_data
            AND hora_solicitada = v_slot
            AND (reservado_ate IS NULL OR reservado_ate > v_agora)
        ) INTO v_ocupado;
      END IF;

      hora       := v_slot;
      disponivel := NOT v_ocupado;
      RETURN NEXT;
    END;

    v_slot := v_slot + (v_duracao || ' minutes')::interval;
  END LOOP;
END;
$$;

GRANT EXECUTE ON FUNCTION public.agendador_slots_disponiveis(text, date) TO anon, authenticated;

-- ── 4. Função: retorna dias com disponibilidade no mês ─────────────────────
CREATE OR REPLACE FUNCTION public.agendador_dias_disponiveis(
  p_slug  text,
  p_ano   int,
  p_mes   int   -- 1-12
)
RETURNS TABLE (data date, tem_slots boolean)
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
  v_owner_id        uuid;
  v_antecedencia    int;
  v_data            date;
  v_data_inicio     date;
  v_data_fim        date;
  v_agora           timestamptz;
  v_db_dow          int;
  v_tem_regra       boolean;
BEGIN
  SELECT c.owner_id, c.antecedencia_minima_horas
  INTO v_owner_id, v_antecedencia
  FROM public.agendador_configuracoes c
  WHERE c.link_slug = p_slug AND c.ativo = true
  LIMIT 1;

  IF v_owner_id IS NULL THEN RETURN; END IF;

  v_agora       := now();
  v_data_inicio := make_date(p_ano, p_mes, 1);
  v_data_fim    := (v_data_inicio + interval '1 month' - interval '1 day')::date;

  v_data := v_data_inicio;
  WHILE v_data <= v_data_fim LOOP
    -- não oferece dias no passado ou dentro da antecedência mínima
    IF v_data::timestamptz + '23:59:59'::interval > v_agora + (v_antecedencia || ' hours')::interval THEN
      v_db_dow := extract(dow from v_data::timestamp)::int;

      SELECT EXISTS (
        SELECT 1 FROM public.agenda_regras_semanais
        WHERE owner_id = v_owner_id AND dia_semana = v_db_dow AND ativo = true
      ) INTO v_tem_regra;

      IF v_tem_regra THEN
        data      := v_data;
        tem_slots := true;
        RETURN NEXT;
      END IF;
    END IF;

    v_data := v_data + 1;
  END LOOP;
END;
$$;

GRANT EXECUTE ON FUNCTION public.agendador_dias_disponiveis(text, int, int) TO anon, authenticated;
