-- ============================================================
-- Sprint 4 (v3.0.0) — Etapa 4G: submit transaccional + cierre de WITH CHECK(true)
--
-- PROBLEMA QUE CIERRA: answers_insert/responses_insert/consent_records_insert
-- permitían INSERT directo a anon con casi ninguna restricción real
-- (answers_insert: WITH CHECK(true) literal) — el cliente era la única
-- fuente de verdad sobre qué se guardaba. Closes BRIEF decisión 15.
--
-- DISEÑO — dos capas, cada una validando lo que le corresponde:
--   1) lib/form-engine.ts + lib/submit-validation.ts, en el Server Action
--      (Next.js, TypeScript): valida COHERENCIA DE FLUJO — visibilidad,
--      instancias legítimas, opciones válidas, requeridas. Esto NO puede
--      vivir en SQL sin reimplementar el motor (prohibido explícitamente).
--   2) Esta RPC (Postgres, SECURITY DEFINER): valida ESTRUCTURA — el
--      question_id pertenece a esta encuesta, la encuesta está viva, el
--      consentimiento llegó — y persiste todo de forma ATÓMICA (una
--      llamada a función = una transacción; cualquier RAISE EXCEPTION
--      revierte TODO, sin huérfanos).
--
-- POR QUÉ SECURITY DEFINER: se revoca el INSERT directo de anon sobre las
-- 3 tablas (paso 1 abajo). Sin SECURITY DEFINER, esta función correría
-- con los privilegios de quien la llama (anon) y esas mismas policies
-- recién revocadas la bloquearían a ELLA también. SECURITY DEFINER la
-- hace correr con los privilegios de quien la creó (el owner de la
-- migración, no anon) — por eso puede insertar después de que anon ya
-- no puede hacerlo directo. Mismo patrón que las RPCs de dashboard
-- existentes (rpc_dashboard_kpis, etc.), aplicado por primera vez a
-- escritura en vez de lectura.
--
-- LOS GRANTS DE TABLA (migration 006: GRANT INSERT ON answers TO anon...)
-- NO se tocan acá — siguen el "diseño de capas" que esa migración ya
-- documenta: Capa 1 (GRANT) da la capacidad de intentar el INSERT,
-- Capa 2 (RLS, esta migración) lo deniega por default al no quedar
-- ninguna policy permisiva. anon puede seguir "intentando" un INSERT
-- directo — RLS lo rechaza igual, con RLS habilitado (migration 002) y
-- cero policies de INSERT restantes en estas 3 tablas.
--
-- RIESGO RESIDUAL DOCUMENTADO (no resuelto en esta etapa, ver reporte):
-- cualquiera que llame a esta RPC directamente vía la REST API de
-- Supabase (no a través de nuestro Server Action) puede mandar un
-- payload que pase las validaciones ESTRUCTURALES de acá pero sea
-- incoherente de flujo — porque el motor de flujo es TypeScript y no
-- corre dentro de Postgres. Es un problema de calidad de datos de
-- investigación, no de privacidad/k-anonimato (el dato sigue siendo
-- anónimo, is_sensitive, sujeto al umbral k en agg_segment). Cerrarlo
-- del todo excede el scope de 4G — candidato a BACKLOG.md si se decide
-- perseguirlo (ej. firma del payload por el Server Action).
-- ============================================================

-- ── 1. Revoca el INSERT directo — el único camino pasa a ser la RPC ───────
DROP POLICY IF EXISTS "consent_records_insert" ON public.consent_records;
DROP POLICY IF EXISTS "responses_insert" ON public.responses;
DROP POLICY IF EXISTS "answers_insert" ON public.answers;

-- ── 2. RPC de submit ────────────────────────────────────────────────────
CREATE OR REPLACE FUNCTION public.rpc_submit_response(
    p_survey_id         uuid,
    p_ip_hash           text,
    p_qi_age_bucket     text,
    p_qi_sector         text,
    p_qi_zone           text,
    p_consent_operativo boolean,
    p_consent_macro_n1  boolean,
    p_answers           jsonb  -- [{"question_id":"...","instance_id":"familiar_1","value":...}, ...]
)
RETURNS jsonb
LANGUAGE plpgsql
SECURITY DEFINER
SET search_path = public
AS $$
DECLARE
    v_org_id      uuid;
    v_cv_id       uuid;
    v_consent_id  uuid := gen_random_uuid();
    v_response_id uuid := gen_random_uuid();
    v_bad_refs    int;
BEGIN
    -- Defensa en profundidad: el Server Action ya confirmó esto antes de
    -- llamar, pero esta función no debe confiar en el caller.
    SELECT org_id, consent_version_id INTO v_org_id, v_cv_id
    FROM public.surveys
    WHERE id = p_survey_id AND status = 'live';

    IF v_org_id IS NULL THEN
        RAISE EXCEPTION 'survey_not_found_or_not_live' USING ERRCODE = 'check_violation';
    END IF;

    IF NOT p_consent_operativo THEN
        RAISE EXCEPTION 'consent_operativo_required' USING ERRCODE = 'check_violation';
    END IF;

    -- Chequeo ESTRUCTURAL (no de flujo): cada question_id recibido existe
    -- y pertenece a ESTA encuesta; cada instance_id viene no-vacío.
    -- Visibilidad/instancia-legítima/opciones-válidas ya se validó en TS.
    SELECT count(*) INTO v_bad_refs
    FROM jsonb_to_recordset(p_answers) AS r(question_id uuid, instance_id text, value jsonb)
    LEFT JOIN public.questions q ON q.id = r.question_id AND q.survey_id = p_survey_id
    WHERE q.id IS NULL OR r.instance_id IS NULL OR r.instance_id = '';

    IF v_bad_refs > 0 THEN
        RAISE EXCEPTION 'invalid_answer_reference' USING ERRCODE = 'check_violation';
    END IF;

    -- consent_record (inmutable, ADR-003)
    INSERT INTO public.consent_records (id, consent_version_id, granted_scopes, granted_at)
    VALUES (
        v_consent_id, v_cv_id,
        jsonb_build_object('operativo', p_consent_operativo, 'macro_n1', p_consent_macro_n1),
        now()
    );

    -- response (respondent_id SIEMPRE NULL — encuesta anónima, regla #2 CLAUDE.md)
    INSERT INTO public.responses (
        id, survey_id, company_id, respondent_id, ip_hash,
        consent_record_id, qi_age_bucket, qi_sector, qi_zone
    )
    VALUES (
        v_response_id, p_survey_id, v_org_id, NULL, p_ip_hash,
        v_consent_id, p_qi_age_bucket, p_qi_sector, p_qi_zone
    );

    -- answers — un row por entrada validada, con su instance_id real
    INSERT INTO public.answers (response_id, question_id, instance_id, value)
    SELECT v_response_id, r.question_id, r.instance_id, r.value
    FROM jsonb_to_recordset(p_answers) AS r(question_id uuid, instance_id text, value jsonb);

    RETURN jsonb_build_object('response_id', v_response_id);
END;
$$;

REVOKE ALL ON FUNCTION public.rpc_submit_response(
    uuid, text, text, text, text, boolean, boolean, jsonb
) FROM PUBLIC;
GRANT EXECUTE ON FUNCTION public.rpc_submit_response(
    uuid, text, text, text, text, boolean, boolean, jsonb
) TO anon, authenticated;
