-- ============================================================================
-- Compliance CFP #7 — RPCs de assinatura eletrônica
-- ----------------------------------------------------------------------------
-- Cria 2 RPCs que registram assinatura capturando IP server-side (anti-spoof)
-- via inet_client_addr() e user-agent via request headers do Supabase.
--
--   • sign_document_by_signature_id — paciente logado assina via portal
--   • sign_document_by_token        — terceiro assina via share link público
--
-- ROADMAP item #1.2 #7 (Assinatura eletrônica pelo paciente no portal,
-- simples, com IP+timestamp). Não usa ICP-Brasil — é assinatura simples
-- com audit trail (IP, UA, timestamp, hash SHA-256 do documento).
-- ============================================================================

BEGIN;

-- ──────────────────────────────────────────────────────────────────────────
-- 1. sign_document_by_signature_id
-- ----------------------------------------------------------------------------
-- Para signatários LOGADOS no portal/sistema. SECURITY INVOKER — a RLS de
-- document_signatures continua aplicando (signatario_id = auth.uid() ou
-- tenant_members). RPC só serve pra centralizar captura de IP + UA + hash.
-- ──────────────────────────────────────────────────────────────────────────
CREATE OR REPLACE FUNCTION public.sign_document_by_signature_id(
    p_signature_id uuid,
    p_hash_documento text DEFAULT NULL
) RETURNS public.document_signatures
LANGUAGE plpgsql
SECURITY INVOKER
AS $$
DECLARE
    v_row public.document_signatures;
    v_ip  inet;
    v_ua  text;
BEGIN
    IF p_signature_id IS NULL THEN
        RAISE EXCEPTION 'p_signature_id obrigatório' USING ERRCODE = '22023';
    END IF;

    -- Captura IP e UA do request (best-effort — pode vir NULL em alguns ambientes)
    v_ip := inet_client_addr();
    BEGIN
        v_ua := current_setting('request.headers', true)::json ->> 'user-agent';
    EXCEPTION WHEN OTHERS THEN
        v_ua := NULL;
    END;

    UPDATE public.document_signatures
       SET status         = 'assinado',
           ip             = v_ip,
           user_agent     = v_ua,
           assinado_em    = now(),
           hash_documento = COALESCE(p_hash_documento, hash_documento),
           atualizado_em  = now()
     WHERE id = p_signature_id
       AND status IN ('pendente', 'enviado')
     RETURNING * INTO v_row;

    IF v_row.id IS NULL THEN
        RAISE EXCEPTION 'Assinatura não encontrada ou já processada' USING ERRCODE = 'P0002';
    END IF;

    RETURN v_row;
END;
$$;

COMMENT ON FUNCTION public.sign_document_by_signature_id(uuid, text) IS
    'Assinatura via portal logado. Captura IP/UA server-side. RLS aplica (SECURITY INVOKER).';

GRANT EXECUTE ON FUNCTION public.sign_document_by_signature_id(uuid, text) TO authenticated;

-- ──────────────────────────────────────────────────────────────────────────
-- 2. sign_document_by_token
-- ----------------------------------------------------------------------------
-- Para signatários NÃO LOGADOS via share link público. SECURITY DEFINER —
-- bypassa RLS. Valida o share_link (token, ativo, expira_em, usos_max),
-- localiza o signatário PENDENTE associado ao documento (signatario_email
-- opcional p/ desambiguar quando há múltiplos), assina, incrementa usos.
-- ──────────────────────────────────────────────────────────────────────────
CREATE OR REPLACE FUNCTION public.sign_document_by_token(
    p_token text,
    p_signature_id uuid DEFAULT NULL,
    p_hash_documento text DEFAULT NULL
) RETURNS public.document_signatures
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_link    public.document_share_links;
    v_sig     public.document_signatures;
    v_ip      inet;
    v_ua      text;
