<?php

namespace App\Http\Controllers;

use App\Models\Option;
use App\Models\Question;
use App\Models\QuizAttempt;
use App\Models\QuizAttemptItem;
use App\Models\Subject;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\QuizAttemptEvent;
use App\Models\AppSetting;
class QuizAttemptController extends Controller
{
    public function start(Request $request)
    {
        $request->validate([
            'subject_id' => ['required','exists:subjects,id'],
            'max_items'  => ['required','integer','in:15,30,45,60'],
        ]);

        $attempt = QuizAttempt::create([
            'user_id'     => Auth::id(),
            'subject_id'  => (int)$request->subject_id,
            'max_items'   => (int)$request->max_items,
            'start_level' => AppSetting::getInt('cat.start_level', 5),
            'start_theta' => 0,
            'started_at'  => now(),
        ]);

        return redirect()->route('attempts.show', $attempt->id);
    }

    // Placeholder page — we’ll flesh this out next step

    public function show(QuizAttempt $attempt)
    {
        abort_unless($attempt->user_id === Auth::id(), 403);
        return view('attempts.show', compact('attempt'));
    }

    /**
     * Return next question (JSON) at current target difficulty, avoiding repeats.
     */
    public function next(QuizAttempt $attempt, Request $request)
    {
        abort_unless($attempt->user_id === Auth::id(), 403);

        // 1) If there is a previously served-but-unanswered question, return it
        $pending = QuizAttemptEvent::where('attempt_id', $attempt->id)
            ->where('type', 'served')
            ->latest('created_at')
            ->first();

        if ($pending) {
            $qid = data_get($pending->payload_json, 'question_id');
            if ($qid && !QuizAttemptItem::where('attempt_id', $attempt->id)->where('question_id', $qid)->exists()) {
                $q = Question::where('id', $qid)->where('is_active', true)->first();
                if ($q) {
                    $options = $q->options()->ordered()->get(['id','label','text_html'])->shuffle()->values()->map(fn($o) => [
                        'id'=>$o->id, 'label'=>$o->label, 'text'=>$o->text_html,
                    ]);
                    return response()->json([
                        'status'=>'ok',
                        'question'=>[
                            'id'=>$q->id, 'level'=>$q->level, 'b'=>$q->b,
                            'lang'=>$q->question_language, 'stem'=>$q->stem_html, 'options'=>$options,
                        ],
                        'progress'=>[
                            'answered'=>$attempt->items()->count(),
                            'total'=>$attempt->max_items,
                        ],
                        'target_level'=>($attempt->items()->latest('position_index')->first()?->target_level_next ?? $attempt->start_level),
                    ]);
                }
            }
        }

        // 2) Otherwise, compute target level and pick a new question (with language fallback)
        $last = $attempt->items()->orderByDesc('position_index')->first();
        $targetLevel = $last?->target_level_next ?? $attempt->start_level;
        $servedIds = $attempt->items()->pluck('question_id')->all();
        $subjectId = $attempt->subject_id;

        $wanted = Auth::user()->preferred_language ?? app()->getLocale();
        $fallback = $wanted === 'ar' ? 'en' : 'ar';
        $langOrders = array_unique([$wanted, $fallback, '*']);

        $levels = [$targetLevel];
        for ($d=1; $d<=4; $d++) {
            if ($targetLevel-$d >= 1) $levels[] = $targetLevel-$d;
            if ($targetLevel+$d <= 9) $levels[] = $targetLevel+$d;
        }

        $q = null;
        foreach ($langOrders as $langTry) {
            foreach ($levels as $lvl) {
                $q = Question::query()
                    ->where('subject_id', $subjectId)
                    ->when($langTry !== '*', fn($qq) => $qq->where('question_language', $langTry))
                    ->where('level', $lvl)->where('is_active', true)
                    ->when(!empty($servedIds), fn($qq) => $qq->whereNotIn('id', $servedIds))
                    ->inRandomOrder()->first();
                if ($q) break 2;
            }
        }

        if (!$q) {
            return response()->json(['status'=>'no_question','message'=>'No more questions available for the selected filters.']);
        }

        // 3) Log served event so a refresh re-shows this exact question
        QuizAttemptEvent::create([
            'attempt_id'   => $attempt->id,
            'type'         => 'served',
            'payload_json' => ['question_id' => $q->id],
            'created_at'   => now(),
        ]);

        $options = $q->options()->ordered()->get(['id','label','text_html'])->shuffle()->values()->map(fn($o) => [
            'id'=>$o->id, 'label'=>$o->label, 'text'=>$o->text_html,
        ]);

        return response()->json([
            'status'=>'ok',
            'question'=>[
                'id'=>$q->id, 'level'=>$q->level, 'b'=>$q->b,
                'lang'=>$q->question_language, 'stem'=>$q->stem_html, 'options'=>$options,
            ],
            'progress'=>[
                'answered'=>$attempt->items()->count(),
                'total'=>$attempt->max_items,
            ],
            'target_level'=>$targetLevel,
        ]);
    }


