-- ==========================================================================
-- Agencia PSI — Migracao: Analytics de tempo de 1ª resposta (Grupo 7.1)
-- ==========================================================================
-- Criado por: Leonardo Nohama
-- Data: 2026-04-23 · Sao Carlos/SP — Brasil
--
-- 3 funcoes RPC pra popular card do dashboard:
--   1. first_response_stats(tenant, from, to, therapist?)
--        Agregados do periodo: avg, median, count, sla_compliance_rate
--   2. first_response_by_therapist(tenant, from, to)
--        Ranking: terapeuta → volume + tempo medio
--   3. first_response_evolution(tenant, from, to, bucket_days)
--        Serie temporal (sparkline): buckets de N dias com avg e count
--
-- Metodo:
--   Um "run" de inbound = sequencia de mensagens inbound consecutivas sem
--   nenhuma outbound entre elas. Isso evita contar multiplas mensagens
--   do paciente (o famoso "vi sua mensagem mas so respondi depois de 3
--   disparos dele"). Pra cada run, o tempo conta a partir do PRIMEIRO
--   inbound ate a PROXIMA outbound.
--
-- Thread key identifico como patient_id OU 'anon:'+from_number, consistente
-- com a view conversation_threads.
--
-- SLA compliance: usa threshold da config conversation_sla_rules.
-- Se nao tem regra ou enabled=false, retorna sla_total=0.
-- ==========================================================================

-- ---------------------------------------------------------------------------
-- fn helper interna: monta CTE dos "runs" de inbound com tempo de resposta
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public._first_response_runs(
    p_tenant_id UUID,
    p_from TIMESTAMPTZ,
    p_to TIMESTAMPTZ
)
RETURNS TABLE (
    thread_key TEXT,
    inbound_started_at TIMESTAMPTZ,
    responded_at TIMESTAMPTZ,
    response_seconds INT,
    responder_id UUID
)
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
    WITH msgs AS (
        SELECT
            m.id,
            m.tenant_id,
            m.direction,
            m.created_at,
            m.patient_id,
            m.from_number,
            m.to_number,
            -- mesma logica da view conversation_threads
            COALESCE(
                m.patient_id::text,
                'anon:' || COALESCE(
                    CASE WHEN m.direction = 'inbound' THEN m.from_number ELSE m.to_number END,
                    'unknown'
                )
            ) AS tk
        FROM public.conversation_messages m
        WHERE m.tenant_id = p_tenant_id
          AND m.direction IN ('inbound', 'outbound')
          AND m.created_at >= p_from
          AND m.created_at <= p_to
    ),
    with_prev AS (
        SELECT *,
               LAG(direction) OVER (PARTITION BY tenant_id, tk ORDER BY created_at, id) AS prev_direction
        FROM msgs
    ),
    run_starts AS (
        -- Primeira mensagem de cada "run inbound"
        SELECT tk, tenant_id, created_at AS inbound_started_at
        FROM with_prev
        WHERE direction = 'inbound'
          AND (prev_direction IS NULL OR prev_direction = 'outbound')
    )
    SELECT
        r.tk AS thread_key,
        r.inbound_started_at,
        o.created_at AS responded_at,
        EXTRACT(EPOCH FROM (o.created_at - r.inbound_started_at))::INT AS response_seconds,
        -- Quem respondeu: pega o assigned_to atual da thread (snapshot aproximado)
        a.assigned_to AS responder_id
    FROM run_starts r
    LEFT JOIN LATERAL (
        SELECT created_at
        FROM public.conversation_messages m2
        WHERE m2.tenant_id = r.tenant_id
          AND COALESCE(m2.patient_id::text, 'anon:' || COALESCE(m2.to_number, m2.from_number, 'unknown')) = r.tk
          AND m2.direction = 'outbound'
          AND m2.created_at > r.inbound_started_at
        ORDER BY m2.created_at
        LIMIT 1
    ) o ON true
    LEFT JOIN public.conversation_assignments a
        ON a.tenant_id = r.tenant_id AND a.thread_key = r.tk
    WHERE o.created_at IS NOT NULL;   -- so runs que foram respondidos
$$;


-- ---------------------------------------------------------------------------
-- first_response_stats: agregados do periodo
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.first_response_stats(
    p_tenant_id UUID,
    p_from TIMESTAMPTZ DEFAULT (now() - interval '30 days'),
    p_to TIMESTAMPTZ DEFAULT now(),
    p_therapist_id UUID DEFAULT NULL
)
RETURNS TABLE (
    runs_count INT,
    avg_seconds INT,
    median_seconds INT,
    min_seconds INT,
    max_seconds INT,
    sla_threshold_seconds INT,
    sla_compliant_count INT,
    sla_compliance_rate NUMERIC
)
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_threshold_seconds INT;
BEGIN
    -- Pega threshold do SLA (se habilitado)
    SELECT CASE WHEN enabled THEN threshold_minutes * 60 ELSE NULL END
        INTO v_threshold_seconds
        FROM public.conversation_sla_rules
        WHERE tenant_id = p_tenant_id;

    RETURN QUERY
    WITH runs AS (
        SELECT r.response_seconds, r.responder_id
        FROM public._first_response_runs(p_tenant_id, p_from, p_to) r
        WHERE (p_therapist_id IS NULL OR r.responder_id = p_therapist_id)
    )
    SELECT
        COUNT(*)::INT AS runs_count,
        COALESCE(AVG(response_seconds)::INT, 0) AS avg_seconds,
        COALESCE(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY response_seconds)::INT, 0) AS median_seconds,
        COALESCE(MIN(response_seconds), 0) AS min_seconds,
        COALESCE(MAX(response_seconds), 0) AS max_seconds,
        v_threshold_seconds AS sla_threshold_seconds,
        COUNT(*) FILTER (WHERE v_threshold_seconds IS NOT NULL AND response_seconds <= v_threshold_seconds)::INT AS sla_compliant_count,
        CASE
            WHEN v_threshold_seconds IS NULL OR COUNT(*) = 0 THEN NULL
            ELSE ROUND(100.0 * COUNT(*) FILTER (WHERE response_seconds <= v_threshold_seconds) / COUNT(*), 1)
        END AS sla_compliance_rate
    FROM runs;
