<?php

require_once __DIR__ . '/exam_utils.php';
require_once __DIR__ . '/exam_ai_generator.php';
require_once __DIR__ . '/python_exam_bridge.php';

function extractFallbackSeeds($materials, $requestData) {
    $seeds = [];
    foreach (preg_split('/[,;\n]+/', (string) ($requestData['topics'] ?? '')) as $topic) {
        $topic = trim($topic);
        if ($topic !== '') {
            $seeds[] = $topic;
        }
    }
    foreach (preg_split('/[\r\n]+/', (string) ($requestData['sample_questions'] ?? '')) as $sample) {
        $sample = trim($sample);
        if ($sample !== '') {
            $seeds[] = $sample;
        }
    }
    foreach (($requestData['retrieval_context'] ?? []) as $chunk) {
        if (!empty($chunk['title'])) {
            $seeds[] = trim($chunk['title']);
        }
    }
    foreach ($materials as $material) {
        if (!empty($material['title'])) {
            $seeds[] = trim($material['title']);
        }
    }
    $seeds = array_values(array_unique(array_filter($seeds)));
    return empty($seeds) ? [$requestData['subject_name'] ?? 'Subject', 'Core concepts', 'Applied skills'] : $seeds;
}

function buildSubjectAwareStem($subjectName, $seed, $type, $index) {
    $subject = strtolower((string) $subjectName);
    if ($type === 'objective') {
        if (str_contains($subject, 'mathematics')) {
            $templates = [
                'Which of the following best solves a problem on %s?',
                'The correct mathematical statement about %s is',
                'Which option shows the best reasoning for %s?'
            ];
        } elseif (str_contains($subject, 'english')) {
            $templates = [
                'Which option best explains the use of %s?',
                'Choose the sentence that correctly reflects %s.',
                'Which option best matches the meaning of %s?'
            ];
        } elseif (str_contains($subject, 'science')) {
            $templates = [
                'Which option best explains %s?',
                'The correct scientific statement about %s is',
                'Which option shows the best application of %s?'
            ];
        } else {
            $templates = [
                'Which of the following best explains %s?',
                'One correct statement about %s is',
                'Which option is most closely related to %s?'
            ];
        }
    } else {
        if (str_contains($subject, 'mathematics')) {
            $templates = [
                'Solve a problem on %s and show all workings.',
                'Explain the steps involved in %s with a worked example.',
                'Use %s to solve a practical problem.'
            ];
        } elseif (str_contains($subject, 'english')) {
            $templates = [
                'Explain %s clearly and give suitable examples.',
                'Discuss %s with correct language use and structure.',
                'Write short notes on %s and its importance.'
            ];
        } elseif (str_contains($subject, 'science')) {
            $templates = [
                'Explain %s and relate it to everyday life.',
                'Describe %s and state its practical importance.',
                'Discuss the process or idea behind %s.'
            ];
        } else {
            $templates = [
                'Explain %s in detail and give suitable examples.',
                'Discuss the importance of %s in this subject.',
                'Outline key points learners should know about %s.'
            ];
        }
    }
    return sprintf($templates[$index % count($templates)], $seed);
}

