<?php

namespace App\Http\Controllers\Api\V1\Client\Quiz;

use App\Enums\CoinMatterEnum;
use App\Enums\QuizStatusEnum;
use App\Enums\UserQuizStatusEnum;
use App\Events\BadgeEarned;
use App\Events\CoinLogEvent;
use App\Exceptions\CustomWebException;
use App\Http\Controllers\Controller;
use App\Models\Quiz;
use App\Models\QuizCategory;
use App\Models\QuizQuestion;
use App\Models\User;
use App\Services\Coin\CoinService;
use App\Services\ModalIndexQuey;
use App\Traits\Formatter;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;

class QuizController extends Controller
{
    use Formatter;

    /**
     * @OA\Get(
     *     path="/api/v1/quiz-categories",
     *     tags={"Quiz"},
     *     summary="get all category",
     *     description="getting all categories",
     *
     *     @OA\Response(
     *         response=200,
     *         description="list of categories",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=200),
     *             @OA\Property(property="token", type="object",
     *                 @OA\Property(property="id", type="integer", example=1),
     *             )
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=404,
     *         description="Not Found",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="not found")
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=500,
     *         description="Internal Server Error",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="Something went wrong.")
     *         )
     *     )
     * )
     * )
     */
    public function getCategories()
    {
        $query = QuizCategory::query();

        $categories = ModalIndexQuey::get(
            model: $query
        );

        return $this->withSuccess($categories);
    }

    /**
     * @OA\Get(
     *     path="/api/v1/quizzes/{category}",
     *     tags={"Quiz"},
     *     summary="get all quizzes by category",
     *     description="getting quizzes for a category",
     *
     *     @OA\Response(
     *         response=200,
     *         description="getting single quiz",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=200),
     *             @OA\Property(property="token", type="object",
     *                 @OA\Property(property="id", type="integer", example=1),
     *             )
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=404,
     *         description="Not Found",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="not found")
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=500,
     *         description="Internal Server Error",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="Something went wrong.")
     *         )
     *     )
     * )
     * )
     */
    public function getQuiz()
    {
        $category = request('category', 'all');
        $query = Quiz::query()
            ->active()
            ->when(
                $category !== 'all',
                fn ($query) => $query->whereRelation('category', 'slug', $category)
            )
            ->withSum('questions', 'time_limit')
            ->withCount(['questionLevels', 'questions', 'userQuizzes'])
            ->with([
                'userQuizzes' => fn ($query) => $query->with('user:id,avatar')->limit(3),
            ]);

        /**
         * @var \App\Models\User $user
         */
        $user = auth('client')->user();

        $quizzes = ModalIndexQuey::get(
            model: $query,
            with: ['category:id,title,slug'],
        );

        if ($user) {

            $favorites = $user->favorites()->where('favorable_type', get_class(new Quiz))->pluck('favorable_id')->toArray();

            // Transform paginated items
            $quizzes->getCollection()->transform(function ($quiz) use ($user, $favorites) {
                $takenQuery = $quiz->userQuizzes()->where('user_id', $user->id);

                return [
                    ...$quiz->toArray(),
                    'taken_status' => $quiz->is_free ? (clone $takenQuery)->whereIn('status', [UserQuizStatusEnum::PENDING, UserQuizStatusEnum::IN_PROGRESS])->first()?->status ?? ((clone $takenQuery)->count() > 0 ? 'retake' : null) : $takenQuery->first()?->status ?? null,
                    'is_favorite' => in_array($quiz->id, $favorites),
                ];
            });
        }

        return $this->withSuccess($quizzes);
    }