BEGIN
    IF p_token IS NULL OR length(p_token) < 32 THEN
        RAISE EXCEPTION 'Token inválido' USING ERRCODE = '22023';
    END IF;

    -- Valida share_link
    SELECT * INTO v_link
      FROM public.document_share_links
     WHERE token = p_token
       AND ativo = true
       AND expira_em > now()
       AND usos < usos_max
     LIMIT 1;

    IF v_link.id IS NULL THEN
        RAISE EXCEPTION 'Link expirado, inválido ou esgotado' USING ERRCODE = 'P0002';
    END IF;

    -- Localiza a signature pendente do documento. Se p_signature_id veio,
    -- é desambiguação (multi-signatário); senão pega a primeira pendente
    -- por ordem.
    IF p_signature_id IS NOT NULL THEN
        SELECT * INTO v_sig
          FROM public.document_signatures
         WHERE id = p_signature_id
           AND documento_id = v_link.documento_id
           AND status IN ('pendente', 'enviado')
         LIMIT 1;
    ELSE
        SELECT * INTO v_sig
          FROM public.document_signatures
         WHERE documento_id = v_link.documento_id
           AND status IN ('pendente', 'enviado')
         ORDER BY ordem ASC, criado_em ASC
         LIMIT 1;
    END IF;

    IF v_sig.id IS NULL THEN
        RAISE EXCEPTION 'Nenhuma assinatura pendente para este documento' USING ERRCODE = 'P0002';
    END IF;

    -- Captura IP/UA
    v_ip := inet_client_addr();
    BEGIN
        v_ua := current_setting('request.headers', true)::json ->> 'user-agent';
    EXCEPTION WHEN OTHERS THEN
        v_ua := NULL;
    END;

    -- Assina
    UPDATE public.document_signatures
       SET status         = 'assinado',
           ip             = v_ip,
           user_agent     = v_ua,
           assinado_em    = now(),
           hash_documento = COALESCE(p_hash_documento, hash_documento),
           atualizado_em  = now()
     WHERE id = v_sig.id
     RETURNING * INTO v_sig;

    -- Incrementa contador de usos do share_link
    UPDATE public.document_share_links
       SET usos = usos + 1
     WHERE id = v_link.id;

    RETURN v_sig;
END;
$$;

COMMENT ON FUNCTION public.sign_document_by_token(text, uuid, text) IS
    'Assinatura via share link público. SECURITY DEFINER — valida token, captura IP/UA, incrementa usos. p_signature_id é opcional pra desambiguar multi-signatário.';

GRANT EXECUTE ON FUNCTION public.sign_document_by_token(text, uuid, text) TO anon, authenticated;

-- ──────────────────────────────────────────────────────────────────────────
-- 3. get_signable_document_by_token
-- ----------------------------------------------------------------------------
-- View helper que retorna info do documento + signatários pendentes via token,
-- sem assinar. Permite a página pública renderizar antes do click.
-- SECURITY DEFINER porque share_link tem RLS pública mas documents+signatures
-- têm RLS por owner/tenant.
-- ──────────────────────────────────────────────────────────────────────────
CREATE OR REPLACE FUNCTION public.get_signable_document_by_token(
    p_token text
) RETURNS jsonb
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_link public.document_share_links;
    v_doc  public.documents;
    v_sigs jsonb;
BEGIN
    IF p_token IS NULL OR length(p_token) < 32 THEN
        RAISE EXCEPTION 'Token inválido' USING ERRCODE = '22023';
    END IF;

    SELECT * INTO v_link
      FROM public.document_share_links
     WHERE token = p_token
       AND ativo = true
       AND expira_em > now()
       AND usos < usos_max
     LIMIT 1;

    IF v_link.id IS NULL THEN
        RETURN jsonb_build_object('valid', false, 'error', 'expired_or_invalid');
    END IF;

    SELECT * INTO v_doc
      FROM public.documents
     WHERE id = v_link.documento_id
       AND deleted_at IS NULL
     LIMIT 1;

    IF v_doc.id IS NULL THEN
        RETURN jsonb_build_object('valid', false, 'error', 'document_not_found');
    END IF;

    SELECT jsonb_agg(
        jsonb_build_object(
            'id', s.id,
            'signatario_tipo', s.signatario_tipo,
            'signatario_nome', s.signatario_nome,
            'signatario_email', s.signatario_email,
            'ordem', s.ordem,
            'status', s.status,
            'assinado_em', s.assinado_em
        ) ORDER BY s.ordem
    ) INTO v_sigs
      FROM public.document_signatures s
     WHERE s.documento_id = v_doc.id;

    RETURN jsonb_build_object(
        'valid', true,
        'document', jsonb_build_object(
            'id', v_doc.id,
            'nome_original', v_doc.nome_original,
            'mime_type', v_doc.mime_type,
            'tamanho_bytes', v_doc.tamanho_bytes,
            'bucket_path', v_doc.bucket_path,
            'storage_bucket', v_doc.storage_bucket,
            'tipo_documento', v_doc.tipo_documento
        ),
        'signatures', COALESCE(v_sigs, '[]'::jsonb),
        'expira_em', v_link.expira_em,
        'usos_restantes', v_link.usos_max - v_link.usos
    );
END;
$$;

COMMENT ON FUNCTION public.get_signable_document_by_token(text) IS
    'Retorna documento + signatários pendentes via token. Usado pela página pública antes de assinar.';

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

COMMIT;
