-- ==========================================================================
-- Agencia PSI — Migracao: tabelas de Documentos & Arquivos
-- ==========================================================================
-- Criado por: Leonardo Nohama
-- Data: 2026-03-29 · Sao Carlos/SP — Brasil
--
-- Proposito:
--   Modulo completo de documentos do paciente.
--   Tabelas: documents, document_access_logs, document_signatures,
--            document_share_links.
--
-- Relacionamentos:
--   documents.patient_id       → patients(id)
--   documents.owner_id         → auth.users(id)
--   documents.tenant_id        → tenants(id)
--   documents.agenda_evento_id → agenda_eventos(id) (opcional)
--   document_access_logs.documento_id  → documents(id)
--   document_signatures.documento_id   → documents(id)
--   document_share_links.documento_id  → documents(id)
--
-- RLS: owner_id = auth.uid() para documents, signatures e share_links.
--      access_logs: somente INSERT (imutavel) + SELECT por tenant.
-- ==========================================================================


-- --------------------------------------------------------------------------
-- 1. Tabela principal: documents
-- --------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS public.documents (
    id                      uuid DEFAULT gen_random_uuid() NOT NULL,

    -- Contexto de acesso
    owner_id                uuid NOT NULL,
    tenant_id               uuid NOT NULL,

    -- Vinculo com paciente
    patient_id              uuid NOT NULL REFERENCES public.patients(id) ON DELETE CASCADE,

    -- Arquivo no Storage
    bucket_path             text NOT NULL,
    storage_bucket          text NOT NULL DEFAULT 'documents',
    nome_original           text NOT NULL,
    mime_type               text,
    tamanho_bytes           bigint,

    -- Classificacao
    tipo_documento          text NOT NULL DEFAULT 'outro',
        -- laudo | receita | exame | termo_assinado | relatorio_externo
        -- identidade | convenio | declaracao | atestado | recibo | outro
    categoria               text,
    descricao               text,
    tags                    text[] DEFAULT '{}',

    -- Vinculo opcional com sessao/nota
    agenda_evento_id        uuid REFERENCES public.agenda_eventos(id) ON DELETE SET NULL,
    session_note_id         uuid,

    -- Visibilidade & controle de acesso
    visibilidade            text NOT NULL DEFAULT 'privado',
        -- privado | compartilhado_supervisor | compartilhado_portal
    compartilhado_portal    boolean DEFAULT false NOT NULL,
    compartilhado_supervisor boolean DEFAULT false NOT NULL,
    compartilhado_em        timestamptz,
    expira_compartilhamento timestamptz,

    -- Upload pelo paciente (portal)
    enviado_pelo_paciente   boolean DEFAULT false NOT NULL,
    status_revisao          text DEFAULT 'aprovado',
        -- pendente | aprovado | rejeitado
    revisado_por            uuid,
    revisado_em             timestamptz,

    -- Quem fez upload
    uploaded_by             uuid NOT NULL,
    uploaded_at             timestamptz DEFAULT now() NOT NULL,

    -- Soft delete com retencao (LGPD / CFP)
    deleted_at              timestamptz,
    deleted_by              uuid,
    retencao_ate            timestamptz,

    -- Controle
    created_at              timestamptz DEFAULT now(),
    updated_at              timestamptz DEFAULT now(),

    CONSTRAINT documents_pkey PRIMARY KEY (id),

    -- Validacoes
    CONSTRAINT documents_tipo_check CHECK (
        tipo_documento = ANY (ARRAY[
            'laudo', 'receita', 'exame', 'termo_assinado', 'relatorio_externo',
            'identidade', 'convenio', 'declaracao', 'atestado', 'recibo', 'outro'
        ])
    ),
    CONSTRAINT documents_visibilidade_check CHECK (
        visibilidade = ANY (ARRAY['privado', 'compartilhado_supervisor', 'compartilhado_portal'])
    ),
    CONSTRAINT documents_status_revisao_check CHECK (
        status_revisao = ANY (ARRAY['pendente', 'aprovado', 'rejeitado'])
    )
);


-- --------------------------------------------------------------------------
-- 2. Indices — documents
-- --------------------------------------------------------------------------
CREATE INDEX IF NOT EXISTS docs_patient_idx
    ON public.documents USING btree (patient_id);

CREATE INDEX IF NOT EXISTS docs_owner_idx
    ON public.documents USING btree (owner_id);

CREATE INDEX IF NOT EXISTS docs_tenant_idx
    ON public.documents USING btree (tenant_id);

CREATE INDEX IF NOT EXISTS docs_tipo_idx
    ON public.documents USING btree (patient_id, tipo_documento);

CREATE INDEX IF NOT EXISTS docs_tags_idx
    ON public.documents USING gin (tags);