    /**
     * @OA\Get(
     *     path="/api/v1/quiz-details/{quiz}",
     *     tags={"Quiz"},
     *     summary="get the quiz details",
     *     description="get the quiz details",
     *     security={{"bearer": {}}},
     *
     *     @OA\Response(
     *         response=200,
     *         description="getting quiz details",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=200),
     *             @OA\Property(property="token", type="object",
     *                 @OA\Property(property="id", type="integer", example=1),
     *             )
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=404,
     *         description="Not Found",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="not found")
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=500,
     *         description="Internal Server Error",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="Something went wrong.")
     *         )
     *     )
     * )
     * )
     */
    public function getSingleQuiz($slug)
    {
        $quiz = $this->getQuizBySlug($slug);

        /**
         * @var \App\Models\User $user
         */
        $user = auth('client')->user();
        $user_quiz = null;

        if ($quiz->status !== QuizStatusEnum::ACTIVE) {
            return $this->withError(__('This quiz is not active'));
        }

        // check quiz is eligible for the user
        if ($user) {
            $userQuery = $user->quizzes()
                ->where(column: 'quiz_id', operator: '=', value: $quiz->id);
            $user_quiz = $quiz->is_free ? (clone $userQuery)->where(column: 'status', operator: '!=', value: UserQuizStatusEnum::COMPLETED)->first() : $userQuery->first();
        }

        $quiz_details = null;
        $quiz_details = $quiz->load([
            'category:id,title',
            'levels' => function ($query) {
                $query->select('levels.id', 'title', 'slug')->withPivot('id');
            },
        ])
            ->loadCount(['questions', 'levels', 'userQuizzes'])
            ->loadSum('questions', 'time_limit');

        if ($user) {
            $quiz_details->taken_status = $user_quiz ? $user_quiz->status : ((clone $userQuery)->count() > 0 ? 'retake' : null);

            if ($quiz_details->has_level && $user_quiz) {
                $user_levels = $user_quiz->UserLevels()->select('status', 'question_level_id')->get();
                $quiz_details->levels = $quiz_details->levels->map(function ($level) use ($user_levels) {
                    $status = $user_levels->where('question_level_id', operator: $level->pivot->id)->first()->status;
                    $level->status = $status;

                    return $level;
                });
            }
        }

        return $this->withSuccess($quiz_details);
    }

    /**
     * @OA\Get(
     *     path="/api/v1/questions/{quiz}",
     *     tags={"Quiz"},
     *     summary="get the all questions",
     *     description="get the all questions",
     *     security={{"bearer": {}}},
     *
     *     @OA\Response(
     *         response=200,
     *         description="getting all questions",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=200),
     *             @OA\Property(property="token", type="object",
     *                 @OA\Property(property="id", type="integer", example=1),
     *             )
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=404,
     *         description="Not Found",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="not found")
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=500,
     *         description="Internal Server Error",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="Something went wrong.")
     *         )
     *     )
     * )
     * )
     */
    public function getQuestions($slug)
    {
        $quiz = $this->getQuizBySlug($slug);

        /**
         * @var \App\Models\User $user
         */
        $user = auth('client')->user();

        $user_quiz_query = $user->quizzes()
            ->where(column: 'quiz_id', operator: '=', value: $quiz->id);

        if (! $user_quiz_query->first()) {
            return $this->withError(__('You have not started this quiz'));
        }

        $levelSlug = request()->level;

        if (! $levelSlug && $quiz->has_level) {
            throw new CustomWebException(__('Please select a level'));
        }

        $level = null;
        // check if level is active

        if ($levelSlug) {
            $level = $quiz->questionLevels()->whereRelation('level', 'slug', $levelSlug)->first();

            if (! $level) {
                throw new CustomWebException(__('level not found'));
            }

            $level_completed = $level->userLevels()
                ->where(column: 'status', operator: '=', value: UserQuizStatusEnum::COMPLETED)
                ->first();

            if ($level_completed) {
                return $this->withSuccess([
                    'status' => 'completed',
                    'message' => __('You have already completed this level'),
                ]);
            }

            $user_level = $level->userLevels()
                ->whereIn('status', [UserQuizStatusEnum::IN_PROGRESS, UserQuizStatusEnum::PENDING])
                ->first();

            if (! $user_level) {
                throw new CustomWebException(__('You are not eligible for this level'));
            }
        }

        $already_played = $user_quiz_query->where(column: 'status', operator: '=', value: UserQuizStatusEnum::COMPLETED)->first();

        if ($already_played && ! $quiz->is_free) {
            return $this->withSuccess([
                'status' => 'completed',
                'message' => __('You have already completed this quiz'),
            ]);
        }

        // check quiz is eligible for the user
        $user_quiz = $user->quizzes()
            ->where(column: 'quiz_id', operator: '=', value: $quiz->id)
            ->whereIn('status', [UserQuizStatusEnum::PENDING, UserQuizStatusEnum::IN_PROGRESS])
            ->first();

        if (! $user_quiz) {
            throw new CustomWebException(__('You are not eligible for this quiz'));
        }

        $questions = ($level ?: $quiz)->questions()
            ->select('id', 'question_level_id', 'question_input_type', 'quiz_id', 'order', 'question_type', 'explanation_type', 'options', 'time_limit')
            ->get();

        $questions = $questions->map(function ($question) use ($user_quiz) {
            $question->isAlreadyAnswered = $question->answers()->where('user_quiz_id', $user_quiz->id)->exists();

            return $question;
        });

        return $this->withSuccess($questions);
    }

