<?php

namespace App\Services;

use App\Models\Attachment;
use App\Models\BoardPattern;
use App\Models\CompetitionAssignment;
use App\Models\GameplayHistory;
use App\Models\MatchContest;
use App\Models\PageDetail;
use App\Models\QuestionManagement;
use App\Models\QuestionSetup;
use App\Models\SchoolDetail;
use App\Models\StudentActivityLog;
use App\Models\SiteDetail;
use App\Models\StudentDeviceSession;
use App\Models\StudentExamSession;
use App\Models\StudentGroup;
use App\Models\StudentProfile;
use App\Models\User;
use App\Models\UserAnswerDetail;
use App\Models\WalletHistory;
use App\Models\WalletUser;
use App\Models\WinningHistory;
use App\Models\WithdrawHistory;
use App\Support\LegacyJson;
use DB;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Hash;
use Throwable;

class StudentExamService
{
    public function __construct(
        private readonly StudentActivityLogger $activityLogger,
    ) {
    }

    public function buildStudentHomePayload(User $user): array
    {
        $profile = $this->currentStudentProfile($user);
        $sections = $this->buildCompetitionSections($profile);
        $timeline = $this->buildStudentCompetitionTimeline($profile, (int) $user->id);
        $competitionCount = array_sum(array_map(
            static fn (array $section): int => count($section['cards'] ?? []),
            $sections
        ));

        return [
            'student' => $this->buildStudentProfilePayload($user, $profile),
            'wallet_balance' => $this->currentStudentWalletAmount((int) $user->id),
            'active_exam_session' => $this->activeExamSessionBrief($user),
            'summary' => [
                'board_sections' => count($sections),
                'open_competitions' => $competitionCount,
                'current_class' => $profile->classname ?? 'Profile pending',
            ],
            'competition_sections' => $sections,
            'competition_timeline' => $timeline,
        ];
    }

    public function buildContestDetailsPayload(
        MatchContest $contest,
        User $user,
        ?int $preferredAssignmentId = null,
    ): array
    {
        $profile = $this->currentStudentProfile($user);
        $assignmentQuery = CompetitionAssignment::query()
            ->where('compitetionid', $contest->id)
            ->when(
                $profile->classid,
                fn ($query) => $query->where('groupclassid', $profile->classid)
            );

        $assignment = null;

        if ($preferredAssignmentId) {
            $assignment = (clone $assignmentQuery)
                ->whereKey($preferredAssignmentId)
                ->first();
        }

        if (! $assignment) {
            $assignment = (clone $assignmentQuery)
                ->orderByDesc('id')
                ->first();
        }

        $joinedCount = GameplayHistory::query()->where('contestid', $contest->id)->count();
        $contestSeat = max(0, (int) $contest->contestseat);
        $joinedPercent = $contestSeat > 0 ? round(($joinedCount / $contestSeat) * 100, 2) : 0;
        $rankRows = $this->buildContestRankRows($contest);
        $walletAmount = $this->currentStudentWalletAmount((int) $user->id);
        $walletShortfall = 0.0;
        $seatAvailable = $this->isCompetitionSeatAvailable($contest, $joinedCount);
        $latestGameplay = null;
        $userAnswer = null;
        $showAlreadyJoined = false;
        $showJoinButton = false;
        $showWalletButton = false;
        $canStartExam = false;
        $answersheetAvailable = false;
        $resultAvailable = false;
        $joinDisabledReason = null;
        $examStatusMessage = null;

        if ($assignment) {
            $allowedEntries = max(1, (int) $contest->userentery);
            $joinedEntries = GameplayHistory::query()
                ->where('questionsetid', $assignment->questionsetid)
                ->where('contestid', $contest->id)
                ->where('userid', $user->id)
                ->orderByDesc('id')
                ->get();

            $showAlreadyJoined = $joinedEntries->count() >= $allowedEntries;

            if (! $showAlreadyJoined) {
                $joinDisabledReason = $this->joinWindowStatusMessage($assignment);

                if (! $joinDisabledReason && ! $seatAvailable) {
                    $joinDisabledReason = 'All available seats are already filled.';
                }

                $walletShortfall = max(0, (float) $contest->joinamt - $walletAmount);

                if (! $joinDisabledReason && $walletShortfall > 0) {
                    $joinDisabledReason = 'Add wallet balance before joining this competition.';
                    $showWalletButton = true;
                }

                $showJoinButton = $joinDisabledReason === null;
            }

            $latestGameplay = GameplayHistory::query()
                ->where('contestid', $contest->id)
                ->where('userid', $user->id)
                ->orderByDesc('id')
                ->first();

            if ($latestGameplay) {
                $userAnswer = UserAnswerDetail::query()
                    ->where('competitionid', $contest->id)
                    ->where('createdby', $user->id)
                    ->orderByDesc('id')
                    ->first();

                if ($userAnswer) {
                    $answersheetAvailable = $assignment->answersheetdate
                        && now()->toDateString() >= $assignment->answersheetdate->format('Y-m-d');
                    $resultAvailable = $assignment->resultdate
                        && now()->toDateString() >= $assignment->resultdate->format('Y-m-d');
                } else {
                    $canStartExam = $this->isAssignmentExamLive($assignment);

                    if (! $canStartExam) {
                        if ($latestGameplay && $assignment->examdate && now()->toDateString() > $assignment->examdate->format('Y-m-d')) {
                            $examStatusMessage = 'You joined this competition earlier, but no final exam submission was recorded before the exam window closed.';
                        } else {
                            $examStatusMessage = $this->examWindowStatusMessage($assignment);
                        }
                    }
                }
            }
        }

        $lastRankRow = ! empty($rankRows) ? $rankRows[array_key_last($rankRows)] : null;

        return [
            'contest' => [
                'id' => (int) $contest->id,
                'title' => (string) $contest->contesttitile,
                'join_amount' => (float) $contest->joinamt,
                'contest_seat' => $contestSeat,
                'win_percentage' => (float) $contest->winpertange,
                'max_entries_per_student' => max(1, (int) $contest->userentery),
            ],
            'assignment' => $assignment ? [
                'id' => (int) $assignment->id,
                'class_id' => (int) $assignment->groupclassid,
                'class_name' => (string) $assignment->groupclassname,
                'board_id' => (int) $assignment->boardpatternid,
                'exam_date' => $assignment->examdate?->toDateString(),
                'exam_end_time' => (string) ($assignment->examendtime ?? ''),
                'answer_sheet_date' => $assignment->answersheetdate?->toDateString(),
                'result_date' => $assignment->resultdate?->toDateString(),
            ] : null,
            'reward_rows' => $rankRows,
            'progress' => [
                'joined_count' => $joinedCount,
                'remaining_seat' => max(0, $contestSeat - $joinedCount),
                'joined_percent' => $joinedPercent,
                'first_prize' => $rankRows[0]['prize_amount'] ?? 0,
                'last_rank' => $lastRankRow['end_rank'] ?? null,
            ],
            'student_state' => [
                'wallet_balance' => $walletAmount,
                'wallet_shortfall' => $walletShortfall,
                'seat_available' => $seatAvailable,
                'already_joined' => $showAlreadyJoined,
                'can_join' => (bool) ($assignment && $showJoinButton),
                'show_wallet_action' => (bool) ($assignment && $showWalletButton),
                'join_disabled_reason' => $joinDisabledReason,
                'has_joined_entry' => (bool) $latestGameplay,
                'has_submitted_answer' => (bool) $userAnswer,
                'can_start_exam' => $canStartExam,
                'exam_status_message' => $examStatusMessage,
                'answer_sheet_available' => $answersheetAvailable,
                'result_available' => $resultAvailable,
            ],
        ];
    }

    public function joinCompetitionAssignment(
        User $user,
        CompetitionAssignment $assignment,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        $profile = $this->currentStudentProfile($user);
        $contest = $assignment->contest;

        if (! $contest) {
            return [
                'status' => 'invalid_contest',
                'message' => 'Competition could not be loaded.',
            ];
        }

        if (! $profile->classid || (int) $profile->classid !== (int) $assignment->groupclassid) {
            $this->activityLogger->log(
                (int) $user->id,
                'competition_join_blocked_class_mismatch',
                [
                    'competition_assignment_id' => $assignment->id,
                    'student_class_id' => $profile->classid,
                    'assignment_class_id' => $assignment->groupclassid,
                ],
                'warning',
                $request,
                $deviceSession,
            );

            return [
                'status' => 'class_mismatch',
                'message' => 'This competition is not assigned to the current student class.',
            ];
        }

        $joinWindowMessage = $this->joinWindowStatusMessage($assignment);

        if ($joinWindowMessage !== null) {
            return [
                'status' => 'registration_closed',
                'message' => $joinWindowMessage,
            ];
        }

        if (! $this->isCompetitionSeatAvailable($contest)) {
            return [
                'status' => 'full',
                'message' => 'All available seats are already filled for this competition.',
            ];
        }

        $joinAmount = (float) $contest->joinamt;
        $joinResult = DB::transaction(function () use ($user, $assignment, $contest, $joinAmount) {
            $lockedContest = MatchContest::query()
                ->whereKey($contest->id)
                ->lockForUpdate()
                ->firstOrFail();

            $joinedCount = GameplayHistory::query()
                ->where('contestid', $lockedContest->id)
                ->count();

            if (! $this->isCompetitionSeatAvailable($lockedContest, $joinedCount)) {
                return [
                    'status' => 'full',
                    'wallet_balance' => $this->currentStudentWalletAmount((int) $user->id),
                ];
            }

            $existingGameplay = GameplayHistory::query()
                ->where('userid', $user->id)
                ->where('assignquestionid', $assignment->id)
                ->lockForUpdate()
                ->first();

            if ($existingGameplay) {
                return [
                    'status' => 'already_joined',
                    'wallet_balance' => $this->currentStudentWalletAmount((int) $user->id),
                ];
            }

            $walletUser = WalletUser::query()
                ->where('userid', $user->id)
                ->lockForUpdate()
                ->first();

            if (! $walletUser) {
                $walletUser = WalletUser::query()->create([
                    'userid' => $user->id,
                    'username' => $this->studentDisplayName($user),
                    'walletamount' => 0,
                    'updatedtime' => now(),
                ]);

                $walletUser = WalletUser::query()
                    ->whereKey($walletUser->id)
                    ->lockForUpdate()
                    ->firstOrFail();
            }

            if ((float) $walletUser->walletamount < $joinAmount) {
                return [
                    'status' => 'insufficient_wallet',
                    'wallet_balance' => (float) $walletUser->walletamount,
                ];
            }

            $walletUser->update([
                'walletamount' => (float) $walletUser->walletamount - $joinAmount,
                'updatedtime' => now(),
            ]);

            GameplayHistory::query()->create([
                'userid' => $user->id,
                'username' => $user->name,
                'contestid' => $assignment->compitetionid,
                'questionsetid' => $assignment->questionsetid,
                'questionsetname' => $assignment->questiomnsetname,
                'contestname' => $assignment->compitetionname,
                'assignquestionid' => $assignment->id,
                'assignquestionname' => $assignment->compitetiontitle,
                'playedamount' => $joinAmount,
                'createtime' => now(),
            ]);

            return [
                'status' => 'joined',
                'wallet_balance' => (float) $walletUser->walletamount,
            ];
        });

        $eventType = match ($joinResult['status']) {
            'joined' => 'competition_joined',
            'already_joined' => 'competition_join_existing',
            'insufficient_wallet' => 'competition_join_blocked_wallet',
            'full' => 'competition_join_blocked_full',
            default => 'competition_join_processed',
        };

        $this->activityLogger->log(
            (int) $user->id,
            $eventType,
            [
                'competition_assignment_id' => $assignment->id,
                'competition_id' => $assignment->compitetionid,
                'wallet_balance' => (float) ($joinResult['wallet_balance'] ?? 0),
            ],
            in_array($joinResult['status'], ['insufficient_wallet', 'full'], true) ? 'warning' : 'info',
            $request,
            $deviceSession,
        );

        return [
            'status' => $joinResult['status'],
            'message' => match ($joinResult['status']) {
                'joined' => 'Competition joined successfully.',
                'already_joined' => 'Competition already joined on this account.',
                'insufficient_wallet' => 'Insufficient wallet balance to join this competition.',
                'full' => 'All available seats are already filled for this competition.',
                default => 'Competition join request processed.',
            },
            'wallet_balance' => (float) ($joinResult['wallet_balance'] ?? $this->currentStudentWalletAmount((int) $user->id)),
            'contest_details' => $this->buildContestDetailsPayload($contest->fresh(), $user),
        ];
    }

