<?php

namespace Modules\Exam\Services;

use App\Models\User;
use Modules\Exam\Models\Exam;
use Modules\Exam\Models\ExamAttempt;
use Modules\Exam\Models\ExamAttemptAnswer;
use Illuminate\Support\Facades\DB;

class ExamAttemptService
{
   /**
    * Start a new exam attempt
    */
   public function startAttempt(User $user, Exam $exam): ?ExamAttempt
   {
      $previousAttempts = ExamAttempt::where('user_id', $user->id)
         ->where('exam_id', $exam->id)
         ->count();

      if ($previousAttempts >= $exam->max_attempts) {
         return null;
      }

      // Create new attempt
      return ExamAttempt::create([
         'user_id' => $user->id,
         'exam_id' => $exam->id,
         'attempt_number' => $previousAttempts + 1,
         'start_time' => now(),
         'total_marks' => $exam->total_marks,
         'status' => 'in_progress',
      ]);
   }

   /**
    * Submit exam answers with automatic grading for objective questions
    */
   public function submitAttempt(ExamAttempt $attempt, array $answers): ExamAttempt
   {
      DB::beginTransaction();
      try {
         $totalObtainedMarks = 0;
         $correctAnswers = 0;
         $incorrectAnswers = 0;
         $needsManualReview = false;

         foreach ($answers as $answer) {
            $question = \Modules\Exam\Models\ExamQuestion::find($answer['exam_question_id']);

            if (!$question) {
               continue;
            }

            // Auto-grade objective questions, leave subjective for manual review
            $gradingResult = $this->gradeAnswer($question, $answer['answer_data']);

            // Check if this question needs manual review
            if ($gradingResult['is_correct'] === null) {
               $needsManualReview = true;
            }

            // Save the answer with grading
            ExamAttemptAnswer::create([
               'exam_attempt_id' => $attempt->id,
               'exam_question_id' => $question->id,
               'answer_data' => $answer['answer_data'],
               'is_correct' => $gradingResult['is_correct'],
               'marks_obtained' => $gradingResult['marks_obtained'],
            ]);

            // Update counters
            $totalObtainedMarks += $gradingResult['marks_obtained'];
            if ($gradingResult['is_correct'] === true) {
               $correctAnswers++;
            } elseif ($gradingResult['is_correct'] === false) {
               $incorrectAnswers++;
            }
         }

         // Calculate percentage
         $percentage = $attempt->total_marks > 0
            ? round(($totalObtainedMarks / $attempt->total_marks) * 100, 2)
            : 0;

         // Check if passed
         // If manual review needed, set to false initially (will be updated after review)
         $isPassed = $totalObtainedMarks >= $attempt->exam->pass_mark;

         // Update attempt status
         $attempt->update([
            'end_time' => now(),
            'status' => $needsManualReview ? 'submitted' : 'completed',
            'obtained_marks' => $totalObtainedMarks,
            'percentage' => $percentage,
            'correct_answers' => $correctAnswers,
            'incorrect_answers' => $incorrectAnswers,
            'is_passed' => $isPassed,
         ]);

         DB::commit();
         return $attempt->fresh();
      } catch (\Exception $e) {
         DB::rollBack();
         throw $e;
      }
   }

   /**
    * Grade an answer based on question type
    */
   private function gradeAnswer($question, $answerData): array
   {
      $result = [
         'is_correct' => null,
         'marks_obtained' => 0,
      ];

      switch ($question->question_type) {
         case 'multiple_choice':
            $result = $this->gradeMultipleChoice($question, $answerData);
            break;

         case 'multiple_select':
            $result = $this->gradeMultipleSelect($question, $answerData);
            break;

         case 'matching':
            $result = $this->gradeMatching($question, $answerData);
            break;

         case 'fill_blank':
            $result = $this->gradeFillBlank($question, $answerData);
            break;

         case 'ordering':
            $result = $this->gradeOrdering($question, $answerData);
            break;

         case 'listening':
            $result = $this->gradeListening($question, $answerData);
            break;

         case 'short_answer':
            // Short answers need manual grading
            $result['is_correct'] = null;
            $result['marks_obtained'] = 0;
            break;
      }

      return $result;
   }