function generatePhpFallbackExam($requestData, $materials) {
    $seeds = extractFallbackSeeds($materials, $requestData);
    $exam = [
        'title' => $requestData['exam_title'],
        'subject' => $requestData['subject_name'],
        'class' => $requestData['class_name'],
        'time' => $requestData['duration'] . ' minutes',
        'instructions' => 'Answer all questions. Use clear steps and workings where necessary.',
        'sections' => []
    ];

    if (($requestData['include_objective'] ?? 'false') === 'true') {
        $questions = [];
        for ($i = 1; $i <= (int) $requestData['obj_count']; $i++) {
            $seed = $seeds[($i - 1) % count($seeds)];
            $questions[] = [
                'number' => $i,
                'text' => buildSubjectAwareStem($requestData['subject_name'], $seed, 'objective', $i - 1),
                'marks' => (int) $requestData['obj_marks'],
                'options' => [
                    'A' => 'A correct answer about ' . $seed,
                    'B' => 'A partly correct answer about ' . $seed,
                    'C' => 'A common misunderstanding about ' . $seed,
                    'D' => 'An unrelated answer about ' . $seed
                ]
            ];
        }
        $exam['sections'][] = [
            'section' => 'A',
            'title' => 'Objective Test',
            'instructions' => 'Choose the best answer for each question.',
            'type' => 'objective',
            'questions' => $questions
        ];
    }

    if (($requestData['include_subjective'] ?? 'false') === 'true') {
        $questions = [];
        for ($i = 1; $i <= (int) $requestData['subj_count']; $i++) {
            $seed = $seeds[($i * 2 - 1) % count($seeds)];
            $questions[] = [
                'number' => $i,
                'text' => buildSubjectAwareStem($requestData['subject_name'], $seed, 'subjective', $i - 1),
                'marks' => (int) $requestData['subj_marks']
            ];
        }
        $exam['sections'][] = [
            'section' => chr(65 + count($exam['sections'])),
            'title' => 'Theory / Structured Questions',
            'instructions' => 'Answer all questions in this section.',
            'type' => 'subjective',
            'questions' => $questions
        ];
    }

    if (($requestData['include_comprehension'] ?? 'false') === 'true') {
        $seed = $seeds[0];
        $exam['sections'][] = [
            'section' => chr(65 + count($exam['sections'])),
            'title' => 'Comprehension',
            'instructions' => 'Read the passage carefully and answer the questions that follow.',
            'type' => 'comprehension',
            'passage' => $seed . ' is an important topic in ' . $requestData['subject_name'] . '. It connects to classroom learning, revision, and practical understanding.',
            'questions' => [
                ['number' => 1, 'text' => 'State the main idea of the passage.', 'marks' => 2],
                ['number' => 2, 'text' => 'Explain one point raised in the passage.', 'marks' => 3],
                ['number' => 3, 'text' => 'Give one suitable title for the passage.', 'marks' => 2]
            ]
        ];
    }

    return normalizeExamData($exam, $requestData);
}

function normalizeGenerationRequest(array $input) {
    $requestData = $input;
    $requestData['exam_type'] = $requestData['exam_type'] ?? 'mid_term';
    $requestData['exam_title'] = trim((string) ($requestData['exam_title'] ?? '')) ?: (($requestData['subject_name'] ?? 'Subject') . ' Examination');
    $requestData['duration'] = max(1, (int) ($requestData['duration'] ?? 60));
    $requestData['total_marks'] = max(1, (int) ($requestData['total_marks'] ?? 100));
    $requestData['include_objective'] = $requestData['include_objective'] ?? 'true';
    $requestData['include_subjective'] = $requestData['include_subjective'] ?? 'true';
    $requestData['include_comprehension'] = $requestData['include_comprehension'] ?? 'false';
    $requestData['obj_count'] = max(1, (int) ($requestData['obj_count'] ?? 20));
    $requestData['obj_marks'] = max(1, (int) ($requestData['obj_marks'] ?? 2));
    $requestData['subj_count'] = max(1, (int) ($requestData['subj_count'] ?? 5));
    $requestData['subj_marks'] = max(1, (int) ($requestData['subj_marks'] ?? 8));
    $requestData['sample_questions'] = trim((string) ($requestData['sample_questions'] ?? ''));
    $requestData['topics'] = trim((string) ($requestData['topics'] ?? ''));
    $requestData['ai_notes'] = trim((string) ($requestData['ai_notes'] ?? ''));
    return $requestData;
}

function validateGenerationRequest(array $requestData) {
    $errors = [];
    foreach (['class_name', 'subject_id', 'subject_name', 'term'] as $field) {
        if (empty($requestData[$field])) {
            $errors[] = 'Missing required field: ' . $field;
        }
    }
    $formatsEnabled = (($requestData['include_objective'] ?? 'false') === 'true')
        || (($requestData['include_subjective'] ?? 'false') === 'true')
        || (($requestData['include_comprehension'] ?? 'false') === 'true');
    if (!$formatsEnabled) {
        $errors[] = 'Select at least one question format before generating an exam.';
    }
    return [
        'valid' => empty($errors),
        'errors' => $errors,
    ];
}