    public function buildAnswerSheetPayload(User $user, CompetitionAssignment $assignment): array
    {
        $answerDetail = UserAnswerDetail::query()
            ->where('createdby', $user->id)
            ->where('competitionassignid', $assignment->id)
            ->latest('id')
            ->first();

        if (! $answerDetail) {
            return [
                'status' => 'missing',
                'message' => 'Answer sheet is not available yet.',
            ];
        }

        if ($assignment->answersheetdate && now()->toDateString() < $assignment->answersheetdate->format('Y-m-d')) {
            return [
                'status' => 'locked',
                'message' => 'Answer sheet will be available on ' . $assignment->answersheetdate->format('d M Y') . '.',
            ];
        }

        $questionSetId = $this->parseLegacyIdList((string) $answerDetail->questionsetid)[0]
            ?? $this->parseLegacyIdList((string) $assignment->questionsetid)[0]
            ?? null;
        $questionSet = $this->resolveAssignedQuestionSetup($assignment, $questionSetId);

        if (! $questionSet) {
            return [
                'status' => 'invalid_question_set',
                'message' => 'Question set could not be loaded for this answer sheet.',
            ];
        }

        return [
            'status' => 'ok',
            'assignment' => [
                'id' => (int) $assignment->id,
                'competition_id' => (int) $assignment->compitetionid,
                'competition_name' => (string) $assignment->compitetionname,
                'competition_title' => (string) $assignment->compitetiontitle,
                'class_name' => (string) $assignment->groupclassname,
                'answer_sheet_date' => $assignment->answersheetdate?->toDateString(),
                'result_date' => $assignment->resultdate?->toDateString(),
            ],
            'question_set' => [
                'id' => (int) $questionSet->id,
                'title' => (string) ($questionSet->questionsettitle ?? 'Question Set'),
            ],
            'summary' => [
                'total_marks' => (float) $answerDetail->totalmarks,
                'total_percentage' => (float) $answerDetail->totalpercentage,
                'pass_status' => (int) $answerDetail->status === 1 ? 'pass' : 'fail',
                'submitted_at' => $answerDetail->createdtime?->toIso8601String(),
                'user_time' => (string) ($answerDetail->usertime ?? '00:00'),
            ],
            'sections' => $this->buildExamSections($questionSet, $answerDetail),
        ];
    }

    public function buildPublishedResultsPayload(User $user): array
    {
        $profile = $this->currentStudentProfile($user);
        $submittedAssignmentIds = UserAnswerDetail::query()
            ->where('createdby', $user->id)
            ->pluck('competitionassignid')
            ->filter()
            ->map(fn ($assignmentId) => (int) $assignmentId)
            ->unique()
            ->values();

        if ($submittedAssignmentIds->isEmpty()) {
            return [
                'results' => [],
                'summary' => [
                    'published_results' => 0,
                ],
            ];
        }

        $assignments = CompetitionAssignment::query()
            ->with('contest')
            ->when(
                $profile->classid,
                fn ($query) => $query->where('groupclassid', $profile->classid),
                fn ($query) => $query->whereRaw('1 = 0')
            )
            ->whereIn('id', $submittedAssignmentIds->all())
            ->whereNotNull('resultdate')
            ->whereDate('resultdate', '<=', now()->toDateString())
            ->orderBy('resultdate', 'DESC')
            ->orderBy('startdate', 'ASC')
            ->get();

        $joinedCounts = GameplayHistory::query()
            ->select('contestid', DB::raw('COUNT(*) as total'))
            ->groupBy('contestid')
            ->pluck('total', 'contestid');
        $winningHistories = WinningHistory::query()
            ->where('userid', $user->id)
            ->get()
            ->keyBy('competitionid');

        $results = $assignments
            ->map(function (CompetitionAssignment $assignment) use ($joinedCounts, $winningHistories, $user) {
                $contest = $assignment->contest;

                if (! $contest) {
                    return null;
                }

                $rankRows = $this->buildContestRankRows($contest);
                $lastRankRow = ! empty($rankRows) ? $rankRows[array_key_last($rankRows)] : null;
                $joinedCount = (int) ($joinedCounts[$assignment->compitetionid] ?? 0);
                $contestSeat = max(0, (int) $contest->contestseat);
                $answerDetail = UserAnswerDetail::query()
                    ->where('createdby', $user->id)
                    ->where('competitionid', $contest->id)
                    ->where('competitionassignid', $assignment->id)
                    ->latest('id')
                    ->first();
                $winning = $winningHistories->get($contest->id);

                return [
                    'competition_id' => (int) $contest->id,
                    'assignment_id' => (int) $assignment->id,
                    'title' => (string) $contest->contesttitile,
                    'class_name' => (string) $assignment->groupclassname,
                    'first_prize' => $rankRows[0]['prize_amount'] ?? 0,
                    'last_rank' => $lastRankRow['end_rank'] ?? null,
                    'remaining_seat' => max(0, $contestSeat - $joinedCount),
                    'exam_date' => $assignment->examdate?->toDateString(),
                    'answer_sheet_date' => $assignment->answersheetdate?->toDateString(),
                    'result_date' => $assignment->resultdate?->toDateString(),
                    'result_summary' => $answerDetail ? [
                        'answer_detail_id' => (int) $answerDetail->id,
                        'total_marks' => (float) $answerDetail->totalmarks,
                        'total_percentage' => (float) $answerDetail->totalpercentage,
                        'pass_status' => (int) $answerDetail->status === 1 ? 'pass' : 'fail',
                    ] : null,
                    'winning' => $winning ? [
                        'rank' => (int) $winning->rank,
                        'amount' => (float) $winning->useramount,
                    ] : null,
                ];
            })
            ->filter()
            ->values()
            ->all();

        return [
            'results' => $results,
            'summary' => [
                'published_results' => count($results),
            ],
        ];
    }

    public function buildCompetitionResultPayload(
        User $user,
        MatchContest $contest,
        ?int $preferredAssignmentId = null,
    ): array
    {
        $answerDetailQuery = UserAnswerDetail::query()
            ->where('createdby', $user->id)
            ->where('competitionid', $contest->id);

        if ($preferredAssignmentId) {
            $answerDetailQuery->where('competitionassignid', $preferredAssignmentId);
        }

        $answerDetail = $answerDetailQuery
            ->latest('id')
            ->first();

        if (! $answerDetail) {
            return [
                'status' => 'missing',
                'message' => 'Result details will be available after exam submission.',
            ];
        }

        $assignment = null;

        if ($preferredAssignmentId) {
            $assignment = CompetitionAssignment::query()
                ->where('compitetionid', $contest->id)
                ->whereKey($preferredAssignmentId)
                ->first();
        }

        if (! $assignment) {
            $assignment = CompetitionAssignment::query()->find($answerDetail->competitionassignid)
                ?? CompetitionAssignment::query()
                    ->where('compitetionid', $contest->id)
                    ->orderByDesc('id')
                    ->first();
        }

        if (! $assignment) {
            return [
                'status' => 'missing_assignment',
                'message' => 'Competition assignment could not be loaded.',
            ];
        }

        if (! $assignment->resultdate || now()->toDateString() < $assignment->resultdate->format('Y-m-d')) {
            return [
                'status' => 'locked',
                'message' => 'Result will be available on ' . ($assignment->resultdate?->format('d M Y') ?? 'the published date') . '.',
            ];
        }

        $questionSetId = $this->parseLegacyIdList((string) $answerDetail->questionsetid)[0]
            ?? $this->parseLegacyIdList((string) $assignment->questionsetid)[0]
            ?? null;
        $questionSet = $this->resolveAssignedQuestionSetup($assignment, $questionSetId);

        if (! $questionSet) {
            return [
                'status' => 'invalid_question_set',
                'message' => 'Question set could not be loaded for this result.',
            ];
        }

        $rankedEntries = UserAnswerDetail::query()
            ->with('user')
            ->where('competitionid', $contest->id)
            ->where('competitionassignid', $assignment->id)
            ->where('status', 1)
            ->orderByDesc('totalpercentage')
            ->orderBy('id')
            ->get()
            ->values()
            ->map(function (UserAnswerDetail $entry, int $index) {
                return [
                    'answer_detail_id' => (int) $entry->id,
                    'rank' => $index + 1,
                    'student_name' => (string) ($entry->user?->name ?? 'Student'),
                    'masked_mobile' => $this->maskMobileNumber($entry->user?->mobileno),
                    'total_marks' => (float) $entry->totalmarks,
                    'total_percentage' => (float) $entry->totalpercentage,
                ];
            })
            ->values();

        $currentUserRank = $rankedEntries
            ->firstWhere('answer_detail_id', (int) $answerDetail->id);
        $winning = WinningHistory::query()
            ->where('userid', $user->id)
            ->where('competitionid', $contest->id)
            ->latest('id')
            ->first();

        return [
            'status' => 'ok',
            'contest' => [
                'id' => (int) $contest->id,
                'title' => (string) $contest->contesttitile,
                'join_amount' => (float) $contest->joinamt,
            ],
            'assignment' => [
                'id' => (int) $assignment->id,
                'class_name' => (string) $assignment->groupclassname,
                'exam_date' => $assignment->examdate?->toDateString(),
                'answer_sheet_date' => $assignment->answersheetdate?->toDateString(),
                'result_date' => $assignment->resultdate?->toDateString(),
            ],
            'summary' => [
                'answer_detail_id' => (int) $answerDetail->id,
                'total_marks' => (float) $answerDetail->totalmarks,
                'total_percentage' => (float) $answerDetail->totalpercentage,
                'pass_status' => (int) $answerDetail->status === 1 ? 'pass' : 'fail',
                'user_time' => (string) ($answerDetail->usertime ?? '00:00'),
                'user_rank' => $currentUserRank['rank'] ?? null,
            ],
            'subject_rows' => [
                ['label' => 'G.K.', 'marks' => (float) $answerDetail->gkmarks, 'percentage' => (float) $answerDetail->gkpercentage],
                ['label' => 'Science', 'marks' => (float) $answerDetail->sciencemarks, 'percentage' => (float) $answerDetail->sciencepercentage],
                ['label' => 'Hindi', 'marks' => (float) $answerDetail->hindimarks, 'percentage' => (float) $answerDetail->hindipercentage],
                ['label' => 'English', 'marks' => (float) $answerDetail->englishmarks, 'percentage' => (float) $answerDetail->englishpercentage],
                ['label' => 'Maths', 'marks' => (float) $answerDetail->mathsmarks, 'percentage' => (float) $answerDetail->mathspercentage],
            ],
            'leaderboard' => [
                'total_qualified' => $rankedEntries->count(),
                'top_entries' => $rankedEntries->take(20)->all(),
            ],
            'winning' => $winning ? [
                'rank' => (int) $winning->rank,
                'amount' => (float) $winning->useramount,
                'awarded_at' => $winning->createdtime?->toIso8601String(),
            ] : null,
            'answer_sheet' => [
                'question_set_id' => (int) $questionSet->id,
                'sections' => $this->buildExamSections($questionSet, $answerDetail),
            ],
        ];
    }

