<?php

namespace App\Services\Admin\CompetitionWinners;

use App\Models\ClassSection;
use App\Models\Competition;
use App\Models\CompetitionClassWinner;
use App\Models\CompetitionScore;
use App\Models\CompetitionWinner;
use App\Models\Configuration;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

/**
 * Administrative competition-winner workflows (ported from legacy {@see \App\Controllers\Admin\CompetitionWinnersController}).
 */
class CompetitionWinnersAdminService
{
    protected string $classStudentTable = '';

    protected bool $hasClassWinnerTable = false;

    protected bool $hasQuestionCount = false;

    public function __construct()
    {
        if (Schema::hasTable('class_student')) {
            $this->classStudentTable = 'class_student';
        } elseif (Schema::hasTable('student_class')) {
            $this->classStudentTable = 'student_class';
        }

        $this->hasClassWinnerTable = Schema::hasTable('competition_class_winners');
        $this->hasQuestionCount = $this->hasClassWinnerTable
            && Schema::hasColumn('competition_class_winners', 'question_count');
    }

    /** @return array<int, string> */
    public function getClassSectionMap(): array
    {
        $sections = ClassSection::query()
            ->select('class_section_id', 'class_section_name')
            ->orderBy('class_section_name', 'ASC')
            ->get();

        $map = [];
        foreach ($sections as $section) {
            $id = (int) ($section->class_section_id ?? 0);
            if ($id > 0) {
                $map[$id] = (string) ($section->class_section_name ?? $id);
            }
        }

        return $map;
    }

    public function getClassSectionName(int $classSectionId): ?string
    {
        if ($classSectionId <= 0) {
            return null;
        }

        $row = ClassSection::query()
            ->where('class_section_id', $classSectionId)
            ->value('class_section_name');

        return $row !== null ? (string) $row : null;
    }

    /** @return array<int, int> */
    public function getClassStudentCounts(?string $schoolYear): array
    {
        if ($this->classStudentTable === '') {
            return [];
        }

        $q = DB::table($this->classStudentTable)
            ->select('class_section_id', DB::raw('COUNT(*) as total'))
            ->whereNotNull('class_section_id')
            ->groupBy('class_section_id');

        if ($schoolYear && Schema::hasColumn($this->classStudentTable, 'school_year')) {
            $q->where('school_year', $schoolYear);
        }

        $map = [];
        foreach ($q->get() as $row) {
            $classId = (int) ($row->class_section_id ?? 0);
            if ($classId > 0) {
                $map[$classId] = (int) ($row->total ?? 0);
            }
        }

        return $map;
    }

    /**
     * @return array{overrides: array<int,int>, questions: array<int,int>, prizes: array<int, array<int, float|null>>}
     */
    public function getClassSettings(int $competitionId): array
    {
        if (! $this->hasClassWinnerTable) {
            return ['overrides' => [], 'questions' => [], 'prizes' => []];
        }

        $rows = CompetitionClassWinner::query()
            ->where('competition_id', $competitionId)
            ->get();

        $overrides = [];
        $questions = [];
        $prizes = [];

        foreach ($rows as $row) {
            $classId = (int) ($row->class_section_id ?? 0);
            if ($classId <= 0) {
                continue;
            }
            if ($row->winners_override !== null) {
                $overrides[$classId] = (int) $row->winners_override;
            }
            if ($this->hasQuestionCount && $row->question_count !== null) {
                $questions[$classId] = (int) $row->question_count;
            }
            $prizes[$classId] = [
                1 => CompetitionWinnersDomain::castPrize($row->prize_1 ?? null),
                2 => CompetitionWinnersDomain::castPrize($row->prize_2 ?? null),
                3 => CompetitionWinnersDomain::castPrize($row->prize_3 ?? null),
                4 => CompetitionWinnersDomain::castPrize($row->prize_4 ?? null),
                5 => CompetitionWinnersDomain::castPrize($row->prize_5 ?? null),
                6 => CompetitionWinnersDomain::castPrize($row->prize_6 ?? null),
            ];
        }

        return ['overrides' => $overrides, 'questions' => $questions, 'prizes' => $prizes];
    }

