<?php

namespace App\Services\Admin\CompetitionWinners;

/** Pure domain logic ported from legacy `Admin\CompetitionWinnersController` (private helpers). */
final class CompetitionWinnersDomain
{
    /** @var array<int, array{min:int, max:?int, winners:int}> */
    private const WINNER_TIERS = [
        ['min' => 1, 'max' => 12, 'winners' => 2],
        ['min' => 13, 'max' => 16, 'winners' => 3],
        ['min' => 17, 'max' => 22, 'winners' => 4],
        ['min' => 23, 'max' => 27, 'winners' => 5],
        ['min' => 28, 'max' => null, 'winners' => 6],
    ];

    public static function winnersForCount(int $count): int
    {
        if ($count <= 0) {
            return 0;
        }

        foreach (self::WINNER_TIERS as $tier) {
            $min = (int) $tier['min'];
            $max = $tier['max'];
            if ($count < $min) {
                continue;
            }
            if ($max === null || $count <= (int) $max) {
                return (int) $tier['winners'];
            }
        }

        return 0;
    }

    /**
     * @param  array<int, array<string, mixed>>  $classSections
     * @param  array<int, int>  $classCounts
     * @param  array<int, int>  $overrides
     * @param  array<int, int|null>  $questionCounts
     * @param  array<int, array<int, float|null>>  $prizeMap
     * @return list<array<string, mixed>>
     */
    public static function buildClassRows(
        array $classSections,
        array $classCounts,
        array $overrides,
        array $questionCounts,
        array $prizeMap,
        int $limitClassId = 0
    ): array {
        $rows = [];
        foreach ($classSections as $section) {
            $classId = (int) ($section['class_section_id'] ?? 0);
            if ($classId <= 0) {
                continue;
            }
            if ($limitClassId > 0 && $classId !== $limitClassId) {
                continue;
            }

            $count = $classCounts[$classId] ?? 0;
            if ($count <= 0) {
                continue;
            }
            $auto = self::winnersForCount($count);
            $override = isset($overrides[$classId]) ? (int) $overrides[$classId] : null;
            $questionCount = isset($questionCounts[$classId]) ? (int) $questionCounts[$classId] : null;
            $prizes = $prizeMap[$classId] ?? [];
            $rows[] = [
                'class_section_id' => $classId,
                'class_section_name' => $section['class_section_name'] ?? (string) $classId,
                'student_count' => $count,
                'auto_winners' => $auto,
                'override_winners' => $override,
                'final_winners' => $override !== null ? $override : $auto,
                'question_count' => $questionCount,
                'prize_1' => $prizes[1] ?? null,
                'prize_2' => $prizes[2] ?? null,
                'prize_3' => $prizes[3] ?? null,
                'prize_4' => $prizes[4] ?? null,
                'prize_5' => $prizes[5] ?? null,
                'prize_6' => $prizes[6] ?? null,
            ];
        }

        return $rows;
    }

    /**
     * @param  array<int, array<string, mixed>>  $classSections
     * @param  array<int, int>  $classCounts
     * @return array<int, array<string, mixed>>
     */
    public static function filterSectionsWithStudents(array $classSections, array $classCounts): array
    {
        return array_values(array_filter($classSections, static function ($section) use ($classCounts) {
            $classId = (int) ($section['class_section_id'] ?? 0);
            if ($classId <= 0) {
                return false;
            }

            return ($classCounts[$classId] ?? 0) > 0;
        }));
    }

    /**
     * @param  array<string, mixed>  $competition  Row including optional class_section_id
     * @param  array<int, int>  $classCounts
     * @param  array<int, int>  $scoreCounts
     * @param  array<int, int>  $overrides
     * @return array<int, int>
     */
    public static function getWinnersCountByClass(
        array $competition,
        array $classCounts,
        array $scoreCounts,
        array $overrides
    ): array {
        $classIds = array_unique(array_merge(
            array_keys($classCounts),
            array_keys($scoreCounts),
            array_keys($overrides)
        ));

        $limitClassId = (int) ($competition['class_section_id'] ?? 0);
        $map = [];
        foreach ($classIds as $classId) {
            $classId = (int) $classId;
            if ($classId <= 0) {
                continue;
            }
            if ($limitClassId > 0 && $classId !== $limitClassId) {
                continue;
            }

            $override = $overrides[$classId] ?? null;
            if ($override !== null) {
                $map[$classId] = (int) $override;
                continue;
            }

            $baseCount = $classCounts[$classId] ?? ($scoreCounts[$classId] ?? 0);
            if ($baseCount <= 0) {
                $map[$classId] = 0;
                continue;
            }

            $maxWinners = self::winnersForCount((int) $baseCount);
            $minWinners = 3;
            $map[$classId] = $maxWinners >= $minWinners ? $maxWinners : $minWinners;
        }

        return $map;
    }

