<?php

namespace App\Controllers\View;

use App\Controllers\BaseController;
use App\Models\ConfigurationModel;

class TrophyController extends BaseController
{
    private const PERCENTILE     = 75.0;

    protected ConfigurationModel $configModel;

    public function __construct()
    {
        $this->configModel = new ConfigurationModel();
    }

    public function index()
    {
        $db = \Config\Database::connect();

        $currentYear = $this->currentSchoolYearName();
        $selectedYear = $this->request->getGet('school_year') ?? $currentYear;

        $percentile = (float) ($this->request->getGet('percentile') ?? 75);
        $percentile = max(1.0, min(99.0, $percentile));

        // Available school years from class assignments so the current year can
        // still appear even if fall scores are not fully entered yet.
        $years = $db->table('student_class')
            ->select('school_year')
            ->distinct()
            ->orderBy('school_year', 'DESC')
            ->get()
            ->getResultArray();
        $years = array_column($years, 'school_year');

        // Build a fall-score-based projection. We start from class assignments so
        // students without a recorded fall score still appear as "not yet
        // projected" rather than disappearing from the page.
        $rows = $db->table('student_class sc')
            ->select([
                'sc.student_id',
                'sc.class_section_id',
                'MAX(ss.semester_score) AS fall_score',
                'cs.class_section_name',
                'CONCAT(s.firstname, " ", s.lastname) AS student_name',
                's.school_id',
                's.gender',
            ])
            ->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'left')
            ->join('students s', 's.id = sc.student_id', 'left')
            ->join(
                'semester_scores ss',
                'ss.student_id = sc.student_id'
                . ' AND ss.class_section_id = sc.class_section_id'
                . ' AND ss.school_year = ' . $db->escape($selectedYear)
                . ' AND LOWER(ss.semester) = "fall"',
                'left'
            )
            ->where('sc.school_year', $selectedYear)
            ->orderBy('cs.class_section_name', 'ASC')
            ->orderBy('fall_score', 'DESC')
            ->orderBy('student_name', 'ASC')
            ->groupBy('sc.student_id, sc.class_section_id, cs.class_section_name, s.firstname, s.lastname, s.school_id')
            ->get()
            ->getResultArray();

        // Group rows by class section
        $sections = [];
        foreach ($rows as $row) {
            $sid  = (int) ($row['class_section_id'] ?? 0);
            $name = $row['class_section_name'] ?? 'Section ' . $sid;
            if (!isset($sections[$sid])) {
                $sections[$sid] = ['name' => $name, 'students' => []];
            }
            $sections[$sid]['students'][] = [
                'student_id'   => (int) $row['student_id'],
                'name'         => $row['student_name'],
                'school_id'    => $row['school_id'],
                'gender'       => $row['gender'] ?? '',
                'fall_score'   => is_numeric($row['fall_score']) ? (float) $row['fall_score'] : null,
            ];
        }