    /**
     * @param  array<int|string, mixed>  $overrides
     * @param  array<int|string, mixed>  $questionCounts
     * @param  array<int|string, mixed>  $prizes
     */
    public function saveClassSettings(
        int $competitionId,
        array $overrides,
        array $questionCounts,
        array $prizes,
        ?int $limitClassId = null
    ): void {
        if (! $this->hasClassWinnerTable) {
            return;
        }

        $classIds = array_unique(array_merge(array_keys($overrides), array_keys($questionCounts), array_keys($prizes)));

        foreach ($classIds as $classId) {
            $classId = (int) $classId;
            if ($classId <= 0) {
                continue;
            }
            if ($limitClassId !== null && $classId !== $limitClassId) {
                continue;
            }

            $rawOverride = trim((string) ($overrides[$classId] ?? ''));
            $overrideValue = $rawOverride === '' ? null : (int) $rawOverride;

            $rawQuestions = trim((string) ($questionCounts[$classId] ?? ''));
            $questionValue = $rawQuestions === '' ? null : (int) $rawQuestions;

            $existing = CompetitionClassWinner::query()
                ->where('competition_id', $competitionId)
                ->where('class_section_id', $classId)
                ->first();

            $prizeValues = CompetitionWinnersDomain::normalizePrizeInputs((array) ($prizes[$classId] ?? []));
            $hasPrize = array_filter($prizeValues, static fn ($v) => $v !== null);

            if ($overrideValue === null && $questionValue === null && empty($hasPrize)) {
                if ($existing) {
                    $existing->delete();
                }
                continue;
            }

            $payload = [
                'competition_id' => $competitionId,
                'class_section_id' => $classId,
                'winners_override' => $overrideValue,
                'prize_1' => $prizeValues[1],
                'prize_2' => $prizeValues[2],
                'prize_3' => $prizeValues[3],
                'prize_4' => $prizeValues[4],
                'prize_5' => $prizeValues[5],
                'prize_6' => $prizeValues[6],
            ];

            if ($this->hasQuestionCount) {
                $payload['question_count'] = $questionValue;
            }

            if ($existing) {
                $existing->fill($payload);
                $existing->save();
            } else {
                CompetitionClassWinner::query()->create($payload);
            }
        }
    }

    /**
     * @param  array<string, mixed>  $competition
     * @return list<array<string, mixed>>
     */
    public function getStudentsForCompetition(array $competition, int $classSectionId): array
    {
        if ($classSectionId <= 0 || $this->classStudentTable === '') {
            return [];
        }

        $q = DB::table('students as s')
            ->select('s.id', 's.school_id', 's.firstname', 's.lastname')
            ->join($this->classStudentTable.' as cs', 'cs.student_id', '=', 's.id')
            ->where('cs.class_section_id', $classSectionId);

        if (Schema::hasColumn($this->classStudentTable, 'school_year') && ! empty($competition['school_year'])) {
            $q->where('cs.school_year', $competition['school_year']);
        }

        return $q->distinct()->orderBy('s.lastname')->orderBy('s.firstname')->get()->map(fn ($r) => (array) $r)->all();
    }

    /** @return array<int, int> */
    public function getQuestionCountsForCompetition(int $competitionId): array
    {
        if (! $this->hasQuestionCount || ! $this->hasClassWinnerTable) {
            return [];
        }

        $rows = CompetitionClassWinner::query()
            ->where('competition_id', $competitionId)
            ->whereNotNull('question_count')
            ->get(['class_section_id', 'question_count']);

        $out = [];
        foreach ($rows as $row) {
            $classId = (int) ($row->class_section_id ?? 0);
            if ($classId > 0 && $row->question_count !== null) {
                $out[$classId] = (int) $row->question_count;
            }
        }

        return $out;
    }

    /** @return array{competitions: \Illuminate\Support\Collection, sectionMap: array<int, string>} */
    public function indexData(): array
    {
        $competitions = Competition::query()->orderByDesc('id')->get();
        $sectionMap = $this->getClassSectionMap();

        return ['competitions' => $competitions, 'sectionMap' => $sectionMap];
    }