    public function buildScoringHistoryPayload(User $user): array
    {
        $winningHistories = WinningHistory::query()
            ->where('userid', $user->id)
            ->orderByDesc('id')
            ->get()
            ->keyBy('competitionid');

        $entries = GameplayHistory::query()
            ->with(['contest', 'assignment'])
            ->where('userid', $user->id)
            ->orderByDesc('id')
            ->get()
            ->map(function (GameplayHistory $history) use ($winningHistories, $user) {
                $winning = $winningHistories->get($history->contestid);
                $answerDetail = UserAnswerDetail::query()
                    ->where('createdby', $user->id)
                    ->where('competitionassignid', $history->assignquestionid)
                    ->latest('id')
                    ->first();

                return [
                    'id' => (int) $history->id,
                    'competition_id' => (int) $history->contestid,
                    'competition_name' => (string) ($history->contestname ?? $history->contest?->contesttitile ?? ''),
                    'assignment_id' => (int) $history->assignquestionid,
                    'assignment_title' => (string) ($history->assignquestionname ?? $history->assignment?->compitetiontitle ?? ''),
                    'question_set_id' => (string) ($history->questionsetid ?? ''),
                    'question_set_name' => (string) ($history->questionsetname ?? ''),
                    'played_amount' => (float) $history->playedamount,
                    'joined_at' => $history->createtime?->toIso8601String(),
                    'submitted' => (bool) $answerDetail,
                    'result_summary' => $answerDetail ? [
                        'total_marks' => (float) $answerDetail->totalmarks,
                        'total_percentage' => (float) $answerDetail->totalpercentage,
                        'pass_status' => (int) $answerDetail->status === 1 ? 'pass' : 'fail',
                    ] : null,
                    'winning' => $winning ? [
                        'rank' => (int) $winning->rank,
                        'amount' => (float) $winning->useramount,
                    ] : null,
                ];
            })
            ->values()
            ->all();

        return [
            'entries' => $entries,
            'summary' => [
                'joined_competitions' => count($entries),
                'submitted_exams' => count(array_filter($entries, static fn (array $entry): bool => (bool) $entry['submitted'])),
                'winning_entries' => count(array_filter($entries, static fn (array $entry): bool => $entry['winning'] !== null)),
            ],
        ];
    }

    public function buildWalletOverviewPayload(User $user): array
    {
        [$rechargeAmounts, $firstAmount] = $this->buildWalletRechargeAmounts($user);
        $withdrawSummary = $this->buildWithdrawSummaryData((int) $user->id);

        return [
            'wallet_balance' => $this->currentStudentWalletAmount((int) $user->id),
            'recharge_amounts' => $rechargeAmounts,
            'default_recharge_amount' => $firstAmount,
            'recharge_meta' => [
                'upi_id' => (string) config('mobile_exam.recharge_upi_id', '8619586477-2@ybl'),
                'upi_label' => (string) config('mobile_exam.recharge_upi_label', 'Wallet Recharge'),
            ],
            'withdraw_summary' => $withdrawSummary,
            'pending_recharge_count' => WalletHistory::query()
                ->where('userid', $user->id)
                ->where('status', 2)
                ->count(),
        ];
    }

    public function buildWalletHistoryPayload(User $user): array
    {
        $entries = WalletHistory::query()
            ->where('userid', $user->id)
            ->orderByDesc('id')
            ->get()
            ->map(function (WalletHistory $history) {
                $attachment = $history->transectionimg
                    ? Attachment::query()->find($history->transectionimg)
                    : null;

                return [
                    'id' => (int) $history->id,
                    'amount' => (float) $history->addamount,
                    'transaction_id' => (string) ($history->transectionid ?? ''),
                    'proof_attachment_id' => $history->transectionimg ? (int) $history->transectionimg : null,
                    'proof_url' => $attachment ? $this->storedFileUrl((string) $attachment->att_url) : null,
                    'status' => (int) $history->status,
                    'status_label' => $this->walletStatusLabel((int) $history->status),
                    'created_at' => $history->createdtime?->toIso8601String(),
                ];
            })
            ->values()
            ->all();

        return [
            'entries' => $entries,
            'summary' => [
                'total_transactions' => count($entries),
                'total_recharge_amount' => array_sum(array_map(static fn (array $entry): float => (float) $entry['amount'], $entries)),
                'proof_count' => count(array_filter($entries, static fn (array $entry): bool => $entry['transaction_id'] !== '')),
            ],
        ];
    }

    public function buildWithdrawHistoryPayload(User $user): array
    {
        $entries = WithdrawHistory::query()
            ->where('createdby', $user->id)
            ->orderByDesc('id')
            ->get()
            ->map(function (WithdrawHistory $history) {
                return [
                    'id' => (int) $history->id,
                    'payment_type' => (string) ($history->paymenttype ?? ''),
                    'mobile_number' => (string) ($history->mobilenumber ?? ''),
                    'withdraw_amount' => (float) $history->withdrawamt,
                    'status' => (int) $history->status,
                    'status_label' => $this->withdrawStatusLabel((int) $history->status),
                    'status_tone' => $this->withdrawStatusTone((int) $history->status),
                    'created_at' => $history->createdtime?->toIso8601String(),
                ];
            })
            ->values()
            ->all();

        return [
            'entries' => $entries,
            'summary' => $this->buildWithdrawSummaryData((int) $user->id),
        ];
    }

    public function buildEditableProfilePayload(User $user): array
    {
        $profile = $this->currentStudentProfile($user);
        $classOptions = StudentGroup::query()
            ->orderBy('id')
            ->get()
            ->map(fn (StudentGroup $group) => [
                'id' => (int) $group->id,
                'name' => (string) $group->groupname,
            ])
            ->values()
            ->all();

        return [
            'student' => $this->buildStudentProfilePayload($user, $profile),
            'profile' => [
                'father_name' => (string) ($profile->fathername ?? ''),
                'mother_name' => (string) ($profile->mothername ?? ''),
                'date_of_birth' => $profile->dateofbirth ? date('Y-m-d', strtotime((string) $profile->dateofbirth)) : null,
                'sr_number' => (string) ($profile->srnumber ?? ''),
                'upi_id' => (string) ($profile->studentupiid ?? ''),
                'upi_locked' => filled($profile->studentupiid ?? null),
                'pan_card_number' => (string) ($profile->pancardnumber ?? ''),
                'bank_account_number' => (string) ($profile->bankaccountnumber ?? ''),
                'bank_account_holder_name' => (string) ($profile->bankaccountholdername ?? ''),
                'bank_branch' => (string) ($profile->bankbranch ?? ''),
                'bank_ifsc_code' => (string) ($profile->bankifsccode ?? ''),
                'religion' => (string) ($profile->religion ?? ''),
                'aadhaar_card_number' => (string) ($user->aadharcard ?? ''),
                'profile_image_attachment_id' => $user->profileimh ? (int) $user->profileimh : null,
                'email_id' => (string) ($user->useremailid ?? ''),
            ],
            'verification' => [
                'mobile_verified' => (bool) ($user->mobilevarification ?? false),
                'email_verified' => (bool) ($user->emailvarify ?? false),
                'mobile' => (string) ($user->mobileno ?? ''),
                'email' => (string) ($user->useremailid ?? ''),
            ],
            'class_options' => $classOptions,
        ];
    }

    public function updateStudentProfile(
        User $user,
        array $data,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        $class = StudentGroup::query()->find($data['class_id']);

        if (! $class) {
            return [
                'status' => 'invalid_class',
                'message' => 'Selected class is invalid.',
            ];
        }

        $profile = StudentProfile::query()->where('studentid', $user->id)->first();
        $existingUpiId = (string) ($profile?->studentupiid ?? '');
        $requestedUpiId = trim((string) ($data['upi_id'] ?? ''));

        if ($existingUpiId !== '' && $requestedUpiId !== '' && strcasecmp($existingUpiId, $requestedUpiId) !== 0) {
            return [
                'status' => 'upi_locked',
                'message' => 'UPI ID safety lock is active. Please contact admin support to update it.',
            ];
        }

        $user->update([
            'name' => $data['name'],
            'aadharcard' => $data['aadhaar_card_number'],
            'updated_at' => now(),
        ]);

        StudentProfile::query()->updateOrCreate(
            ['studentid' => $user->id],
            [
                'classid' => (int) $class->id,
                'classname' => (string) $class->groupname,
                'fathername' => $data['father_name'],
                'mothername' => $data['mother_name'],
                'dateofbirth' => $data['date_of_birth'],
                'srnumber' => $data['sr_number'] ?? null,
                'studentupiid' => $requestedUpiId !== '' ? $requestedUpiId : $existingUpiId,
                'pancardnumber' => $data['pan_card_number'],
                'bankaccountnumber' => $data['bank_account_number'],
                'bankaccountholdername' => $data['bank_account_holder_name'],
                'bankbranch' => $data['bank_branch'] ?? null,
                'bankifsccode' => $data['bank_ifsc_code'],
                'religion' => $data['religion'],
                'updatetime' => now(),
            ]
        );

        $this->activityLogger->log(
            (int) $user->id,
            'student_profile_updated',
            [
                'class_id' => (int) $class->id,
                'class_name' => (string) $class->groupname,
            ],
            'info',
            $request,
            $deviceSession,
        );

        return [
            'status' => 'updated',
            'message' => 'Student profile updated successfully.',
            'data' => $this->buildEditableProfilePayload($user->fresh()),
        ];
    }

    public function updateStudentProfileImage(
        User $user,
        int $attachmentId,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        if (! $this->studentOwnsAttachment((int) $user->id, $attachmentId)) {
            return [
                'status' => 'invalid_attachment',
                'message' => 'Invalid profile image selected. Please upload again.',
            ];
        }

        $user->update([
            'profileimh' => $attachmentId,
            'updated_at' => now(),
        ]);

        $this->activityLogger->log(
            (int) $user->id,
            'student_profile_image_updated',
            [
                'attachment_id' => $attachmentId,
            ],
            'info',
            $request,
            $deviceSession,
        );

        return [
            'status' => 'updated',
            'message' => 'Profile image updated successfully.',
            'profile_image_url' => $this->buildProfileImageUrl($user->fresh()),
            'attachment_id' => $attachmentId,
        ];
    }

    public function changeStudentPassword(
        User $user,
        string $currentPassword,
        string $newPassword,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        if (! Hash::check($currentPassword, (string) $user->password)) {
            $this->activityLogger->log(
                (int) $user->id,
                'student_password_change_failed',
                ['reason' => 'invalid_current_password'],
                'warning',
                $request,
                $deviceSession,
            );

            return [
                'status' => 'invalid_current_password',
                'message' => 'Current password is incorrect.',
            ];
        }

        if (Hash::check($newPassword, (string) $user->password)) {
            return [
                'status' => 'same_password',
                'message' => 'New password must be different from the current password.',
            ];
        }

        $user->update([
            'password' => $newPassword,
            'showpassword' => '',
            'updated_at' => now(),
        ]);

        $this->activityLogger->log(
            (int) $user->id,
            'student_password_changed',
            [],
            'info',
            $request,
            $deviceSession,
        );

        return [
            'status' => 'updated',
            'message' => 'Password changed successfully.',
        ];
    }