   /**
    * Grade multiple choice question
    */
   private function gradeMultipleChoice($question, $answerData): array
   {
      $correctOption = $question->question_options()
         ->where('is_correct', true)
         ->first();

      $isCorrect = $correctOption && $correctOption->id == $answerData['selected_option_id'];

      return [
         'is_correct' => $isCorrect,
         'marks_obtained' => $isCorrect ? $question->marks : 0,
      ];
   }

   /**
    * Grade multiple select question
    */
   private function gradeMultipleSelect($question, $answerData): array
   {
      $correctOptionIds = $question->question_options()
         ->where('is_correct', true)
         ->pluck('id')
         ->sort()
         ->values()
         ->toArray();

      $selectedIds = collect($answerData['selected_option_ids'] ?? [])
         ->sort()
         ->values()
         ->toArray();

      $isCorrect = $correctOptionIds === $selectedIds;

      return [
         'is_correct' => $isCorrect,
         'marks_obtained' => $isCorrect ? $question->marks : 0,
      ];
   }

   /**
    * Grade matching question
    */
   private function gradeMatching($question, $answerData): array
   {
      $correctMatches = $question->options['matches'] ?? [];
      $userMatches = $answerData['matches'] ?? [];

      $correctCount = 0;
      foreach ($correctMatches as $match) {
         $userMatch = collect($userMatches)->firstWhere('id', $match['id']);
         if ($userMatch && $userMatch['answer'] === $match['answer']) {
            $correctCount++;
         }
      }

      $totalMatches = count($correctMatches);
      $percentage = $totalMatches > 0 ? ($correctCount / $totalMatches) : 0;
      $isCorrect = $percentage === 1.0;

      return [
         'is_correct' => $isCorrect,
         'marks_obtained' => $percentage * $question->marks,
      ];
   }

   /**
    * Grade fill in the blank question
    */
   private function gradeFillBlank($question, $answerData): array
   {
      $correctAnswers = $question->options['answers'] ?? [];
      $userAnswers = $answerData['answers'] ?? [];

      $correctCount = 0;
      foreach ($correctAnswers as $index => $correctAnswer) {
         $userAnswer = $userAnswers[$index] ?? '';
         if (strtolower(trim($userAnswer)) === strtolower(trim($correctAnswer))) {
            $correctCount++;
         }
      }

      $totalBlanks = count($correctAnswers);
      $percentage = $totalBlanks > 0 ? ($correctCount / $totalBlanks) : 0;
      $isCorrect = $percentage === 1.0;

      return [
         'is_correct' => $isCorrect,
         'marks_obtained' => $percentage * $question->marks,
      ];
   }

   /**
    * Grade ordering question
    */
   private function gradeOrdering($question, $answerData): array
   {
      $correctOrder = $question->options['correct_order'] ?? [];
      $userOrder = $answerData['order'] ?? [];

      $isCorrect = $correctOrder === $userOrder;

      return [
         'is_correct' => $isCorrect,
         'marks_obtained' => $isCorrect ? $question->marks : 0,
      ];
   }

   /**
    * Grade listening question (similar to multiple choice)
    */
   private function gradeListening($question, $answerData): array
   {
      return $this->gradeMultipleChoice($question, $answerData);
   }

   /**
    * Get user's attempt history
    */
   public function getUserAttempts(User $user, Exam $exam)
   {
      return ExamAttempt::where('user_id', $user->id)
         ->where('exam_id', $exam->id)
         ->with('attempt_answers.exam_question')
         ->orderBy('created_at', 'desc')
         ->get();
   }