    /**
     * Record answer, update streaks & decide next target level.
     */
    public function answer(QuizAttempt $attempt, Request $request)
    {
        abort_unless($attempt->user_id === auth()->id(), 403);

        $data = $request->validate([
            'question_id' => ['required','integer','exists:questions,id'],
            'option_id'   => ['required','integer','exists:options,id'],
        ]);

        $question = Question::findOrFail($data['question_id']);

        // Ensure the chosen option belongs to that question
        $option = Option::where('id', $data['option_id'])
            ->where('question_id', $question->id)
            ->firstOrFail();

        // If this question is already recorded for this attempt (double submit), skip
        if ($attempt->items()->where('question_id', $question->id)->exists()) {
            $answered = $attempt->items()->count();
            return response()->json([
                'status' => ($answered >= $attempt->max_items) ? 'complete' : 'continue'
            ]);
        }

        // Previous state
        $last         = $attempt->items()->orderByDesc('position_index')->first();
        $baseLevel    = $last?->target_level_next ?? $attempt->start_level;
        $prevCorrect  = $last?->streak_after_correct ?? 0;
        $prevWrong    = $last?->streak_after_wrong   ?? 0;

        // Configurable thresholds & bounds
        $upThresh   = AppSetting::getInt('cat.streak_up_threshold', 3);
        $downThresh = AppSetting::getInt('cat.streak_down_threshold', 3);
        $minLevel   = AppSetting::getInt('cat.level_min', 1);
        $maxLevel   = AppSetting::getInt('cat.level_max', 9);

        // Scoring
        $isCorrect  = $option->weight >= 0.999;

        // Update streaks
        $newCorrect = $isCorrect ? ($prevCorrect + 1) : 0;
        $newWrong   = $isCorrect ? 0 : ($prevWrong + 1);

        // Decide next level
        $nextLevel = $baseLevel;
        if ($newCorrect >= $upThresh) {
            $nextLevel  = min($maxLevel, $baseLevel + 1);
            $newCorrect = 0; // reset after bump
        }
        if ($newWrong >= $downThresh) {
            $nextLevel = max($minLevel, $baseLevel - 1);
            $newWrong  = 0; // reset after drop
        }

        // Position index
        $pos = $attempt->items()->count() + 1;

        // Persist item
        QuizAttemptItem::create([
            'attempt_id'           => $attempt->id,
            'question_id'          => $question->id,
            'level_presented'      => $question->level,
            'b_presented'          => $question->b,
            'response_option_id'   => $option->id,
            'is_correct'           => $isCorrect,
            'position_index'       => $pos,
            'streak_after_correct' => $newCorrect,
            'streak_after_wrong'   => $newWrong,
            'target_level_next'    => $nextLevel,
        ]);

        // Log answered event (pairs with "served" to support resume-on-refresh)
        QuizAttemptEvent::create([
            'attempt_id'   => $attempt->id,
            'type'         => 'answered',
            'payload_json' => ['question_id' => $question->id],
            'created_at'   => now(),
        ]);

        $answered = $attempt->items()->count();
        if ($answered >= $attempt->max_items) {
            return response()->json(['status' => 'complete']);
        }
        return response()->json(['status' => 'continue']);
    }



