<?php

declare(strict_types=1);

namespace App\Libraries;

use App\Models\DocumentChunkModel;
use App\Models\DocumentModel;
use RuntimeException;

class QuizGeneratorService
{
    private OpenAiService $openAi;
    private DocumentModel $documentModel;
    private DocumentChunkModel $chunkModel;

    public function __construct()
    {
        $this->openAi        = new OpenAiService();
        $this->documentModel = new DocumentModel();
        $this->chunkModel    = new DocumentChunkModel();
    }

    public function isAiConfigured(): bool
    {
        return $this->openAi->isConfigured();
    }

    private function gatherContextChunks(array $documentIds, int $minChunks = 20, int $maxChars = 28000): string
    {
        $docIds = [];
        foreach ($documentIds as $id) {
            $id = (int)$id;
            if ($id > 0) $docIds[] = $id;
        }
        if ($docIds === []) return '';

        $chunks = $this->chunkModel
            ->whereIn('document_id', $docIds)
            ->orderBy('document_id', 'ASC')
            ->orderBy('chunk_index', 'ASC')
            ->findAll($minChunks * 5);

        $docs = $this->documentModel
            ->whereIn('id', $docIds)
            ->findAll();
        $docTitles = [];
        foreach ($docs as $d) $docTitles[(int)($d['id'] ?? 0)] = (string)($d['title'] ?? 'Document');

        $out = '';
        $count = 0;
        foreach ($chunks as $c) {
            if ($count >= $minChunks && strlen($out) > 8000) break;
            $text = (string)($c['content_text'] ?? '');
            if ($text === '') continue;
            $did = (int)($c['document_id'] ?? 0);
            $title = $docTitles[$did] ?? 'Document';
            $chunkIdx = (int)($c['chunk_index'] ?? 0);
            $line = "\n--- Doc: {$title} (#{$did}) | chunk {$chunkIdx} ---\n" . $text . "\n";
            if (strlen($out) + strlen($line) > $maxChars) break;
            $out .= $line;
            $count++;
        }
        if (trim($out) === '') {
            foreach ($docs as $d) {
                $txt = (string)($d['content_text'] ?? $d['summary'] ?? '');
                if ($txt === '') continue;
                $out .= "\n--- Doc: " . ($d['title'] ?? 'Document') . " ---\n" . mb_substr($txt, 0, 6000) . "\n";
                if (strlen($out) > $maxChars) break;
            }
        }
        return $out;
    }

    public function generate(array $documentIds, int $count, string $questionType, ?array $excludeSignatures = null): array
    {
        $count = max(1, min(30, $count));
        $type = in_array($questionType, ['multiple_choice', 'fill_up'], true) ? $questionType : 'multiple_choice';
        $exclude = is_array($excludeSignatures) ? $excludeSignatures : [];
        $excludeText = $exclude !== [] ? 'Avoid repeating these question signatures (sha1 of question): [' . implode(', ', array_slice(array_keys($exclude), 0, 100)) . ']. Prefer fresh angles and different factual points than prior attempts. If a question would repeat, rewrite it from a different angle or pick a different fact. ' : '';

        $context = $this->gatherContextChunks($documentIds, $count * 3, 28000);
        if (trim($context) === '') {
            throw new RuntimeException('No content found in the selected documents. Train documents first, then try again.');
        }
        if (!$this->openAi->isConfigured()) {
            return $this->generateFallback($context, $count, $type);
        }

        $formatHint = '';
        if ($type === 'multiple_choice') {
            $formatHint = 'Return ONLY a valid JSON array with EXACTLY ' . $count . ' objects. No extra text, no markdown, no commentary. '
                . 'Each object shape: {"question":"string","correct_answer":"string (exact choice letter value like A, B, C or D)","options":[{"letter":"A","text":"..."},{"letter":"B","text":"..."},{"letter":"C","text":"..."},{"letter":"D","text":"..."}],"rationale":"brief 1-sentence why correct","source_hint":"short substring from context"}';
        } else {
            $formatHint = 'Return ONLY a valid JSON array with EXACTLY ' . $count . ' objects. No extra text, no markdown, no commentary. '
                . 'Each object shape: {"question":"string fill-in-the-blank sentence with ___ or short-answer prompt","correct_answer":"concise canonical answer 1-20 words","rationale":"brief 1-sentence why","source_hint":"short substring from context"}';
        }

        $system = 'You generate high-quality training quizzes for outbound sales/customer-service agents using ONLY the provided training documents. '
            . 'Rules:'
            . "\n" . '- Questions must test factual recall, objection handling, process steps, rebuttals, pricing, qualifying, scripts, and policy from the documents.'
            . "\n" . '- Correct answer MUST appear literally or near-literally in the provided context documents. Do not invent answers outside the context.'
            . "\n" . '- Make each question unique and non-obvious; avoid trivial yes/no or duplicate angles.'
            . "\n" . '- Use a diverse mix of topics spread across all provided documents.'
            . "\n" . ' ' . $excludeText
            . "\n" . $formatHint;

        $user = "Generate EXACTLY {$count} {$type} questions using ONLY the training-doc content below as the source of truth.\n\nTRAINING DOCS:\n" . $context;

        try {
            $raw = $this->openAi->chatCompletions([
                ['role' => 'system', 'content' => $system],
                ['role' => 'user',   'content' => $user],
            ], 0.35);
            return $this->parseQuestions($raw, $count, $type);
        } catch (\Throwable $e) {
            log_message('error', 'QuizGeneratorService generate failed: ' . $e->getMessage());
            return $this->generateFallback($context, $count, $type);
        }
    }