    public function sendMobileVerificationOtp(
        User $user,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        $mobile = trim((string) ($user->mobileno ?? ''));

        if ($mobile === '') {
            return [
                'status' => 'missing_mobile',
                'message' => 'Please update your mobile number first.',
            ];
        }

        if ((bool) $user->mobilevarification) {
            return [
                'status' => 'already_verified',
                'message' => 'Mobile number is already verified.',
            ];
        }

        $otp = random_int(100000, 999999);

        $user->update([
            'otpnumber' => $otp,
            'updated_at' => now(),
        ]);

        $this->dispatchSmsOtp($mobile, $otp);
        $this->activityLogger->log(
            (int) $user->id,
            'student_mobile_otp_sent',
            [
                'mobile' => $this->maskMobileNumber($mobile),
            ],
            'info',
            $request,
            $deviceSession,
        );

        $response = [
            'status' => 'sent',
            'message' => 'OTP has been sent to the registered mobile number.',
        ];

        if (app()->environment('local') && config('app.debug')) {
            $response['debug_otp'] = (string) $otp;
        }

        return $response;
    }

    public function verifyMobileOtp(
        User $user,
        string $otp,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        if ((string) $otp !== (string) ($user->otpnumber ?? '')) {
            $this->activityLogger->log(
                (int) $user->id,
                'student_mobile_otp_verify_failed',
                [],
                'warning',
                $request,
                $deviceSession,
            );

            return [
                'status' => 'invalid_otp',
                'message' => 'Enter valid OTP number.',
            ];
        }

        $user->update([
            'mobilevarification' => 1,
            'otpnumber' => null,
            'updated_at' => now(),
        ]);

        $this->activityLogger->log(
            (int) $user->id,
            'student_mobile_verified',
            [],
            'info',
            $request,
            $deviceSession,
        );

        return [
            'status' => 'verified',
            'message' => 'Mobile verification completed successfully.',
        ];
    }

    public function sendEmailVerificationCode(
        User $user,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        $email = trim((string) ($user->useremailid ?? ''));

        if ($email === '') {
            return [
                'status' => 'missing_email',
                'message' => 'Please update your email ID first.',
            ];
        }

        if ((bool) $user->emailvarify) {
            return [
                'status' => 'already_verified',
                'message' => 'Email ID is already verified.',
            ];
        }

        $otp = random_int(100000, 999999);

        $user->update([
            'emailotp' => $otp,
            'updated_at' => now(),
        ]);

        $this->dispatchEmailVerificationCode($email, $otp);
        $this->activityLogger->log(
            (int) $user->id,
            'student_email_otp_sent',
            [
                'email' => $this->maskEmail($email),
            ],
            'info',
            $request,
            $deviceSession,
        );

        $response = [
            'status' => 'sent',
            'message' => 'Verification code has been sent to the registered email ID.',
        ];

        if (app()->environment('local') && config('app.debug')) {
            $response['debug_otp'] = (string) $otp;
        }

        return $response;
    }

    public function verifyEmailCode(
        User $user,
        string $otp,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        if ((string) $otp !== (string) ($user->emailotp ?? '')) {
            $this->activityLogger->log(
                (int) $user->id,
                'student_email_otp_verify_failed',
                [],
                'warning',
                $request,
                $deviceSession,
            );

            return [
                'status' => 'invalid_otp',
                'message' => 'Enter valid code number.',
            ];
        }

        $user->update([
            'emailvarify' => 1,
            'emailotp' => null,
            'updated_at' => now(),
        ]);

        $this->activityLogger->log(
            (int) $user->id,
            'student_email_verified',
            [],
            'info',
            $request,
            $deviceSession,
        );

        return [
            'status' => 'verified',
            'message' => 'Email verification completed successfully.',
        ];
    }

    public function buildPageListPayload(): array
    {
        $pages = PageDetail::query()
            ->whereNotNull('pageslug')
            ->orderByRaw('CASE WHEN pageorder IS NULL THEN 1 ELSE 0 END')
            ->orderBy('pageorder')
            ->orderBy('pagename')
            ->get()
            ->map(function (PageDetail $page) {
                return [
                    'id' => (int) $page->id,
                    'title' => (string) $page->pagename,
                    'slug' => (string) $page->pageslug,
                    'image_url' => $page->pageimg ? $this->attachmentUrl((int) $page->pageimg, ASSETURL . 'public/frontend/images/logo.png') : null,
                    'order' => $page->pageorder !== null ? (int) $page->pageorder : null,
                ];
            })
            ->values()
            ->all();

        return [
            'pages' => $pages,
        ];
    }

    public function buildPageDetailPayload(string|int $slug): array
    {
        $page = is_numeric((string) $slug)
            ? PageDetail::query()->where('id', $slug)->first()
            : PageDetail::query()->where('pageslug', $slug)->first();

        if (! $page) {
            return [
                'status' => 'missing',
                'message' => 'Page not found.',
            ];
        }

        return [
            'status' => 'ok',
            'page' => [
                'id' => (int) $page->id,
                'title' => (string) $page->pagename,
                'slug' => (string) $page->pageslug,
                'content' => (string) ($page->pagecontent ?? ''),
                'image_url' => $page->pageimg ? $this->attachmentUrl((int) $page->pageimg, ASSETURL . 'public/frontend/images/logo.png') : null,
                'order' => $page->pageorder !== null ? (int) $page->pageorder : null,
            ],
        ];
    }

    public function storeStudentAttachment(
        User $user,
        UploadedFile $file,
        string $purpose,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        $attachment = $this->storeAttachmentFile($file, (int) $user->id);

        $this->activityLogger->log(
            (int) $user->id,
            'student_attachment_uploaded',
            [
                'attachment_id' => (int) $attachment->id,
                'purpose' => $purpose,
                'file_name' => (string) $attachment->att_name,
            ],
            'info',
            $request,
            $deviceSession,
        );

        return [
            'attachment_id' => (int) $attachment->id,
            'preview_url' => $this->storedFileUrl((string) $attachment->att_url),
            'purpose' => $purpose,
            'message' => 'Upload successful.',
        ];
    }

    public function submitWalletRechargeRequest(
        User $user,
        float $amount,
        string $transactionId,
        ?int $attachmentId,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        if ($attachmentId !== null && ! $this->studentOwnsAttachment((int) $user->id, $attachmentId)) {
            return [
                'status' => 'invalid_attachment',
                'message' => 'Invalid wallet proof selected. Please upload again.',
            ];
        }

        if (WalletHistory::query()->where('transectionid', $transactionId)->exists()) {
            return [
                'status' => 'duplicate_transaction',
                'message' => 'This transaction ID is already used.',
            ];
        }

        $history = WalletHistory::query()->create([
            'username' => $this->studentDisplayName($user),
            'userid' => $user->id,
            'addamount' => $amount,
            'transectionid' => $transactionId,
            'transectionimg' => $attachmentId,
            'status' => 2,
            'createdby' => $user->id,
            'createdtime' => now(),
        ]);

        $this->activityLogger->log(
            (int) $user->id,
            'wallet_recharge_requested',
            [
                'wallet_history_id' => (int) $history->id,
                'amount' => $amount,
                'transaction_id' => $transactionId,
                'attachment_id' => $attachmentId,
            ],
            'info',
            $request,
            $deviceSession,
        );

        return [
            'status' => 'submitted',
            'message' => 'Wallet recharge request submitted successfully.',
            'request' => [
                'id' => (int) $history->id,
                'amount' => (float) $history->addamount,
                'transaction_id' => (string) $history->transectionid,
                'status' => (int) $history->status,
                'status_label' => $this->walletStatusLabel((int) $history->status),
                'created_at' => $history->createdtime?->toIso8601String(),
            ],
        ];
    }

    public function submitWithdrawRequest(
        User $user,
        string $paymentType,
        string $mobileNumber,
        float $withdrawAmount,
        Request $request,
        ?StudentDeviceSession $deviceSession = null,
    ): array {
        $withdrawSummary = $this->buildWithdrawSummaryData((int) $user->id);

        if ($withdrawAmount > (float) $withdrawSummary['available_winning_amount']) {
            return [
                'status' => 'insufficient_winning',
                'message' => 'Sorry, withdraw amount is not sufficient.',
                'summary' => $withdrawSummary,
            ];
        }

        $history = WithdrawHistory::query()->create([
            'username' => $this->studentDisplayName($user),
            'paymenttype' => $paymentType,
            'mobilenumber' => $mobileNumber,
            'withdrawamt' => $withdrawAmount,
            'status' => 2,
            'createdby' => $user->id,
            'createdtime' => now(),
        ]);

        $this->activityLogger->log(
            (int) $user->id,
            'withdraw_requested',
            [
                'withdraw_history_id' => (int) $history->id,
                'payment_type' => $paymentType,
                'mobile_number' => $mobileNumber,
                'withdraw_amount' => $withdrawAmount,
            ],
            'info',
            $request,
            $deviceSession,
        );

        return [
            'status' => 'submitted',
            'message' => 'Withdraw request sent successfully.',
            'request' => [
                'id' => (int) $history->id,
                'payment_type' => (string) $history->paymenttype,
                'mobile_number' => (string) $history->mobilenumber,
                'withdraw_amount' => (float) $history->withdrawamt,
                'status' => (int) $history->status,
                'status_label' => $this->withdrawStatusLabel((int) $history->status),
                'created_at' => $history->createdtime?->toIso8601String(),
            ],
            'summary' => $this->buildWithdrawSummaryData((int) $user->id),
        ];
    }

    public function buildActivityLogPayload(User $user, int $limit = 50, ?string $level = null): array
    {
        $query = StudentActivityLog::query()
            ->with(['deviceSession', 'examSession'])
            ->where('user_id', $user->id)
            ->orderByDesc('occurred_at')
            ->orderByDesc('id');

        if ($level !== null && $level !== '') {
            $query->where('level', $level);
        }

        $entries = $query
            ->limit(max(1, min($limit, 200)))
            ->get()
            ->map(function (StudentActivityLog $log) {
                return [
                    'id' => (int) $log->id,
                    'event_type' => (string) $log->event_type,
                    'level' => (string) $log->level,
                    'source' => (string) $log->source,
                    'occurred_at' => $log->occurred_at?->toIso8601String(),
                    'ip_address' => (string) ($log->ip_address ?? ''),
                    'payload' => $log->payload ?? [],
                    'device_session' => $log->deviceSession ? [
                        'id' => (int) $log->deviceSession->id,
                        'device_uid' => (string) ($log->deviceSession->device_uid ?? ''),
                        'device_name' => (string) ($log->deviceSession->device_name ?? ''),
                        'platform' => (string) ($log->deviceSession->platform ?? ''),
                    ] : null,
                    'exam_session' => $log->examSession ? [
                        'id' => (int) $log->examSession->id,
                        'competition_assignment_id' => (int) $log->examSession->competition_assignment_id,
                        'status' => (string) $log->examSession->status,
                    ] : null,
                ];
            })
            ->values()
            ->all();

        return [
            'entries' => $entries,
            'summary' => [
                'count' => count($entries),
                'warning_count' => count(array_filter($entries, static fn (array $entry): bool => $entry['level'] === 'warning')),
                'error_count' => count(array_filter($entries, static fn (array $entry): bool => $entry['level'] === 'error')),
            ],
        ];
    }

