<?php

namespace App\Http\Controllers;

use App\Events\LeaderboardUpdated;
use App\Events\ParticipantJoined;
use App\Models\Participant;
use App\Models\Quiz;
use App\Models\QuizAnswer;
use App\Models\QuizOption;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Illuminate\View\View;

class QuizController extends Controller
{
    public function welcome(): RedirectResponse
    {
        return redirect()->route('quiz.join');
    }

    public function join(?string $quizSlug = null): View
    {
        $activeQuizzes = Quiz::query()
            ->where('is_enabled', true)
            ->withCount('questions')
            ->orderBy('title')
            ->get();

        $quiz = $quizSlug
            ? $activeQuizzes->firstWhere('slug', $quizSlug)
            : $activeQuizzes->first();

        return view('join', [
            'quiz' => $quiz,
            'activeQuizzes' => $activeQuizzes,
        ]);
    }

    public function storeParticipant(Request $request): RedirectResponse
    {
        $validated = $request->validate([
            'name' => ['required', 'string', 'max:80'],
            'quiz_id' => ['nullable', 'integer', 'exists:quizzes,id'],
        ]);

        $quiz = $this->quiz($validated['quiz_id'] ?? null);

        if (! $quiz->is_enabled) {
            return back()
                ->withErrors(['name' => 'This test is currently inactive.'])
                ->withInput();
        }

        $participant = Participant::create([
            'quiz_id' => $quiz->id,
            'name' => trim($validated['name']),
            'session_uuid' => (string) Str::uuid(),
            'score' => 0,
            'last_seen_at' => now(),
        ]);

        ParticipantJoined::dispatch($participant);

        return redirect()->route('quiz.play', ['sessionUuid' => $participant->session_uuid]);
    }

    public function play(string $sessionUuid): View
    {
        $participant = Participant::query()
            ->where('session_uuid', $sessionUuid)
            ->with(['quiz.currentQuestion.options'])
            ->firstOrFail();

        $participant->update(['last_seen_at' => now()]);
        $shouldShowFinalResult = $this->shouldShowFinalResult($participant->quiz);
        $displayStatus = $shouldShowFinalResult ? 'finished' : $participant->quiz->status;

        return view('play', [
            'participant' => $participant,
            'quiz' => $participant->quiz,
            'isPlayable' => (bool) $participant->quiz->is_enabled,
            'state' => $this->state($participant->quiz, $displayStatus),
            'topThree' => $shouldShowFinalResult
                ? $this->finalTopThree($participant->quiz)
                : [],
        ]);
    }

    public function answer(Request $request, string $sessionUuid): JsonResponse
    {
        $validated = $request->validate([
            'option_id' => ['required', 'integer', 'exists:quiz_options,id'],
        ]);

        $participant = Participant::query()
            ->where('session_uuid', $sessionUuid)
            ->with('quiz.currentQuestion')
            ->firstOrFail();

        $quiz = $participant->quiz;

        if (! $quiz->is_enabled) {
            return response()->json([
                'ok' => false,
                'message' => 'This test is currently inactive.',
            ], 422);
        }

        if ($quiz->status !== 'active' || ! $quiz->currentQuestion) {
            return response()->json([
                'ok' => false,
                'message' => 'No active question right now.',
            ], 422);
        }

        if ($quiz->current_question_ends_at && now()->greaterThan($quiz->current_question_ends_at)) {
            return response()->json([
                'ok' => false,
                'message' => 'Time is over for this question.',
            ], 422);
        }

        $existingAnswer = QuizAnswer::query()
            ->where('participant_id', $participant->id)
            ->where('quiz_question_id', $quiz->currentQuestion->id)
            ->first();

        if ($existingAnswer) {
            return response()->json([
                'ok' => false,
                'message' => 'You already answered this question.',
            ], 409);
        }

        $option = QuizOption::query()
            ->where('id', $validated['option_id'])
            ->where('quiz_question_id', $quiz->currentQuestion->id)
            ->firstOrFail();

        $answeredAt = now();
        $startMs = (int) $quiz->current_question_started_at->getPreciseTimestamp(3);
        $answeredMs = (int) $answeredAt->getPreciseTimestamp(3);
        $responseMs = max(0, $answeredMs - $startMs);

        $isCorrect = $option->is_correct;
        $scoreAwarded = $isCorrect ? max(100, 1000 - (int) floor($responseMs / 100)) : 0;

        DB::transaction(function () use ($quiz, $participant, $option, $isCorrect, $responseMs, $scoreAwarded, $answeredAt) {
            QuizAnswer::create([
                'quiz_id' => $quiz->id,
                'quiz_question_id' => $quiz->currentQuestion->id,
                'participant_id' => $participant->id,
                'quiz_option_id' => $option->id,
                'is_correct' => $isCorrect,
                'response_ms' => $responseMs,
                'score_awarded' => $scoreAwarded,
                'answered_at' => $answeredAt,
            ]);

            if ($isCorrect) {
                $participant->increment('score', $scoreAwarded);
            }
        });

        $participant->refresh();
        $topThree = $this->topThreeForQuestion($quiz, $quiz->currentQuestion->id);
        $overall = $this->overallLeaderboard($quiz);
        LeaderboardUpdated::dispatch($quiz, $quiz->currentQuestion, $topThree, $overall);

        return response()->json([
            'ok' => true,
            'correct' => $isCorrect,
            'response_ms' => $responseMs,
            'score_awarded' => $scoreAwarded,
            'total_score' => $participant->score,
            'top_three' => $topThree,
            'overall' => $overall,
        ]);
    }