    private function parseQuestions(string $raw, int $count, string $type): array
    {
        $trim = trim($raw);
        $start = strpos($trim, '[');
        $end = strrpos($trim, ']');
        if ($start !== false && $end !== false) {
            $json = substr($trim, $start, $end - $start + 1);
            $arr = json_decode($json, true);
            if (is_array($arr)) {
                $out = [];
                foreach ($arr as $item) {
                    if (!is_array($item)) continue;
                    $q = trim((string)($item['question'] ?? ''));
                    $a = trim((string)($item['correct_answer'] ?? ''));
                    if ($q === '' || $a === '') continue;
                    $rec = [
                        'question_text' => $q,
                        'correct_answer' => $a,
                        'question_type' => $type,
                        'rationale' => trim((string)($item['rationale'] ?? '')),
                        'source_hint' => trim((string)($item['source_hint'] ?? '')),
                    ];
                    if ($type === 'multiple_choice') {
                        $opts = $item['options'] ?? [];
                        $normalized = [];
                        if (is_array($opts)) {
                            $letters = range('A', 'Z');
                            $li = 0;
                            foreach ($opts as $o) {
                                $letter = '';
                                $text = '';
                                if (is_array($o)) {
                                    $letter = (string)($o['letter'] ?? '');
                                    $text = trim((string)($o['text'] ?? ''));
                                } elseif (is_string($o)) {
                                    $text = trim($o);
                                }
                                if ($letter === '') $letter = $letters[$li] ?? (string)($li + 1);
                                if ($text !== '') {
                                    $normalized[] = ['letter' => strtoupper($letter), 'text' => $text];
                                }
                                $li++;
                            }
                        }
                        if (count($normalized) < 2) continue;
                        $rec['options'] = $normalized;
                    }
                    $out[] = $rec;
                    if (count($out) >= $count) break;
                }
                if (count($out) >= max(1, (int)ceil($count / 2))) return $out;
            }
        }

        // Fallback line-based parse
        $lines = preg_split('/\r\n|\r|\n/', $trim);
        $out = [];
        $cur = null;
        foreach ($lines as $line) {
            $line = trim($line);
            if ($line === '') continue;
            if (preg_match('/^\s*\d+[\.\)]\s*(.+)\s*$/', $line, $m)) {
                if ($cur !== null) $out[] = $cur;
                $cur = ['question_text' => $m[1], 'correct_answer' => '', 'question_type' => $type, 'rationale' => '', 'source_hint' => ''];
                if ($type === 'multiple_choice') $cur['options'] = [];
            } elseif ($cur !== null) {
                if (stripos($line, 'correct') !== false || stripos($line, 'answer') !== false) {
                    $cur['correct_answer'] = preg_replace('/^[^:：]+[:：]\s*/', '', $line);
                } elseif ($type === 'multiple_choice' && preg_match('/^\s*([A-Z])[\.\)]\s*(.+)$/', $line, $om)) {
                    $cur['options'][] = ['letter' => $om[1], 'text' => $om[2]];
                }
            }
        }
        if ($cur !== null) $out[] = $cur;
        $out = array_values(array_filter($out, static fn($x) => $x['question_text'] !== '' && $x['correct_answer'] !== ''));
        if ($out === []) $out = $this->generateFallback('', $count, $type);
        return array_slice($out, 0, $count);
    }