END;
$$;

REVOKE ALL ON FUNCTION public.first_response_stats(UUID, TIMESTAMPTZ, TIMESTAMPTZ, UUID) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.first_response_stats(UUID, TIMESTAMPTZ, TIMESTAMPTZ, UUID) TO authenticated, service_role;


-- ---------------------------------------------------------------------------
-- first_response_by_therapist: ranking por terapeuta
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.first_response_by_therapist(
    p_tenant_id UUID,
    p_from TIMESTAMPTZ DEFAULT (now() - interval '30 days'),
    p_to TIMESTAMPTZ DEFAULT now()
)
RETURNS TABLE (
    therapist_id UUID,
    runs_count INT,
    avg_seconds INT,
    median_seconds INT
)
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
    SELECT
        r.responder_id AS therapist_id,
        COUNT(*)::INT AS runs_count,
        AVG(r.response_seconds)::INT AS avg_seconds,
        PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY r.response_seconds)::INT AS median_seconds
    FROM public._first_response_runs(p_tenant_id, p_from, p_to) r
    WHERE r.responder_id IS NOT NULL
    GROUP BY r.responder_id
    ORDER BY avg_seconds ASC;
$$;

REVOKE ALL ON FUNCTION public.first_response_by_therapist(UUID, TIMESTAMPTZ, TIMESTAMPTZ) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.first_response_by_therapist(UUID, TIMESTAMPTZ, TIMESTAMPTZ) TO authenticated, service_role;


-- ---------------------------------------------------------------------------
-- first_response_evolution: serie temporal (buckets de N dias)
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.first_response_evolution(
    p_tenant_id UUID,
    p_from TIMESTAMPTZ DEFAULT (now() - interval '30 days'),
    p_to TIMESTAMPTZ DEFAULT now(),
    p_bucket_days INT DEFAULT 7,
    p_therapist_id UUID DEFAULT NULL
)
RETURNS TABLE (
    bucket_start TIMESTAMPTZ,
    runs_count INT,
    avg_seconds INT
)
LANGUAGE sql
STABLE
SECURITY DEFINER
SET search_path = public
AS $$
    WITH runs AS (
        SELECT r.inbound_started_at, r.response_seconds
        FROM public._first_response_runs(p_tenant_id, p_from, p_to) r
        WHERE (p_therapist_id IS NULL OR r.responder_id = p_therapist_id)
    ),
    bucketed AS (
        SELECT
            -- Janela alinhada a p_from: bucket_index * N dias + p_from
            p_from + (
                FLOOR(EXTRACT(EPOCH FROM (inbound_started_at - p_from)) / (p_bucket_days * 86400))::INT
                * p_bucket_days * interval '1 day'
            ) AS bucket_start,
            response_seconds
        FROM runs
    )
    SELECT
        bucket_start,
        COUNT(*)::INT AS runs_count,
        AVG(response_seconds)::INT AS avg_seconds
    FROM bucketed
    GROUP BY bucket_start
    ORDER BY bucket_start;
$$;

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

COMMENT ON FUNCTION public.first_response_stats(UUID, TIMESTAMPTZ, TIMESTAMPTZ, UUID) IS
    'Metricas agregadas de tempo de 1a resposta no periodo. Opcionalmente filtra por responder_id.';

COMMENT ON FUNCTION public.first_response_by_therapist(UUID, TIMESTAMPTZ, TIMESTAMPTZ) IS
    'Ranking de tempo medio de 1a resposta por terapeuta atribuido.';

COMMENT ON FUNCTION public.first_response_evolution(UUID, TIMESTAMPTZ, TIMESTAMPTZ, INT, UUID) IS
    'Serie temporal em buckets de N dias pra sparkline.';