    /**
     * Finish the attempt: MLE theta (Rasch 1PL) + report payload.
     */
    public function finish(QuizAttempt $attempt)
    {
        abort_unless($attempt->user_id === Auth::id(), 403);

        $items = $attempt->items()->get(['b_presented','is_correct']);
        if ($items->isEmpty()) {
            return response()->json(['status'=>'error','message'=>'No responses recorded.'], 400);
        }

        // MLE Newton–Raphson
        $theta = 0.0; $eps=1e-3; $max=25;
        for ($t=0; $t<$max; $t++) {
            $sum1 = 0.0; $info = 0.0;
            foreach ($items as $it) {
                $b = (float)$it->b_presented;
                $u = $it->is_correct ? 1.0 : 0.0;
                $P = 1.0/(1.0 + exp(-($theta - $b)));
                $sum1 += ($u - $P);
                $info += $P * (1.0 - $P);
            }
            if ($info <= 1e-9) break;
            $step = $sum1 / $info;
            $new  = max(-3.0, min(3.0, $theta + $step));
            if (abs($new - $theta) < $eps) { $theta = $new; break; }
            $theta = $new;
        }
        $se = ($info ?? 0) > 0 ? (1.0/sqrt($info)) : null;

        $totalItems   = $attempt->items()->count();
        $totalCorrect = $attempt->items()->where('is_correct',true)->count();

        $recommended = max(1, min(9, (int)round(2*$theta + 5)));
        $band = $theta <= -0.5 ? 'Below grade' : ($theta >= 0.5 ? 'Above grade' : 'At grade');

        $report = [
            'theta' => round($theta, 3),
            'se'    => $se !== null ? round($se, 3) : null,
            'recommended_level' => $recommended,
            'band'  => $band,
            'correct' => $totalCorrect,
            'total'   => $totalItems,
        ];

        $attempt->end_theta    = $theta;
        $attempt->se           = $se;
        $attempt->total_items  = $totalItems;
        $attempt->total_correct= $totalCorrect;
        $attempt->finished_at  = now();
        $attempt->result_json  = $report;
        $attempt->save();

        return response()->json(['status'=>'ok','report'=>$report]);
    }

    public function results(QuizAttempt $attempt)
    {
        abort_unless($attempt->user_id === auth()->id(), 403);

        if ($attempt->exam_id) {
            // Students may not view exam results immediately
            if (auth()->user()->role !== 'admin') {
                return redirect()->route('dashboard')
                    ->withErrors(['results' => __('app.student.exams.no_results_now')]);
            }
        }
        // If not finished, send to resume
        if (is_null($attempt->finished_at)) {
            return redirect()->route('attempts.show', $attempt->id);
        }

        $report = $attempt->result_json ?: [
            'theta' => $attempt->end_theta,
            'se' => $attempt->se,
            'recommended_level' => max(1, min(9, (int) round(2 * ($attempt->end_theta ?? 0) + 5))),
            'band' => ($attempt->end_theta ?? 0) <= -0.5 ? 'Below grade' : (($attempt->end_theta ?? 0) >= 0.5 ? 'Above grade' : 'At grade'),
            'correct' => $attempt->total_correct,
            'total' => $attempt->total_items,
        ];

        // Fetch the difficulty series
        $items = $attempt->items()
            ->orderBy('position_index')
            ->get(['position_index','level_presented','is_correct','b_presented','target_level_next']);

        // Prepare lightweight array for the chart
        $series = $items->map(fn($it) => [
            'x' => (int)$it->position_index,
            'y' => (int)$it->level_presented,
            'correct' => (bool)$it->is_correct,
            'b' => $it->b_presented,
            'next' => $it->target_level_next,
        ]);

        return view('attempts.results', compact('attempt', 'report', 'series'));
    }

//----------------EXAMS