CREATE INDEX IF NOT EXISTS docs_uploaded_at_idx
    ON public.documents USING btree (patient_id, uploaded_at DESC);

-- Excluir soft-deleted da listagem padrao
CREATE INDEX IF NOT EXISTS docs_active_idx
    ON public.documents USING btree (patient_id, uploaded_at DESC)
    WHERE deleted_at IS NULL;

-- Busca textual no nome do arquivo
CREATE INDEX IF NOT EXISTS docs_nome_trgm_idx
    ON public.documents USING gin (nome_original gin_trgm_ops);


-- --------------------------------------------------------------------------
-- 3. Trigger updated_at — documents
-- --------------------------------------------------------------------------
CREATE TRIGGER trg_documents_updated_at
    BEFORE UPDATE ON public.documents
    FOR EACH ROW
    EXECUTE FUNCTION public.set_updated_at();


-- --------------------------------------------------------------------------
-- 4. Trigger: registrar na patient_timeline ao adicionar documento
-- --------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION public.fn_documents_timeline_insert()
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER AS $$
BEGIN
    INSERT INTO public.patient_timeline (
        patient_id, tenant_id, evento_tipo,
        titulo, descricao, icone_cor,
        link_ref_tipo, link_ref_id,
        gerado_por, ocorrido_em
    ) VALUES (
        NEW.patient_id,
        NEW.tenant_id,
        'documento_adicionado',
        'Documento adicionado: ' || COALESCE(NEW.nome_original, 'arquivo'),
        'Tipo: ' || COALESCE(NEW.tipo_documento, 'outro'),
        'blue',
        'documento',
        NEW.id,
        NEW.uploaded_by,
        NEW.uploaded_at
    );
    RETURN NEW;
END;
$$;

CREATE TRIGGER trg_documents_timeline_insert
    AFTER INSERT ON public.documents
    FOR EACH ROW
    EXECUTE FUNCTION public.fn_documents_timeline_insert();


-- --------------------------------------------------------------------------
-- 5. RLS — documents
-- --------------------------------------------------------------------------
ALTER TABLE public.documents ENABLE ROW LEVEL SECURITY;

CREATE POLICY "documents: owner full access"
    ON public.documents
    USING  (owner_id = auth.uid())
    WITH CHECK (owner_id = auth.uid());


-- --------------------------------------------------------------------------
-- 6. Comentarios — documents
-- --------------------------------------------------------------------------
COMMENT ON TABLE  public.documents                              IS 'Documentos e arquivos vinculados a pacientes. Armazenados no Supabase Storage.';
COMMENT ON COLUMN public.documents.owner_id                     IS 'Terapeuta dono do documento (auth.uid()).';
COMMENT ON COLUMN public.documents.tenant_id                    IS 'Tenant do terapeuta.';
COMMENT ON COLUMN public.documents.patient_id                   IS 'Paciente ao qual o documento pertence.';
COMMENT ON COLUMN public.documents.bucket_path                  IS 'Caminho do arquivo no Supabase Storage bucket.';
COMMENT ON COLUMN public.documents.storage_bucket               IS 'Nome do bucket no Storage. Default: documents.';
COMMENT ON COLUMN public.documents.nome_original                IS 'Nome original do arquivo enviado.';
COMMENT ON COLUMN public.documents.mime_type                    IS 'MIME type do arquivo. Ex: application/pdf, image/jpeg.';
COMMENT ON COLUMN public.documents.tamanho_bytes                IS 'Tamanho do arquivo em bytes.';
COMMENT ON COLUMN public.documents.tipo_documento               IS 'Tipo: laudo|receita|exame|termo_assinado|relatorio_externo|identidade|convenio|declaracao|atestado|recibo|outro.';
COMMENT ON COLUMN public.documents.categoria                    IS 'Categoria livre para organizacao adicional.';
COMMENT ON COLUMN public.documents.tags                         IS 'Tags livres para busca e filtro. Array de text.';
COMMENT ON COLUMN public.documents.visibilidade                 IS 'privado|compartilhado_supervisor|compartilhado_portal.';
COMMENT ON COLUMN public.documents.compartilhado_portal         IS 'true = visivel para o paciente no portal.';
COMMENT ON COLUMN public.documents.compartilhado_supervisor     IS 'true = visivel para o supervisor.';
COMMENT ON COLUMN public.documents.enviado_pelo_paciente        IS 'true = upload feito pelo paciente via portal.';
COMMENT ON COLUMN public.documents.status_revisao               IS 'pendente|aprovado|rejeitado — para uploads do paciente.';
COMMENT ON COLUMN public.documents.deleted_at                   IS 'Soft delete: data da exclusao. NULL = ativo.';
COMMENT ON COLUMN public.documents.retencao_ate                 IS 'LGPD/CFP: arquivo retido ate esta data mesmo apos soft delete.';