    public function startExamSession(
        User $user,
        CompetitionAssignment $assignment,
        StudentDeviceSession $deviceSession,
        Request $request,
    ): array {
        if (! $this->hasJoinedCompetitionAssignment((int) $user->id, (int) $assignment->id)) {
            return [
                'status' => 'not_joined',
                'message' => 'Please join this competition before starting the exam.',
            ];
        }

        if ($this->hasSubmittedCompetitionAssignment((int) $user->id, (int) $assignment->id)) {
            return [
                'status' => 'already_submitted',
                'message' => 'This exam has already been submitted.',
            ];
        }

        if (! $this->isAssignmentExamLive($assignment)) {
            return [
                'status' => 'closed',
                'message' => $this->examWindowStatusMessage($assignment),
            ];
        }

        $existingSession = StudentExamSession::query()
            ->where('user_id', $user->id)
            ->where('competition_assignment_id', $assignment->id)
            ->where('status', 'active')
            ->latest('id')
            ->first();

        if ($existingSession) {
            $this->activityLogger->log(
                (int) $user->id,
                'exam_start_blocked_existing_session',
                [
                    'competition_assignment_id' => $assignment->id,
                    'existing_session_key' => $existingSession->session_key,
                ],
                'warning',
                $request,
                $deviceSession,
                $existingSession,
            );

            return [
                'status' => 'session_active',
                'message' => 'This exam already has an active session on the registered device.',
            ];
        }

        $questionSetIds = $this->parseLegacyIdList((string) $assignment->questionsetid);

        if ($questionSetIds === []) {
            return [
                'status' => 'missing_question_set',
                'message' => 'No question set is assigned to this competition right now.',
            ];
        }

        $randomIndex = count($questionSetIds) === 1 ? 0 : random_int(0, count($questionSetIds) - 1);
        $questionSetId = $questionSetIds[$randomIndex];
        $questionSet = $this->resolveAssignedQuestionSetup($assignment, $questionSetId);

        if (! $questionSet) {
            return [
                'status' => 'invalid_question_set',
                'message' => 'Assigned question set could not be loaded.',
            ];
        }

        $session = StudentExamSession::query()->create([
            'user_id' => $user->id,
            'competition_assignment_id' => $assignment->id,
            'competition_id' => $assignment->compitetionid,
            'question_set_id' => $questionSet->id,
            'student_device_session_id' => $deviceSession->id,
            'mobile_access_token_id' => $deviceSession->mobile_access_token_id,
            'session_key' => (string) Str::uuid(),
            'status' => 'active',
            'duration_minutes' => $this->examDurationMinutes(),
            'started_at' => now(),
            'last_heartbeat_at' => now(),
            'metadata' => [
                'class_name' => $assignment->groupclassname,
            ],
        ]);

        $this->activityLogger->log(
            (int) $user->id,
            'exam_session_started',
            [
                'competition_assignment_id' => $assignment->id,
                'question_set_id' => $questionSet->id,
            ],
            'info',
            $request,
            $deviceSession,
            $session,
        );

        return [
            'status' => 'ok',
            'session' => $session,
        ];
    }

    public function getExamSessionPayload(
        User $user,
        CompetitionAssignment $assignment,
        StudentDeviceSession $deviceSession,
        Request $request,
    ): array {
        $session = StudentExamSession::query()
            ->where('user_id', $user->id)
            ->where('competition_assignment_id', $assignment->id)
            ->where('student_device_session_id', $deviceSession->id)
            ->where('status', 'active')
            ->latest('id')
            ->first();

        if (! $session) {
            return [
                'status' => 'missing',
                'message' => 'Active exam session not found.',
            ];
        }

        $session = $this->autoSubmitIfHeartbeatExpired($session, $request);

        if ($session->status !== 'active') {
            return [
                'status' => 'submitted',
                'message' => 'Exam session is no longer active.',
                'session' => $this->buildSessionSummary($session),
            ];
        }

        $questionSet = $this->resolveAssignedQuestionSetup($assignment, (int) $session->question_set_id);

        if (! $questionSet) {
            return [
                'status' => 'invalid_question_set',
                'message' => 'Question set could not be loaded for this exam session.',
            ];
        }

        return [
            'status' => 'ok',
            'session' => $this->buildSessionSummary($session),
            'assignment' => [
                'id' => (int) $assignment->id,
                'competition_id' => (int) $assignment->compitetionid,
                'competition_name' => (string) $assignment->compitetionname,
                'competition_title' => (string) $assignment->compitetiontitle,
                'class_name' => (string) $assignment->groupclassname,
                'exam_date' => $assignment->examdate?->toDateString(),
                'exam_end_time' => (string) ($assignment->examendtime ?? ''),
            ],
            'question_set' => [
                'id' => (int) $questionSet->id,
                'title' => (string) ($questionSet->questionsettitle ?? 'Question Set'),
            ],
            'countdown' => $this->buildExamCountdownFromDateTime($session->started_at, (int) $session->duration_minutes),
            'draft_answers' => $session->draft_answers ?? [],
            'elapsed_seconds' => (int) $session->elapsed_seconds,
            'sections' => $this->buildExamSections($questionSet),
            'security' => [
                'heartbeat_timeout_seconds' => (int) config('mobile_exam.heartbeat_timeout_seconds', 20),
                'auto_submit_events' => array_values(config('mobile_exam.auto_submit_events', [])),
            ],
        ];
    }

    public function syncExamHeartbeat(
        User $user,
        CompetitionAssignment $assignment,
        StudentDeviceSession $deviceSession,
        array $answers,
        int $elapsedSeconds,
        Request $request,
    ): array {
        $session = StudentExamSession::query()
            ->where('user_id', $user->id)
            ->where('competition_assignment_id', $assignment->id)
            ->where('student_device_session_id', $deviceSession->id)
            ->where('status', 'active')
            ->latest('id')
            ->first();

        if (! $session) {
            return [
                'status' => 'missing',
                'message' => 'Active exam session not found.',
            ];
        }

        $session = $this->autoSubmitIfHeartbeatExpired($session, $request);

        if ($session->status !== 'active') {
            return [
                'status' => 'submitted',
                'message' => 'Exam session is no longer active.',
                'session' => $this->buildSessionSummary($session),
            ];
        }

        $session->forceFill([
            'draft_answers' => $this->normalizeSubmittedAnswers($answers),
            'elapsed_seconds' => max(0, $elapsedSeconds),
            'last_heartbeat_at' => now(),
        ])->save();

        return [
            'status' => 'ok',
            'countdown' => $this->buildExamCountdownFromDateTime($session->started_at, (int) $session->duration_minutes),
            'session' => $this->buildSessionSummary($session),
        ];
    }

    public function registerExamEvent(
        User $user,
        CompetitionAssignment $assignment,
        StudentDeviceSession $deviceSession,
        string $eventType,
        array $payload,
        Request $request,
    ): array {
        $session = StudentExamSession::query()
            ->where('user_id', $user->id)
            ->where('competition_assignment_id', $assignment->id)
            ->where('student_device_session_id', $deviceSession->id)
            ->where('status', 'active')
            ->latest('id')
            ->first();

        if (! $session) {
            return [
                'status' => 'missing',
                'message' => 'Active exam session not found.',
            ];
        }

        if (isset($payload['answers']) || isset($payload['elapsed_seconds'])) {
            $session->forceFill([
                'draft_answers' => isset($payload['answers'])
                    ? $this->normalizeSubmittedAnswers((array) $payload['answers'])
                    : ($session->draft_answers ?? []),
                'elapsed_seconds' => isset($payload['elapsed_seconds'])
                    ? max(0, (int) $payload['elapsed_seconds'])
                    : (int) $session->elapsed_seconds,
                'last_heartbeat_at' => now(),
            ])->save();
        }

        $session->increment('warning_count');
        $session->increment('suspicious_event_count');

        $this->activityLogger->log(
            (int) $user->id,
            $eventType,
            $payload,
            'warning',
            $request,
            $deviceSession,
            $session,
        );

        if (in_array($eventType, config('mobile_exam.auto_submit_events', []), true)) {
            return $this->submitExamSession(
                $session,
                $session->draft_answers ?? [],
                (int) $session->elapsed_seconds,
                $eventType,
                $request,
                true,
            );
        }

        return [
            'status' => 'logged',
            'message' => 'Exam event recorded.',
            'session' => $this->buildSessionSummary($session->fresh()),
        ];
    }

    public function submitExamByAssignment(
        User $user,
        CompetitionAssignment $assignment,
        StudentDeviceSession $deviceSession,
        array $answers,
        int $elapsedSeconds,
        Request $request,
    ): array {
        $session = StudentExamSession::query()
            ->where('user_id', $user->id)
            ->where('competition_assignment_id', $assignment->id)
            ->where('student_device_session_id', $deviceSession->id)
            ->where('status', 'active')
            ->latest('id')
            ->first();

        if (! $session) {
            return [
                'status' => 'missing',
                'message' => 'Active exam session not found.',
            ];
        }

        return $this->submitExamSession($session, $answers, $elapsedSeconds, 'manual_submit', $request, false);
    }

