<?php
require_once '../../config/database.php';
require_once '../../config/auth.php';
require_once '../../config/rbac.php';
require_once '../../includes/ExamGradingService.php';

header('Content-Type: application/json');

function sendExamJson(array $data, int $httpCode = 200): void
{
    http_response_code($httpCode);
    echo json_encode($data);
    exit;
}

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

$db = getDB();
$userId = (int) $_SESSION['user_id'];
$role = $_SESSION['role'] ?? 'student';

if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
    sendExamJson(['success' => false, 'message' => 'Method not allowed'], 405);
}

$rawInput = file_get_contents('php://input');
$data = json_decode($rawInput, true);
if (!is_array($data)) {
    sendExamJson(['success' => false, 'message' => 'Invalid JSON data'], 400);
}

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

if (!$examId || !$action) {
    sendExamJson(['success' => false, 'message' => 'Exam ID and action are required'], 400);
}

try {
    $stmt = $db->prepare("
        SELECT e.*, c.title as course_title
        FROM exams e
        LEFT JOIN courses c ON c.id = e.course_id
        WHERE e.id = ?
    ");
    $stmt->execute([$examId]);
    $exam = $stmt->fetch(PDO::FETCH_ASSOC);

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

    if (in_array($role, ['student', 'instructor', 'admin'], true)) {
        $studentId = $userId;

        if ($role === 'student') {
            if ($exam['course_id']) {
                $stmt = $db->prepare("
                    SELECT id
                    FROM course_enrollments
                    WHERE course_id = ? AND student_id = ? AND status IN ('enrolled','in_progress','completed')
                ");
                $stmt->execute([$exam['course_id'], $studentId]);
                if (!$stmt->fetch()) {
                    throw new Exception('You must be enrolled in this course to take this exam');
                }
            }

            $stmt = $db->prepare("
                SELECT id
                FROM exam_access
                WHERE exam_id = ? AND student_id = ? AND payment_status IN ('paid','free')
            ");
            $stmt->execute([$examId, $studentId]);
            if (!$stmt->fetch() && (int) $exam['is_free'] !== 1) {
                throw new Exception('You do not have access to this exam');
            }
        } elseif ($role === 'instructor' || $role === 'admin') {
            if ($role === 'instructor' && (int) $exam['instructor_id'] !== $userId) {
                throw new Exception('Access denied');
            }
            $studentId = (int) ($data['student_id'] ?? $userId);
        } else {
            throw new Exception('Access denied');
        }
    } else {
        throw new Exception('Access denied');
    }

    switch ($action) {
        case 'start':
            handleStartAttempt($db, $examId, $studentId, $exam);
            break;
        case 'save':
            handleSaveProgress($db, $examId, $studentId, $data);
            break;
        case 'submit':
            handleSubmitExam($db, $examId, $studentId, $data, $exam);
            break;
        case 'get':
            handleGetAttempt($db, $examId, $studentId);
            break;
        default:
            throw new Exception('Invalid action');
    }
} catch (Throwable $e) {
    sendExamJson(['success' => false, 'message' => $e->getMessage()], 400);
}

function handleStartAttempt(PDO $db, int $examId, int $studentId, array $exam): void
{
    $stmt = $db->prepare("
        SELECT id, attempt_number, started_at, time_spent_seconds, time_spent, 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]);
    $existing = $stmt->fetch(PDO::FETCH_ASSOC);

    if ($existing) {
        $timeLimit = (int) ($exam['time_limit'] ?? 0);
        if ($timeLimit > 0) {
            $expiresAt = strtotime($existing['started_at']) + ($timeLimit * 60);
            if ($expiresAt <= time()) {
                autoSubmitAttempt($db, $existing, $examId, $studentId, $exam);
            } else {
                sendExamJson([
                    'success' => true,
                    'message' => 'Resumed existing attempt',
                    'attempt_id' => (int) $existing['id'],
                    'attempt_number' => (int) $existing['attempt_number'],
                    'time_remaining' => max(0, $expiresAt - time())
                ]);
            }
        } else {
            sendExamJson([
                'success' => true,
                'message' => 'Resumed existing attempt',
                'attempt_id' => (int) $existing['id'],
                'attempt_number' => (int) $existing['attempt_number'],
                'time_remaining' => null
            ]);
        }
    }

    $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 = ((int) $attemptInfo['max_attempt'] ?: 0) + 1;
    $maxAttempts = (int) ($exam['max_attempts'] ?? 0);

    if ($maxAttempts > 0 && $nextAttemptNumber > $maxAttempts) {
        throw new Exception('Maximum attempts exceeded');
    }

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

    $stmt = $db->prepare("
        INSERT INTO exam_attempts (
            exam_id, student_id, attempt_number, status, started_at,
            last_accessed, ip_address, browser_info, answers
        ) VALUES (?, ?, ?, 'in_progress', NOW(), NOW(), ?, ?, '{}')
    ");
    $stmt->execute([$examId, $studentId, $nextAttemptNumber, $ipAddress, $browserInfo]);
    $attemptId = (int) $db->lastInsertId();

    sendExamJson([
        'success' => true,
        'message' => 'Exam attempt started',
        'attempt_id' => $attemptId,
        'attempt_number' => $nextAttemptNumber,
        'time_limit' => (int) ($exam['time_limit'] ?? 0) * 60
    ]);
}

function handleSaveProgress(PDO $db, int $examId, int $studentId, array $data): void
{
    $attempt = getActiveAttempt($db, $examId, $studentId);
    if (!$attempt) {
        throw new Exception('No active attempt found');
    }

    $exam = getExamById($db, $examId);
    assertTimeLimit($attempt, $exam);

    $answers = is_array($data['answers'] ?? null) ? $data['answers'] : [];
    $timeSpent = max(0, (int) ($data['time_spent'] ?? 0));
    $existingAnswers = ExamGradingService::decodeJson($attempt['answers'] ?? '{}');
    if (!is_array($existingAnswers)) {
        $existingAnswers = [];
    }
    $mergedAnswers = array_replace_recursive($existingAnswers, $answers);

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

    if (!empty($answers)) {
        ExamGradingService::saveStudentAnswers($db, $examId, $studentId, (int) $attempt['id'], $answers, [
            'question_scores' => []
        ], $timeSpent);
    }

    sendExamJson([
        'success' => true,
        'message' => 'Progress saved',
        'time_remaining' => getTimeRemaining($attempt, $exam)
    ]);
}

function handleSubmitExam(PDO $db, int $examId, int $studentId, array $data, array $exam): void
{
    $attempt = getActiveAttempt($db, $examId, $studentId);
    if (!$attempt) {
        throw new Exception('No active attempt found');
    }

    assertTimeLimit($attempt, $exam);

    $answers = is_array($data['answers'] ?? null) ? $data['answers'] : [];
    $existingAnswers = ExamGradingService::decodeJson($attempt['answers'] ?? '{}');
    if (!is_array($existingAnswers)) {
        $existingAnswers = [];
    }
    $mergedAnswers = array_replace_recursive($existingAnswers, $answers);
    $timeSpent = max(0, (int) ($data['time_spent'] ?? 0));

    $scoreData = ExamGradingService::gradeExam($db, $examId, $mergedAnswers, (float) $exam['passing_score']);
    $status = $scoreData['needs_manual_review'] ? 'submitted' : 'graded';

    ExamGradingService::saveStudentAnswers($db, $examId, $studentId, (int) $attempt['id'], $mergedAnswers, $scoreData, $timeSpent);

    $stmt = $db->prepare("
        UPDATE exam_attempts
        SET status = ?,
            score = ?,
            max_score = ?,
            percentage = ?,
            is_passed = ?,
            completed_at = NOW(),
            time_spent_seconds = ?,
            time_spent = ?,
            answers = ?,
            last_accessed = NOW()
        WHERE id = ?
    ");
    $stmt->execute([
        $status,
        $scoreData['score'],
        $scoreData['max_score'],
        $scoreData['percentage'],
        ExamGradingService::boolValue($scoreData['is_passed']),
        $timeSpent,
        $timeSpent,
        json_encode($mergedAnswers, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
        $attempt['id']
    ]);

    sendExamJson([
        'success' => true,
        'message' => 'Exam submitted successfully',
        'attempt_id' => (int) $attempt['id'],
        'score' => $scoreData['score'],
        'max_score' => $scoreData['max_score'],
        'percentage' => $scoreData['percentage'],
        'passed' => $scoreData['is_passed'],
        'needs_manual_review' => $scoreData['needs_manual_review']
    ]);
}

function handleGetAttempt(PDO $db, int $examId, int $studentId): void
{
    $stmt = $db->prepare("
        SELECT ea.*, e.title as exam_title, e.total_points, e.passing_score, e.time_limit,
               COALESCE(ea.time_spent_seconds, ea.time_spent, 0) as time_spent
        FROM exam_attempts ea
        JOIN exams e ON e.id = ea.exam_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');
    }

    $answers = ExamGradingService::getAttemptAnswers($db, (int) $attempt['id']);

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

function getActiveAttempt(PDO $db, int $examId, int $studentId): ?array
{
    $stmt = $db->prepare("
        SELECT *
        FROM exam_attempts
        WHERE exam_id = ? AND student_id = ? AND status = 'in_progress'
        ORDER BY started_at DESC
        LIMIT 1
    ");
    $stmt->execute([$examId, $studentId]);
    return $stmt->fetch(PDO::FETCH_ASSOC) ?: null;
}

function getExamById(PDO $db, int $examId): array
{
    $stmt = $db->prepare("SELECT * FROM exams WHERE id = ?");
    $stmt->execute([$examId]);
    $exam = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$exam) {
        throw new Exception('Exam not found');
    }
    return $exam;
}

function assertTimeLimit(array $attempt, array $exam): void
{
    $timeLimit = (int) ($exam['time_limit'] ?? 0);
    if ($timeLimit <= 0) {
        return;
    }

    $expiresAt = strtotime($attempt['started_at']) + ($timeLimit * 60);
    if ($expiresAt <= time()) {
        autoSubmitAttempt(getDB(), $attempt, (int) $attempt['exam_id'], (int) $attempt['student_id'], $exam);
        throw new Exception('Time expired - attempt auto-submitted');
    }
}

function getTimeRemaining(array $attempt, array $exam): ?int
{
    $timeLimit = (int) ($exam['time_limit'] ?? 0);
    if ($timeLimit <= 0) {
        return null;
    }

    return max(0, strtotime($attempt['started_at']) + ($timeLimit * 60) - time());
}

function autoSubmitAttempt(PDO $db, array $attempt, int $examId, int $studentId, array $exam): void
{
    $answers = ExamGradingService::getAttemptAnswers($db, (int) $attempt['id']);
    $scoreData = ExamGradingService::gradeExam($db, $examId, $answers, (float) $exam['passing_score']);
    $timeSpent = (int) ($attempt['time_spent_seconds'] ?? $attempt['time_spent'] ?? 0);
    $status = $scoreData['needs_manual_review'] ? 'submitted' : 'graded';

    ExamGradingService::saveStudentAnswers($db, $examId, $studentId, (int) $attempt['id'], $answers, $scoreData, $timeSpent);

    $stmt = $db->prepare("
        UPDATE exam_attempts
        SET status = ?,
            score = ?,
            max_score = ?,
            percentage = ?,
            is_passed = ?,
            completed_at = NOW(),
            time_spent_seconds = ?,
            time_spent = ?,
            last_accessed = NOW()
        WHERE id = ?
    ");
    $stmt->execute([
        $status,
        $scoreData['score'],
        $scoreData['max_score'],
        $scoreData['percentage'],
        ExamGradingService::boolValue($scoreData['is_passed']),
        $timeSpent,
        $timeSpent,
        $attempt['id']
    ]);
}
