<?php

namespace App\Http\Controllers\Student;

use Carbon\Carbon;
use App\Models\Grade;
use App\Models\Answer;
use App\Models\Exam;
use App\Models\Question;
use App\Models\ExamGroup;
use App\Support\AnswerEvaluator;
use App\Services\GradingService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use App\Http\Controllers\Controller;

class ExamController extends Controller
{
    /**
     * confirmation
     *
     * @param  mixed $id
     * @return void
     */
    public function confirmation($id)
    {
        //get exam group
        $exam_group = ExamGroup::with('exam.lesson', 'exam_session', 'student.classroom')
            ->where('student_id', auth()->guard('student')->user()->id)
            ->where('id', $id)
            ->first();

        //get grade / nilai
        $grade = Grade::where('exam_id', $exam_group->exam->id)
            ->where('exam_session_id', $exam_group->exam_session->id)
            ->where('student_id', auth()->guard('student')->user()->id)
            ->first();

        //return with inertia
        return inertia('Student/Exams/Confirmation', [
            'exam_group' => $exam_group,
            'grade' => $grade,
        ]);
    }

    /**
     * startExam
     *
     * @param  mixed $id
     * @return void
     */
    public function startExam($id)
    {
        //get exam group
        $exam_group = ExamGroup::with('exam.lesson', 'exam_session', 'student.classroom')
            ->where('student_id', auth()->guard('student')->user()->id)
            ->where('id', $id)
            ->first();

        //get grade / nilai
        $grade = Grade::where('exam_id', $exam_group->exam->id)
            ->where('exam_session_id', $exam_group->exam_session->id)
            ->where('student_id', auth()->guard('student')->user()->id)
            ->first();

        // Jika ujian sudah selesai, arahkan ke halaman hasil
        if ($grade && !is_null($grade->end_time)) {
            return redirect()->route('student.exams.resultExam', $exam_group->id);
        }

        //update start time di table grades
        $grade->start_time = Carbon::now();
        $grade->update();

        //cek apakah questions / soal ujian di random
        if ($exam_group->exam->random_question == 'Y') {
            $questions = $exam_group->exam->questions()->inRandomOrder()->get();
        } else {
            $questions = $exam_group->exam->questions()->get();
        }

        $studentId     = auth()->guard('student')->user()->id;
        $examId        = $exam_group->exam->id;
        $examSessionId = $exam_group->exam_session->id;
        $randomAnswer  = $exam_group->exam->random_answer == 'Y';
        $now           = Carbon::now();

        // ✅ FIX N+1: Ambil semua jawaban yang sudah ada dalam 1 query (batch SELECT)
        $existingAnswers = Answer::where('student_id', $studentId)
            ->where('exam_id', $examId)
            ->where('exam_session_id', $examSessionId)
            ->whereIn('question_id', $questions->pluck('id'))
            ->get()
            ->keyBy('question_id'); // index by question_id untuk O(1) lookup

        $toInsert = []; // rows baru yang akan di-bulk INSERT
        $toUpdate = []; // rows lama yang perlu diupdate question_order

        $question_order = 1;
        foreach ($questions as $question) {

            //buat array pilihan jawaban
            $options = [1, 2];
            if (!empty($question->option_3)) $options[] = 3;
            if (!empty($question->option_4)) $options[] = 4;
            if (!empty($question->option_5)) $options[] = 5;

            //acak jawaban jika diaktifkan
            if ($randomAnswer) {
                shuffle($options);
            }

            $existingAnswer = $existingAnswers->get($question->id);

            if ($existingAnswer) {
                // Kumpulkan update untuk di-batch nanti (hanya jika question_order berubah)
                if ($existingAnswer->question_order !== $question_order) {
                    $toUpdate[] = [
                        'id'             => $existingAnswer->id,
                        'question_order' => $question_order,
                    ];
                }
            } else {
                // Kumpulkan row baru untuk di-bulk INSERT
                $toInsert[] = [
                    'exam_id'         => $examId,
                    'exam_session_id' => $examSessionId,
                    'question_id'     => $question->id,
                    'student_id'      => $studentId,
                    'question_order'  => $question_order,
                    'answer_order'    => implode(',', $options),
                    'answer'          => '',
                    'is_correct'      => 'N',
                    'created_at'      => $now,
                    'updated_at'      => $now,
                ];
            }

            $question_order++;
        }

        // ✅ Bulk INSERT semua jawaban baru dalam 1 query (bukan N query)
        if (!empty($toInsert)) {
            Answer::insert($toInsert);
        }

        // ✅ Batch UPDATE question_order menggunakan CASE WHEN — 1 query untuk semua update
        if (!empty($toUpdate)) {
            $ids   = array_column($toUpdate, 'id');
            $cases = implode(' ', array_map(
                fn ($row) => "WHEN {$row['id']} THEN {$row['question_order']}",
                $toUpdate
            ));
            $inList = implode(',', $ids);

            DB::statement("
                UPDATE answers
                SET question_order = CASE id {$cases} END,
                    updated_at = ?
                WHERE id IN ({$inList})
            ", [$now]);
        }

        //redirect ke ujian halaman 1
        return redirect()->route('student.exams.show', [
            'id'   => $exam_group->id,
            'page' => 1,
        ]);
    }

    /**
     * show
     *
     * @param  mixed $id
     * @param  mixed $page
     * @return void
     */
    public function show($id, $page)
    {
        //get exam group
        $exam_group = ExamGroup::with('exam.lesson', 'exam_session', 'student.classroom')
            ->where('student_id', auth()->guard('student')->user()->id)
            ->where('id', $id)
            ->first();

        if (!$exam_group) {
            return redirect()->route('student.dashboard');
        }

        //get duration / grade
        $duration = Grade::where('exam_id', $exam_group->exam->id)
            ->where('exam_session_id', $exam_group->exam_session->id)
            ->where('student_id', auth()->guard('student')->user()->id)
            ->first();

        // Jika ujian sudah selesai (end_time terisi), redirect ke hasil
        if ($duration && !is_null($duration->end_time)) {
            return redirect()->route('student.exams.resultExam', $exam_group->id);
        }

        //get all questions
        $all_questions = Answer::with('question')
            ->where('student_id', auth()->guard('student')->user()->id)
            ->where('exam_id', $exam_group->exam->id)
            ->orderBy('question_order', 'ASC')
            ->get();

        //count all question answered
        $question_answered = Answer::with('question')
            ->where('student_id', auth()->guard('student')->user()->id)
            ->where('exam_id', $exam_group->exam->id)
            ->whereNotNull('answer')
            ->where('answer', '!=', '')
            ->where('answer', '!=', '0')
            ->count();


        //get question active
        $question_active = Answer::with('question')
            ->where('student_id', auth()->guard('student')->user()->id)
            ->where('exam_id', $exam_group->exam->id)
            ->where('question_order', $page)
            ->first();

        //explode atau pecah jawaban
        if ($question_active) {
            $answer_order = explode(",", $question_active->answer_order);
        } else {
            $answer_order = [];
        }

        //return with inertia
        return inertia('Student/Exams/Show', [
            'id' => (int) $id,
            'page' => (int) $page,
            'exam_group' => $exam_group,
            'all_questions' => $all_questions,
            'question_answered' => $question_answered,
            'question_active' => $question_active,
            'answer_order' => $answer_order,
            'duration' => $duration,
        ]);
    }

    /**
     * updateDuration
     *
     * @param  mixed $request
     * @param  mixed $grade_id
     * @return void
     */
    public function updateDuration(Request $request, $grade_id)
    {
        $grade = Grade::find($grade_id);
        $grade->duration = $request->duration;
        $grade->update();

        return response()->json([
            'success' => true,
            'message' => 'Duration updated successfully.'
        ]);
    }

    /**
     * answerQuestion
     *
     * @param  mixed $request
     * @return void
     */
    public function answerQuestion(Request $request)
    {
        //update duration
        $grade = Grade::where('exam_id', $request->exam_id)
            ->where('exam_session_id', $request->exam_session_id)
            ->where('student_id', auth()->guard('student')->user()->id)
            ->first();

        // Jika ujian sudah selesai, tolak jawaban
        if ($grade && !is_null($grade->end_time)) {
            return redirect()->route('student.dashboard');
        }

        $grade->duration = $request->duration;
        $grade->update();

        //get question
        $question = Question::find($request->question_id);

        $result = AnswerEvaluator::evaluate($question, $request->answer);

        //get answer
        $answer = Answer::where('exam_id', $request->exam_id)
            ->where('exam_session_id', $request->exam_session_id)
            ->where('student_id', auth()->guard('student')->user()->id)
            ->where('question_id', $request->question_id)
            ->first();

        //update jawaban
        if ($answer) {
            // Konversi dari array ke string json jika perlu
            $answerVal = is_array($request->answer) ? json_encode($request->answer) : $request->answer;

            $answer->answer = $answerVal;
            $answer->is_correct = $result;
            $answer->update();
        }

        return redirect()->back();
    }

    /**
     * endExam
     *
     * @param  mixed $request
     * @return void
     */
    public function endExam(Request $request)
    {
        $request->validate([
            'exam_group_id' => ['required', 'integer'],
            'exam_id' => ['required', 'integer'],
            'exam_session_id' => ['required', 'integer'],
            'duration' => ['nullable', 'integer', 'min:0'],
            'answers' => ['nullable', 'array'],
            'answers.*.question_id' => ['required', 'integer'],
        ]);

        //get exam
        $examObj = Exam::find($request->exam_id);
        if (!$examObj) {
            return redirect()->route('student.dashboard');
        }

        $grade = Grade::where('exam_id', $request->exam_id)
            ->where('exam_session_id', $request->exam_session_id)
            ->where('student_id', auth()->guard('student')->user()->id)
            ->first();

        if (!$grade) {
            return redirect()->route('student.dashboard');
        }

        if ($grade && !is_null($grade->end_time)) {
            return redirect()->route('student.exams.resultExam', $request->exam_group_id);
        }

        $answers = $this->syncSubmittedAnswers(
            $request->exam_id,
            $request->exam_session_id,
            auth()->guard('student')->user()->id,
            $request->input('answers', [])
        );

        $summary = GradingService::calculateGradeSummary($examObj, $answers);

        //update nilai di table grades
        $grade->duration = $request->integer('duration', $grade->duration);
        $grade->end_time = Carbon::now();
        $grade->total_correct = $summary['total_correct'];
        $grade->grade = $summary['grade'];
        $grade->save();

        //redirect hasil
        return redirect()->route('student.exams.resultExam', $request->exam_group_id);
    }

    /**
     * resultExam
     *
     * @param  mixed $id
     * @return void
     */
    public function resultExam($exam_group_id)
    {
        //get exam group
        $exam_group = ExamGroup::with('exam.lesson', 'exam_session', 'student.classroom')
            ->where('student_id', auth()->guard('student')->user()->id)
            ->where('id', $exam_group_id)
            ->first();

        GradingService::syncAutomaticAnswers(
            $exam_group->exam->id,
            auth()->guard('student')->user()->id,
            $exam_group->exam_session->id
        );

        GradingService::refreshGradeSummary(
            $exam_group->exam,
            auth()->guard('student')->user()->id,
            $exam_group->exam_session->id
        );

        //get grade / nilai
        $grade = Grade::where('exam_id', $exam_group->exam->id)
            ->where('exam_session_id', $exam_group->exam_session->id)
            ->where('student_id', auth()->guard('student')->user()->id)
            ->first();

        $hasPendingManualScoringTypes = Answer::where('exam_id', $exam_group->exam->id)
            ->where('exam_session_id', $exam_group->exam_session->id)
            ->where('student_id', auth()->guard('student')->user()->id)
            ->whereHas('question', function ($query) {
                $query->whereIn('type', ['isian', 'uraian']);
            })
            ->exists();

        //return with inertia
        return inertia('Student/Exams/Result', [
            'exam_group' => $exam_group,
            'grade' => $grade,
            'hasPendingManualScoringTypes' => $hasPendingManualScoringTypes,
        ]);
    }

    /**
     * leaveExam
     *
     * @param  mixed $exam_group_id
     * @return void
     */
    public function leaveExam($exam_group_id)
    {
        $exam_group = ExamGroup::with('exam.lesson', 'exam_session', 'student.classroom')
            ->where('student_id', auth()->guard('student')->user()->id)
            ->where('id', $exam_group_id)
            ->first();

        if ($exam_group) {
            $grade = Grade::where('exam_id', $exam_group->exam->id)
                ->where('exam_session_id', $exam_group->exam_session->id)
                ->where('student_id', auth()->guard('student')->user()->id)
                ->first();

            if ($grade && is_null($grade->end_time)) {
                $grade->leave_count = $grade->leave_count + 1;
                $grade->save();
                
                return response()->json([
                    'success' => true,
                    'leave_count' => $grade->leave_count
                ]);
            }
        }

        return response()->json([
            'success' => false,
        ], 404);
    }

    private function syncSubmittedAnswers(int $examId, int $examSessionId, int $studentId, array $submittedAnswers)
    {
        $answers = Answer::with('question')
            ->where('exam_id', $examId)
            ->where('exam_session_id', $examSessionId)
            ->where('student_id', $studentId)
            ->get();

        $answersByQuestionId = collect($submittedAnswers)
            ->filter(fn ($item) => is_array($item) && isset($item['question_id']))
            ->mapWithKeys(fn ($item) => [
                (int) $item['question_id'] => $item['answer'] ?? null,
            ]);

        foreach ($answers as $answer) {
            $submittedAnswer = $answersByQuestionId->get($answer->question_id);
            $serializedAnswer = $this->serializeSubmittedAnswer($submittedAnswer);

            $answer->answer = $serializedAnswer;
            $answer->is_correct = AnswerEvaluator::evaluate($answer->question, $submittedAnswer);
            $answer->save();
        }

        return $answers;
    }



    private function serializeSubmittedAnswer(mixed $answer): string
    {
        if ($this->isEmptySubmittedAnswer($answer)) {
            return '';
        }

        if (is_array($answer)) {
            return json_encode($answer, JSON_UNESCAPED_UNICODE);
        }

        return trim((string) $answer);
    }

    private function isEmptySubmittedAnswer(mixed $answer): bool
    {
        if ($answer === null) {
            return true;
        }

        if (is_string($answer)) {
            return trim($answer) === '' || trim($answer) === '0';
        }

        if (is_numeric($answer)) {
            return (string) $answer === '0';
        }

        if (is_array($answer)) {
            foreach ($answer as $item) {
                if (!$this->isEmptySubmittedAnswer($item)) {
                    return false;
                }
            }

            return true;
        }

        return false;
    }
}