    public function submitExamSession(
        StudentExamSession $session,
        array $answers,
        int $elapsedSeconds,
        string $reason,
        Request $request,
        bool $autoSubmit,
    ): array {
        $normalizedAnswers = $this->normalizeSubmittedAnswers($answers);

        $result = DB::transaction(function () use ($session, $normalizedAnswers, $elapsedSeconds, $reason, $autoSubmit) {
            $lockedSession = StudentExamSession::query()
                ->whereKey($session->id)
                ->lockForUpdate()
                ->firstOrFail();

            if ($lockedSession->status !== 'active') {
                return [
                    'status' => 'already_closed',
                    'session' => $lockedSession,
                ];
            }

            $assignment = CompetitionAssignment::query()->findOrFail($lockedSession->competition_assignment_id);

            if ($this->hasSubmittedCompetitionAssignment((int) $lockedSession->user_id, (int) $assignment->id)) {
                $lockedSession->forceFill([
                    'status' => $autoSubmit ? 'auto_submitted' : 'submitted',
                    'submitted_at' => now(),
                    'submit_reason' => $reason,
                    'elapsed_seconds' => max(0, $elapsedSeconds),
                    'draft_answers' => $normalizedAnswers !== [] ? $normalizedAnswers : ($lockedSession->draft_answers ?? []),
                    'auto_submitted_at' => $autoSubmit ? now() : $lockedSession->auto_submitted_at,
                ])->save();

                return [
                    'status' => 'already_submitted',
                    'session' => $lockedSession,
                ];
            }

            $questionSet = $this->resolveAssignedQuestionSetup($assignment, (int) $lockedSession->question_set_id);

            if (! $questionSet) {
                $lockedSession->forceFill([
                    'status' => 'locked',
                    'submitted_at' => now(),
                    'submit_reason' => 'invalid_question_set',
                    'elapsed_seconds' => max(0, $elapsedSeconds),
                ])->save();

                return [
                    'status' => 'invalid_question_set',
                    'session' => $lockedSession,
                ];
            }

            $answerMap = $this->buildExamAnswerMap($questionSet);
            $answerSource = $normalizedAnswers !== [] ? $normalizedAnswers : ($lockedSession->draft_answers ?? []);
            $gkResult = $this->calculateSubjectResult($answerMap['gk'], $answerSource, 0);
            $scienceResult = $this->calculateSubjectResult($answerMap['science'], $answerSource, 11);
            $hindiResult = $this->calculateSubjectResult($answerMap['hindi'], $answerSource, 21);
            $englishResult = $this->calculateSubjectResult($answerMap['english'], $answerSource, 31);
            $mathsResult = $this->calculateSubjectResult($answerMap['maths'], $answerSource, 41);

            $totalMarks = $gkResult['marks']
                + $scienceResult['marks']
                + $hindiResult['marks']
                + $englishResult['marks']
                + $mathsResult['marks'];
            $totalPercentage = (
                $gkResult['percentage']
                + $scienceResult['percentage']
                + $hindiResult['percentage']
                + $englishResult['percentage']
                + $mathsResult['percentage']
            ) / 5;
            $status = (
                $gkResult['percentage'] >= 40
                && $scienceResult['percentage'] >= 40
                && $hindiResult['percentage'] >= 40
                && $englishResult['percentage'] >= 40
                && $mathsResult['percentage'] >= 40
            ) ? 1 : 0;

            $answerDetail = UserAnswerDetail::query()->create([
                'competitiontitle' => $assignment->compitetionname,
                'competitionassignid' => $assignment->id,
                'competitionassigntitle' => $assignment->compitetiontitle,
                'competitionid' => $assignment->compitetionid,
                'classgroupid' => $assignment->groupclassid,
                'classgroupname' => $assignment->groupclassname,
                'questionsetid' => $questionSet->id,
                'questionsettitle' => $questionSet->questionsettitle,
                'gkuseranswer' => $gkResult['user_answers'],
                'gkcorrectanswer' => $gkResult['correct_answers'],
                'scienceuseranswer' => $scienceResult['user_answers'],
                'sciencecorrectanswer' => $scienceResult['correct_answers'],
                'hindiuseranswer' => $hindiResult['user_answers'],
                'hindicorrectanswer' => $hindiResult['correct_answers'],
                'englishuseranswer' => $englishResult['user_answers'],
                'englishcorrectanswer' => $englishResult['correct_answers'],
                'mathsusersanswer' => $mathsResult['user_answers'],
                'mathscorrectanswer' => $mathsResult['correct_answers'],
                'gkmarks' => $gkResult['marks'],
                'gkpercentage' => $gkResult['percentage'],
                'sciencemarks' => $scienceResult['marks'],
                'sciencepercentage' => $scienceResult['percentage'],
                'hindimarks' => $hindiResult['marks'],
                'hindipercentage' => $hindiResult['percentage'],
                'englishmarks' => $englishResult['marks'],
                'englishpercentage' => $englishResult['percentage'],
                'mathsmarks' => $mathsResult['marks'],
                'mathspercentage' => $mathsResult['percentage'],
                'totalmarks' => $totalMarks,
                'totalpercentage' => $totalPercentage,
                'usertime' => $this->formatSecondsToClock($elapsedSeconds),
                'status' => $status,
                'createdby' => $lockedSession->user_id,
                'createdtime' => now(),
            ]);

            $lockedSession->forceFill([
                'status' => $autoSubmit ? 'auto_submitted' : 'submitted',
                'submitted_at' => now(),
                'auto_submitted_at' => $autoSubmit ? now() : null,
                'submit_reason' => $reason,
                'elapsed_seconds' => max(0, $elapsedSeconds),
                'draft_answers' => $answerSource,
            ])->save();

            return [
                'status' => 'submitted',
                'session' => $lockedSession,
                'answer_detail' => $answerDetail,
            ];
        });

        $this->activityLogger->log(
            (int) $session->user_id,
            $autoSubmit ? 'exam_auto_submitted' : 'exam_submitted',
            [
                'reason' => $reason,
                'elapsed_seconds' => max(0, $elapsedSeconds),
            ],
            $autoSubmit ? 'warning' : 'info',
            $request,
            $session->deviceSession,
            $result['session'] ?? $session,
        );

        if (($result['status'] ?? null) === 'submitted' && isset($result['answer_detail'])) {
            /** @var UserAnswerDetail $answerDetail */
            $answerDetail = $result['answer_detail'];

            return [
                'status' => 'submitted',
                'message' => $autoSubmit
                    ? 'Exam automatically submitted due to security event.'
                    : 'Exam submitted successfully.',
                'session' => $this->buildSessionSummary($result['session']),
                'result' => [
                    'answer_detail_id' => (int) $answerDetail->id,
                    'total_marks' => (float) $answerDetail->totalmarks,
                    'total_percentage' => (float) $answerDetail->totalpercentage,
                    'pass_status' => (int) $answerDetail->status === 1 ? 'pass' : 'fail',
                ],
            ];
        }

        return [
            'status' => (string) ($result['status'] ?? 'closed'),
            'message' => 'Exam session already closed.',
            'session' => isset($result['session']) ? $this->buildSessionSummary($result['session']) : $this->buildSessionSummary($session),
        ];
    }

    public function activeExamSessionBrief(User $user): ?array
    {
        $activeSession = StudentExamSession::query()
            ->where('user_id', $user->id)
            ->where('status', 'active')
            ->latest('id')
            ->first();

        return $activeSession ? $this->buildSessionSummary($activeSession) : null;
    }

    public function currentStudentProfile(User $user): object
    {
        $profile = StudentProfile::query()->where('studentid', $user->id)->first();

        if ($profile) {
            return $profile;
        }

        return (object) [
            'id' => $user->id,
            'studentid' => $user->id,
            'classid' => null,
            'classname' => null,
            'studentupiid' => null,
        ];
    }

    public function buildStudentProfilePayload(User $user, ?object $profile = null): array
    {
        $profile ??= $this->currentStudentProfile($user);

        return [
            'id' => (int) $user->id,
            'name' => trim($user->name . ' ' . ($user->lastname ?? '')),
            'first_name' => (string) ($user->name ?? ''),
            'last_name' => (string) ($user->lastname ?? ''),
            'email' => (string) ($user->email ?? ''),
            'mobile' => (string) ($user->mobileno ?? ''),
            'user_type' => (int) ($user->usertype ?? 0),
            'status' => (int) ($user->status ?? 0),
            'mobile_verified' => (bool) ($user->mobilevarification ?? false),
            'email_verified' => (bool) ($user->emailvarify ?? false),
            'profile_image_url' => $this->buildProfileImageUrl($user),
            'class' => [
                'id' => (int) ($profile->classid ?? 0),
                'name' => (string) ($profile->classname ?? ''),
            ],
            'upi_id' => (string) ($profile->studentupiid ?? ''),
        ];
    }

    private function buildProfileImageUrl(User $user): string
    {
        $fallbackUrl = ASSETURL . 'public/frontend/images/logo.png';

        if (! $user->profileimh) {
            return $fallbackUrl;
        }

        $attachment = Attachment::query()->find($user->profileimh);

        return $attachment ? ASSETURL . 'files/' . $attachment->att_url : $fallbackUrl;
    }

    private function currentStudentWalletAmount(int $userId): float
    {
        return (float) WalletUser::query()->where('userid', $userId)->sum('walletamount');
    }

    private function storedFileUrl(string $relativePath): string
    {
        return ASSETURL . 'files/' . ltrim(str_replace('\\', '/', $relativePath), '/');
    }

    private function attachmentUrl(?int $attachmentId, string $fallbackUrl): string
    {
        if (! $attachmentId) {
            return $fallbackUrl;
        }

        $attachment = Attachment::query()->find($attachmentId);

        return $attachment ? $this->storedFileUrl((string) $attachment->att_url) : $fallbackUrl;
    }

    private function studentDisplayName(User $user): string
    {
        return trim($user->name . ' ' . ($user->lastname ?? ''));
    }

    private function maskEmail(string $email): string
    {
        $email = trim($email);

        if ($email === '' || ! str_contains($email, '@')) {
            return $email;
        }

        [$name, $domain] = explode('@', $email, 2);

        if (strlen($name) <= 2) {
            $maskedName = substr($name, 0, 1) . '*';
        } else {
            $maskedName = substr($name, 0, 2) . str_repeat('*', max(1, strlen($name) - 2));
        }

        return $maskedName . '@' . $domain;
    }

    private function dispatchSmsOtp(string $mobile, int $otp): void
    {
        $number = urlencode($mobile);
        $message = rawurlencode(
            "Dear user your registration mobile number verification OTP is {$otp} please do not share the otp with any one. HARIHAR LANGUAGE"
        );
        $smsUrl = 'http://msg.smscluster.com/rest/services/sendSMS/sendGroupSms?AUTH_KEY=f470fdd03171fb2bfe46d29c9f3e936&message='
            . $message
            . '&senderId=HRIHRL&routeId=1&mobileNos='
            . $number;

        try {
            $ch = curl_init($smsUrl);

            if ($ch === false) {
                return;
            }

            curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
            curl_exec($ch);
            curl_close($ch);
        } catch (Throwable) {
            // Keep verification flow resilient in local environments.
        }
    }

    private function dispatchEmailVerificationCode(string $email, int $otp): void
    {
        $subject = 'Just Exam League Email Verification';
        $message = '
        <html>
        <head><title>Just Exam League Email Verification</title></head>
        <body>
            <table>
                <tr><th>Email ID:</th><td>' . e($email) . '</td></tr>
                <tr><th>Verification Code:</th><td>' . e((string) $otp) . '</td></tr>
            </table>
        </body>
        </html>';

        $headers = "MIME-Version: 1.0\r\n";
        $headers .= "Content-type:text/html;charset=UTF-8\r\n";
        $headers .= "From: <info@justexamleague.com>\r\n";

        try {
            @mail($email, $subject, $message, $headers);
        } catch (Throwable) {
            // Keep verification flow resilient in local environments.
        }
    }

    private function studentOwnsAttachment(int $userId, ?int $attachmentId): bool
    {
        if (! $attachmentId) {
            return false;
        }

        return Attachment::query()
            ->where('id', $attachmentId)
            ->where('userid', $userId)
            ->exists();
    }

    private function storeAttachmentFile(UploadedFile $file, int $userId): Attachment
    {
        $extension = strtolower($file->getClientOriginalExtension() ?: $file->extension() ?: 'jpg');
        $fileName = now()->format('Ymd_His') . '_' . $userId . '_' . Str::random(12) . '.' . $extension;
        $relativePath = 'images/' . $fileName;
        $targetDirectory = base_path('files/images');

        File::ensureDirectoryExists($targetDirectory);
        $file->move($targetDirectory, $fileName);

        return Attachment::query()->create([
            'att_name' => $fileName,
            'att_slug' => $fileName,
            'att_url' => $relativePath,
            'att_date' => now(),
            'userid' => $userId,
        ]);
    }

    private function buildWalletRechargeAmounts(User $user): array
    {
        $siteDetails = SiteDetail::query()->find(1);

        if (! $siteDetails || blank($siteDetails->appamount)) {
            return [[], 0];
        }

        $schoolDetails = null;
        $instituteCode = str_replace('jel_', '', (string) $user->institutecode);

        if ($instituteCode !== '') {
            $schoolDetails = SchoolDetail::query()->where('schoolid', $instituteCode)->first();
        }

        $commission = (float) ($schoolDetails->schoolcommision ?? 0);
        $amounts = [];

        foreach (explode(',', (string) $siteDetails->appamount) as $amount) {
            $baseAmount = (float) trim($amount);

            if ($baseAmount <= 0) {
                continue;
            }

            $adjustedAmount = $baseAmount;

            if ($commission > 0) {
                $adjustedAmount += ($commission / 100) * $baseAmount;
            }

            $amounts[] = round($adjustedAmount, 2);
        }

        $amounts = array_values(array_unique(array_filter($amounts, static fn ($amount) => $amount > 0)));
        sort($amounts);

        return [$amounts, $amounts[0] ?? 0];
    }

    private function buildWithdrawSummaryData(int $userId): array
    {
        $walletAmount = (float) WalletUser::query()->where('userid', $userId)->sum('walletamount');
        $totalWithdrawn = (float) WithdrawHistory::query()->where('createdby', $userId)->sum('withdrawamt');
        $totalWinning = (float) WinningHistory::query()->where('userid', $userId)->sum('useramount');

        return [
            'wallet_amount' => $walletAmount,
            'total_withdrawn' => $totalWithdrawn,
            'total_winning' => $totalWinning,
            'available_winning_amount' => max(0, $totalWinning - $totalWithdrawn),
        ];
    }

    private function maskMobileNumber(?string $mobile): string
    {
        $mobile = preg_replace('/\D+/', '', (string) $mobile) ?? '';

        if (strlen($mobile) !== 10) {
            return $mobile;
        }

        return substr($mobile, 0, 2) . 'XXXXXX' . substr($mobile, -2);
    }

    private function walletStatusLabel(int $status): string
    {
        return match ($status) {
            1 => 'Added',
            2 => 'Pending',
            3 => 'Cancelled',
            default => 'Unknown',
        };
    }