    /**
     * @param  array<int, array<string, mixed>>  $rows
     * @return array<int, list<array<string, mixed>>>
     */
    public static function groupScoresByClass(array $rows): array
    {
        $grouped = [];

        foreach ($rows as $row) {
            $classSectionId = (int) ($row['class_section_id'] ?? 0);
            if ($classSectionId <= 0) {
                continue;
            }

            $name = trim(($row['firstname'] ?? '').' '.($row['lastname'] ?? ''));
            $score = (float) ($row['score'] ?? 0);

            $grouped[$classSectionId][] = [
                'student_id' => $row['student_id'],
                'name' => $name !== '' ? $name : ('Student #'.$row['student_id']),
                'score' => $score,
            ];
        }

        foreach ($grouped as &$rowsForClass) {
            usort($rowsForClass, static function ($a, $b) {
                return $b['score'] <=> $a['score'];
            });
        }
        unset($rowsForClass);

        return $grouped;
    }

    /**
     * @param  array<int, list<array<string, mixed>>>  $rankedByClass
     * @return array<int, int>
     */
    public static function getScoreCountsByClass(array $rankedByClass): array
    {
        $counts = [];
        foreach ($rankedByClass as $classId => $rows) {
            $counts[(int) $classId] = count($rows);
        }

        return $counts;
    }

    /**
     * @param  array<int, list<array<string, mixed>>>  $rankedByClass
     * @param  array<int, int>  $winnersCountByClass
     * @param  array<int, array<int, float|null>>  $prizeMap
     * @param  array<int, int|null>  $questionCounts
     * @return array<int, list<array<string, mixed>>>
     */
    public static function getTopWinnersByClass(
        array $rankedByClass,
        array $winnersCountByClass,
        array $prizeMap,
        array $questionCounts
    ): array {
        $out = [];

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

            $out[$classSectionId] = self::assignPrizesByScore(
                $rows,
                $prizeMap[$classSectionId] ?? [],
                $limit,
                $questionCounts[$classSectionId] ?? null
            );
        }

        return $out;
    }

    /**
     * @param  array<int, mixed>  $prizes
     * @return array<int, float|null>
     */
    public static function normalizePrizeInputs(array $prizes): array
    {
        $out = [1 => null, 2 => null, 3 => null, 4 => null, 5 => null, 6 => null];
        foreach ($out as $rank => $_value) {
            $raw = trim((string) ($prizes[$rank] ?? ''));
            if ($raw === '') {
                $out[$rank] = null;
            } else {
                $out[$rank] = (float) $raw;
            }
        }

        return $out;
    }

    public static function castPrize(mixed $value): ?float
    {
        if ($value === null || $value === '') {
            return null;
        }

        return (float) $value;
    }

    /**
     * @param  array<int, float|null>  $classPrizes
     */
    public static function getPrizeForRank(array $classPrizes, int $rank): float
    {
        $val = $classPrizes[$rank] ?? null;

        return $val !== null ? (float) $val : 0.0;
    }

    /**
     * @param  list<array<string, mixed>>  $rows
     * @param  array<int, float|null>  $classPrizes
     * @return list<array<string, mixed>>
     */
    public static function assignPrizesByScore(array $rows, array $classPrizes, int $limit, ?int $questionCount = null): array
    {
        if ($limit <= 0) {
            return [];
        }

        usort($rows, static function ($a, $b) {
            return ($b['score'] ?? 0) <=> ($a['score'] ?? 0);
        });

        $scoreGroups = [];
        $scoreOrder = [];
        foreach ($rows as $row) {
            $score = (float) ($row['score'] ?? 0);
            $scoreKey = number_format($score, 5, '.', '');
            if (! array_key_exists($scoreKey, $scoreGroups)) {
                $scoreGroups[$scoreKey] = [];
                $scoreOrder[] = $scoreKey;
            }
            $scoreGroups[$scoreKey][] = $row;
        }

        $top = [];
        $rank = 1;
        foreach ($scoreOrder as $scoreKey) {
            if (count($top) >= $limit) {
                break;
            }
            foreach ($scoreGroups[$scoreKey] as $row) {
                $row['rank'] = $rank;
                $top[] = $row;
            }
            $rank++;
        }

        $sharedPrize = null;
        if ($questionCount !== null && $questionCount > 0 && ! empty($top)) {
            $allMax = true;
            foreach ($top as $row) {
                $score = (float) ($row['score'] ?? 0);
                if ($score < $questionCount) {
                    $allMax = false;
                    break;
                }
            }
            if ($allMax) {
                $sharedPrize = self::getPrizeForRank($classPrizes, 1);
            }
        }

        $out = [];
        foreach ($top as $row) {
            if ($sharedPrize !== null) {
                $prize = $sharedPrize;
            } else {
                $prize = self::getPrizeForRank($classPrizes, (int) ($row['rank'] ?? 1));
            }

            $row['prize_amount'] = $prize;
            $out[] = $row;
        }

        return $out;
    }
}