-- ==========================================================================
-- 7. Tabela: document_access_logs (imutavel — auditoria)
-- ==========================================================================
CREATE TABLE IF NOT EXISTS public.document_access_logs (
    id              uuid DEFAULT gen_random_uuid() NOT NULL,

    documento_id    uuid NOT NULL REFERENCES public.documents(id) ON DELETE CASCADE,
    tenant_id       uuid NOT NULL,

    -- Acao realizada
    acao            text NOT NULL,
        -- visualizou | baixou | imprimiu | compartilhou | assinou
    user_id         uuid,
    ip              inet,
    user_agent      text,

    acessado_em     timestamptz DEFAULT now() NOT NULL,

    CONSTRAINT document_access_logs_pkey PRIMARY KEY (id),

    CONSTRAINT dal_acao_check CHECK (
        acao = ANY (ARRAY['visualizou', 'baixou', 'imprimiu', 'compartilhou', 'assinou'])
    )
);

-- Indices
CREATE INDEX IF NOT EXISTS dal_documento_idx
    ON public.document_access_logs USING btree (documento_id, acessado_em DESC);

CREATE INDEX IF NOT EXISTS dal_tenant_idx
    ON public.document_access_logs USING btree (tenant_id, acessado_em DESC);

CREATE INDEX IF NOT EXISTS dal_user_idx
    ON public.document_access_logs USING btree (user_id, acessado_em DESC);

-- RLS — somente INSERT (imutavel) + SELECT
ALTER TABLE public.document_access_logs ENABLE ROW LEVEL SECURITY;

CREATE POLICY "dal: tenant members can insert"
    ON public.document_access_logs
    FOR INSERT
    WITH CHECK (true);

CREATE POLICY "dal: tenant members can select"
    ON public.document_access_logs
    FOR SELECT
    USING (tenant_id IN (
        SELECT tm.tenant_id FROM public.tenant_members tm
        WHERE tm.user_id = auth.uid() AND tm.status = 'active'
    ));

-- Comentarios
COMMENT ON TABLE  public.document_access_logs           IS 'Log imutavel de acessos a documentos. Conformidade CFP e LGPD. Sem UPDATE/DELETE.';
COMMENT ON COLUMN public.document_access_logs.acao      IS 'visualizou|baixou|imprimiu|compartilhou|assinou.';


-- ==========================================================================
-- 8. Tabela: document_signatures (assinatura eletronica)
-- ==========================================================================
CREATE TABLE IF NOT EXISTS public.document_signatures (
    id                  uuid DEFAULT gen_random_uuid() NOT NULL,

    documento_id        uuid NOT NULL REFERENCES public.documents(id) ON DELETE CASCADE,
    tenant_id           uuid NOT NULL,

    -- Signatario
    signatario_tipo     text NOT NULL,
        -- paciente | responsavel_legal | terapeuta
    signatario_id       uuid,
    signatario_nome     text,
    signatario_email    text,

    -- Ordem e status
    ordem               smallint DEFAULT 1 NOT NULL,
    status              text NOT NULL DEFAULT 'pendente',
        -- pendente | enviado | assinado | recusado | expirado

    -- Dados da assinatura (preenchidos ao assinar)
    ip                  inet,
    user_agent          text,
    assinado_em         timestamptz,
    hash_documento      text,

    -- Controle
    criado_em           timestamptz DEFAULT now(),
    atualizado_em       timestamptz DEFAULT now(),

    CONSTRAINT document_signatures_pkey PRIMARY KEY (id),

    CONSTRAINT ds_signatario_tipo_check CHECK (
        signatario_tipo = ANY (ARRAY['paciente', 'responsavel_legal', 'terapeuta'])
    ),
    CONSTRAINT ds_status_check CHECK (
        status = ANY (ARRAY['pendente', 'enviado', 'assinado', 'recusado', 'expirado'])
    )
);

-- Indices
CREATE INDEX IF NOT EXISTS ds_documento_idx
    ON public.document_signatures USING btree (documento_id, ordem);

CREATE INDEX IF NOT EXISTS ds_tenant_idx
    ON public.document_signatures USING btree (tenant_id);

CREATE INDEX IF NOT EXISTS ds_status_idx
    ON public.document_signatures USING btree (documento_id, status);

-- Trigger updated_at
CREATE TRIGGER trg_ds_updated_at
    BEFORE UPDATE ON public.document_signatures
    FOR EACH ROW
    EXECUTE FUNCTION public.set_updated_at();

-- Trigger: ao assinar, registrar na patient_timeline
CREATE OR REPLACE FUNCTION public.fn_document_signature_timeline()
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER AS $$
DECLARE
    v_patient_id uuid;
    v_tenant_id  uuid;
    v_doc_nome   text;