    private function withdrawStatusLabel(int $status): string
    {
        return match ($status) {
            1 => 'Success',
            2 => 'Pending',
            3 => 'Cancelled',
            default => 'Unknown',
        };
    }

    private function withdrawStatusTone(int $status): string
    {
        return match ($status) {
            1 => 'success',
            2 => 'warning',
            3 => 'danger',
            default => 'secondary',
        };
    }

    private function parseLegacyIdList(?string $value): array
    {
        if (! $value) {
            return [];
        }

        return array_values(array_filter(
            array_map(
                static fn (string $item): int => (int) trim($item),
                explode(',', $value)
            ),
            static fn (int $id): bool => $id > 0
        ));
    }

    private function splitLegacyList(?string $value): array
    {
        if (blank($value)) {
            return [];
        }

        return array_values(array_filter(
            array_map('trim', explode(',', (string) $value)),
            static fn ($item) => $item !== ''
        ));
    }

    private function buildContestRankRows(MatchContest $contest): array
    {
        $prizeAmounts = $this->splitLegacyList($contest->contestprizeamt);
        $startRanks = $this->splitLegacyList($contest->conteststartseat);
        $endRanks = $this->splitLegacyList($contest->contestendseat);
        $rows = [];
        $totalRows = max(count($prizeAmounts), count($startRanks), count($endRanks));

        for ($index = 0; $index < $totalRows; $index++) {
            $startRank = isset($startRanks[$index]) ? (int) $startRanks[$index] : null;
            $endRank = isset($endRanks[$index]) ? (int) $endRanks[$index] : $startRank;
            $prizeAmount = isset($prizeAmounts[$index]) ? (float) $prizeAmounts[$index] : 0.0;

            if ($startRank === null && $endRank === null && $prizeAmount <= 0) {
                continue;
            }

            $rows[] = [
                'start_rank' => $startRank,
                'end_rank' => $endRank,
                'prize_amount' => $prizeAmount,
            ];
        }

        return $rows;
    }

    private function isCompetitionSeatAvailable(MatchContest $contest, ?int $joinedCount = null): bool
    {
        $contestSeat = max(0, (int) $contest->contestseat);

        if ($contestSeat === 0) {
            return false;
        }

        $joinedCount ??= GameplayHistory::query()->where('contestid', $contest->id)->count();

        return $joinedCount < $contestSeat;
    }

    private function isAssignmentExamLive(CompetitionAssignment $assignment): bool
    {
        if (! $assignment->examdate) {
            return false;
        }

        if (now()->toDateString() !== $assignment->examdate->format('Y-m-d')) {
            return false;
        }

        if (! $assignment->examendtime) {
            return true;
        }

        return now()->format('H:i:s') <= (string) $assignment->examendtime;
    }

    private function examWindowStatusMessage(CompetitionAssignment $assignment): string
    {
        if (! $assignment->examdate) {
            return 'Exam schedule is not available right now.';
        }

        $today = now()->toDateString();
        $examDate = $assignment->examdate->format('Y-m-d');

        if ($today < $examDate) {
            return 'Exam will start on ' . $assignment->examdate->format('d M Y') . '.';
        }

        if ($today > $examDate) {
            return 'Exam window is closed for this competition.';
        }

        if ($assignment->examendtime && now()->format('H:i:s') > (string) $assignment->examendtime) {
            return 'Exam time is over for today.';
        }

        return 'Exam is not available right now.';
    }

    private function joinWindowStatusMessage(CompetitionAssignment $assignment): ?string
    {
        $today = now()->toDateString();

        if ($assignment->startdate && $today < $assignment->startdate->format('Y-m-d')) {
            return 'Registration will open on ' . $assignment->startdate->format('d M Y') . '.';
        }

        if ($assignment->enddate && $today > $assignment->enddate->format('Y-m-d')) {
            return 'Registration is closed for this competition.';
        }

        return null;
    }

    private function buildCompetitionSections(object $studentsprofile): array
    {
        $currentDate = now()->toDateString();

        $assignments = CompetitionAssignment::query()
            ->with(['contest', 'board'])
            ->when(
                $studentsprofile->classid,
                fn ($query) => $query->where('groupclassid', $studentsprofile->classid),
                fn ($query) => $query->whereRaw('1 = 0')
            )
            ->where('startdate', '<=', $currentDate)
            ->where('enddate', '>=', $currentDate)
            ->orderBy('startdate', 'ASC')
            ->get();

        $contestIds = $assignments->pluck('compitetionid')->filter()->unique()->values();
        $boardIds = $assignments->pluck('boardpatternid')->filter()->unique()->values();

        $joinedCounts = $contestIds->isEmpty()
            ? collect()
            : GameplayHistory::query()
                ->select('contestid', DB::raw('COUNT(*) as total'))
                ->whereIn('contestid', $contestIds)
                ->groupBy('contestid')
                ->pluck('total', 'contestid');

        $assignmentsByBoard = $assignments->groupBy('boardpatternid');

        return BoardPattern::query()
            ->when(
                $boardIds->isNotEmpty(),
                fn ($query) => $query->whereIn('id', $boardIds),
                fn ($query) => $query->whereRaw('1 = 0')
            )
            ->orderBy('showname', 'ASC')
            ->get()
            ->map(function (BoardPattern $board) use ($assignmentsByBoard, $joinedCounts) {
                $cards = $assignmentsByBoard
                    ->get($board->id, collect())
                    ->map(function (CompetitionAssignment $assignment) use ($joinedCounts) {
                        $contest = $assignment->contest;

                        if (! $contest) {
                            return null;
                        }

                        $joinedCount = (int) ($joinedCounts[$assignment->compitetionid] ?? 0);
                        $contestSeat = max(0, (int) $contest->contestseat);
                        $joinedPercent = $contestSeat > 0 ? round(($joinedCount / $contestSeat) * 100, 2) : 0;
                        $rankRows = $this->buildContestRankRows($contest);
                        $lastRankRow = ! empty($rankRows) ? $rankRows[array_key_last($rankRows)] : null;

                        return [
                            'assignment_id' => (int) $assignment->id,
                            'competition_id' => (int) $contest->id,
                            'title' => (string) $contest->contesttitile,
                            'class_name' => (string) $assignment->groupclassname,
                            'join_amount' => (float) $contest->joinamt,
                            'remaining_seat' => max(0, $contestSeat - $joinedCount),
                            'joined_percent' => $joinedPercent,
                            'first_prize' => $rankRows[0]['prize_amount'] ?? 0,
                            'last_rank' => $lastRankRow['end_rank'] ?? null,
                            'start_date' => $assignment->startdate?->toDateString(),
                            'end_date' => $assignment->enddate?->toDateString(),
                            'exam_date' => $assignment->examdate?->toDateString(),
                            'exam_end_time' => (string) ($assignment->examendtime ?? ''),
                        ];
                    })
                    ->filter()
                    ->values()
                    ->all();

                return [
                    'board' => [
                        'id' => (int) $board->id,
                        'name' => (string) $board->showname,
                    ],
                    'cards' => $cards,
                ];
            })
            ->filter(fn (array $section): bool => ! empty($section['cards']))
            ->values()
            ->all();
    }

    private function buildStudentCompetitionTimeline(object $studentsprofile, int $userId): array
    {
        if (! $studentsprofile->classid) {
            return [];
        }

        $today = now()->toDateString();
        $joinedAssignments = GameplayHistory::query()
            ->where('userid', $userId)
            ->pluck('assignquestionid')
            ->map(fn ($assignmentId) => (int) $assignmentId)
            ->flip();
        $submittedAssignments = UserAnswerDetail::query()
            ->where('createdby', $userId)
            ->pluck('competitionassignid')
            ->map(fn ($assignmentId) => (int) $assignmentId)
            ->flip();

        return CompetitionAssignment::query()
            ->with(['contest', 'board'])
            ->where('groupclassid', $studentsprofile->classid)
            ->orderByDesc('examdate')
            ->orderByDesc('startdate')
            ->limit(6)
            ->get()
            ->map(function (CompetitionAssignment $assignment) use ($today, $joinedAssignments, $submittedAssignments) {
                $contest = $assignment->contest;

                if (! $contest) {
                    return null;
                }

                $rankRows = $this->buildContestRankRows($contest);
                $lastRankRow = ! empty($rankRows) ? $rankRows[array_key_last($rankRows)] : null;
                $isJoined = $joinedAssignments->has((int) $assignment->id);
                $isSubmitted = $submittedAssignments->has((int) $assignment->id);
                $statusLabel = 'Open';
                $statusTone = 'success';
                $statusCopy = 'Competition details available for review.';

                if ($isSubmitted) {
                    $statusLabel = 'Submitted';
                    $statusTone = 'success';
                    $statusCopy = 'Your exam submission is already recorded.';
                } elseif ($assignment->resultdate && $today >= $assignment->resultdate->format('Y-m-d')) {
                    $statusLabel = 'Result Live';
                    $statusTone = 'success';
                    $statusCopy = 'Result date is active for this competition.';
                } elseif ($assignment->answersheetdate && $today >= $assignment->answersheetdate->format('Y-m-d')) {
                    $statusLabel = 'Answer Sheet';
                    $statusTone = 'secondary';
                    $statusCopy = 'Answer sheet date is active for this competition.';
                } elseif ($assignment->examdate && $today === $assignment->examdate->format('Y-m-d') && $this->isAssignmentExamLive($assignment)) {
                    $statusLabel = 'Exam Live';
                    $statusTone = 'success';
                    $statusCopy = 'Exam window is currently live.';
                } elseif ($assignment->startdate && $today < $assignment->startdate->format('Y-m-d')) {
                    $statusLabel = 'Upcoming';
                    $statusTone = 'secondary';
                    $statusCopy = 'Registration will open soon.';
                } elseif ($assignment->enddate && $today > $assignment->enddate->format('Y-m-d')) {
                    $statusLabel = 'Closed';
                    $statusTone = 'danger';
                    $statusCopy = $isJoined
                        ? 'You joined this competition earlier, but the registration window is closed now.'
                        : 'Registration window is closed for this competition.';
                } elseif ($isJoined) {
                    $statusLabel = 'Joined';
                    $statusTone = 'warning';
                    $statusCopy = 'You have already joined this competition.';
                }

                return [
                    'assignment_id' => (int) $assignment->id,
                    'competition_id' => (int) $contest->id,
                    'title' => (string) $contest->contesttitile,
                    'board_name' => (string) ($assignment->board?->showname ?? 'Board not linked'),
                    'status_label' => $statusLabel,
                    'status_tone' => $statusTone,
                    'status_copy' => $statusCopy,
                    'join_amount' => (float) $contest->joinamt,
                    'exam_date' => $assignment->examdate?->toDateString(),
                    'result_date' => $assignment->resultdate?->toDateString(),
                    'first_prize' => $rankRows[0]['prize_amount'] ?? 0,
                    'last_rank' => $lastRankRow['end_rank'] ?? null,
                ];
            })
            ->filter()
            ->values()
            ->all();
    }

    private function resolveAssignedQuestionSetup(CompetitionAssignment $assignment, ?int $questionSetId): ?QuestionSetup
    {
        if (! $questionSetId) {
            return null;
        }

        if (! in_array($questionSetId, $this->parseLegacyIdList((string) $assignment->questionsetid), true)) {
            return null;
        }

        return QuestionSetup::query()->find($questionSetId);
    }

    private function loadQuestionBanks(QuestionSetup $questionsetup): array
    {
        return [
            'questions_general' => QuestionManagement::query()->find($questionsetup->questionsetgkid),
            'questions_science' => QuestionManagement::query()->find($questionsetup->questionsetscienceid),
            'questions_hindi' => QuestionManagement::query()->find($questionsetup->questionsethindiid),
            'questions_english' => QuestionManagement::query()->find($questionsetup->questionsetenglishid),
            'questions_maths' => QuestionManagement::query()->find($questionsetup->questionsetmathsid),
        ];
    }