   /**
    * Get attempt analytics
    */
   public function getAttemptAnalytics(ExamAttempt $attempt): array
   {
      $answers = $attempt->attempt_answers()->with('exam_question')->get();

      $byQuestionType = [];
      foreach ($answers as $answer) {
         $type = $answer->exam_question->question_type;
         if (!isset($byQuestionType[$type])) {
            $byQuestionType[$type] = [
               'total' => 0,
               'correct' => 0,
               'incorrect' => 0,
               'pending' => 0,
            ];
         }

         $byQuestionType[$type]['total']++;

         if ($answer->is_correct === true) {
            $byQuestionType[$type]['correct']++;
         } elseif ($answer->is_correct === false) {
            $byQuestionType[$type]['incorrect']++;
         } else {
            $byQuestionType[$type]['pending']++;
         }
      }

      return [
         'attempt' => $attempt,
         'by_question_type' => $byQuestionType,
         'percentage' => $attempt->percentage,
         'duration_minutes' => $attempt->duration,
         'time_per_question' => $attempt->duration && $answers->count() > 0
            ? $attempt->duration / $answers->count()
            : 0,
      ];
   }

   public function getExamAttempt(string $id, array $data = [])
   {
      return ExamAttempt::with(['exam', 'user', 'attempt_answers.exam_question.question_options'])
         ->where('id', $id)
         ->when(isset($data['user_id']), function ($query) use ($data) {
            return $query->where('user_id', $data['user_id']);
         })
         ->first();
   }

   public function getExamAttempts(array $data, bool $pagination = false)
   {
      $per_page = array_key_exists('per_page', $data) ? intval($data['per_page']) : 10;

      $exams = ExamAttempt::with(['user', 'attempt_answers.exam_question'])
         ->where('exam_id', $data['exam_id'])
         ->when(array_key_exists('user_id', $data) && $data['user_id'], function ($query) use ($data) {
            return $query->where('user_id', $data['user_id']);
         })
         ->when(array_key_exists('name', $data) && $data['name'], function ($query) use ($data) {
            return $query->whereHas('user', function ($query) use ($data) {
               $query->where('name', 'like', '%' . $data['name'] . '%');
            });
         })
         ->when(array_key_exists('status', $data) && $data['status'] !== 'all', function ($query) use ($data) {
            return $query->where('status', $data['status']);
         })
         ->when(array_key_exists('is_passed', $data) && $data['is_passed'] !== 'all', function ($query) use ($data) {
            return $query->where('is_passed', $data['is_passed']);
         })
         ->orderBy('created_at', 'desc');

      if ($pagination) {
         return $exams->paginate($per_page);
      }

      return $exams->get();
   }

   public function getBestExamAttempt(string $exam_id, string $user_id)
   {
      return ExamAttempt::where('exam_id', $exam_id)
         ->where('user_id', $user_id)
         ->whereIn('status', ['completed', 'submitted'])
         ->orderBy('obtained_marks', 'desc')
         ->first();
   }

   public function getAttemptResult(string $exam_id, string $attempt_id): ExamAttempt
   {
      return ExamAttempt::with(['exam', 'attempt_answers.exam_question.question_options'])
         ->where('id', $attempt_id)
         ->where('exam_id', $exam_id)
         ->first();
   }

   /**
    * Abandon an in-progress attempt
    */
   public function abandonAttempt(ExamAttempt $attempt): ExamAttempt
   {
      $attempt->update([
         'status' => 'abandoned',
         'end_time' => now(),
      ]);

      return $attempt;
   }

