-- ==========================================================================
-- Agencia PSI — Migracao: SLA de conversas WhatsApp (Grupo 3.4)
-- ==========================================================================
-- Criado por: Leonardo Nohama
-- Data: 2026-04-23 · Sao Carlos/SP — Brasil
--
-- Modelo:
--   - conversation_sla_rules      → config (1 linha por tenant)
--   - conversation_sla_breaches   → incidents (1 aberto por thread — UNIQUE)
--   - Trigger AFTER INSERT outbound → resolve breach automatico
--   - RPCs pra edge cron: sla_open_breach, sla_mark_notified
--
-- Regras (combinado com o user):
--   1. Threshold GLOBAL por tenant (1 valor unico)
--   2. Respeita horario comercial (pausa cronometro fora) — configuravel
--   3. Escopo configuravel: 'assigned_only' (default) ou 'all'
--   4. Notifica terapeuta atribuido + CC admin opcional
--
-- Anti-spam: notification_count + notified_at na tabela breach,
-- idempotencia via UNIQUE parcial (so 1 breach aberto por tenant+thread).
-- ==========================================================================

-- ---------------------------------------------------------------------------
-- Tabela: conversation_sla_rules (config)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.conversation_sla_rules (
    tenant_id UUID PRIMARY KEY REFERENCES public.tenants(id) ON DELETE CASCADE,

    enabled BOOLEAN NOT NULL DEFAULT false,
    threshold_minutes INT NOT NULL DEFAULT 60
        CHECK (threshold_minutes >= 1 AND threshold_minutes <= 1440),  -- 1 min a 24h

    respect_business_hours BOOLEAN NOT NULL DEFAULT true,
    business_hours_start TIME NOT NULL DEFAULT '08:00',
    business_hours_end TIME NOT NULL DEFAULT '18:00',
    -- ISO: 1=seg ... 7=dom. Default: seg a sex
    business_days SMALLINT[] NOT NULL DEFAULT ARRAY[1,2,3,4,5]::SMALLINT[]
        CHECK (array_length(business_days, 1) BETWEEN 1 AND 7),

    alert_scope TEXT NOT NULL DEFAULT 'assigned_only'
        CHECK (alert_scope IN ('assigned_only', 'all')),
    notify_admin_on_breach BOOLEAN NOT NULL DEFAULT false,

    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

DROP TRIGGER IF EXISTS trg_sla_rules_updated_at ON public.conversation_sla_rules;
CREATE TRIGGER trg_sla_rules_updated_at
    BEFORE UPDATE ON public.conversation_sla_rules
    FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();

COMMENT ON TABLE public.conversation_sla_rules IS
    'Configuracao de SLA por tenant. 1 linha por tenant. Threshold global.';


-- ---------------------------------------------------------------------------
-- Tabela: conversation_sla_breaches (incidents)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.conversation_sla_breaches (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
    thread_key TEXT NOT NULL,

    -- Snapshots pra auditoria e pra notificacao
    assigned_to UUID,
    last_inbound_at TIMESTAMPTZ NOT NULL,
    threshold_minutes_at_breach INT NOT NULL,

    breached_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    resolved_at TIMESTAMPTZ,
    resolved_by_message_id BIGINT,

    -- Controle de notificacao (anti-spam)
    notified_at TIMESTAMPTZ,
    notification_count INT NOT NULL DEFAULT 0,

    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

DROP TRIGGER IF EXISTS trg_sla_breaches_updated_at ON public.conversation_sla_breaches;
CREATE TRIGGER trg_sla_breaches_updated_at
    BEFORE UPDATE ON public.conversation_sla_breaches
    FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();

-- Apenas 1 breach aberto por (tenant, thread) — idempotencia do open
CREATE UNIQUE INDEX IF NOT EXISTS uq_sla_breaches_open_per_thread
    ON public.conversation_sla_breaches (tenant_id, thread_key)
    WHERE resolved_at IS NULL;

CREATE INDEX IF NOT EXISTS idx_sla_breaches_tenant_breached
    ON public.conversation_sla_breaches (tenant_id, breached_at DESC);

CREATE INDEX IF NOT EXISTS idx_sla_breaches_open
    ON public.conversation_sla_breaches (resolved_at)
    WHERE resolved_at IS NULL;

COMMENT ON TABLE public.conversation_sla_breaches IS
    'Estouros de SLA detectados pelo cron. Max 1 aberto por thread (UNIQUE parcial). Resolve automatico via trigger outbound.';


-- ---------------------------------------------------------------------------
-- Trigger: resolve breach automatico quando nova outbound responde a thread
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.fn_sla_resolve_on_outbound()
RETURNS TRIGGER
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_thread_key TEXT;
BEGIN
    -- So processa outbound
    IF NEW.direction <> 'outbound' THEN RETURN NEW; END IF;

    -- Calcula thread_key no mesmo padrao da view conversation_threads
    v_thread_key := COALESCE(
        NEW.patient_id::text,
        'anon:' || COALESCE(NEW.to_number, 'unknown')
    );

    UPDATE public.conversation_sla_breaches
        SET resolved_at = now(),
            resolved_by_message_id = NEW.id
        WHERE tenant_id = NEW.tenant_id
          AND thread_key = v_thread_key
          AND resolved_at IS NULL;

    RETURN NEW;
END;
$$;

DROP TRIGGER IF EXISTS trg_sla_resolve_on_outbound ON public.conversation_messages;
CREATE TRIGGER trg_sla_resolve_on_outbound
    AFTER INSERT ON public.conversation_messages
    FOR EACH ROW
    EXECUTE FUNCTION public.fn_sla_resolve_on_outbound();


-- ---------------------------------------------------------------------------
-- RPC: sla_open_breach (idempotente pra cron)
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sla_open_breach(
    p_tenant_id UUID,
    p_thread_key TEXT,
    p_assigned_to UUID,
    p_last_inbound_at TIMESTAMPTZ,
    p_threshold_minutes INT
)
RETURNS UUID
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_existing_id UUID;
    v_new_id UUID;
BEGIN
    IF p_tenant_id IS NULL OR p_thread_key IS NULL THEN
        RAISE EXCEPTION 'tenant_and_thread_required';
    END IF;

    -- Ja tem aberto? Retorna o mesmo id (idempotente)
    SELECT id INTO v_existing_id
        FROM public.conversation_sla_breaches
        WHERE tenant_id = p_tenant_id
          AND thread_key = p_thread_key
          AND resolved_at IS NULL;

    IF FOUND THEN
        UPDATE public.conversation_sla_breaches
            SET assigned_to = COALESCE(p_assigned_to, assigned_to),
                last_inbound_at = COALESCE(p_last_inbound_at, last_inbound_at)
            WHERE id = v_existing_id;
        RETURN v_existing_id;
    END IF;

    INSERT INTO public.conversation_sla_breaches
        (tenant_id, thread_key, assigned_to, last_inbound_at, threshold_minutes_at_breach)
    VALUES
        (p_tenant_id, p_thread_key, p_assigned_to, p_last_inbound_at, p_threshold_minutes)
    RETURNING id INTO v_new_id;

    RETURN v_new_id;
END;
$$;

REVOKE ALL ON FUNCTION public.sla_open_breach(UUID, TEXT, UUID, TIMESTAMPTZ, INT) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.sla_open_breach(UUID, TEXT, UUID, TIMESTAMPTZ, INT) TO service_role;


-- ---------------------------------------------------------------------------
-- RPC: sla_mark_notified (anti-spam)
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.sla_mark_notified(p_breach_id UUID)
RETURNS VOID
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
BEGIN
    UPDATE public.conversation_sla_breaches
        SET notified_at = now(),
            notification_count = notification_count + 1
        WHERE id = p_breach_id;
END;
$$;

REVOKE ALL ON FUNCTION public.sla_mark_notified(UUID) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.sla_mark_notified(UUID) TO service_role;


-- ---------------------------------------------------------------------------
-- RLS: conversation_sla_rules
-- ---------------------------------------------------------------------------
ALTER TABLE public.conversation_sla_rules ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "sla_rules: select membros/admin" ON public.conversation_sla_rules;
CREATE POLICY "sla_rules: select membros/admin"
    ON public.conversation_sla_rules
    FOR SELECT
    TO authenticated
    USING (
        public.is_saas_admin()
        OR EXISTS (
            SELECT 1 FROM public.tenant_members tm
            WHERE tm.tenant_id = conversation_sla_rules.tenant_id
              AND tm.user_id = auth.uid()
              AND tm.status = 'active'
        )
    );

DROP POLICY IF EXISTS "sla_rules: write admins" ON public.conversation_sla_rules;
CREATE POLICY "sla_rules: write admins"
    ON public.conversation_sla_rules
    FOR ALL
    TO authenticated
    USING (
        public.is_saas_admin()
        OR EXISTS (
            SELECT 1 FROM public.tenant_members tm
            WHERE tm.tenant_id = conversation_sla_rules.tenant_id
              AND tm.user_id = auth.uid()
              AND tm.role IN ('clinic_admin', 'tenant_admin')
              AND tm.status = 'active'
        )
    )
    WITH CHECK (
        public.is_saas_admin()
        OR EXISTS (
            SELECT 1 FROM public.tenant_members tm
            WHERE tm.tenant_id = conversation_sla_rules.tenant_id
              AND tm.user_id = auth.uid()
              AND tm.role IN ('clinic_admin', 'tenant_admin')
              AND tm.status = 'active'
        )
    );


-- ---------------------------------------------------------------------------
-- RLS: conversation_sla_breaches
-- ---------------------------------------------------------------------------
ALTER TABLE public.conversation_sla_breaches ENABLE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "sla_breaches: select membros/admin" ON public.conversation_sla_breaches;
CREATE POLICY "sla_breaches: select membros/admin"
    ON public.conversation_sla_breaches
    FOR SELECT
    TO authenticated
    USING (
        public.is_saas_admin()
        OR EXISTS (
            SELECT 1 FROM public.tenant_members tm
            WHERE tm.tenant_id = conversation_sla_breaches.tenant_id
              AND tm.user_id = auth.uid()
              AND tm.status = 'active'
        )
    );

DROP POLICY IF EXISTS "sla_breaches: write service_role" ON public.conversation_sla_breaches;
CREATE POLICY "sla_breaches: write service_role"
    ON public.conversation_sla_breaches
    FOR ALL
    TO service_role
    USING (true)
    WITH CHECK (true);


-- ---------------------------------------------------------------------------
-- Cron job (TEMPLATE — descomentar pra ativar)
-- ---------------------------------------------------------------------------
-- Checa SLA de todos os tenants com enabled=true a cada 5 minutos.
--
-- SELECT cron.schedule(
--     'conversation-sla-check-every-5min',
--     '*/5 * * * *',
--     $$
--     SELECT net.http_post(
--         url := current_setting('app.settings.supabase_url') || '/functions/v1/conversation-sla-check',
--         headers := jsonb_build_object(
--             'Authorization', 'Bearer ' || current_setting('app.settings.service_role_key'),
--             'Content-Type', 'application/json'
--         ),
--         body := '{}'::jsonb
--     );
--     $$
-- );
--
-- Desativar: SELECT cron.unschedule('conversation-sla-check-every-5min');
