-- =============================================================================
-- Migration: 20260420000005_conversation_messages
-- Sessao 11 - Fase 5a (CRM de WhatsApp / inbox).
--
-- Cria infraestrutura para receber mensagens inbound de WhatsApp (Twilio e
-- Evolution API) e exibir num Kanban de conversas.
--
--   - conversation_messages    — todas as mensagens (in/out) com link opcional
--                                 ao paciente via telefone matching
--   - function match_patient_by_phone(tenant_id, phone) — encontra paciente
--   - view conversation_threads — agregado por paciente/numero pra UI Kanban
--
-- RLS: tenant members leem; service_role (edge function) escreve via SECURITY
-- DEFINER match_and_insert. App nao escreve direto.
-- =============================================================================

-- ---------------------------------------------------------------------------
-- Tabela de mensagens
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.conversation_messages (
    id BIGSERIAL PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES public.tenants(id) ON DELETE CASCADE,
    patient_id UUID REFERENCES public.patients(id) ON DELETE SET NULL,

    channel TEXT NOT NULL CHECK (channel IN ('whatsapp', 'sms', 'email')),
    direction TEXT NOT NULL CHECK (direction IN ('inbound', 'outbound')),

    from_number TEXT,
    to_number TEXT,
    body TEXT,
    media_url TEXT,
    media_mime TEXT,

    provider TEXT NOT NULL CHECK (provider IN ('twilio', 'evolution', 'manual')),
    provider_message_id TEXT,
    provider_raw JSONB,

    -- estado Kanban
    kanban_status TEXT NOT NULL DEFAULT 'awaiting_us'
        CHECK (kanban_status IN ('urgent', 'awaiting_us', 'awaiting_patient', 'resolved')),
    priority INT NOT NULL DEFAULT 0,

    read_at TIMESTAMPTZ,
    responded_at TIMESTAMPTZ,
    resolved_at TIMESTAMPTZ,

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

CREATE INDEX IF NOT EXISTS idx_conv_msg_tenant_created
    ON public.conversation_messages (tenant_id, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_conv_msg_patient
    ON public.conversation_messages (patient_id, created_at DESC) WHERE patient_id IS NOT NULL;

CREATE INDEX IF NOT EXISTS idx_conv_msg_from_number
    ON public.conversation_messages (tenant_id, from_number);

CREATE INDEX IF NOT EXISTS idx_conv_msg_kanban
    ON public.conversation_messages (tenant_id, kanban_status, priority DESC, created_at DESC);

CREATE INDEX IF NOT EXISTS idx_conv_msg_provider_msg_id
    ON public.conversation_messages (provider_message_id) WHERE provider_message_id IS NOT NULL;

-- Trigger de updated_at (usa funcao existente set_updated_at)
DROP TRIGGER IF EXISTS trg_conv_messages_updated_at ON public.conversation_messages;
CREATE TRIGGER trg_conv_messages_updated_at
    BEFORE UPDATE ON public.conversation_messages
    FOR EACH ROW EXECUTE FUNCTION public.set_updated_at();

COMMENT ON TABLE public.conversation_messages IS
    'Mensagens in/out de WhatsApp/SMS/email. Timeline de conversas do tenant com pacientes.';

-- ---------------------------------------------------------------------------
-- Funcao: normaliza telefone BR (remove tudo que nao seja digito, tira DDI 55)
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.normalize_phone_br(p_phone TEXT)
RETURNS TEXT
LANGUAGE plpgsql
IMMUTABLE
AS $$
DECLARE
    v_digits TEXT;
BEGIN
    IF p_phone IS NULL THEN RETURN NULL; END IF;

    -- remove tudo que nao seja digito
    v_digits := regexp_replace(p_phone, '\D', '', 'g');

    -- remove DDI 55 se tem 12+ digitos (+55 + DDD + numero)
    IF length(v_digits) >= 12 AND left(v_digits, 2) = '55' THEN
        v_digits := substr(v_digits, 3);
    END IF;

    -- pega os ultimos 11 digitos (DDD + 9digito + 8numero) ou 10 (DDD + 8numero)
    IF length(v_digits) > 11 THEN
        v_digits := right(v_digits, 11);
    END IF;

    RETURN v_digits;
END;
$$;

COMMENT ON FUNCTION public.normalize_phone_br(TEXT) IS
    'Normaliza telefone BR para os ultimos 11 digitos (DDD+numero), removendo DDI +55 e formatacao.';

-- ---------------------------------------------------------------------------
-- Funcao: match paciente por telefone dentro de um tenant
-- ---------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.match_patient_by_phone(p_tenant_id UUID, p_phone TEXT)
RETURNS UUID
LANGUAGE plpgsql
STABLE
SECURITY DEFINER
SET search_path = public, pg_temp
AS $$
DECLARE
    v_normalized TEXT;
    v_patient_id UUID;
BEGIN
    v_normalized := public.normalize_phone_br(p_phone);
    IF v_normalized IS NULL OR length(v_normalized) < 10 THEN
        RETURN NULL;
    END IF;

    -- prioridade: telefone principal, depois alternativo, depois responsavel
    SELECT id INTO v_patient_id FROM public.patients
    WHERE tenant_id = p_tenant_id
      AND public.normalize_phone_br(telefone) = v_normalized
    LIMIT 1;
    IF v_patient_id IS NOT NULL THEN RETURN v_patient_id; END IF;

    SELECT id INTO v_patient_id FROM public.patients
    WHERE tenant_id = p_tenant_id
      AND public.normalize_phone_br(telefone_alternativo) = v_normalized
    LIMIT 1;
    IF v_patient_id IS NOT NULL THEN RETURN v_patient_id; END IF;

    SELECT id INTO v_patient_id FROM public.patients
    WHERE tenant_id = p_tenant_id
      AND public.normalize_phone_br(telefone_responsavel) = v_normalized
    LIMIT 1;

    RETURN v_patient_id;
END;
$$;

COMMENT ON FUNCTION public.match_patient_by_phone(UUID, TEXT) IS
    'Encontra patient_id do tenant cujo telefone (principal/alternativo/responsavel) bate com o numero.';

-- ---------------------------------------------------------------------------
-- View: threads agrupadas por paciente ou numero anonimo
-- ---------------------------------------------------------------------------
DROP VIEW IF EXISTS public.conversation_threads CASCADE;

CREATE VIEW public.conversation_threads
WITH (security_invoker = true)
AS
WITH base AS (
    SELECT
        cm.id,
        cm.tenant_id,
        cm.patient_id,
        cm.channel,
        cm.body,
        cm.direction,
        cm.kanban_status,
        cm.read_at,
        cm.created_at,
        CASE WHEN cm.direction = 'inbound' THEN cm.from_number ELSE cm.to_number END AS contact_number,
        COALESCE(cm.patient_id::text, 'anon:' || COALESCE(
            CASE WHEN cm.direction = 'inbound' THEN cm.from_number ELSE cm.to_number END,
            'unknown'
        )) AS thread_key
    FROM public.conversation_messages cm
),
latest AS (
    SELECT DISTINCT ON (tenant_id, thread_key)
        tenant_id, thread_key, patient_id, channel, contact_number,
        body AS last_message_body,
        direction AS last_message_direction,
        kanban_status,
        created_at AS last_message_at
    FROM base
    ORDER BY tenant_id, thread_key, created_at DESC
),
counts AS (
    SELECT
        tenant_id, thread_key,
        COUNT(*) AS message_count,
        COUNT(*) FILTER (WHERE direction = 'inbound' AND read_at IS NULL) AS unread_count
    FROM base
    GROUP BY tenant_id, thread_key
)
SELECT
    l.tenant_id,
    l.thread_key,
    l.patient_id,
    p.nome_completo AS patient_name,
    l.contact_number,
    l.channel,
    c.message_count,
    c.unread_count,
    l.last_message_at,
    l.last_message_body,
    l.last_message_direction,
    l.kanban_status
FROM latest l
JOIN counts c ON c.tenant_id = l.tenant_id AND c.thread_key = l.thread_key
LEFT JOIN public.patients p ON p.id = l.patient_id;

COMMENT ON VIEW public.conversation_threads IS
    'Agregado de conversas por paciente ou por numero anonimo. Base do Kanban.';

GRANT SELECT ON public.conversation_threads TO authenticated;

-- ---------------------------------------------------------------------------
-- RLS: tenant member le; ninguem escreve direto (so via edge function service_role)
-- ---------------------------------------------------------------------------
ALTER TABLE public.conversation_messages ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.conversation_messages FORCE ROW LEVEL SECURITY;

DROP POLICY IF EXISTS "conv_msg: select tenant" ON public.conversation_messages;
DROP POLICY IF EXISTS "conv_msg: update kanban" ON public.conversation_messages;
DROP POLICY IF EXISTS "conv_msg: no direct insert" ON public.conversation_messages;
DROP POLICY IF EXISTS "conv_msg: no direct delete" ON public.conversation_messages;

CREATE POLICY "conv_msg: select tenant" ON public.conversation_messages
    FOR SELECT TO authenticated
    USING (
        public.is_saas_admin()
        OR tenant_id IN (
            SELECT tm.tenant_id FROM public.tenant_members tm
            WHERE tm.user_id = auth.uid() AND tm.status = 'active'
        )
    );

-- tenant member pode atualizar apenas kanban_status/read_at/responded_at/resolved_at
-- (nao pode mexer em body, provider, etc)
CREATE POLICY "conv_msg: update kanban" ON public.conversation_messages
    FOR UPDATE TO authenticated
    USING (
        tenant_id IN (
            SELECT tm.tenant_id FROM public.tenant_members tm
            WHERE tm.user_id = auth.uid() AND tm.status = 'active'
        )
    )
    WITH CHECK (
        tenant_id IN (
            SELECT tm.tenant_id FROM public.tenant_members tm
            WHERE tm.user_id = auth.uid() AND tm.status = 'active'
        )
    );

CREATE POLICY "conv_msg: no direct insert" ON public.conversation_messages
    FOR INSERT TO authenticated
    WITH CHECK (false);

CREATE POLICY "conv_msg: no direct delete" ON public.conversation_messages
    FOR DELETE TO authenticated
    USING (false);