BEGIN
    IF NEW.status = 'assinado' AND (OLD.status IS NULL OR OLD.status <> 'assinado') THEN
        SELECT d.patient_id, d.tenant_id, d.nome_original
        INTO v_patient_id, v_tenant_id, v_doc_nome
        FROM public.documents d
        WHERE d.id = NEW.documento_id;

        IF v_patient_id IS NOT NULL THEN
            INSERT INTO public.patient_timeline (
                patient_id, tenant_id, evento_tipo,
                titulo, descricao, icone_cor,
                link_ref_tipo, link_ref_id,
                gerado_por, ocorrido_em
            ) VALUES (
                v_patient_id,
                v_tenant_id,
                'documento_assinado',
                'Documento assinado: ' || COALESCE(v_doc_nome, 'documento'),
                'Assinado por ' || COALESCE(NEW.signatario_nome, NEW.signatario_tipo),
                'green',
                'documento',
                NEW.documento_id,
                NEW.signatario_id,
                NEW.assinado_em
            );
        END IF;
    END IF;
    RETURN NEW;
END;
$$;

CREATE TRIGGER trg_ds_timeline
    AFTER UPDATE ON public.document_signatures
    FOR EACH ROW
    EXECUTE FUNCTION public.fn_document_signature_timeline();

-- RLS
ALTER TABLE public.document_signatures ENABLE ROW LEVEL SECURITY;

CREATE POLICY "ds: tenant members access"
    ON public.document_signatures
    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'
    ));

-- Comentarios
COMMENT ON TABLE  public.document_signatures                    IS 'Assinaturas eletronicas de documentos. Cada signatario tem seu registro.';
COMMENT ON COLUMN public.document_signatures.signatario_tipo    IS 'paciente|responsavel_legal|terapeuta.';
COMMENT ON COLUMN public.document_signatures.status             IS 'pendente|enviado|assinado|recusado|expirado.';
COMMENT ON COLUMN public.document_signatures.hash_documento     IS 'Hash SHA-256 do documento no momento da assinatura. Garante integridade.';
COMMENT ON COLUMN public.document_signatures.ip                 IS 'IP do signatario no momento da assinatura.';


-- ==========================================================================
-- 9. Tabela: document_share_links (links temporarios)
-- ==========================================================================
CREATE TABLE IF NOT EXISTS public.document_share_links (
    id              uuid DEFAULT gen_random_uuid() NOT NULL,

    documento_id    uuid NOT NULL REFERENCES public.documents(id) ON DELETE CASCADE,
    tenant_id       uuid NOT NULL,

    -- Token unico para o link
    token           text NOT NULL DEFAULT encode(gen_random_bytes(32), 'hex'),

    -- Limites
    expira_em       timestamptz NOT NULL,
    usos_max        smallint DEFAULT 5 NOT NULL,
    usos            smallint DEFAULT 0 NOT NULL,

    -- Quem criou
    criado_por      uuid NOT NULL,
    criado_em       timestamptz DEFAULT now(),

    -- Controle
    ativo           boolean DEFAULT true NOT NULL,

    CONSTRAINT document_share_links_pkey PRIMARY KEY (id),
    CONSTRAINT dsl_token_unique UNIQUE (token)
);

-- Indices
CREATE INDEX IF NOT EXISTS dsl_documento_idx
    ON public.document_share_links USING btree (documento_id);

CREATE INDEX IF NOT EXISTS dsl_token_idx
    ON public.document_share_links USING btree (token)
    WHERE ativo = true;

CREATE INDEX IF NOT EXISTS dsl_expira_idx
    ON public.document_share_links USING btree (expira_em)
    WHERE ativo = true;

-- RLS
ALTER TABLE public.document_share_links ENABLE ROW LEVEL SECURITY;

CREATE POLICY "dsl: creator full access"
    ON public.document_share_links
    USING  (criado_por = auth.uid())
    WITH CHECK (criado_por = auth.uid());

-- Politica publica de leitura por token (para acesso externo sem login)
CREATE POLICY "dsl: public read by token"
    ON public.document_share_links
    FOR SELECT
    USING (ativo = true AND expira_em > now() AND usos < usos_max);

-- Comentarios
COMMENT ON TABLE  public.document_share_links           IS 'Links temporarios assinados para compartilhar documento com profissional externo.';
COMMENT ON COLUMN public.document_share_links.token     IS 'Token unico gerado automaticamente (32 bytes hex).';
COMMENT ON COLUMN public.document_share_links.expira_em IS 'Data/hora de expiracao do link.';
COMMENT ON COLUMN public.document_share_links.usos_max  IS 'Numero maximo de acessos permitidos.';
COMMENT ON COLUMN public.document_share_links.usos      IS 'Numero de vezes que o link ja foi acessado.';


-- ==========================================================================
-- FIM DA MIGRACAO 005
-- ==========================================================================