        // Calculate trophy thresholds from fall scores only, then project who is
        // currently on track for a year-end trophy.
        $classResults = [];
        foreach ($sections as $sid => $section) {
            usort($section['students'], static function (array $a, array $b): int {
                $aScore = $a['fall_score'];
                $bScore = $b['fall_score'];

                if ($aScore === null && $bScore === null) {
                    return strcmp($a['name'], $b['name']);
                }

                if ($aScore === null) {
                    return 1;
                }

                if ($bScore === null) {
                    return -1;
                }

                return $bScore <=> $aScore ?: strcmp($a['name'], $b['name']);
            });

            $scores    = array_column($section['students'], 'fall_score');
            $calc      = $this->calculateThreshold($scores, $percentile);
            $threshold = $calc['threshold'];

            $students = array_map(function (array $student) use ($threshold): array {
                $student['projected_trophy'] = $threshold !== null
                    && $student['fall_score'] !== null
                    && $student['fall_score'] >= $threshold;
                return $student;
            }, $section['students']);

            $scoredCount = count(array_filter(
                array_column($students, 'fall_score'),
                static fn ($score) => $score !== null
            ));

            $boys         = array_filter($students, static fn ($s) => strtolower($s['gender']) === 'male');
            $girls        = array_filter($students, static fn ($s) => strtolower($s['gender']) === 'female');
            $trophyBoys   = array_filter($boys,  static fn ($s) => $s['projected_trophy']);
            $trophyGirls  = array_filter($girls, static fn ($s) => $s['projected_trophy']);
            $nBoys        = count($boys);
            $nGirls       = count($girls);
            $nTrophyBoys  = count($trophyBoys);
            $nTrophyGirls = count($trophyGirls);
            $total        = count($students);

            $classResults[] = [
                'section_id'       => $sid,
                'section_name'     => $section['name'],
                'students'         => $students,
                'threshold'        => $threshold,
                'trophy_count'     => $calc['winners'],
                'student_count'    => $total,
                'scored_count'     => $scoredCount,
                'method'           => $calc['method'],
                'boys'             => $nBoys,
                'girls'            => $nGirls,
                'trophy_boys'      => $nTrophyBoys,
                'trophy_girls'     => $nTrophyGirls,
                'pct_boys'         => $total   > 0 ? round($nBoys  / $total * 100)        : 0,
                'pct_girls'        => $total   > 0 ? round($nGirls / $total * 100)        : 0,
                'pct_trophy_boys'  => $nBoys   > 0 ? round($nTrophyBoys  / $nBoys  * 100) : 0,
                'pct_trophy_girls' => $nGirls  > 0 ? round($nTrophyGirls / $nGirls * 100) : 0,
                'pct_trophy_total' => $total   > 0 ? round($calc['winners'] / $total * 100): 0,
            ];
        }

