<?php
// Start output buffering to prevent accidental HTML/warnings from breaking JSON responses
ob_start();
require_once '../../config/database.php';
require_once '../../config/auth.php';
require_once '../../config/rbac.php';
require_once '../../includes/functions.php';

// Helper to send clean JSON responses (clears any buffered output first)
function send_json($data, $httpCode = 200) {
    if (ob_get_length()) {
        $buf = ob_get_clean();
        if (!empty($buf)) {
            error_log('[attempts.php] buffered output before JSON response: ' . substr($buf, 0, 2000));
        }
    }
    http_response_code($httpCode);
    header('Content-Type: application/json');
    echo json_encode($data);
    exit;
}

// Check authentication
if (!isLoggedIn()) {
    send_json(['success' => false, 'message' => 'Authentication required'], 401);
}

$db = getDB();
$userId = $_SESSION['user_id'];
$method = $_SERVER['REQUEST_METHOD'];

try {
    if ($method !== 'POST') {
        throw new Exception('Method not allowed');
    }

    $rawInput = file_get_contents('php://input');
    // Log raw input for debugging save/submit failures
    if ($rawInput !== false) {
        error_log('[attempts.php] raw input: ' . substr($rawInput, 0, 2000));
    } else {
        error_log('[attempts.php] raw input: (false)');
    }
    $data = json_decode($rawInput, true);
    if ($rawInput === false || ($data === null && json_last_error() !== JSON_ERROR_NONE)) {
        throw new Exception('Invalid JSON data: ' . json_last_error_msg());
    }

    $examId = intval($data['exam_id'] ?? 0);
    $action = $data['action'] ?? '';

    if (!$examId || !$action) {
        throw new Exception('Exam ID and action are required');
    }

    // Verify exam exists and is published
    $stmt = $db->prepare("SELECT * FROM exams WHERE id = ? AND status = 'published'");
    $stmt->execute([$examId]);
    $exam = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$exam) {
        throw new Exception('Exam not found or not available');
    }

    // Check permissions based on user role
    if (hasAnyRole(['student'])) {
        // Student permissions - can only access their own attempts
        $studentId = $userId;

        // Check if student is enrolled in course (if exam is course-specific)
        if ($exam['course_id']) {
            $stmt = $db->prepare("SELECT id FROM course_enrollments WHERE course_id = ? AND student_id = ? AND status IN ('enrolled', 'in_progress')");
            $stmt->execute([$exam['course_id'], $studentId]);
            if (!$stmt->fetch()) {
                throw new Exception('You must be enrolled in this course to take this exam');
            }
        }
    } elseif (hasAnyRole(['instructor', 'admin'])) {
        // Instructor permissions - can access any attempt for their exams
        if ($exam['instructor_id'] !== $userId && !hasAnyRole(['admin'])) {
            throw new Exception('Access denied');
        }
        $studentId = intval($data['student_id'] ?? 0);
        if (!$studentId) {
            throw new Exception('Student ID required for instructor actions');
        }
    } else {
        throw new Exception('Access denied');
    }

    switch ($action) {
        case 'start':
            // Start a new exam attempt
            handleStartAttempt($db, $examId, $studentId, $exam);
            break;

        case 'save':
            // Save progress
            handleSaveProgress($db, $examId, $studentId, $data);
            break;

        case 'submit':
            // Submit exam
            handleSubmitExam($db, $examId, $studentId, $data, $exam);
            break;

        case 'get':
            // Get attempt details
            handleGetAttempt($db, $examId, $studentId);
            break;

        default:
            throw new Exception('Invalid action');
    }

} catch (Exception $e) {
    // Log exception for debugging
    error_log('[attempts.php] exception: ' . $e->getMessage());
    send_json(['success' => false, 'message' => $e->getMessage()], 400);
}