    /**
     * @OA\Post(
     *     path="/api/v1/assign-quiz/{quiz}",
     *     summary="Assign a new quiz to the user",
     *     description="Assign a new quiz to the user",
     *     tags={"Quiz"},
     *     security={{"bearer": {}}},
     *
     *     @OA\Response(
     *         response=200,
     *          description="Success",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=200),
     *             @OA\Property(property="is_correct", type="boolean", example=true),
     *             @OA\Property(property="correct_answers", type="string", example="")
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=404,
     *         description="User not found.",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=404),
     *             @OA\Property(property="message", type="string", example="User not found")
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=422,
     *         description="Validation error.",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=422),
     *             @OA\Property(
     *                 property="errors",
     *                 type="object",
     *                 additionalProperties={
     *                     @OA\Property(type="array", @OA\Items(type="string"))
     *                 },
     *             )
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=401,
     *         description="Invalid OTP.",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=401),
     *             @OA\Property(property="message", type="string", example="Invalid OTP")
     *         )
     *     )
     * )
     */
    public function assignNewQuiz(Request $request, $slug)
    {
        $quiz = $this->getQuizBySlug($slug);
        /**
         * @var \App\Models\User $user
         */
        $user = auth('client')->user();

        if ($quiz->status !== QuizStatusEnum::ACTIVE) {
            throw new CustomWebException(__('This quiz is not active'));
        }

        $user_quiz = $user->quizzes()
            ->where('quiz_id', $quiz->id)
            ->whereIn('status', [UserQuizStatusEnum::PENDING, UserQuizStatusEnum::IN_PROGRESS])
            ->first();

        if ($user_quiz) {
            return $this->withSuccess([
                'status' => 'success',
                'message' => __('You are already assigned to this quiz'),
            ]);
        }

        if (! $quiz->is_free && $quiz->point_to_pass > $user->coins) {
            throw new CustomWebException(__('You do not have enough points to start this quiz'));
        }

        $count = $user->quizzes()
            ->where('quiz_id', $quiz->id)
            ->count();

        if (! $quiz->is_free && $count >= 1) {
            throw new CustomWebException(__('You can only attempt this quiz once as it is paid quizzes!'));
        }

        DB::beginTransaction();

        try {
            $user_quiz = $user->quizzes()->create([
                'quiz_id' => $quiz->id,
                'status' => UserQuizStatusEnum::PENDING,
            ]);

            if ($quiz->has_level) {
                $levelsData = $quiz->questionLevels->map(function ($level, $key) {
                    return [
                        'question_level_id' => $level->id,
                        'status' => ! $key
                            ? UserQuizStatusEnum::IN_PROGRESS
                            : UserQuizStatusEnum::PENDING,
                    ];
                })->toArray();

                $user_quiz->userLevels()->createMany($levelsData);
            }

            if (! $quiz->is_free) {
                $user->decrement('coins', $quiz->point_to_pass);
                event(new CoinLogEvent($user, CoinMatterEnum::BALANCE_DEDUCT, $quiz->point_to_pass, '-'));
            }

            event(new BadgeEarned($user));

            DB::commit();

        } catch (\Throwable $th) {
            DB::rollBack();

            return $this->withError($th->getMessage());
        }

        return $this->withSuccess([
            'status' => 'success',
            'message' => __('Quiz assigned successfully'),
        ]);
    }