    public function showExam(QuizAttempt $attempt)
    {
        abort_unless($attempt->user_id === auth()->id(), 403);
        abort_unless($attempt->exam_id, 404); // must be tied to an exam

        if ($attempt->finished_at) {
            return redirect()->route('exams.attempts.submitted', $attempt->id);
        }

        // Reuse the same Blade but tell it we're in "exam mode"
        $current = (int) $attempt->total_items;
        $max     = (int) $attempt->max_items;
        $isExam  = true;

        // URLs the JS will call
        $urls = [
            'next'       => route('attempts.next',   $attempt->id),   // same as practice
            'answer'     => route('attempts.answer', $attempt->id),   // same as practice
            'finish'     => route('exams.attempts.finish', $attempt->id), // exam-specific finish
            'submitted'  => route('exams.attempts.submitted', $attempt->id),
            'results'    => route('attempts.results', $attempt->id), // not used in exam flow
        ];

        return view('attempts.show', compact('attempt','current','max','isExam','urls'));
    }

    public function finishExam(QuizAttempt $attempt)
    {
        abort_unless($attempt->user_id === auth()->id(), 403);
        abort_unless($attempt->exam_id, 404);

        // Reuse your existing finish() logic to compute theta & persist,
        // but DO NOT return the report to the client.
        // You can literally call your existing finish() internals here:

        $items = $attempt->items()->get(['b_presented','is_correct']);
        if ($items->isEmpty()) {
            return response()->json(['status'=>'error','message'=>'No responses recorded.'], 400);
        }

        // ---- Begin: copy of your MLE block from finish() ----
        $theta = 0.0; $eps=1e-3; $max=25; $info = 0.0;
        for ($t=0; $t<$max; $t++) {
            $sum1 = 0.0; $info = 0.0;
            foreach ($items as $it) {
                $b = (float)$it->b_presented;
                $u = $it->is_correct ? 1.0 : 0.0;
                $P = 1.0/(1.0 + exp(-($theta - $b)));
                $sum1 += ($u - $P);
                $info += $P * (1.0 - $P);
            }
            if ($info <= 1e-9) break;
            $step = $sum1 / $info;
            $new  = max(-3.0, min(3.0, $theta + $step));
            if (abs($new - $theta) < $eps) { $theta = $new; break; }
            $theta = $new;
        }
        $se = ($info ?? 0) > 0 ? (1.0/sqrt($info)) : null;

        $totalItems   = $attempt->items()->count();
        $totalCorrect = $attempt->items()->where('is_correct',true)->count();

        $report = [
            'theta' => round($theta, 3),
            'se'    => $se !== null ? round($se, 3) : null,
            'correct' => $totalCorrect,
            'total'   => $totalItems,
            // keep any other metadata you want for admins
        ];

        $attempt->end_theta     = $theta;
        $attempt->se            = $se;
        $attempt->total_items   = $totalItems;
        $attempt->total_correct = $totalCorrect;
        $attempt->finished_at   = now();
        $attempt->result_json   = $report;
        $attempt->save();
        // ---- End: copy of your MLE block ----

        // Return minimal payload: no student-visible report
        return response()->json(['status' => 'ok']);
    }

    public function submitted(QuizAttempt $attempt)
    {
        abort_unless($attempt->user_id === auth()->id(), 403);
        abort_unless($attempt->exam_id, 404);

        // If unfinished (e.g., direct hit), send back to continue
        if (!$attempt->finished_at) {
            return redirect()->route('exams.attempts.show', $attempt->id);
        }

        return view('attempts.submitted', compact('attempt'));
    }

}