    /** @return array<string, mixed> */
    public function createFormData(): array
    {
        $schoolYear = Configuration::getConfig('school_year');
        $classCounts = $this->getClassStudentCounts($schoolYear);
        $classSections = ClassSection::query()->orderBy('class_section_name')->get()->map(fn ($r) => $r->toArray())->all();
        $classSections = CompetitionWinnersDomain::filterSectionsWithStudents($classSections, $classCounts);
        $classRows = CompetitionWinnersDomain::buildClassRows($classSections, $classCounts, [], [], []);

        return [
            'mode' => 'create',
            'competition' => null,
            'classSections' => $classSections,
            'classRows' => $classRows,
            'defaultSemester' => Configuration::getConfig('semester'),
            'defaultSchoolYear' => $schoolYear,
        ];
    }

    /** @return array<string, mixed>|null */
    public function settingsFormData(int $id): ?array
    {
        $competition = Competition::query()->find($id);
        if (! $competition) {
            return null;
        }

        $row = $competition->toArray();
        $schoolYear = $row['school_year'] ?? Configuration::getConfig('school_year');
        $classCounts = $this->getClassStudentCounts($schoolYear);
        $classSections = ClassSection::query()->orderBy('class_section_name')->get()->map(fn ($r) => $r->toArray())->all();
        $classSections = CompetitionWinnersDomain::filterSectionsWithStudents($classSections, $classCounts);
        $settings = $this->getClassSettings((int) $id);
        $limitClassId = (int) ($row['class_section_id'] ?? 0);
        $classRows = CompetitionWinnersDomain::buildClassRows(
            $classSections,
            $classCounts,
            $settings['overrides'],
            $settings['questions'],
            $settings['prizes'],
            $limitClassId
        );

        return [
            'mode' => 'edit',
            'competition' => $row,
            'classSections' => $classSections,
            'classRows' => $classRows,
            'defaultSemester' => Configuration::getConfig('semester'),
            'defaultSchoolYear' => $schoolYear,
        ];
    }

    /**
     * @param  array<string, mixed>  $validated
     */
    public function storeCompetition(array $validated, array $overrides, array $questionCounts, array $prizes, ?int $actorId): int
    {
        $classSectionId = $validated['class_section_id'] ?? null;
        $classSectionId = $classSectionId !== '' && $classSectionId !== null ? (int) $classSectionId : null;

        $competition = Competition::query()->create([
            'title' => $validated['title'],
            'semester' => $validated['semester'] ?? null,
            'school_year' => $validated['school_year'] ?? null,
            'class_section_id' => $classSectionId,
            'start_date' => $validated['start_date'] ?? null,
            'end_date' => $validated['end_date'] ?? null,
            'created_by' => $actorId,
        ]);

        $this->saveClassSettings((int) $competition->id, $overrides, $questionCounts, $prizes, $classSectionId);

        return (int) $competition->id;
    }

    /**
     * @param  array<string, mixed>  $validated
     */
    public function updateCompetition(int $id, array $validated, array $overrides, array $questionCounts, array $prizes): bool
    {
        $competition = Competition::query()->find($id);
        if (! $competition) {
            return false;
        }

        $classSectionId = $validated['class_section_id'] ?? null;
        $classSectionId = $classSectionId !== '' && $classSectionId !== null ? (int) $classSectionId : null;

        $competition->update([
            'title' => $validated['title'],
            'semester' => $validated['semester'] ?? null,
            'school_year' => $validated['school_year'] ?? null,
            'class_section_id' => $classSectionId,
            'start_date' => $validated['start_date'] ?? null,
            'end_date' => $validated['end_date'] ?? null,
        ]);

        $this->saveClassSettings($id, $overrides, $questionCounts, $prizes, $classSectionId);

        return true;
    }