    private function generateFallback(string $context, int $count, string $type): array
    {
        $rawSents = [];
        $text = preg_replace('/[\r\n\t]+/', ' ', $context);
        if ($text !== '') {
            $sents = preg_split('/(?<=[.!?])\s+/u', $text, -1, PREG_SPLIT_NO_EMPTY);
            if (is_array($sents)) {
                foreach ($sents as $s) {
                    $s = trim($s);
                    if (mb_strlen($s) >= 20 && mb_strlen($s) <= 240) $rawSents[] = $s;
                }
            }
        }
        if ($rawSents === []) {
            for ($i = 0; $i < $count; $i++) {
                $rawSents[] = 'Sales agents should always verify customer information before making recommendations.';
            }
        }
        $keys = array_rand($rawSents, min(count($rawSents), $count * 2));
        if (!is_array($keys)) $keys = [$keys];
        shuffle($keys);
        $out = [];
        $seen = [];
        $letters = ['A', 'B', 'C', 'D'];
        foreach ($keys as $k) {
            if (count($out) >= $count) break;
            $sent = $rawSents[$k];
            $words = preg_split('/\s+/u', $sent, -1, PREG_SPLIT_NO_EMPTY);
            if (!is_array($words) || count($words) < 6) continue;
            $candidates = [];
            foreach ($words as $w) {
                $clean = preg_replace('/[^A-Za-z0-9\-]/u', '', $w);
                if (mb_strlen($clean) >= 5) $candidates[] = $clean;
            }
            if ($candidates === []) continue;
            $blanks = array_values(array_unique($candidates));
            $blank = $blanks[array_rand($blanks)] ?? null;
            if ($blank === null) continue;
            $normKey = mb_strtolower($blank);
            if (isset($seen[$normKey])) continue;
            $seen[$normKey] = true;
            $qText = str_ireplace($blank, '__________', $sent);
            if ($type === 'multiple_choice') {
                $wrongPool = array_values(array_filter($blanks, static fn($x) => mb_strtolower($x) !== $normKey));
                shuffle($wrongPool);
                $opts = [$blank];
                while (count($opts) < 4 && $wrongPool !== []) $opts[] = array_shift($wrongPool);
                while (count($opts) < 4) $opts[] = 'Option ' . (count($opts) + 1);
                shuffle($opts);
                $correctLetter = '';
                $optObjs = [];
                foreach (array_values($opts) as $idx => $optText) {
                    $letter = $letters[$idx] ?? (string)($idx + 1);
                    $optObjs[] = ['letter' => $letter, 'text' => $optText];
                    if (mb_strtolower($optText) === $normKey) $correctLetter = $letter;
                }
                $out[] = [
                    'question_text' => $qText,
                    'correct_answer' => $correctLetter,
                    'question_type' => 'multiple_choice',
                    'options' => $optObjs,
                    'rationale' => $blank . ' is the term that completes the statement per the training document.',
                    'source_hint' => mb_substr($sent, 0, 80),
                ];
            } else {
                $out[] = [
                    'question_text' => $qText,
                    'correct_answer' => $blank,
                    'question_type' => 'fill_up',
                    'rationale' => $blank . ' is the missing term in this sentence.',
                    'source_hint' => mb_substr($sent, 0, 80),
                ];
            }
        }
        while (count($out) < $count) {
            $out[] = [
                'question_text' => 'Describe one key responsibility of an outbound agent per the training materials.',
                'correct_answer' => 'Follow the script and document call outcomes',
                'question_type' => $type,
                'rationale' => 'Following scripts and accurate documentation are core agent responsibilities.',
                'source_hint' => '',
            ] + ($type === 'multiple_choice' ? ['options' => [
                ['letter' => 'A', 'text' => 'Follow the script and document call outcomes'],
                ['letter' => 'B', 'text' => 'Ignore customer objections'],
                ['letter' => 'C', 'text' => 'Skip logging notes'],
                ['letter' => 'D', 'text' => 'Hang up before greeting'],
            ], 'correct_answer' => 'A'] : []);
        }
        return array_slice($out, 0, $count);
    }