    private function examSubjectDefinitions(): array
    {
        return [
            [
                'key' => 'gk',
                'label' => 'G.K.',
                'title' => 'General Knowledge',
                'bank_key' => 'questions_general',
                'palette_start' => 0,
                'submission_start' => 0,
                'supports_hindi' => true,
                'user_answer_field' => 'gkuseranswer',
                'correct_answer_field' => 'gkcorrectanswer',
            ],
            [
                'key' => 'science',
                'label' => 'Science',
                'title' => 'Science',
                'bank_key' => 'questions_science',
                'palette_start' => 10,
                'submission_start' => 11,
                'supports_hindi' => true,
                'user_answer_field' => 'scienceuseranswer',
                'correct_answer_field' => 'sciencecorrectanswer',
            ],
            [
                'key' => 'hindi',
                'label' => 'Hindi',
                'title' => 'Hindi',
                'bank_key' => 'questions_hindi',
                'palette_start' => 20,
                'submission_start' => 21,
                'supports_hindi' => false,
                'user_answer_field' => 'hindiuseranswer',
                'correct_answer_field' => 'hindicorrectanswer',
            ],
            [
                'key' => 'english',
                'label' => 'English',
                'title' => 'English',
                'bank_key' => 'questions_english',
                'palette_start' => 30,
                'submission_start' => 31,
                'supports_hindi' => false,
                'user_answer_field' => 'englishuseranswer',
                'correct_answer_field' => 'englishcorrectanswer',
            ],
            [
                'key' => 'maths',
                'label' => 'Maths',
                'title' => 'Mathematics',
                'bank_key' => 'questions_maths',
                'palette_start' => 40,
                'submission_start' => 41,
                'supports_hindi' => true,
                'user_answer_field' => 'mathsusersanswer',
                'correct_answer_field' => 'mathscorrectanswer',
            ],
        ];
    }

    private function buildExamSections(QuestionSetup $questionsetup, ?UserAnswerDetail $userAnswerDetail = null): array
    {
        $questionBanks = $this->loadQuestionBanks($questionsetup);
        $sections = [];
        $displayNumber = 1;

        foreach ($this->examSubjectDefinitions() as $definition) {
            $bank = $questionBanks[$definition['bank_key']] ?? null;
            $questions = LegacyJson::toArray($bank?->questionlist);
            $questionHindi = LegacyJson::toArray($bank?->questionhindi);
            $firstOptions = LegacyJson::toArray($bank?->questionobjectfirst);
            $secondOptions = LegacyJson::toArray($bank?->questionobjectsecond);
            $thirdOptions = LegacyJson::toArray($bank?->questionobjectthird);
            $fourthOptions = LegacyJson::toArray($bank?->questionobjectfourth);
            $firstOptionsHindi = LegacyJson::toArray($bank?->questionobjectfirsthindi);
            $secondOptionsHindi = LegacyJson::toArray($bank?->questionobjectsecondhindi);
            $thirdOptionsHindi = LegacyJson::toArray($bank?->questionobjectthirdhindi);
            $fourthOptionsHindi = LegacyJson::toArray($bank?->questionobjectfourthhindi);
            $userAnswers = $userAnswerDetail
                ? LegacyJson::toArray($userAnswerDetail->{$definition['user_answer_field']})
                : [];
            $correctAnswers = $userAnswerDetail
                ? LegacyJson::toArray($userAnswerDetail->{$definition['correct_answer_field']})
                : [];
            $sectionQuestions = [];

            foreach ($questions as $localIndex => $questionText) {
                $normalizedPrompt = trim((string) $questionText);

                if ($normalizedPrompt === '') {
                    continue;
                }

                $submissionKey = $definition['submission_start'] + $localIndex;
                $paletteIndex = $definition['palette_start'] + $localIndex;
                $userAnswer = $userAnswers[$localIndex] ?? null;
                $correctAnswer = $correctAnswers[$localIndex] ?? null;
                $status = $userAnswer === null || $userAnswer === ''
                    ? 'unanswered'
                    : (($correctAnswer !== null && $userAnswer === $correctAnswer) ? 'correct' : 'wrong');

                $sectionQuestions[] = [
                    'global_number' => $displayNumber,
                    'palette_index' => $paletteIndex,
                    'submission_key' => $submissionKey,
                    'prompt' => $normalizedPrompt,
                    'prompt_hindi' => $definition['supports_hindi']
                        ? $this->normalizeLegacyExamText($questionHindi[$localIndex] ?? null)
                        : null,
                    'options' => [
                        [
                            'key' => 'A',
                            'english' => $this->normalizeLegacyExamText($firstOptions[$localIndex] ?? null),
                            'hindi' => $definition['supports_hindi']
                                ? $this->normalizeLegacyExamText($firstOptionsHindi[$localIndex] ?? null)
                                : null,
                        ],
                        [
                            'key' => 'B',
                            'english' => $this->normalizeLegacyExamText($secondOptions[$localIndex] ?? null),
                            'hindi' => $definition['supports_hindi']
                                ? $this->normalizeLegacyExamText($secondOptionsHindi[$localIndex] ?? null)
                                : null,
                        ],
                        [
                            'key' => 'C',
                            'english' => $this->normalizeLegacyExamText($thirdOptions[$localIndex] ?? null),
                            'hindi' => $definition['supports_hindi']
                                ? $this->normalizeLegacyExamText($thirdOptionsHindi[$localIndex] ?? null)
                                : null,
                        ],
                        [
                            'key' => 'D',
                            'english' => $this->normalizeLegacyExamText($fourthOptions[$localIndex] ?? null),
                            'hindi' => $definition['supports_hindi']
                                ? $this->normalizeLegacyExamText($fourthOptionsHindi[$localIndex] ?? null)
                                : null,
                        ],
                    ],
                    'user_answer' => $userAnswer,
                    'correct_answer' => $correctAnswer,
                    'status' => $status,
                ];

                $displayNumber++;
            }

            $sections[] = [
                'key' => $definition['key'],
                'label' => $definition['label'],
                'title' => $definition['title'],
                'supports_hindi' => $definition['supports_hindi'],
                'palette_start' => $definition['palette_start'],
                'count' => count($sectionQuestions),
                'questions' => $sectionQuestions,
            ];
        }

        return $sections;
    }

    private function buildExamAnswerMap(QuestionSetup $questionsetup): array
    {
        $questionBanks = $this->loadQuestionBanks($questionsetup);

        return [
            'gk' => LegacyJson::toArray($questionBanks['questions_general']?->questionobjectanswer),
            'science' => LegacyJson::toArray($questionBanks['questions_science']?->questionobjectanswer),
            'hindi' => LegacyJson::toArray($questionBanks['questions_hindi']?->questionobjectanswer),
            'english' => LegacyJson::toArray($questionBanks['questions_english']?->questionobjectanswer),
            'maths' => LegacyJson::toArray($questionBanks['questions_maths']?->questionobjectanswer),
        ];
    }

    private function calculateSubjectResult(array $correctAnswers, array $userOptions, int $userStartIndex): array
    {
        $userAnswers = [];
        $correctAnswerList = [];
        $correctMarks = [];
        $wrongMarks = [];

        for ($offset = 0; $offset < 10; $offset++) {
            if (! array_key_exists($offset, $correctAnswers)) {
                continue;
            }

            $correctAnswerList[$offset] = $correctAnswers[$offset];
            $userKey = $userStartIndex + $offset;

            if (! isset($userOptions[$userKey])) {
                $userAnswers[$offset] = null;
                continue;
            }

            $selectedOption = $userOptions[$userKey];
            $userAnswers[$offset] = $selectedOption;

            if ($selectedOption === $correctAnswers[$offset]) {
                $correctMarks[$userKey] = 1;
            } else {
                $wrongMarks[$userKey] = 0.25;
            }
        }

        $marks = array_sum($correctMarks) - array_sum($wrongMarks);

        return [
            'user_answers' => $userAnswers,
            'correct_answers' => $correctAnswerList,
            'marks' => $marks,
            'percentage' => $marks > 0 ? round($marks / 10 * 100, 2) : 0,
        ];
    }

    private function normalizeLegacyExamText(mixed $value): ?string
    {
        $normalized = trim((string) $value);

        return $normalized !== '' ? $normalized : null;
    }

    private function buildSessionSummary(StudentExamSession $session): array
    {
        return [
            'id' => (int) $session->id,
            'session_key' => (string) $session->session_key,
            'competition_assignment_id' => (int) $session->competition_assignment_id,
            'competition_id' => (int) ($session->competition_id ?? 0),
            'question_set_id' => (int) ($session->question_set_id ?? 0),
            'status' => (string) $session->status,
            'duration_minutes' => (int) $session->duration_minutes,
            'elapsed_seconds' => (int) $session->elapsed_seconds,
            'warning_count' => (int) $session->warning_count,
            'suspicious_event_count' => (int) $session->suspicious_event_count,
            'started_at' => $session->started_at?->toIso8601String(),
            'last_heartbeat_at' => $session->last_heartbeat_at?->toIso8601String(),
            'submitted_at' => $session->submitted_at?->toIso8601String(),
            'auto_submitted_at' => $session->auto_submitted_at?->toIso8601String(),
            'submit_reason' => $session->submit_reason,
        ];
    }

    private function autoSubmitIfHeartbeatExpired(StudentExamSession $session, Request $request): StudentExamSession
    {
        if ($session->status !== 'active') {
            return $session;
        }

        $lastHeartbeat = $session->last_heartbeat_at ?? $session->started_at;
        $timeoutSeconds = max(5, (int) config('mobile_exam.heartbeat_timeout_seconds', 20));

        if (! $lastHeartbeat || now()->diffInSeconds($lastHeartbeat) < $timeoutSeconds) {
            return $session;
        }

        $this->submitExamSession(
            $session,
            $session->draft_answers ?? [],
            (int) $session->elapsed_seconds,
            'heartbeat_timeout',
            $request,
            true,
        );

        return $session->fresh();
    }

    private function buildExamCountdownFromDateTime($startedAt, int $durationMinutes): string
    {
        if (! $startedAt) {
            return sprintf('%02d:%02d', $durationMinutes, 0);
        }

        $elapsedSeconds = max(0, $startedAt->diffInSeconds(now()));
        $remainingSeconds = max(0, ($durationMinutes * 60) - $elapsedSeconds);

        return sprintf('%02d:%02d', intdiv($remainingSeconds, 60), $remainingSeconds % 60);
    }

    private function formatSecondsToClock(int $seconds): string
    {
        $seconds = max(0, $seconds);

        return sprintf('%02d:%02d', intdiv($seconds, 60), $seconds % 60);
    }

    private function hasJoinedCompetitionAssignment(int $userId, int $assignmentId): bool
    {
        return GameplayHistory::query()
            ->where('userid', $userId)
            ->where('assignquestionid', $assignmentId)
            ->exists();
    }

    private function hasSubmittedCompetitionAssignment(int $userId, int $assignmentId): bool
    {
        return UserAnswerDetail::query()
            ->where('createdby', $userId)
            ->where('competitionassignid', $assignmentId)
            ->exists();
    }

    private function normalizeSubmittedAnswers(array $answers): array
    {
        $normalized = [];

        foreach ($answers as $key => $value) {
            $submissionKey = (int) $key;
            $answer = strtoupper(trim((string) $value));

            if ($submissionKey < 0 || ! in_array($answer, ['A', 'B', 'C', 'D'], true)) {
                continue;
            }

            $normalized[$submissionKey] = $answer;
        }

        ksort($normalized);

        return $normalized;
    }

    private function examDurationMinutes(): int
    {
        return max(1, (int) config('mobile_exam.exam_duration_minutes', 25));
    }
}