    /**
     * @OA\Post(
     *     path="/api/v1/quiz-answer/{quiz}/{question}",
     *     summary="Submit quiz answer",
     *     description="Submit quiz answer",
     *     tags={"Quiz"},
     *
     *     @OA\RequestBody(
     *         required=true,
     *
     *         @OA\JsonContent(
     *             required={"answers", "taken_time"},
     *
     *             @OA\Property(
     *                 property="answers",
     *                 type="string",
     *                 description="The answers submitted by the user."
     *             ),
     *             @OA\Property(
     *                 property="taken_time",
     *                 type="string",
     *                 description="The time taken by the user to complete the quiz."
     *             )
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=200,
     *          description="Success",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=200),
     *             @OA\Property(property="is_correct", type="boolean", example=true),
     *             @OA\Property(property="correct_answers", type="string", example="")
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=404,
     *         description="User not found.",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=404),
     *             @OA\Property(property="message", type="string", example="User not found")
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=422,
     *         description="Validation error.",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=422),
     *             @OA\Property(
     *                 property="errors",
     *                 type="object",
     *                 additionalProperties={
     *                     @OA\Property(type="array", @OA\Items(type="string"))
     *                 },
     *             )
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=401,
     *         description="Invalid OTP.",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=401),
     *             @OA\Property(property="message", type="string", example="Invalid OTP")
     *         )
     *     )
     * )
     */
    public function submitAnswer(Request $request, $slug, $question_slug)
    {
        $quiz = $this->getQuizBySlug($slug);

        $question = $this->getQuestionBySlug($question_slug);

        $validated = $request->validate([
            'level_slug' => 'nullable|string',
            'answers' => 'nullable|array',
            'taken_time' => 'required|integer',
        ]);

        if (empty($validated['answers'])) {
            $validated['answers'] = [];
        }

        $questionLevel = $question->questionLevel;

        if (isset($validated['level_slug'])) {
            $questionLevel = $quiz->questionLevels()->whereRelation('level', 'slug', $validated['level_slug'])->first();
        }

        // check the quiz is active or not
        $this->authorizeQuestion($quiz, $question, $questionLevel);

        try {
            // check the answers is correct
            $is_correct = $question->checkAnswer($validated['answers']);
            $score = $is_correct ? $question->score : 0;

            /**
             * @var \App\Models\User $user
             */
            $user = auth('client')->user();

            $userQuiz = $user->quizzes()
                ->where('quiz_id', $quiz->id)
                ->whereIn('status', [UserQuizStatusEnum::PENDING, UserQuizStatusEnum::IN_PROGRESS])
                ->first();

            if (! $userQuiz) {
                throw new CustomWebException(__('You have not started this quiz'));
            }

            // check already submitted or not
            $answer = $userQuiz->answers()->where('question_id', $question->id)->first();
            if ($answer) {
                return $this->withSuccess([
                    'is_correct' => $answer->is_correct,
                    'correct_answers' => $question->correct_answers,
                    'message' => __('You have already submitted the answer for this question'),
                ]);
            }
            DB::beginTransaction();
            // increment the user's score

            $user->increment('score', $score);
            // create the quiz answer
            $userQuiz->answers()->create([
                'question_id' => $question->id,
                'question_level_id' => $question->question_level_id,
                'answer_text' => $validated['answers'],
                'taken_time' => $validated['taken_time'],
                'score' => $score,
                'is_correct' => $is_correct,
            ]);

            $userLevel = $userQuiz->userLevels()->where('question_level_id', $question->question_level_id)->first();

            // add coins based on the score if the full quiz is completed
            // this only works if the user has completed the quiz
            if ($quiz->isCompletedByUser($userQuiz, $question->question_level_id)) {

                $all_answers = $userQuiz->answers()->where([
                    'question_level_id' => $question->question_level_id,
                    'is_correct' => true,
                ])->get();

                $full_score = $all_answers->sum('score');
                $tull_time = $all_answers->sum('taken_time');

                // update status the user level
                if ($userLevel) {
                    $userLevel->update([
                        'status' => UserQuizStatusEnum::COMPLETED,
                    ]);

                    $is_last_level_complete = $userQuiz->userLevels()->where('status', UserQuizStatusEnum::PENDING)->doesntExist();

                    if ($is_last_level_complete) {
                        // update status the user quiz
                        $userQuiz->update([
                            'score' => $full_score,
                            'taken_time' => $tull_time,
                            'status' => UserQuizStatusEnum::COMPLETED,
                        ]);
                    } else {
                        // determine the next level
                        $nextLevel = $userQuiz->userLevels()
                            ->where('question_level_id', $userLevel->nextLevel()->id);

                        if (! $nextLevel) {
                            throw new CustomWebException(__('Next level not found'));
                        }

                        $nextLevel->update(['status' => UserQuizStatusEnum::IN_PROGRESS]);
                    }
                } else {

                    // update status the user quiz
                    $userQuiz->update([
                        'score' => $full_score,
                        'taken_time' => $tull_time,
                        'status' => UserQuizStatusEnum::COMPLETED,
                    ]);
                }

                if ($full_score > 0) {
                    // add coins based on the score
                    $coins = CoinService::calculateCoinsFromPoints($full_score);
                    if ($coins > 0) {
                        // increment the user's coins
                        $user->increment('coins', $coins);
                        event(new CoinLogEvent($user, CoinMatterEnum::BALANCE_ADD, $coins, '+'));
                    }

                }
            }

            event(new BadgeEarned($user));

            DB::commit();
        } catch (\Throwable $th) {
            DB::rollBack();

            return $this->withError($th->getMessage());
        }

        return $this->withSuccess([
            'is_correct' => $is_correct,
            'correct_answers' => $question->correct_answers,
            'message' => $is_correct ? __('Correct answer!') : __('Incorrect answer!'),
        ]);
    }

