-- ============================================================================
-- SCHEMA: education
-- TABLE: quiz_attempts
-- DESCRIPTION: Intentos de quizzes por usuarios
-- VERSION: 1.0.0
-- CREATED: 2026-01-16
-- SPRINT: Sprint 2 - DDL Implementation Roadmap Q1-2026
-- ============================================================================

-- Enum para estado del intento
DO $$ BEGIN
    CREATE TYPE education.attempt_status AS ENUM (
        'in_progress',      -- En progreso
        'submitted',        -- Enviado, pendiente calificacion
        'graded',           -- Calificado
        'abandoned',        -- Abandonado (tiempo expirado)
        'voided'            -- Anulado por admin
    );
EXCEPTION
    WHEN duplicate_object THEN null;
END $$;

-- Tabla de Intentos de Quiz
CREATE TABLE IF NOT EXISTS education.quiz_attempts (
    -- Identificadores
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    quiz_id UUID NOT NULL REFERENCES education.quizzes(id) ON DELETE CASCADE,
    enrollment_id UUID NOT NULL REFERENCES education.enrollments(id) ON DELETE CASCADE,
    user_id UUID NOT NULL REFERENCES users.users(id) ON DELETE CASCADE,
    tenant_id UUID NOT NULL REFERENCES tenants.tenants(id) ON DELETE CASCADE,

    -- Numero de intento
    attempt_number INTEGER NOT NULL DEFAULT 1,

    -- Estado
    status education.attempt_status NOT NULL DEFAULT 'in_progress',

    -- Tiempo
    started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    submitted_at TIMESTAMPTZ,
    graded_at TIMESTAMPTZ,
    time_limit_at TIMESTAMPTZ,                      -- Cuando expira el tiempo
    time_spent_seconds INTEGER,

    -- Preguntas
    questions_shown JSONB NOT NULL DEFAULT '[]'::JSONB,  -- IDs de preguntas mostradas (si shuffle)
    questions_total INTEGER NOT NULL DEFAULT 0,
    questions_answered INTEGER NOT NULL DEFAULT 0,

    -- Respuestas
    -- [{ "question_id": "...", "answer": "...", "is_correct": true, "points": 1, "time_spent": 30 }]
    answers JSONB NOT NULL DEFAULT '[]'::JSONB,

    -- Calificacion
    score DECIMAL(5, 2),                            -- Puntos obtenidos
    max_score DECIMAL(5, 2),                        -- Puntos maximos posibles
    score_percent DECIMAL(5, 2),                    -- Porcentaje (score/max_score * 100)
    passed BOOLEAN,
    grade VARCHAR(2),                               -- 'A', 'B', 'C', 'D', 'F'

    -- Desglose
    correct_count INTEGER NOT NULL DEFAULT 0,
    incorrect_count INTEGER NOT NULL DEFAULT 0,
    partial_count INTEGER NOT NULL DEFAULT 0,       -- Respuestas parcialmente correctas
    unanswered_count INTEGER NOT NULL DEFAULT 0,

    -- Feedback
    feedback_shown BOOLEAN NOT NULL DEFAULT FALSE,
    instructor_feedback TEXT,

    -- XP y gamificacion
    xp_earned INTEGER NOT NULL DEFAULT 0,
    badge_earned UUID,

    -- Metadata
    ip_address INET,
    user_agent TEXT,
    metadata JSONB DEFAULT '{}'::JSONB,

    -- Timestamps
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

COMMENT ON TABLE education.quiz_attempts IS
'Intentos de quiz con respuestas y calificaciones';

COMMENT ON COLUMN education.quiz_attempts.questions_shown IS
'Array de IDs de preguntas en el orden mostrado (puede variar por shuffle)';

-- Indices
CREATE INDEX IF NOT EXISTS idx_quiz_attempts_quiz
    ON education.quiz_attempts(quiz_id);

CREATE INDEX IF NOT EXISTS idx_quiz_attempts_enrollment
    ON education.quiz_attempts(enrollment_id);

CREATE INDEX IF NOT EXISTS idx_quiz_attempts_user
    ON education.quiz_attempts(user_id);

CREATE INDEX IF NOT EXISTS idx_quiz_attempts_tenant
    ON education.quiz_attempts(tenant_id);

CREATE INDEX IF NOT EXISTS idx_quiz_attempts_status
    ON education.quiz_attempts(status);

CREATE INDEX IF NOT EXISTS idx_quiz_attempts_in_progress
    ON education.quiz_attempts(user_id, quiz_id)
    WHERE status = 'in_progress';

CREATE INDEX IF NOT EXISTS idx_quiz_attempts_passed
    ON education.quiz_attempts(quiz_id, passed)
    WHERE passed = TRUE;

CREATE INDEX IF NOT EXISTS idx_quiz_attempts_score
    ON education.quiz_attempts(quiz_id, score_percent DESC);

-- GIN index para respuestas
CREATE INDEX IF NOT EXISTS idx_quiz_attempts_answers_gin
    ON education.quiz_attempts USING GIN (answers);

-- Trigger para updated_at
DROP TRIGGER IF EXISTS quiz_attempt_updated_at ON education.quiz_attempts;
CREATE TRIGGER quiz_attempt_updated_at
    BEFORE UPDATE ON education.quiz_attempts
    FOR EACH ROW
    EXECUTE FUNCTION education.update_education_timestamp();

-- Trigger para calcular calificacion al enviar
CREATE OR REPLACE FUNCTION education.grade_quiz_attempt()
RETURNS TRIGGER AS $$
DECLARE
    v_quiz RECORD;
    v_answer RECORD;
    v_total_points DECIMAL := 0;
    v_earned_points DECIMAL := 0;
    v_correct INTEGER := 0;
    v_incorrect INTEGER := 0;
    v_partial INTEGER := 0;
BEGIN
    -- Solo procesar cuando cambia a submitted
    IF NEW.status = 'submitted' AND OLD.status = 'in_progress' THEN
        -- Obtener info del quiz
        SELECT * INTO v_quiz FROM education.quizzes WHERE id = NEW.quiz_id;

        -- Calcular puntos
        FOR v_answer IN SELECT * FROM jsonb_array_elements(NEW.answers)
        LOOP
            v_total_points := v_total_points + COALESCE((v_answer.value->>'max_points')::DECIMAL, 1);

            IF (v_answer.value->>'is_correct')::BOOLEAN = TRUE THEN
                v_earned_points := v_earned_points + COALESCE((v_answer.value->>'points')::DECIMAL, 1);
                v_correct := v_correct + 1;
            ELSIF (v_answer.value->>'points')::DECIMAL > 0 THEN
                v_earned_points := v_earned_points + (v_answer.value->>'points')::DECIMAL;
                v_partial := v_partial + 1;
            ELSE
                v_incorrect := v_incorrect + 1;
            END IF;
        END LOOP;

        -- Calcular porcentaje
        NEW.score := v_earned_points;
        NEW.max_score := v_total_points;
        IF v_total_points > 0 THEN
            NEW.score_percent := (v_earned_points / v_total_points * 100);
        END IF;

        -- Determinar si paso
        NEW.passed := NEW.score_percent >= v_quiz.passing_score;

        -- Calcular grade
        NEW.grade := CASE
            WHEN NEW.score_percent >= 90 THEN 'A'
            WHEN NEW.score_percent >= 80 THEN 'B'
            WHEN NEW.score_percent >= 70 THEN 'C'
            WHEN NEW.score_percent >= 60 THEN 'D'
            ELSE 'F'
        END;

        -- Contadores
        NEW.correct_count := v_correct;
        NEW.incorrect_count := v_incorrect;
        NEW.partial_count := v_partial;
        NEW.unanswered_count := NEW.questions_total - jsonb_array_length(NEW.answers);

        -- Tiempo
        NEW.submitted_at := NOW();
        NEW.time_spent_seconds := EXTRACT(EPOCH FROM (NOW() - NEW.started_at))::INTEGER;

        -- XP
        IF NEW.passed THEN
            NEW.xp_earned := v_quiz.xp_reward;
        ELSE
            NEW.xp_earned := v_quiz.xp_reward / 4;  -- XP parcial por intentar
        END IF;

        -- Marcar como graded
        NEW.status := 'graded';
        NEW.graded_at := NOW();
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS quiz_attempt_grade ON education.quiz_attempts;
CREATE TRIGGER quiz_attempt_grade
    BEFORE UPDATE OF status ON education.quiz_attempts
    FOR EACH ROW
    EXECUTE FUNCTION education.grade_quiz_attempt();

-- Trigger para actualizar estadisticas del quiz
CREATE OR REPLACE FUNCTION education.update_quiz_stats_on_attempt()
RETURNS TRIGGER AS $$
BEGIN
    IF NEW.status = 'graded' THEN
        UPDATE education.quizzes
        SET attempt_count = attempt_count + 1,
            average_score = (
                SELECT AVG(score_percent)
                FROM education.quiz_attempts
                WHERE quiz_id = NEW.quiz_id AND status = 'graded'
            ),
            pass_rate = (
                SELECT (COUNT(*) FILTER (WHERE passed = TRUE)::DECIMAL / COUNT(*) * 100)
                FROM education.quiz_attempts
                WHERE quiz_id = NEW.quiz_id AND status = 'graded'
            )
        WHERE id = NEW.quiz_id;

        -- Actualizar enrollment
        UPDATE education.enrollments
        SET quizzes_completed = (
                SELECT COUNT(DISTINCT quiz_id)
                FROM education.quiz_attempts
                WHERE enrollment_id = NEW.enrollment_id AND status = 'graded'
            ),
            quizzes_passed = (
                SELECT COUNT(DISTINCT quiz_id)
                FROM education.quiz_attempts
                WHERE enrollment_id = NEW.enrollment_id AND status = 'graded' AND passed = TRUE
            ),
            xp_earned = xp_earned + NEW.xp_earned
        WHERE id = NEW.enrollment_id;

        -- Actualizar lesson_progress si el quiz esta asociado a una leccion
        UPDATE education.lesson_progress
        SET quiz_attempted = TRUE,
            quiz_passed = NEW.passed,
            quiz_score = NEW.score_percent,
            is_completed = NEW.passed  -- Completar leccion si paso el quiz
        WHERE enrollment_id = NEW.enrollment_id
          AND lesson_id = (SELECT lesson_id FROM education.quizzes WHERE id = NEW.quiz_id);
    END IF;

    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS quiz_attempt_stats ON education.quiz_attempts;
CREATE TRIGGER quiz_attempt_stats
    AFTER UPDATE OF status ON education.quiz_attempts
    FOR EACH ROW
    EXECUTE FUNCTION education.update_quiz_stats_on_attempt();

-- Funcion para iniciar un intento
CREATE OR REPLACE FUNCTION education.start_quiz_attempt(
    p_quiz_id UUID,
    p_enrollment_id UUID
)
RETURNS UUID AS $$
DECLARE
    v_quiz RECORD;
    v_enrollment RECORD;
    v_attempt_count INTEGER;
    v_questions UUID[];
    v_attempt_id UUID;
BEGIN
    -- Obtener quiz
    SELECT * INTO v_quiz FROM education.quizzes WHERE id = p_quiz_id;

    IF v_quiz IS NULL THEN
        RAISE EXCEPTION 'Quiz not found';
    END IF;

    -- Obtener enrollment
    SELECT * INTO v_enrollment FROM education.enrollments WHERE id = p_enrollment_id;

    -- Verificar intentos previos
    SELECT COUNT(*) INTO v_attempt_count
    FROM education.quiz_attempts
    WHERE quiz_id = p_quiz_id
      AND enrollment_id = p_enrollment_id
      AND status NOT IN ('abandoned', 'voided');

    IF v_quiz.max_attempts IS NOT NULL AND v_attempt_count >= v_quiz.max_attempts THEN
        RAISE EXCEPTION 'Maximum attempts reached';
    END IF;

    -- Verificar si hay intento en progreso
    IF EXISTS (
        SELECT 1 FROM education.quiz_attempts
        WHERE quiz_id = p_quiz_id
          AND enrollment_id = p_enrollment_id
          AND status = 'in_progress'
    ) THEN
        RAISE EXCEPTION 'Already have an attempt in progress';
    END IF;

    -- Seleccionar preguntas
    SELECT ARRAY_AGG(id ORDER BY
        CASE WHEN v_quiz.shuffle_questions THEN RANDOM() ELSE sequence_number END
    ) INTO v_questions
    FROM education.quiz_questions
    WHERE quiz_id = p_quiz_id AND is_active = TRUE
    LIMIT COALESCE(v_quiz.questions_to_show, v_quiz.question_count);

    -- Crear intento
    INSERT INTO education.quiz_attempts (
        quiz_id, enrollment_id, user_id, tenant_id,
        attempt_number, questions_shown, questions_total,
        time_limit_at
    ) VALUES (
        p_quiz_id, p_enrollment_id, v_enrollment.user_id, v_enrollment.tenant_id,
        v_attempt_count + 1,
        to_jsonb(v_questions),
        array_length(v_questions, 1),
        CASE WHEN v_quiz.time_limit_minutes IS NOT NULL
             THEN NOW() + (v_quiz.time_limit_minutes || ' minutes')::INTERVAL
             ELSE NULL
        END
    )
    RETURNING id INTO v_attempt_id;

    RETURN v_attempt_id;
END;
$$ LANGUAGE plpgsql;

-- Vista de intentos de un usuario
CREATE OR REPLACE VIEW education.v_my_quiz_attempts AS
SELECT
    qa.id,
    qa.quiz_id,
    q.title AS quiz_title,
    q.type AS quiz_type,
    qa.enrollment_id,
    qa.attempt_number,
    qa.status,
    qa.score_percent,
    qa.passed,
    qa.grade,
    qa.correct_count,
    qa.questions_total,
    qa.time_spent_seconds,
    qa.started_at,
    qa.submitted_at
FROM education.quiz_attempts qa
JOIN education.quizzes q ON qa.quiz_id = q.id
ORDER BY qa.started_at DESC;

-- RLS Policy para multi-tenancy
ALTER TABLE education.quiz_attempts ENABLE ROW LEVEL SECURITY;

CREATE POLICY quiz_attempts_tenant_isolation ON education.quiz_attempts
    FOR ALL
    USING (tenant_id = current_setting('app.current_tenant_id', true)::UUID);

CREATE POLICY quiz_attempts_user_isolation ON education.quiz_attempts
    FOR SELECT
    USING (user_id = current_setting('app.current_user_id', true)::UUID);

-- Grants
GRANT SELECT, INSERT, UPDATE ON education.quiz_attempts TO trading_app;
GRANT SELECT ON education.quiz_attempts TO trading_readonly;
GRANT SELECT ON education.v_my_quiz_attempts TO trading_app;
GRANT EXECUTE ON FUNCTION education.start_quiz_attempt TO trading_app;