    /** @return array<string, mixed>|null */
    public function editScoresData(int $id, ?int $queryClassSectionId): ?array
    {
        $competition = Competition::query()->find($id);
        if (! $competition) {
            return null;
        }

        $c = $competition->toArray();
        $lockedClassSectionId = (int) ($c['class_section_id'] ?? 0);
        $selectedClassSectionId = (int) ($queryClassSectionId ?? 0);
        $activeClassSectionId = $lockedClassSectionId > 0 ? $lockedClassSectionId : $selectedClassSectionId;

        $students = [];
        $scoreMap = [];

        if ($activeClassSectionId > 0) {
            $students = $this->getStudentsForCompetition($c, $activeClassSectionId);
            $existingScores = CompetitionScore::query()
                ->where('competition_id', $id)
                ->where('class_section_id', $activeClassSectionId)
                ->get();

            foreach ($existingScores as $row) {
                $scoreMap[(int) $row->student_id] = $row->score;
            }
        }

        $schoolYear = $c['school_year'] ?? null;
        $classCounts = $this->getClassStudentCounts($schoolYear);
        $classSections = ClassSection::query()->orderBy('class_section_name')->get()->map(fn ($r) => $r->toArray())->all();
        $classSections = CompetitionWinnersDomain::filterSectionsWithStudents($classSections, $classCounts);
        $settings = $this->getClassSettings((int) $id);
        $classStudentCount = $classCounts[$activeClassSectionId] ?? 0;
        $classOverride = $settings['overrides'][$activeClassSectionId] ?? null;
        $classAuto = CompetitionWinnersDomain::winnersForCount($classStudentCount);
        $classFinal = $classOverride !== null ? $classOverride : $classAuto;
        $classQuestionCount = $settings['questions'][$activeClassSectionId] ?? null;

        return [
            'competition' => $c,
            'students' => $students,
            'scoreMap' => $scoreMap,
            'classSections' => $classSections,
            'activeClassSectionId' => $activeClassSectionId,
            'classSectionName' => $this->getClassSectionName($activeClassSectionId),
            'classSelectionLocked' => $lockedClassSectionId > 0,
            'classStudentCount' => $classStudentCount,
            'classAutoWinners' => $classAuto,
            'classOverrideWinners' => $classOverride,
            'classFinalWinners' => $classFinal,
            'classQuestionCount' => $classQuestionCount,
            'classPrizeMap' => $settings['prizes'][$activeClassSectionId] ?? [],
        ];
    }

    /** @param  array<int|string, mixed>  $scores */
    public function saveScores(int $competitionId, array $scores, ?int $postClassSectionId, array $competitionRow): bool
    {
        $classSectionId = (int) ($competitionRow['class_section_id'] ?? 0);
        if ($classSectionId <= 0) {
            $classSectionId = (int) ($postClassSectionId ?? 0);
        }
        if ($classSectionId <= 0) {
            return false;
        }

        foreach ($scores as $studentId => $scoreValue) {
            $scoreValue = trim((string) $scoreValue);
            if ($scoreValue === '') {
                continue;
            }

            $scoreFloat = (float) $scoreValue;

            $existing = CompetitionScore::query()
                ->where('competition_id', $competitionId)
                ->where('student_id', (int) $studentId)
                ->where('class_section_id', $classSectionId)
                ->first();

            if ($existing) {
                $existing->score = $scoreFloat;
                $existing->class_section_id = $classSectionId;
                $existing->save();
            } else {
                CompetitionScore::query()->create([
                    'competition_id' => $competitionId,
                    'student_id' => (int) $studentId,
                    'class_section_id' => $classSectionId,
                    'score' => $scoreFloat,
                ]);
            }
        }

        return true;
    }