    public function gradeAndSuggestions(array $questionsWithAnswers, array $selectedDocumentIds, float $scorePct, bool $passed, string $questionType): string
    {
        $qaLines = [];
        foreach ($questionsWithAnswers as $i => $q) {
            $n = $i + 1;
            $qaLines[] = "Q{$n}: " . ($q['question_text'] ?? '');
            $qaLines[] = "Correct answer: " . ($q['correct_answer'] ?? '');
            $qaLines[] = "User answer: " . (trim((string)($q['user_answer'] ?? '')) === '' ? '(blank / timed out)' : (string)$q['user_answer']);
            $qaLines[] = "Result: " . (!empty($q['is_correct']) ? 'CORRECT' : 'INCORRECT');
            $qaLines[] = '';
        }
        $qaBlock = implode("\n", array_slice($qaLines, 0, 300));
        $docCount = count($selectedDocumentIds);
        $passLabel = $passed ? 'PASSED (meets threshold)' : 'DID NOT PASS (below threshold)';

        $system = 'You are a patient sales/customer-service training coach reviewing a quiz attempt. '
            . 'Produce a 3-section, plain-text (non-markdown) review, about 120-220 words total. Sections: '
            . "\n" . '1) Overall summary — score %, pass status, 1-sentence tone-setting.'
            . "\n" . '2) What went well — 2-3 specific strengths from correct answers; praise by question themes if possible.'
            . "\n" . '3) Actionable improvements — pick exactly 2-3 most-missed topics from incorrect/timed-out questions and suggest concrete study steps (re-read specific docs, practice script aloud, role-play the scenario with a peer, create flashcards for missed facts).';

        $user = "Quiz breakdown:\n"
            . "Score: {$scorePct}% — {$passLabel}\n"
            . "Document count used: {$docCount}\n"
            . "Question type: {$questionType}\n\n"
            . "Question-by-question:\n" . $qaBlock . "\n"
            . "Generate the 3-section feedback now.";

        try {
            if ($this->openAi->isConfigured()) {
                return trim($this->openAi->chatCompletions([
                    ['role' => 'system', 'content' => $system],
                    ['role' => 'user',   'content' => $user],
                ], 0.45));
            }
        } catch (\Throwable $e) {
            log_message('error', 'QuizGeneratorService gradeAndSuggestions ai failed: ' . $e->getMessage());
        }

        $overall = "Overall: Score {$scorePct}% ({$passLabel}). " . ($passed
            ? 'You demonstrated solid grasp of the core material — keep sharpening weak spots to stay consistent.'
            : 'You are below the mastery threshold. Target the missed topics with focused study and re-attempt in 20-30 minutes.');
        $correctCount = 0; $missed = [];
        foreach ($questionsWithAnswers as $q) {
            if (!empty($q['is_correct'])) $correctCount++;
            else { $missed[] = trim((string)($q['question_text'] ?? '')); }
        }
        $well = "What went well: {$correctCount}/" . count($questionsWithAnswers) . " correct. "
            . "You retained factual points across the selected training documents. "
            . ($correctCount > 0 ? "Use those remembered topics as anchors; keep applying the same study habits." : "No questions correct yet — this is common on the first pass. Start by reading the key summaries end-to-end before the next attempt.");
        $missedSnippet = '';
        if ($missed !== []) {
            $titles = [];
            foreach ($missed as $m) {
                $s = mb_substr($m, 0, 48);
                if ($s !== '' && !in_array($s, $titles, true)) $titles[] = $s;
            }
            $missedSnippet = implode("; ", array_slice($titles, 0, 3));
        }
        $improve = "Actionable improvements: (1) Re-read sections of the selected documents that cover: " . ($missedSnippet !== '' ? $missedSnippet : 'every major topic') . ". (2) Make 4-6 flashcards for the missed facts and quiz yourself out loud. (3) Role-play the scenario with a peer if applicable, then re-take this quiz — multiple passes lock in retention.";
        return $overall . "\n\n" . $well . "\n\n" . $improve;
    }
}