function logGenerationEvent($jobId, $level, $message, array $context = []) {
    $conn = getDBConnection();
    if (!$conn) {
        error_log('Exam generation log skipped: missing DB connection. ' . $message);
        return;
    }
    $contextJson = empty($context) ? null : json_encode($context, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $stmt = $conn->prepare('INSERT INTO exam_generation_logs (job_id, log_level, message, context_json) VALUES (?, ?, ?, ?)');
    $stmt->bind_param('isss', $jobId, $level, $message, $contextJson);
    $stmt->execute();
    $stmt->close();
    error_log('Exam generation [' . $level . '] job ' . $jobId . ': ' . $message . (!empty($context) ? ' ' . json_encode($context) : ''));
}

function createExamGenerationJob(array $requestData, $userId) {
    $conn = getDBConnection();
    $payload = json_encode($requestData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $status = 'queued';
    $maxAttempts = 3;
    $stmt = $conn->prepare('INSERT INTO exam_generation_jobs (created_by, status, request_payload, max_attempts, available_at) VALUES (?, ?, ?, ?, NOW())');
    $stmt->bind_param('issi', $userId, $status, $payload, $maxAttempts);
    $stmt->execute();
    $jobId = (int) $stmt->insert_id;
    $stmt->close();
    logGenerationEvent($jobId, 'info', 'Generation job queued.', [
        'subject' => $requestData['subject_name'] ?? null,
        'class' => $requestData['class_name'] ?? null,
    ]);
    return $jobId;
}

function getExamGenerationJob($jobId, $userId = null) {
    $conn = getDBConnection();
    $query = 'SELECT * FROM exam_generation_jobs WHERE id = ?';
    if ($userId !== null) {
        $query .= ' AND created_by = ?';
        $stmt = $conn->prepare($query);
        $stmt->bind_param('ii', $jobId, $userId);
    } else {
        $stmt = $conn->prepare($query);
        $stmt->bind_param('i', $jobId);
    }
    $stmt->execute();
    $result = $stmt->get_result();
    $job = $result->fetch_assoc();
    $stmt->close();
    if ($job) {
        $job['request_payload'] = json_decode((string) ($job['request_payload'] ?? ''), true) ?: [];
        $job['job_warnings'] = json_decode((string) ($job['job_warnings'] ?? ''), true) ?: [];
    }
    return $job ?: null;
}

function getExamGenerationJobLogs($jobId, $limit = 20) {
    $conn = getDBConnection();
    $limit = max(1, (int) $limit);
    $query = 'SELECT log_level, message, context_json, created_at FROM exam_generation_logs WHERE job_id = ? ORDER BY id DESC LIMIT ' . $limit;
    $stmt = $conn->prepare($query);
    $stmt->bind_param('i', $jobId);
    $stmt->execute();
    $result = $stmt->get_result();
    $logs = [];
    while ($row = $result->fetch_assoc()) {
        $row['context'] = json_decode((string) ($row['context_json'] ?? ''), true) ?: [];
        unset($row['context_json']);
        $logs[] = $row;
    }
    $stmt->close();
    return array_reverse($logs);
}

function claimExamGenerationJob($jobId) {
    $conn = getDBConnection();
    $stmt = $conn->prepare("UPDATE exam_generation_jobs
        SET status = 'processing', attempts = attempts + 1, started_at = COALESCE(started_at, NOW()), locked_at = NOW(), last_error = NULL
        WHERE id = ? AND status IN ('queued', 'retrying') AND available_at <= NOW() AND attempts < max_attempts");
    $stmt->bind_param('i', $jobId);
    $stmt->execute();
    $updated = $stmt->affected_rows > 0;
    $stmt->close();
    return $updated ? getExamGenerationJob($jobId) : null;
}

function markExamGenerationJobCompleted($jobId, $examId, $generatorUsed, array $warnings = []) {
    $conn = getDBConnection();
    $warningsJson = json_encode(array_values(array_unique($warnings)), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
    $status = 'completed';
    $stmt = $conn->prepare('UPDATE exam_generation_jobs SET status = ?, exam_id = ?, generator_used = ?, job_warnings = ?, completed_at = NOW(), locked_at = NULL WHERE id = ?');
    $stmt->bind_param('sissi', $status, $examId, $generatorUsed, $warningsJson, $jobId);
    $stmt->execute();
    $stmt->close();
}

function markExamGenerationJobForRetry($jobId, $attempts, $maxAttempts, $errorMessage) {
    $conn = getDBConnection();
    $status = $attempts < $maxAttempts ? 'retrying' : 'failed';
    $delaySeconds = min(60, max(5, $attempts * 10));
    $query = $status === 'retrying'
        ? 'UPDATE exam_generation_jobs SET status = ?, last_error = ?, available_at = DATE_ADD(NOW(), INTERVAL ? SECOND), locked_at = NULL WHERE id = ?'
        : 'UPDATE exam_generation_jobs SET status = ?, last_error = ?, completed_at = NOW(), locked_at = NULL WHERE id = ?';
    $stmt = $conn->prepare($query);
    if ($status === 'retrying') {
        $stmt->bind_param('ssii', $status, $errorMessage, $delaySeconds, $jobId);
    } else {
        $stmt->bind_param('ssi', $status, $errorMessage, $jobId);
    }
    $stmt->execute();
    $stmt->close();
    return $status;
}

function resolvePhpCliCommand() {
    $candidates = [];
    if (defined('PHP_BINARY')) {
        $candidates[] = PHP_BINARY;
    }
    $candidates[] = 'd:\\xampp\\php\\php.exe';
    $candidates[] = 'php';
    foreach ($candidates as $candidate) {
        $output = [];
        $status = 1;
        @exec((str_contains($candidate, ' ') ? '"' . $candidate . '"' : $candidate) . ' -v 2>&1', $output, $status);
        if ($status === 0) {
            return $candidate;
        }
    }
    return null;
}

function spawnExamGenerationWorker($jobId) {
    $php = resolvePhpCliCommand();
    $workerPath = realpath(dirname(__DIR__) . '/worker/process_generation_job.php');
    if (!$php || !$workerPath) {
        return false;
    }
    $phpCommand = str_contains($php, ' ') ? '"' . $php . '"' : $php;
    if (stripos(PHP_OS_FAMILY, 'Windows') !== false) {
        $command = 'start /B "" ' . $phpCommand . ' ' . escapeshellarg($workerPath) . ' --job=' . (int) $jobId;
        @pclose(@popen($command, 'r'));
        return true;
    }
    $command = $phpCommand . ' ' . escapeshellarg($workerPath) . ' --job=' . (int) $jobId . ' > /dev/null 2>&1 &';
    @exec($command);
    return true;
}

function executeGenerationPipeline(array $requestData, $userId, $jobId) {
    $selectedMaterialIds = [];
    if (!empty($requestData['selected_materials'])) {
        if (is_array($requestData['selected_materials'])) {
            $selectedMaterialIds = array_values(array_filter(array_map('intval', $requestData['selected_materials'])));
        } else {
            $selectedMaterialIds = array_values(array_filter(array_map('intval', explode(',', (string) $requestData['selected_materials']))));
        }
    }

    $materials = getLearningMaterialsWithContent($requestData['class_name'], (int) $requestData['subject_id'], $selectedMaterialIds);
    $requestData['subject_rules'] = getSubjectGenerationRules($requestData['subject_name']);
    $requestData['retrieval_context'] = buildRetrievalContext($materials, $requestData);
    $requestData['format_profile'] = getExamFormatProfile($requestData['class_name'], (int) $requestData['subject_id']);

    logGenerationEvent($jobId, 'info', 'Prepared generation context.', [
        'materials_used' => count($materials),
        'retrieval_chunks' => count($requestData['retrieval_context']),
        'format_profile_applied' => $requestData['format_profile'] !== null,
    ]);

    $generator = 'php_fallback';
    $warnings = [];
    $examData = null;

    // Primary path: unified multi-provider AI engine (Claude → Groq → Gemini → OpenAI).
    $aiAttempts = 2;
    for ($aiAttempt = 1; $aiAttempt <= $aiAttempts; $aiAttempt++) {
        $aiResult = generateExamUsingAIEngine($requestData);
        if (!empty($aiResult['success']) && !empty($aiResult['exam'])) {
            $candidateExam = normalizeExamData($aiResult['exam'], $requestData);
            $candidateSections = $candidateExam['sections'] ?? [];
            if (is_array($candidateSections) && !empty($candidateSections)) {
                $examData = $candidateExam;
                $provider = $aiResult['provider'] ?? 'unknown';
                $generator = 'ai:' . $provider;
                logGenerationEvent($jobId, 'info', 'AI engine generation succeeded.', [
                    'attempt' => $aiAttempt,
                    'provider' => $provider,
                ]);
                break;
            }
            $warnings[] = 'AI engine returned an empty exam structure.';
            logGenerationEvent($jobId, 'warning', 'AI engine returned an empty exam structure.', [
                'attempt' => $aiAttempt,
            ]);
            continue;
        }
        $message = trim((string) ($aiResult['message'] ?? 'AI engine failed.'));
        $warnings[] = $message;
        logGenerationEvent($jobId, 'warning', 'AI engine generation attempt failed.', [
            'attempt' => $aiAttempt,
            'message' => $message,
        ]);
    }

    // Last-resort fallback: deterministic PHP template generator. Never silent — surfaced as a warning.
    if (!$examData) {
        $examData = generatePhpFallbackExam($requestData, $materials);
        $warnings[] = 'All AI providers were unavailable; used the offline template generator. Review the questions before publishing.';
        logGenerationEvent($jobId, 'warning', 'Used PHP fallback generator.', [
            'warnings' => $warnings,
        ]);
    }

    $examData = normalizeExamData($examData, $requestData);
    $examData = enrichExamAssessmentData($examData, $requestData);
    $validation = validateExamData($examData, $requestData);
    $warnings = array_values(array_unique(array_merge($warnings, $validation['warnings'], moderateGeneratedExam($examData, $requestData))));

    if (!$validation['valid']) {
        throw new RuntimeException('Generated exam failed validation: ' . implode(' | ', $validation['errors']));
    }

    $examId = saveExamToDatabase(
        $requestData,
        $examData,
        (int) $userId,
        null,
        [
            'generator_used' => $generator,
            'warnings' => $warnings,
        ]
    );

    logGenerationEvent($jobId, 'info', 'Exam draft saved.', [
        'exam_id' => $examId,
        'generator' => $generator,
        'computed_total_marks' => $validation['computed_total_marks'],
        'warnings' => $warnings,
    ]);

    return [
        'exam_id' => $examId,
        'generator' => $generator,
        'warnings' => $warnings,
        'materials_used' => count($materials),
        'retrieval_chunks' => count($requestData['retrieval_context']),
        'computed_total_marks' => $validation['computed_total_marks'],
    ];
}

function processExamGenerationJob($jobId) {
    $job = claimExamGenerationJob($jobId);
    if (!$job) {
        return ['success' => false, 'message' => 'Job is not available for processing.'];
    }

    logGenerationEvent($jobId, 'info', 'Generation worker started.', [
        'attempts' => (int) $job['attempts'],
        'max_attempts' => (int) $job['max_attempts'],
    ]);

    try {
        $result = executeGenerationPipeline($job['request_payload'], (int) $job['created_by'], $jobId);
        markExamGenerationJobCompleted($jobId, (int) $result['exam_id'], $result['generator'], $result['warnings']);
        logGenerationEvent($jobId, 'info', 'Generation job completed.', [
            'exam_id' => $result['exam_id'],
            'generator' => $result['generator'],
        ]);
        return ['success' => true] + $result;
    } catch (Throwable $e) {
        $attempts = (int) $job['attempts'];
        $maxAttempts = (int) $job['max_attempts'];
        $status = markExamGenerationJobForRetry($jobId, $attempts, $maxAttempts, $e->getMessage());
        logGenerationEvent($jobId, $status === 'retrying' ? 'warning' : 'error', 'Generation job failed.', [
            'status' => $status,
            'attempts' => $attempts,
            'max_attempts' => $maxAttempts,
            'message' => $e->getMessage(),
        ]);
        return ['success' => false, 'message' => $e->getMessage(), 'status' => $status];
    }
}

function formatCompletedGenerationJobResponse(array $job, array $sessionUser) {
    $examRecord = loadExamRecord((int) $job['exam_id']);
    if (!$examRecord) {
        return [
            'success' => false,
            'message' => 'The generation job completed, but the exam record could not be loaded.',
        ];
    }

    return [
        'success' => true,
        'queued' => false,
        'job_status' => 'completed',
        'status' => $examRecord['status'] ?? 'draft',
        'job_id' => (int) $job['id'],
        'message' => 'Exam generated successfully.',
        'exam_id' => (int) $job['exam_id'],
        'status_label' => getStatusLabel($examRecord['status'] ?? 'draft'),
        'preview' => generateExamPreviewHtml($examRecord),
        'workflow_html' => generateWorkflowSummaryHtml($examRecord, $sessionUser),
        'exam_data' => $examRecord['exam_data'],
        'generator' => $job['generator_used'] ?? 'unknown',
        'warnings' => $job['job_warnings'] ?? [],
        'details' => [],
        'logs' => getExamGenerationJobLogs((int) $job['id']),
    ];
}