    /** @return array<string, mixed>|null */
    public function previewData(int $id): ?array
    {
        $competition = Competition::query()->find($id);
        if (! $competition) {
            return null;
        }

        $c = $competition->toArray();

        $rows = DB::table('competition_scores as cs')
            ->leftJoin('students as s', 's.id', '=', 'cs.student_id')
            ->select('cs.*', 's.firstname', 's.lastname')
            ->where('cs.competition_id', $id)
            ->get()
            ->map(fn ($r) => (array) $r)
            ->all();

        $rankedByClass = CompetitionWinnersDomain::groupScoresByClass($rows);
        $scoreCounts = CompetitionWinnersDomain::getScoreCountsByClass($rankedByClass);

        $classCounts = $this->getClassStudentCounts($c['school_year'] ?? null);
        $settings = $this->getClassSettings((int) $id);
        $winnersCountByClass = CompetitionWinnersDomain::getWinnersCountByClass(
            $c,
            $classCounts,
            $scoreCounts,
            $settings['overrides']
        );

        $topByClass = CompetitionWinnersDomain::getTopWinnersByClass(
            $rankedByClass,
            $winnersCountByClass,
            $settings['prizes'],
            $settings['questions']
        );

        return [
            'competition' => $c,
            'classMap' => $this->getClassSectionMap(),
            'classCounts' => $classCounts,
            'overrideMap' => $settings['overrides'],
            'questionCounts' => $settings['questions'],
            'prizeMap' => $settings['prizes'],
            'winnersCountByClass' => $winnersCountByClass,
            'rankedByClass' => $rankedByClass,
            'topByClass' => $topByClass,
        ];
    }