        return view('administrator/trophy', [
            'classResults'      => $classResults,
            'selectedYear'      => $selectedYear,
            'selectedPercentile'=> $percentile,
            'years'             => $years,
        ]);
    }

    public function winners()
    {
        $db = \Config\Database::connect();

        $currentYear  = $this->currentSchoolYearName();
        $selectedYear = $this->request->getGet('school_year') ?? $currentYear;
        $percentile   = (float) ($this->request->getGet('percentile') ?? 75);
        $percentile   = max(1.0, min(99.0, $percentile));

        $years = $db->table('student_class')
            ->select('school_year')->distinct()
            ->orderBy('school_year', 'DESC')
            ->get()->getResultArray();
        $years = array_column($years, 'school_year');

        $ey = $db->escape($selectedYear);

        $rows = $db->table('student_class sc')
            ->select([
                'sc.student_id',
                'sc.class_section_id',
                'cs.class_section_name',
                'CONCAT(s.firstname, " ", s.lastname) AS student_name',
                's.school_id',
                's.gender',
                'MAX(sf.semester_score) AS fall_score',
                'MAX(sp.semester_score) AS spring_score',
            ])
            ->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'left')
            ->join('students s', 's.id = sc.student_id', 'left')
            ->join('semester_scores sf',
                'sf.student_id = sc.student_id AND sf.class_section_id = sc.class_section_id'
                . ' AND sf.school_year = ' . $ey . ' AND LOWER(sf.semester) = "fall"', 'left')
            ->join('semester_scores sp',
                'sp.student_id = sc.student_id AND sp.class_section_id = sc.class_section_id'
                . ' AND sp.school_year = ' . $ey . ' AND LOWER(sp.semester) = "spring"', 'left')
            ->where('sc.school_year', $selectedYear)
            ->orderBy('cs.class_section_name', 'ASC')
            ->orderBy('student_name', 'ASC')
            ->groupBy('sc.student_id, sc.class_section_id, cs.class_section_name, s.firstname, s.lastname, s.school_id, s.gender')
            ->get()->getResultArray();

        $sections = [];
        foreach ($rows as $row) {
            $sid  = (int) ($row['class_section_id'] ?? 0);
            $name = $row['class_section_name'] ?? 'Section ' . $sid;
            if (!isset($sections[$sid])) $sections[$sid] = ['name' => $name, 'students' => []];
            $fall   = is_numeric($row['fall_score'])   ? (float) $row['fall_score']   : null;
            $spring = is_numeric($row['spring_score']) ? (float) $row['spring_score'] : null;
            $year   = ($fall !== null && $spring !== null)
                        ? round(($fall + $spring) / 2, 1)
                        : ($fall ?? $spring);
            $sections[$sid]['students'][] = [
                'name'         => $row['student_name'],
                'school_id'    => $row['school_id'],
                'gender'       => $row['gender'] ?? '',
                'fall_score'   => $fall,
                'spring_score' => $spring,
                'year_score'   => $year,
            ];
        }

        $classResults = [];
        foreach ($sections as $section) {
            usort($section['students'], static function ($a, $b) {
                if ($a['fall_score'] === null && $b['fall_score'] === null) return strcmp($a['name'], $b['name']);
                if ($a['fall_score'] === null) return 1;
                if ($b['fall_score'] === null) return -1;
                return $b['fall_score'] <=> $a['fall_score'] ?: strcmp($a['name'], $b['name']);
            });

            $scores    = array_column($section['students'], 'fall_score');
            $calc      = $this->calculateThreshold($scores, $percentile);
            $threshold = $calc['threshold'];

            $allStudents = $section['students'];
            $winners = array_values(array_filter($allStudents, function ($s) use ($threshold) {
                return $threshold !== null && $s['fall_score'] !== null && $s['fall_score'] >= $threshold;
            }));

            if (empty($winners)) continue;

            $boys       = array_filter($allStudents, static fn($s) => strtolower($s['gender']) === 'male');
            $girls      = array_filter($allStudents, static fn($s) => strtolower($s['gender']) === 'female');
            $trophyBoys  = array_filter($winners,    static fn($s) => strtolower($s['gender']) === 'male');
            $trophyGirls = array_filter($winners,    static fn($s) => strtolower($s['gender']) === 'female');
            $n           = count($allStudents);
            $nBoys       = count($boys);
            $nGirls      = count($girls);
            $nTB         = count($trophyBoys);
            $nTG         = count($trophyGirls);

            $classResults[] = [
                'section_name'     => $section['name'],
                'threshold'        => $threshold,
                'winners'          => $winners,
                'student_count'    => $n,
                'boys'             => $nBoys,
                'girls'            => $nGirls,
                'trophy_boys'      => $nTB,
                'trophy_girls'     => $nTG,
                'pct_boys'         => $n    > 0 ? round($nBoys / $n    * 100) : 0,
                'pct_girls'        => $n    > 0 ? round($nGirls / $n   * 100) : 0,
                'pct_trophy_boys'  => $nBoys  > 0 ? round($nTB / $nBoys  * 100) : 0,
                'pct_trophy_girls' => $nGirls > 0 ? round($nTG / $nGirls * 100) : 0,
                'pct_trophy_total' => $n    > 0 ? round(count($winners) / $n * 100) : 0,
            ];
        }

        return view('administrator/trophy_winners', [
            'classResults'       => $classResults,
            'selectedYear'       => $selectedYear,
            'selectedPercentile' => $percentile,
            'years'              => $years,
        ]);
    }

    public function final()
    {
        $db = \Config\Database::connect();

        $currentYear  = $this->currentSchoolYearName();
        $selectedYear = $this->request->getGet('school_year') ?? $currentYear;
        $percentile   = (float) ($this->request->getGet('percentile') ?? 75);
        $percentile   = max(1.0, min(99.0, $percentile));

        $years = $db->table('student_class')
            ->select('school_year')->distinct()
            ->orderBy('school_year', 'DESC')
            ->get()->getResultArray();
        $years = array_column($years, 'school_year');

        $ey = $db->escape($selectedYear);

        $rows = $db->table('student_class sc')
            ->select([
                'sc.student_id',
                'sc.class_section_id',
                'cs.class_section_name',
                'CONCAT(s.firstname, " ", s.lastname) AS student_name',
                's.school_id',
                's.gender',
                'MAX(sf.semester_score) AS fall_score',
                'MAX(sp.semester_score) AS spring_score',
            ])
            ->join('classSection cs', 'cs.class_section_id = sc.class_section_id', 'left')
            ->join('students s', 's.id = sc.student_id', 'left')
            ->join('semester_scores sf',
                'sf.student_id = sc.student_id AND sf.class_section_id = sc.class_section_id'
                . ' AND sf.school_year = ' . $ey . ' AND LOWER(sf.semester) = "fall"', 'left')
            ->join('semester_scores sp',
                'sp.student_id = sc.student_id AND sp.class_section_id = sc.class_section_id'
                . ' AND sp.school_year = ' . $ey . ' AND LOWER(sp.semester) = "spring"', 'left')
            ->where('sc.school_year', $selectedYear)
            ->orderBy('cs.class_section_name', 'ASC')
            ->orderBy('student_name', 'ASC')
            ->groupBy('sc.student_id, sc.class_section_id, cs.class_section_name, s.firstname, s.lastname, s.school_id, s.gender')
            ->get()->getResultArray();

        $sections = [];
        foreach ($rows as $row) {
            $sid  = (int) ($row['class_section_id'] ?? 0);
            $name = $row['class_section_name'] ?? 'Section ' . $sid;
            if (!isset($sections[$sid])) $sections[$sid] = ['name' => $name, 'students' => []];

            $fall   = is_numeric($row['fall_score'])   ? (float) $row['fall_score']   : null;
            $spring = is_numeric($row['spring_score']) ? (float) $row['spring_score'] : null;
            $year   = ($fall !== null && $spring !== null)
                        ? round(($fall + $spring) / 2, 1)
                        : ($fall ?? $spring);

            $sections[$sid]['students'][] = [
                'name'         => $row['student_name'],
                'school_id'    => $row['school_id'],
                'gender'       => $row['gender'] ?? '',
                'fall_score'   => $fall,
                'spring_score' => $spring,
                'year_score'   => $year,
            ];
        }

        $classResults = [];
        foreach ($sections as $section) {
            $students = $section['students'];

            $fallCalc      = $this->calculateThreshold(array_column($students, 'fall_score'), $percentile);
            $yearCalc      = $this->calculateThreshold(array_column($students, 'year_score'), $percentile);
            $fallThreshold = $fallCalc['threshold'];
            $yearThreshold = $yearCalc['threshold'];

            $annotated = array_map(static function (array $s) use ($fallThreshold, $yearThreshold): array {
                $predicted = $fallThreshold !== null && $s['fall_score'] !== null && $s['fall_score'] >= $fallThreshold;
                $actual    = $yearThreshold !== null && $s['year_score'] !== null && $s['year_score'] >= $yearThreshold;
                $status    = match (true) {
                    $predicted && $actual   => 'confirmed',
                    !$predicted && $actual  => 'surprise',
                    $predicted && !$actual  => 'missed',
                    default                 => 'none',
                };
                return $s + compact('predicted', 'actual', 'status');
            }, $students);

            usort($annotated, static function (array $a, array $b): int {
                if ($a['year_score'] === null && $b['year_score'] === null) return strcmp($a['name'], $b['name']);
                if ($a['year_score'] === null) return 1;
                if ($b['year_score'] === null) return -1;
                return $b['year_score'] <=> $a['year_score'] ?: strcmp($a['name'], $b['name']);
            });

            $nConfirmed = count(array_filter($annotated, static fn ($s) => $s['status'] === 'confirmed'));
            $nSurprise  = count(array_filter($annotated, static fn ($s) => $s['status'] === 'surprise'));
            $nMissed    = count(array_filter($annotated, static fn ($s) => $s['status'] === 'missed'));
            $nPredicted = count(array_filter($annotated, static fn ($s) => $s['predicted']));
            $nActual    = count(array_filter($annotated, static fn ($s) => $s['actual']));
            $n          = count($annotated);

            $classResults[] = [
                'section_name'    => $section['name'],
                'students'        => $annotated,
                'student_count'   => $n,
                'fall_threshold'  => $fallThreshold,
                'year_threshold'  => $yearThreshold,
                'predicted_count' => $nPredicted,
                'actual_count'    => $nActual,
                'confirmed'       => $nConfirmed,
                'surprises'       => $nSurprise,
                'missed'          => $nMissed,
                'accuracy'        => $nPredicted > 0 ? round($nConfirmed / $nPredicted * 100) : ($nActual === 0 ? 100 : 0),
            ];
        }

        return view('administrator/trophy_final', [
            'classResults'       => $classResults,
            'selectedYear'       => $selectedYear,
            'selectedPercentile' => $percentile,
            'years'              => $years,
        ]);
    }

    private function calculateThreshold(array $scores, float $percentile = 75.0): array
    {
        $scores = array_values(array_filter(
            $scores,
            static fn ($v) => is_numeric($v) && $v !== null
        ));
        $scores = array_map('floatval', $scores);
        sort($scores); // ascending
        $n = count($scores);

        if ($n === 0) {
            return ['threshold' => null, 'winners' => 0, 'method' => 'empty'];
        }

        $minWinners = 3; // never fewer than 3, regardless of class size or score count
        // Hard maximum: top (100 - percentile)% of class, but never below the minimum.
        $maxWinners = max($minWinners, (int) floor($n * (1 - $percentile / 100)));

        $threshold = $this->empiricalPercentile($scores, $percentile);
        $winners   = $this->countAtOrAbove($scores, $threshold);

        if ($winners < $minWinners) {
            // Too few qualify — REDUCE the threshold down to the score of the
            // 3rd-ranked student so the minimum of 3 is always met.
            // If fewer than 3 students have scores, award all of them.
            $target    = min($minWinners, $n);
            $desc      = array_reverse($scores);          // descending
            $threshold = $desc[$target - 1];              // score at rank $target
            $winners   = $this->countAtOrAbove($scores, $threshold); // ties included
            return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'min3_reduced'];
        }

        if ($winners <= $maxWinners) {
            return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'empirical_percentile'];
        }

        // Too many winners — raise threshold to respect the cap.
        $result = $this->capByRank($scores, $maxWinners);

        // capByRank may overshoot and drop below the minimum (e.g. bimodal scores
        // where only 2 students are in the top cluster). Enforce min=3 here too.
        if ($result['winners'] < $minWinners) {
            $target    = min($minWinners, $n);
            $desc      = array_reverse($scores);
            $threshold = $desc[$target - 1];
            $winners   = $this->countAtOrAbove($scores, $threshold);
            return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'min3_after_cap'];
        }

        return $result;
    }

    /**
     * Raise the threshold until at most $max students qualify.
     * Returns the result without checking the minimum — caller must do that.
     */
    private function capByRank(array $sortedScores, int $max): array
    {
        $desc      = array_reverse($sortedScores);
        $threshold = $desc[$max - 1];
        $winners   = $this->countAtOrAbove($sortedScores, $threshold);

        if ($winners <= $max) {
            return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'capped_25pct'];
        }

        // Ties push it over — walk up through distinct scores.
        $unique = array_values(array_unique(array_filter(
            $sortedScores,
            static fn ($s) => $s > $threshold
        )));
        sort($unique);

        foreach ($unique as $candidate) {
            $w = $this->countAtOrAbove($sortedScores, $candidate);
            if ($w <= $max) {
                return ['threshold' => $candidate, 'winners' => $w, 'method' => 'capped_25pct'];
            }
        }

        // All scores are equal.
        return ['threshold' => $sortedScores[0], 'winners' => count($sortedScores), 'method' => 'all_equal'];
    }

    private function empiricalPercentile(array $sortedScores, float $p): float
    {
        $n = count($sortedScores);
        if ($n === 0) return 0.0;
        $index  = ($p / 100.0) * ($n - 1);
        $lower  = (int) floor($index);
        $upper  = (int) ceil($index);
        if ($lower === $upper) return $sortedScores[$lower];
        return $sortedScores[$lower] + ($index - $lower) * ($sortedScores[$upper] - $sortedScores[$lower]);
    }

    private function countAtOrAbove(array $scores, float $threshold): int
    {
        return count(array_filter($scores, static fn ($s) => $s >= $threshold));
    }

}