function handleStartAttempt($db, $examId, $studentId, $exam) {
    // Check if student already has an in-progress attempt
    $stmt = $db->prepare("
        SELECT id, attempt_number FROM exam_attempts
        WHERE exam_id = ? AND student_id = ? AND status = 'in_progress'
        ORDER BY started_at DESC LIMIT 1
    ");
    $stmt->execute([$examId, $studentId]);
    $existingAttempt = $stmt->fetch(PDO::FETCH_ASSOC);

    if ($existingAttempt) {
        // Resume existing attempt
        send_json([
            'success' => true,
            'message' => 'Resumed existing attempt',
            'attempt_id' => $existingAttempt['id'],
            'attempt_number' => $existingAttempt['attempt_number']
        ]);
    }

    // Check attempt limits
    $stmt = $db->prepare("
        SELECT COUNT(*) as attempt_count, MAX(attempt_number) as max_attempt
        FROM exam_attempts
        WHERE exam_id = ? AND student_id = ?
    ");
    $stmt->execute([$examId, $studentId]);
    $attemptInfo = $stmt->fetch(PDO::FETCH_ASSOC);

    $nextAttemptNumber = ($attemptInfo['max_attempt'] ?? 0) + 1;

    if ($exam['max_attempts'] > 0 && $nextAttemptNumber > $exam['max_attempts']) {
        throw new Exception('Maximum attempts exceeded');
    }

    // Create new attempt
    $stmt = $db->prepare("
        INSERT INTO exam_attempts (
            exam_id, student_id, attempt_number, status, started_at, answers, ip_address, browser_info
        ) VALUES (?, ?, ?, 'in_progress', NOW(), '{}', ?, ?)
    ");

    $ipAddress = $_SERVER['REMOTE_ADDR'] ?? '';
    $browserInfo = json_encode([
        'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? '',
        'accept_language' => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? ''
    ]);

    $stmt->execute([$examId, $studentId, $nextAttemptNumber, $ipAddress, $browserInfo]);
    $attemptId = $db->lastInsertId();

    send_json([
        'success' => true,
        'message' => 'Exam attempt started',
        'attempt_id' => $attemptId,
        'attempt_number' => $nextAttemptNumber
    ]);
}

function handleSaveProgress($db, $examId, $studentId, $data) {
    // Find the in-progress attempt
    $stmt = $db->prepare("
        SELECT id FROM exam_attempts
        WHERE exam_id = ? AND student_id = ? AND status = 'in_progress'
        ORDER BY started_at DESC LIMIT 1
    ");
    $stmt->execute([$examId, $studentId]);
    $attempt = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$attempt) {
        throw new Exception('No active attempt found');
    }

    $answers = json_encode($data['answers'] ?? []);
    $timeSpent = intval($data['time_spent'] ?? 0);

    $stmt = $db->prepare("
        UPDATE exam_attempts
        SET answers = ?, time_spent = ?, last_accessed = NOW()
        WHERE id = ?
    ");
    $stmt->execute([$answers, $timeSpent, $attempt['id']]);

    send_json(['success' => true, 'message' => 'Progress saved']);
}

function handleSubmitExam($db, $examId, $studentId, $data, $exam) {
    // Find the in-progress attempt
    $stmt = $db->prepare("
        SELECT id, answers FROM exam_attempts
        WHERE exam_id = ? AND student_id = ? AND status = 'in_progress'
        ORDER BY started_at DESC LIMIT 1
    ");
    $stmt->execute([$examId, $studentId]);
    $attempt = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$attempt) {
        throw new Exception('No active attempt found');
    }

    // Log incoming submit data for debugging
    error_log('[attempts.php] submit: attempt_db_answers=' . substr($attempt['answers'] ?? '', 0, 2000));
    error_log('[attempts.php] submit: incoming_data=' . substr(json_encode($data ?? []), 0, 2000));

    $answers = array_merge(
        json_decode($attempt['answers'], true) ?: [],
        $data['answers'] ?? []
    );

    // Ensure answers can be JSON-encoded; log and fallback on failure
    $answersJson = json_encode($answers);
    if ($answersJson === false) {
        error_log('[attempts.php] json_encode answers failed: ' . json_last_error_msg());
        // Attempt to coerce problematic values to strings
        array_walk_recursive($answers, function (&$v) { if (is_object($v) || is_resource($v)) $v = (string)$v; });
        $answersJson = json_encode($answers);
        if ($answersJson === false) {
            error_log('[attempts.php] json_encode answers still failed, using empty array');
            $answersJson = json_encode([]);
        }
    }

    $timeSpent = intval($data['time_spent'] ?? 0);

    // Calculate score
    $scoreData = calculateExamScore($db, $examId, $answers, $exam['passing_score']);

    // Update attempt
    $stmt = $db->prepare("
        UPDATE exam_attempts
        SET status = 'completed', completed_at = NOW(), time_spent = ?,
            answers = ?, score = ?, max_score = ?, percentage = ?, is_passed = ?
        WHERE id = ?
    ");

    $res = $stmt->execute([
        $timeSpent,
        $answersJson,
        $scoreData['score'],
        $scoreData['max_score'],
        $scoreData['percentage'],
        $scoreData['is_passed'] ? 1 : 0,
        $attempt['id']
    ]);
    if ($res === false) {
        $err = $stmt->errorInfo();
        error_log('[attempts.php] update attempt failed: ' . implode(' | ', $err));
        throw new Exception('Failed to save submission');
    }

    // Update question analytics
    updateQuestionAnalytics($db, $examId, $answers);

    send_json([
        'success' => true,
        'message' => 'Exam submitted successfully',
        'score' => $scoreData['score'],
        'max_score' => $scoreData['max_score'],
        'percentage' => $scoreData['percentage'],
        'passed' => $scoreData['is_passed']
    ]);
}

function handleGetAttempt($db, $examId, $studentId) {
    $stmt = $db->prepare("
        SELECT ea.*, e.title as exam_title, e.total_points, e.passing_score
        FROM exam_attempts ea
        JOIN exams e ON ea.exam_id = e.id
        WHERE ea.exam_id = ? AND ea.student_id = ?
        ORDER BY ea.started_at DESC LIMIT 1
    ");
    $stmt->execute([$examId, $studentId]);
    $attempt = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$attempt) {
        throw new Exception('Attempt not found');
    }

    send_json([
        'success' => true,
        'attempt' => $attempt
    ]);
}

function calculateExamScore($db, $examId, $answers, $passingScore) {
    // Get exam questions with correct answers
    $stmt = $db->prepare("
        SELECT eq.question_id, eq.points, q.question_type, qo.id as option_id, qo.is_correct
        FROM exam_questions eq
        JOIN questions q ON eq.question_id = q.id
        LEFT JOIN question_options qo ON q.id = qo.question_id
        WHERE eq.exam_id = ?
        ORDER BY eq.question_id, qo.id
    ");
    $stmt->execute([$examId]);
    $questionData = $stmt->fetchAll(PDO::FETCH_ASSOC);

    // Group by question
    $questions = [];
    foreach ($questionData as $row) {
        $qid = $row['question_id'];
        if (!isset($questions[$qid])) {
            $questions[$qid] = [
                'type' => $row['question_type'],
                'points' => $row['points'],
                'options' => []
            ];
        }
        if ($row['option_id']) {
            $questions[$qid]['options'][] = [
                'id' => $row['option_id'],
                'is_correct' => $row['is_correct']
            ];
        }
    }

    $totalScore = 0;
    $maxScore = 0;

    foreach ($questions as $questionId => $question) {
        $maxScore += $question['points'];
        $studentAnswer = $answers[$questionId] ?? null;

        if (!$studentAnswer) {
            continue; // No answer given
        }

        $points = 0;

        switch ($question['type']) {
            case 'mcq_single':
            case 'true_false':
                // Check if selected option is correct
                foreach ($question['options'] as $option) {
                    if ($option['id'] == $studentAnswer && $option['is_correct']) {
                        $points = $question['points'];
                        break;
                    }
                }
                break;

            case 'mcq_multiple':
                // Check multiple selections
                if (is_array($studentAnswer)) {
                    $correctOptions = array_filter($question['options'], fn($opt) => $opt['is_correct']);
                    $selectedCorrect = 0;
                    $totalCorrect = count($correctOptions);

                    foreach ($studentAnswer as $selectedId) {
                        foreach ($correctOptions as $correctOption) {
                            if ($correctOption['id'] == $selectedId) {
                                $selectedCorrect++;
                                break;
                            }
                        }
                    }

                    // Partial credit: points proportional to correct selections
                    if ($totalCorrect > 0) {
                        $points = ($selectedCorrect / $totalCorrect) * $question['points'];
                    }
                }
                break;

            case 'essay':
            case 'short_answer':
            case 'code':
                // Manual grading required - for now, give 0 points
                // This will be updated when instructor grades
                $points = 0;
                break;

            case 'matching':
                // Check matching answers
                if (is_array($studentAnswer)) {
                    $correctMatches = 0;
                    $totalMatches = 0;

                    foreach ($question['options'] as $option) {
                        if ($option['is_correct']) {
                            $totalMatches++;
                            if (isset($studentAnswer[$option['id']]) &&
                                $studentAnswer[$option['id']] == $option['id']) {
                                $correctMatches++;
                            }
                        }
                    }

                    if ($totalMatches > 0) {
                        $points = ($correctMatches / $totalMatches) * $question['points'];
                    }
                }
                break;

            case 'fill_blanks':
                // Automatic grading for fill-in-the-blanks
                if (is_array($studentAnswer)) {
                    // Get correct answers from explanation, split by comma
                    $correctAnswers = array_map('trim', explode(',', $question['explanation'] ?? ''));
                    $correctCount = 0;
                    $totalBlanks = count($correctAnswers);

                    foreach ($studentAnswer as $index => $answer) {
                        $correctIndex = $index - 1; // answers are 1-based
                        if (isset($correctAnswers[$correctIndex]) &&
                            strtolower(trim($answer)) === strtolower($correctAnswers[$correctIndex])) {
                            $correctCount++;
                        }
                    }

                    if ($totalBlanks > 0) {
                        $points = ($correctCount / $totalBlanks) * $question['points'];
                    }
                }
                break;
        }

        $totalScore += $points;
    }

    $percentage = $maxScore > 0 ? round(($totalScore / $maxScore) * 100, 2) : 0;

    return [
        'score' => round($totalScore, 2),
        'max_score' => $maxScore,
        'percentage' => $percentage,
        'is_passed' => $percentage >= $passingScore
    ];
}

function updateQuestionAnalytics($db, $examId, $answers) {
    // Get exam questions
    $stmt = $db->prepare("
        SELECT eq.question_id, q.question_type
        FROM exam_questions eq
        JOIN questions q ON eq.question_id = q.id
        WHERE eq.exam_id = ?
    ");
    $stmt->execute([$examId]);
    $questions = $stmt->fetchAll(PDO::FETCH_ASSOC);

    $date = date('Y-m-d');

    foreach ($questions as $question) {
        $questionId = $question['question_id'];
        $studentAnswer = $answers[$questionId] ?? null;

        // Get correct answers
        $stmt = $db->prepare("
            SELECT COUNT(*) as correct_count FROM question_options
            WHERE question_id = ? AND is_correct = TRUE
        ");
        $stmt->execute([$questionId]);
        $correctCount = $stmt->fetch(PDO::FETCH_ASSOC)['correct_count'];

        $isCorrect = false;

        if ($studentAnswer) {
            switch ($question['question_type']) {
                case 'mcq_single':
                case 'true_false':
                    $stmt = $db->prepare("
                        SELECT is_correct FROM question_options
                        WHERE id = ? AND question_id = ?
                    ");
                    $stmt->execute([$studentAnswer, $questionId]);
                    $result = $stmt->fetch(PDO::FETCH_ASSOC);
                    $isCorrect = $result && $result['is_correct'];
                    break;

                case 'mcq_multiple':
                    if (is_array($studentAnswer) && !empty($studentAnswer)) {
                        $placeholders = str_repeat('?,', count($studentAnswer) - 1) . "?";
                        $stmt = $db->prepare("
                            SELECT COUNT(*) as correct_selected FROM question_options
                            WHERE id IN ($placeholders) AND is_correct = TRUE
                        ");
                        $stmt->execute($studentAnswer);
                        $correctSelected = $stmt->fetch(PDO::FETCH_ASSOC)['correct_selected'];
                        $isCorrect = $correctSelected == $correctCount;
                    } elseif (is_array($studentAnswer) && empty($studentAnswer)) {
                        $isCorrect = $correctCount == 0; // If no correct options, and student selected none, it's correct
                    }
                    break;
            }
        }

        // Update or insert analytics
        $stmt = $db->prepare("
            INSERT INTO question_analytics (
                question_id, exam_id, date, times_used, correct_answers, incorrect_answers
            ) VALUES (?, ?, ?, 1, ?, ?)
            ON DUPLICATE KEY UPDATE
                times_used = times_used + 1,
                correct_answers = correct_answers + ?,
                incorrect_answers = incorrect_answers + ?
        ");

        $correct = $isCorrect ? 1 : 0;
        $incorrect = $isCorrect ? 0 : 1;

        $stmt->execute([
            $questionId, $examId, $date,
            $correct, $incorrect,
            $correct, $incorrect
        ]);
    }
}
?>