    private function quiz(?int $quizId = null): Quiz
    {
        if ($quizId) {
            return Quiz::query()->findOrFail($quizId);
        }

        return Quiz::query()
            ->where('is_enabled', true)
            ->orderBy('title')
            ->firstOrFail();
    }

    private function state(Quiz $quiz, ?string $status = null): array
    {
        $quiz->loadMissing(['currentQuestion.options']);

        return [
            'uuid' => $quiz->uuid,
            'title' => $quiz->title,
            'status' => $status ?? $quiz->status,
            'current_question_id' => $quiz->current_question_id,
            'current_question_started_at' => optional($quiz->current_question_started_at)->toIso8601String(),
            'current_question_ends_at' => optional($quiz->current_question_ends_at)->toIso8601String(),
            'current_question' => $quiz->currentQuestion ? [
                'id' => $quiz->currentQuestion->id,
                'sort_order' => $quiz->currentQuestion->sort_order,
                'text' => $quiz->currentQuestion->question_text,
                'duration_seconds' => $quiz->currentQuestion->duration_seconds,
                'options' => $quiz->currentQuestion->options->map(fn ($option) => [
                    'id' => $option->id,
                    'text' => $option->option_text,
                    'sort_order' => $option->sort_order,
                ])->values(),
            ] : null,
        ];
    }

    private function topThreeForQuestion(Quiz $quiz, int $questionId): array
    {
        return QuizAnswer::query()
            ->with('participant')
            ->where('quiz_id', $quiz->id)
            ->where('quiz_question_id', $questionId)
            ->where('is_correct', true)
            ->orderBy('response_ms')
            ->limit(3)
            ->get()
            ->map(fn ($answer, $index) => [
                'rank' => $index + 1,
                'name' => $answer->participant->name,
                'response_ms' => $answer->response_ms,
                'score_awarded' => $answer->score_awarded,
                'total_score' => $answer->participant->score,
            ])
            ->values()
            ->toArray();
    }

    private function overallLeaderboard(Quiz $quiz): array
    {
        return $quiz->participants()
            ->orderByDesc('score')
            ->orderBy('created_at')
            ->limit(10)
            ->get()
            ->map(fn ($participant, $index) => [
                'rank' => $index + 1,
                'name' => $participant->name,
                'score' => $participant->score,
            ])
            ->values()
            ->toArray();
    }

    private function finalTopThree(Quiz $quiz): array
    {
        return collect($this->overallLeaderboard($quiz))
            ->take(3)
            ->map(fn (array $item) => [
                'rank' => $item['rank'],
                'name' => $item['name'],
                'score' => $item['score'],
                'total_score' => $item['score'],
            ])
            ->values()
            ->toArray();
    }

    private function shouldShowFinalResult(Quiz $quiz): bool
    {
        return $quiz->status === 'finished'
            || (! $quiz->current_question_id && $quiz->answers()->exists());
    }
}