    /** @return list<array<string,mixed>> */
    public function winnersListingRows(int $competitionId): array
    {
        return DB::table('competition_winners as cw')
            ->selectRaw('cw.*, s.firstname, s.lastname, s.school_id, s.photo_consent, cs.class_section_name')
            ->leftJoin('students as s', 's.id', '=', 'cw.student_id')
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'cw.class_section_id')
            ->where('cw.competition_id', $competitionId)
            ->orderBy('cw.class_section_id')
            ->orderBy('cw.rank')
            ->get()
            ->map(fn ($r) => (array) $r)
            ->all();
    }

    public function publishWinners(int $id): bool
    {
        $competition = Competition::query()->find($id);
        if (! $competition) {
            return false;
        }

        $c = $competition->toArray();

        $rows = CompetitionScore::query()->where('competition_id', $id)->get()->map(fn ($r) => $r->toArray())->all();

        $rankedByClass = CompetitionWinnersDomain::groupScoresByClass($rows);
        $scoreCounts = CompetitionWinnersDomain::getScoreCountsByClass($rankedByClass);
        $classCounts = $this->getClassStudentCounts($c['school_year'] ?? null);
        $settings = $this->getClassSettings((int) $id);
        $winnersCountByClass = CompetitionWinnersDomain::getWinnersCountByClass(
            $c,
            $classCounts,
            $scoreCounts,
            $settings['overrides']
        );

        CompetitionWinner::query()->where('competition_id', $id)->delete();

        foreach ($rankedByClass as $classSectionId => $rowsForClass) {
            $limit = (int) ($winnersCountByClass[$classSectionId] ?? 0);
            if ($limit <= 0) {
                continue;
            }

            $topWithPrizes = CompetitionWinnersDomain::assignPrizesByScore(
                $rowsForClass,
                $settings['prizes'][$classSectionId] ?? [],
                $limit,
                $settings['questions'][$classSectionId] ?? null
            );

            foreach ($topWithPrizes as $row) {
                CompetitionWinner::query()->create([
                    'competition_id' => (int) $id,
                    'student_id' => (int) $row['student_id'],
                    'class_section_id' => (int) $classSectionId,
                    'rank' => (int) $row['rank'],
                    'score' => (float) $row['score'],
                    'prize_amount' => (float) $row['prize_amount'],
                    'created_at' => now()->toDateTimeString(),
                ]);
            }
        }

        $competition->update([
            'is_published' => 1,
            'published_at' => now()->toDateTimeString(),
        ]);

        return true;
    }

    public function lockCompetition(int $id, ?int $userId): bool
    {
        $competition = Competition::query()->find($id);
        if (! $competition) {
            return false;
        }

        if ($competition->is_locked) {
            return true;
        }

        $competition->update([
            'is_locked' => 1,
            'locked_at' => now()->toDateTimeString(),
            'locked_by' => $userId,
        ]);

        return true;
    }

    public function unlockCompetition(int $id): bool
    {
        $competition = Competition::query()->find($id);
        if (! $competition) {
            return false;
        }

        if (! $competition->is_locked) {
            return true;
        }

        $competition->update([
            'is_locked' => 0,
            'locked_at' => null,
            'locked_by' => null,
        ]);

        return true;
    }

    /**
     * Export competition scores into quiz rows (SQL preserved from legacy).
     *
     * @return array{ok: bool, message: string, quizIndexes?: array<int,int>}
     */
    public function exportCompetitionToQuiz(int $competitionId, int $classSectionId, ?int $schoolId, ?int $userId, ?string $semester, ?string $schoolYear): array
    {
        if ($competitionId <= 0) {
            return ['ok' => false, 'message' => 'Competition not found.'];
        }

        $competition = Competition::query()->find($competitionId);
        if (! $competition) {
            return ['ok' => false, 'message' => 'Competition not found.'];
        }

        $lockedClassSectionId = (int) ($competition->class_section_id ?? 0);
        if ($lockedClassSectionId > 0) {
            $classSectionId = $lockedClassSectionId;
        }

        if ($classSectionId > 0 && $lockedClassSectionId > 0 && $lockedClassSectionId !== $classSectionId) {
            return ['ok' => false, 'message' => 'This competition is locked to a different class section.'];
        }

        $semester = $semester ?: Configuration::getConfig('semester');
        $schoolYear = $schoolYear ?: Configuration::getConfig('school_year');

        $classSectionIds = [];
        if ($classSectionId > 0) {
            $classSectionIds = [$classSectionId];
        } else {
            $rows = DB::table('competition_scores')
                ->select('class_section_id')
                ->distinct()
                ->where('competition_id', $competitionId)
                ->where('class_section_id', '>', 0)
                ->get();

            foreach ($rows as $row) {
                $cid = (int) ($row->class_section_id ?? 0);
                if ($cid > 0) {
                    $classSectionIds[] = $cid;
                }
            }
        }

        if ($classSectionIds === []) {
            return ['ok' => false, 'message' => 'No class sections with scores found for this competition.'];
        }

        $quizIndexes = [];

        DB::transaction(function () use ($competitionId, $classSectionIds, $schoolId, $userId, $semester, $schoolYear, &$quizIndexes): void {
            foreach ($classSectionIds as $cid) {
                $cid = (int) $cid;
                if ($cid <= 0) {
                    continue;
                }

                $row = DB::selectOne(
                    'SELECT COALESCE(MAX(quiz_index), 0) + 1 AS next_index
                     FROM quiz
                     WHERE class_section_id = ?
                       AND semester = ?
                       AND school_year = ?',
                    [$cid, $semester, $schoolYear]
                );

                $quizIndex = (int) ($row->next_index ?? 1);

                $sql = <<<'SQL'
INSERT INTO quiz
(student_id, school_id, class_section_id, updated_by, quiz_index, score, comment, semester, school_year, created_at, updated_at)
SELECT
  cs.student_id,
  ?,
  cs.class_section_id,
  ?,
  ?,
  ROUND(LEAST(100, GREATEST(0, (cs.score / NULLIF(ccw.question_count, 0)) * 100)), 2),
  CONCAT('Competition ', cs.competition_id, ' normalized from ', cs.score, '/', ccw.question_count),
  ?,
  ?,
  NOW(), NOW()
FROM competition_scores cs
JOIN competition_class_winners ccw
  ON ccw.competition_id = cs.competition_id
 AND ccw.class_section_id = cs.class_section_id
WHERE cs.competition_id = ?
  AND cs.class_section_id = ?
SQL;

                DB::statement($sql, [
                    $schoolId,
                    $userId,
                    $quizIndex,
                    $semester,
                    $schoolYear,
                    $competitionId,
                    $cid,
                ]);

                $quizIndexes[$cid] = $quizIndex;
            }
        });

        if ($quizIndexes === []) {
            return ['ok' => false, 'message' => 'No scores were exported.'];
        }

        return ['ok' => true, 'message' => 'Exported.', 'quizIndexes' => $quizIndexes];
    }
}