   /**
    * Review and grade an exam attempt
    */
   public function reviewAttempt(ExamAttempt $attempt, array $manualGrades = []): ExamAttempt
   {
      DB::beginTransaction();
      try {
         $totalObtainedMarks = 0;
         $correctAnswers = 0;
         $incorrectAnswers = 0;

         // Load attempt with answers and questions
         $attempt->load(['attempt_answers.exam_question']);

         foreach ($attempt->attempt_answers as $attemptAnswer) {
            $question = $attemptAnswer->exam_question;

            if (!$question) {
               continue;
            }

            // Check if this question has manual grading
            $questionId = $question->id;
            if (isset($manualGrades[$questionId])) {
               // Use manual grade
               $marksObtained = floatval($manualGrades[$questionId]);
               $isCorrect = $marksObtained >= $question->marks;

               $attemptAnswer->update([
                  'is_correct' => $isCorrect,
                  'marks_obtained' => $marksObtained,
               ]);
            } else {
               // Auto-grade based on question type
               $result = $this->gradeAnswer($question, $attemptAnswer->answer_data);

               $attemptAnswer->update([
                  'is_correct' => $result['is_correct'],
                  'marks_obtained' => $result['marks_obtained'],
               ]);
            }

            // Refresh to get updated values
            $attemptAnswer->refresh();

            // Update is_correct flag based on marks obtained
            $questionMarks = floatval($question->marks);
            $obtainedMarks = floatval($attemptAnswer->marks_obtained);

            // If student got full marks, mark as correct
            if ($obtainedMarks >= $questionMarks) {
               if ($attemptAnswer->is_correct !== true) {
                  $attemptAnswer->update(['is_correct' => true]);
                  $attemptAnswer->refresh();
               }
               $correctAnswers++;
            } elseif ($obtainedMarks > 0) {
               // Partial marks - still incorrect
               if ($attemptAnswer->is_correct !== false) {
                  $attemptAnswer->update(['is_correct' => false]);
                  $attemptAnswer->refresh();
               }
               $incorrectAnswers++;
            } else {
               // Zero marks - incorrect
               if ($attemptAnswer->is_correct !== false) {
                  $attemptAnswer->update(['is_correct' => false]);
                  $attemptAnswer->refresh();
               }
               $incorrectAnswers++;
            }

            $totalObtainedMarks += $attemptAnswer->marks_obtained;
         }

         // Calculate percentage and pass/fail
         $percentage = $attempt->total_marks > 0
            ? round(($totalObtainedMarks / $attempt->total_marks) * 100, 2)
            : 0;

         $isPassed = $totalObtainedMarks >= $attempt->exam->pass_mark;

         // Update attempt with grading results
         $attempt->update([
            'obtained_marks' => $totalObtainedMarks,
            'percentage' => $percentage,
            'correct_answers' => $correctAnswers,
            'incorrect_answers' => $incorrectAnswers,
            'is_passed' => $isPassed,
            'status' => 'completed',
         ]);

         DB::commit();
         return $attempt->fresh();
      } catch (\Exception $e) {
         DB::rollBack();
         throw $e;
      }
   }

   /**
    * Auto-grade attempt answers without finalizing (for preview during review)
    */
   public function autoGradeAttempt(ExamAttempt $attempt): ExamAttempt
   {
      DB::beginTransaction();
      try {
         // Load attempt with answers and questions
         $attempt->load(['attempt_answers.exam_question']);

         foreach ($attempt->attempt_answers as $attemptAnswer) {
            $question = $attemptAnswer->exam_question;

            if (!$question) {
               continue;
            }

            // Skip manual grading questions (listening, short_answer)
            if (in_array($question->question_type, ['listening', 'short_answer'])) {
               continue;
            }

            // Auto-grade based on question type
            $result = $this->gradeAnswer($question, $attemptAnswer->answer_data);

            $attemptAnswer->update([
               'is_correct' => $result['is_correct'],
               'marks_obtained' => $result['marks_obtained'],
            ]);
         }

         DB::commit();
         return $attempt->fresh();
      } catch (\Exception $e) {
         DB::rollBack();
         throw $e;
      }
   }

   /**
    * Get attempt details for review
    */
   public function getAttemptForReview(string $attemptId)
   {
      return ExamAttempt::with([
         'user',
         'exam',
         'attempt_answers.exam_question.question_options'
      ])
         ->findOrFail($attemptId);
   }
}