    /**
     * @OA\Get(
     *     path="/api/v1/quiz-result/{quiz}",
     *     tags={"Quiz"},
     *     summary="get the quiz result",
     *     description="get the quiz result",
     *     security={{"bearer": {}}},
     *
     *     @OA\Response(
     *         response=200,
     *         description="getting quiz result",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="statusCode", type="number", example=200),
     *             @OA\Property(property="token", type="object",
     *                 @OA\Property(property="id", type="integer", example=1),
     *             )
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=404,
     *         description="Not Found",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="not found")
     *         )
     *     ),
     *
     *     @OA\Response(
     *         response=500,
     *         description="Internal Server Error",
     *
     *         @OA\JsonContent(
     *
     *             @OA\Property(property="message", type="string", example="Something went wrong.")
     *         )
     *     )
     * )
     * )
     */
    public function getQuizResult($slug, $username)
    {
        $quiz = $this->getQuizBySlug($slug);

        // decode username
        $username = decodeUsername($username);
        /**
         * @var \App\Models\User $user
         */
        $user = User::where('username', $username)->first();

        if (! $user) {
            throw new CustomWebException(__('User not found'));
        }

        $validated = request()->validate([
            'level_slug' => 'required_if:has_level,1|string',
        ]);

        $questionLevel = null;

        if ($quiz->has_level && ! isset($validated['level_slug'])) {
            throw new CustomWebException(__('Please select a level'));
        }

        if (isset($validated['level_slug'])) {
            $questionLevel = $quiz->questionLevels()->whereRelation('level', 'slug', $validated['level_slug'])->first();
        }

        $userQuiz = $user->quizzes()->where('quiz_id', $quiz->id)->where('status', UserQuizStatusEnum::COMPLETED)->latest()->first();

        if (! $userQuiz) {
            throw new CustomWebException(__('You have not started this quiz'));
        }

        $answers = $userQuiz->answers()->where('question_level_id', $questionLevel?->id)
            ->get(['question_id', 'answer_text', 'score', 'taken_time', 'is_correct', 'created_at']);

        $questions = ($questionLevel ?: $quiz)->questions()->with(['answers' => fn ($q) => $q->where('user_quiz_id', $userQuiz->id)->where('question_level_id', $questionLevel?->id)])
            ->select('id', 'question_level_id', 'quiz_id', 'order', 'question_input_type', 'question_type', 'explanation_type', 'correct_answers', 'options', 'time_limit')
            ->get();

        return $this->withSuccess([
            'questions' => $questions,
            'answers' => $answers,
            'user' => $user->only(['id', 'first_name', 'last_name', 'username', 'avatar', 'full_name']),
        ]);
    }

    private function authorizeQuestion(Quiz $quiz, QuizQuestion $question, $questionLevel = null)
    {
        if ($questionLevel && ($questionLevel->quiz_id != $quiz->id || $questionLevel->id != $question->question_level_id)) {
            throw new CustomWebException(__('Unauthorized action'));
        }
        if ($question->quiz_id != $quiz->id) {
            throw new CustomWebException(__('Unauthorized action'));
        }
    }

    private function getQuizBySlug($slug): Quiz
    {
        $quiz = Quiz::whereRelation('translations', 'slug', $slug)->first();

        if (! $quiz) {
            throw new CustomWebException(__('Quiz not found'));
        }

        return $quiz;
    }

    private function getQuestionBySlug($slug): QuizQuestion
    {
        $question = QuizQuestion::whereRelation('translations', 'slug', $slug)->first();

        if (! $question) {
            throw new CustomWebException(__('question not found'));
        }

        return $question;
    }
}
