<?php

namespace App\Controllers\Admin;

use App\Controllers\BaseController;
use App\Models\ClassSectionModel;
use App\Models\CompetitionClassWinnerModel;
use App\Models\CompetitionModel;
use App\Models\CompetitionScoreModel;
use App\Models\CompetitionWinnerModel;
use App\Models\ConfigurationModel;
use App\Models\StudentModel;

require_once APPPATH . 'ThirdParty/fpdf/fpdf.php';

class CompetitionWinnersController extends BaseController
{
    protected $db;
    protected ClassSectionModel $classSectionModel;
    protected CompetitionClassWinnerModel $classWinnerModel;
    protected ConfigurationModel $configModel;
    protected string $classStudentTable;
    protected bool $hasClassWinnerTable = false;
    protected bool $hasQuestionCount = false;

    private array $winnerTiers = [
        ['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 function __construct()
    {
        $this->db = \Config\Database::connect();
        $this->classSectionModel = new ClassSectionModel();
        $this->classWinnerModel = new CompetitionClassWinnerModel();
        $this->configModel = new ConfigurationModel();

        if ($this->db->tableExists('class_student')) {
            $this->classStudentTable = 'class_student';
        } elseif ($this->db->tableExists('student_class')) {
            $this->classStudentTable = 'student_class';
        } else {
            $this->classStudentTable = '';
        }

        $this->hasClassWinnerTable = $this->db->tableExists('competition_class_winners');
        $this->hasQuestionCount = $this->hasClassWinnerTable
            && $this->db->fieldExists('question_count', 'competition_class_winners');
    }

    public function index()
    {
        $competitionModel = new CompetitionModel();
        $schoolYear = $this->currentCompetitionSchoolYear();

        if ($schoolYear !== '') {
            $competitionModel->where('school_year', $schoolYear);
        }

        $competitions = $competitionModel
            ->orderBy('id', 'DESC')
            ->findAll();

        return view('admin/competition_winners/index', [
            'competitions' => $competitions,
            'sectionMap'   => $this->getClassSectionMap(),
            'schoolYear'   => $schoolYear,
        ]);
    }

    public function create()
    {
        $schoolYear = $this->currentCompetitionSchoolYear();
        $classCounts = $this->getClassStudentCounts($schoolYear);
        $classSections = $this->classSectionModel
            ->orderBy('class_section_name', 'ASC')
            ->findAll();
        $classSections = $this->filterSectionsWithStudents($classSections, $classCounts);
        $classRows = $this->buildClassRows($classSections, $classCounts, [], [], []);

        return view('admin/competition_winners/form', [
            'mode'             => 'create',
            'competition'      => null,
            'errors'           => session('errors'),
            'classSections'    => $classSections,
            'classRows'        => $classRows,
            'defaultSemester'  => $this->configModel->getConfig('semester'),
            'defaultSchoolYear'=> $schoolYear,
        ]);
    }

    public function settings($id)
    {
        $competitionModel = new CompetitionModel();
        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }

        $schoolYear = $competition['school_year'] ?? $this->configModel->getConfig('school_year');
        $classCounts = $this->getClassStudentCounts($schoolYear);
        $classSections = $this->classSectionModel
            ->orderBy('class_section_name', 'ASC')
            ->findAll();
        $classSections = $this->filterSectionsWithStudents($classSections, $classCounts);
        $settings = $this->getClassSettings((int) $id);
        $overrides = $settings['overrides'];
        $questionCounts = $settings['questions'];
        $prizeMap = $settings['prizes'];
        $limitClassId = (int) ($competition['class_section_id'] ?? 0);
        $classRows = $this->buildClassRows($classSections, $classCounts, $overrides, $questionCounts, $prizeMap, $limitClassId);

        return view('admin/competition_winners/form', [
            'mode'             => 'edit',
            'competition'      => $competition,
            'errors'           => session('errors'),
            'classSections'    => $classSections,
            'classRows'        => $classRows,
            'defaultSemester'  => $this->configModel->getConfig('semester'),
            'defaultSchoolYear'=> $schoolYear,
        ]);
    }

    public function store()
    {
        $rules = [
            'title'            => 'required|min_length[3]',
            'class_section_id' => 'permit_empty|integer',
        ];

        if (!$this->validate($rules)) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        $competitionModel = new CompetitionModel();

        $classSectionId = $this->request->getPost('class_section_id');
        $classSectionId = $classSectionId !== '' ? (int) $classSectionId : null;

        $competitionId = $competitionModel->insert([
            'title'            => $this->request->getPost('title'),
            'semester'         => $this->request->getPost('semester') ?: null,
            'school_year'      => $this->request->getPost('school_year') ?: null,
            'class_section_id' => $classSectionId ?: null,
            'start_date'       => $this->request->getPost('start_date') ?: null,
            'end_date'         => $this->request->getPost('end_date') ?: null,
            'created_by'       => session('user_id') ?: null,
        ], true);

        $overrides = (array) $this->request->getPost('winner_overrides');
        $questionCounts = (array) $this->request->getPost('question_counts');
        $prizes = (array) $this->request->getPost('prizes');
        $this->saveClassSettings((int) $competitionId, $overrides, $questionCounts, $prizes, $classSectionId ? (int) $classSectionId : null);

        return redirect()->to('/admin/competition-winners')->with('success', 'Competition created.');
    }

    public function update($id)
    {
        $competitionModel = new CompetitionModel();
        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }
        if (!empty($competition['is_locked'])) {
            return redirect()->back()->with('error', 'Competition is locked. Unlock it to make changes.');
        }

        $rules = [
            'title'            => 'required|min_length[3]',
            'class_section_id' => 'permit_empty|integer',
        ];

        if (!$this->validate($rules)) {
            return redirect()->back()->withInput()->with('errors', $this->validator->getErrors());
        }

        $classSectionId = $this->request->getPost('class_section_id');
        $classSectionId = $classSectionId !== '' ? (int) $classSectionId : null;

        $competitionModel->update($id, [
            'title'            => $this->request->getPost('title'),
            'semester'         => $this->request->getPost('semester') ?: null,
            'school_year'      => $this->request->getPost('school_year') ?: null,
            'class_section_id' => $classSectionId ?: null,
            'start_date'       => $this->request->getPost('start_date') ?: null,
            'end_date'         => $this->request->getPost('end_date') ?: null,
        ]);

        $overrides = (array) $this->request->getPost('winner_overrides');
        $questionCounts = (array) $this->request->getPost('question_counts');
        $prizes = (array) $this->request->getPost('prizes');
        $this->saveClassSettings((int) $id, $overrides, $questionCounts, $prizes, $classSectionId ? (int) $classSectionId : null);

        return redirect()->to('/admin/competition-winners')->with('success', 'Competition updated.');
    }

    public function edit($id)
    {
        $competitionModel = new CompetitionModel();
        $scoreModel       = new CompetitionScoreModel();
        $studentModel     = new StudentModel();

        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }

        $lockedClassSectionId = (int) ($competition['class_section_id'] ?? 0);
        $selectedClassSectionId = (int) ($this->request->getGet('class_section_id') ?? 0);
        $activeClassSectionId = $lockedClassSectionId > 0 ? $lockedClassSectionId : $selectedClassSectionId;

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

        if ($activeClassSectionId > 0) {
            $students = $this->getStudentsForCompetition($studentModel, $competition, $activeClassSectionId);
            $existingScores = $scoreModel
                ->where('competition_id', $id)
                ->where('class_section_id', $activeClassSectionId)
                ->findAll();

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

        $schoolYear = $competition['school_year'] ?? null;
        $classCounts = $this->getClassStudentCounts($schoolYear);
        $classSections = $this->classSectionModel
            ->orderBy('class_section_name', 'ASC')
            ->findAll();
        $classSections = $this->filterSectionsWithStudents($classSections, $classCounts);
        $settings = $this->getClassSettings((int) $id);
        $overrides = $settings['overrides'];
        $questionCounts = $settings['questions'];
        $prizeMap = $settings['prizes'];
        $classStudentCount = $classCounts[$activeClassSectionId] ?? 0;
        $classOverride = $overrides[$activeClassSectionId] ?? null;
        $classAuto = $this->winnersForCount($classStudentCount);
        $classFinal = $classOverride !== null ? $classOverride : $classAuto;
        $classQuestionCount = $questionCounts[$activeClassSectionId] ?? null;

        return view('admin/competition_winners/scores', [
            'competition'          => $competition,
            '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'        => $prizeMap,
        ]);
    }

    public function saveScores($id)
    {
        $competitionModel = new CompetitionModel();
        $scoreModel       = new CompetitionScoreModel();

        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }
        if (!empty($competition['is_locked'])) {
            return redirect()->back()->with('error', 'Competition is locked. Unlock it to update scores.');
        }

        $classSectionId = (int) ($competition['class_section_id'] ?? 0);
        if ($classSectionId <= 0) {
            $classSectionId = (int) $this->request->getPost('class_section_id');
        }
        if ($classSectionId <= 0) {
            return redirect()->back()->with('error', 'Select a class section before saving scores.');
        }

        $scores = (array) $this->request->getPost('scores');

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

            $scoreValue = (float) $scoreValue;

            $existing = $scoreModel
                ->where('competition_id', $id)
                ->where('student_id', (int) $studentId)
                ->where('class_section_id', $classSectionId)
                ->first();

            if ($existing) {
                $scoreModel->update($existing['id'], [
                    'score'            => $scoreValue,
                    'class_section_id' => $classSectionId,
                ]);
            } else {
                $scoreModel->insert([
                    'competition_id'   => (int) $id,
                    'student_id'       => (int) $studentId,
                    'class_section_id' => $classSectionId,
                    'score'            => $scoreValue,
                ]);
            }
        }

        return redirect()->to("/admin/competition-winners/{$id}/preview")->with('success', 'Scores saved.');
    }

    public function preview($id)
    {
        $competitionModel = new CompetitionModel();
        $scoreModel       = new CompetitionScoreModel();

        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }

        $rows = $scoreModel
            ->select('competition_scores.*, s.firstname, s.lastname')
            ->join('students s', 's.id = competition_scores.student_id', 'left')
            ->where('competition_id', $id)
            ->findAll();

        $rankedByClass = $this->groupScoresByClass($rows);
        $scoreCounts = $this->getScoreCountsByClass($rankedByClass);

        $classCounts = $this->getClassStudentCounts($competition['school_year'] ?? null);
        $settings = $this->getClassSettings((int) $id);
        $overrides = $settings['overrides'];
        $questionCounts = $settings['questions'];
        $prizeMap = $settings['prizes'];
        $winnersCountByClass = $this->getWinnersCountByClass($competition, $classCounts, $scoreCounts, $overrides);
        $topByClass = $this->getTopWinnersByClass(
            $rankedByClass,
            $winnersCountByClass,
            $prizeMap,
            $questionCounts,
            $classCounts,
            $scoreCounts
        );

        return view('admin/competition_winners/preview', [
            'competition'        => $competition,
            'classMap'           => $this->getClassSectionMap(),
            'classCounts'        => $classCounts,
            'overrideMap'        => $overrides,
            'questionCounts'     => $questionCounts,
            'prizeMap'           => $prizeMap,
            'winnersCountByClass'=> $winnersCountByClass,
            'rankedByClass'      => $rankedByClass,
            'topByClass'         => $topByClass,
        ]);
    }

    public function exportCompetitionToQuiz(int $competitionId, int $classSectionId = 0)
    {
        if ($competitionId <= 0) {
            return redirect()->back()->with('error', 'Competition not found.');
        }

        $competitionModel = new CompetitionModel();
        $competition = $competitionModel->find($competitionId);
        if (!$competition) {
            return redirect()->back()->with('error', 'Competition not found.');
        }

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

        if ($classSectionId > 0 && $lockedClassSectionId > 0 && $lockedClassSectionId !== $classSectionId) {
            return redirect()->back()->with('error', 'This competition is locked to a different class section.');
        }

        $db = $this->db ?: db_connect();

        $schoolId   = session('school_id');
        $updatedBy  = session('user_id');
        $semester   = session('semester');
        $schoolYear = session('school_year');
        if ($semester === null || $semester === '') {
            $semester = $this->configModel->getConfig('semester');
        }
        if ($schoolYear === null || $schoolYear === '') {
            $schoolYear = $this->configModel->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()->getResultArray();
            $classSectionIds = array_values(array_filter(array_map(static function ($row) {
                return (int) ($row['class_section_id'] ?? 0);
            }, $rows)));
        }

        if (empty($classSectionIds)) {
            return redirect()->back()->with('error', 'No class sections with scores found for this competition.');
        }

        $db->transStart();

        $quizIndexes = [];
        foreach ($classSectionIds as $cid) {
            $cid = (int) $cid;
            if ($cid <= 0) {
                continue;
            }

            // 1) next quiz_index
            $row = $db->query(
                "SELECT COALESCE(MAX(quiz_index), 0) + 1 AS next_index
                 FROM quiz
                 WHERE class_section_id = ?
                   AND semester = ?
                   AND school_year = ?",
                [$cid, $semester, $schoolYear]
            )->getRowArray();

            $quizIndex = (int) ($row['next_index'] ?? 1);

            // 2) insert normalized competition scores
            $db->query(
                "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 = ?",
                [$schoolId, $updatedBy, $quizIndex, $semester, $schoolYear, $competitionId, $cid]
            );

            $quizIndexes[$cid] = $quizIndex;
        }

        $db->transComplete();

        if (!$db->transStatus()) {
            return redirect()->back()->with('error', 'Failed exporting competition scores.');
        }

        if (empty($quizIndexes)) {
            return redirect()->back()->with('error', 'No scores were exported.');
        }

        if (count($quizIndexes) === 1) {
            $quizIndex = array_values($quizIndexes)[0];
            return redirect()->back()->with('success', "Exported as Quiz {$quizIndex}.");
        }

        $classMap = $this->getClassSectionMap();
        $labels = [];
        foreach ($quizIndexes as $cid => $quizIndex) {
            $label = $classMap[$cid] ?? $cid;
            $labels[] = "{$label}: Quiz {$quizIndex}";
        }
        $detail = implode(', ', $labels);

        return redirect()->back()->with('success', "Exported competition scores. {$detail}.");
    }

    public function winners($id)
    {
        $competitionModel = new CompetitionModel();
        $winnerModel      = new CompetitionWinnerModel();
        $classWinnerModel = new CompetitionClassWinnerModel();

        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }

        $rows = $winnerModel
            ->select('competition_winners.*, s.firstname, s.lastname, s.school_id, s.photo_consent, cs.class_section_name')
            ->join('students s', 's.id = competition_winners.student_id', 'left')
            ->join('classSection cs', 'cs.class_section_id = competition_winners.class_section_id', 'left')
            ->where('competition_id', $id)
            ->orderBy('competition_winners.class_section_id', 'ASC')
            ->orderBy('rank', 'ASC')
            ->findAll();

        $questionCounts = $this->getQuestionCountsForCompetition((int) $id);

        return view('admin/competition_winners/winners', [
            'competition' => $competition,
            'rows'        => $rows,
            'classMap'    => $this->getClassSectionMap(),
            'questionCounts' => $questionCounts,
        ]);
    }

    public function recognitionPdf($id)
    {
        $competitionModel = new CompetitionModel();
        $winnerModel      = new CompetitionWinnerModel();
        $classWinnerModel = new CompetitionClassWinnerModel();

        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }

        $rows = $winnerModel
            ->select('competition_winners.*, s.firstname, s.lastname, s.school_id, cs.class_section_name')
            ->join('students s', 's.id = competition_winners.student_id', 'left')
            ->join('classSection cs', 'cs.class_section_id = competition_winners.class_section_id', 'left')
            ->where('competition_id', $id)
            ->orderBy('competition_winners.class_section_id', 'ASC')
            ->orderBy('rank', 'ASC')
            ->findAll();

        if (empty($rows)) {
            return 'No published winners yet.';
        }

        $questionCounts = $this->getQuestionCountsForCompetition((int) $id);
        $classMap = $this->getClassSectionMap();

        if (ENVIRONMENT !== 'production') {
            unset(service('toolbar')->collectors['CodeIgniter\Debug\Toolbar\Collectors\Timers']);
        }

        $normalize = static function (string $text): string {
            $text = str_replace(["\xC2\xA0", "\xA0"], ' ', $text);
            $text = preg_replace('/\s+/u', ' ', $text) ?? $text;
            $text = preg_replace('/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/u', '', $text) ?? $text;
            return trim($text);
        };
        $toPdf = static function (string $text): string {
            if (function_exists('iconv')) {
                $out = @iconv('UTF-8', 'ISO-8859-1//TRANSLIT', $text);
                if ($out !== false && $out !== '') {
                    return $out;
                }
            }
            if (function_exists('mb_convert_encoding')) {
                $out = @mb_convert_encoding($text, 'ISO-8859-1', 'UTF-8');
                if ($out !== '') {
                    return $out;
                }
            }
            $out = @utf8_decode($text);
            return $out !== '' ? $out : $text;
        };
        $formatScore = static function ($scoreValue, ?int $questionCount): string {
            if ($scoreValue === null || $scoreValue === '' || !is_numeric($scoreValue)) {
                return '-';
            }
            $scoreDisplay = number_format((float) $scoreValue, 0, '.', '');
            if ($questionCount !== null) {
                return $scoreDisplay . '/' . $questionCount;
            }
            return $scoreDisplay;
        };
        $formatOrdinal = static function ($value): string {
            if ($value === null || $value === '' || !is_numeric($value)) {
                return '-';
            }
            $num = (int) $value;
            if ($num <= 0) {
                return '-';
            }
            $suffix = 'th';
            if ($num % 100 < 11 || $num % 100 > 13) {
                switch ($num % 10) {
                    case 1:
                        $suffix = 'st';
                        break;
                    case 2:
                        $suffix = 'nd';
                        break;
                    case 3:
                        $suffix = 'rd';
                        break;
                }
            }
            return $num . $suffix;
        };
        $fitText = static function (\FPDF $pdf, string $text, float $width) use ($normalize, $toPdf): string {
            $suffix = '...';
            $text = $normalize($text);
            $encoded = $toPdf($text);
            if ($pdf->GetStringWidth($encoded) <= $width) {
                return $encoded;
            }
            $maxWidth = $width - $pdf->GetStringWidth($suffix);
            if ($maxWidth <= 0) {
                return $suffix;
            }
            $trimmed = $text;
            while ($trimmed !== '') {
                if (function_exists('mb_substr')) {
                    $trimmed = mb_substr($trimmed, 0, -1);
                } else {
                    $trimmed = substr($trimmed, 0, -1);
                }
                $encoded = $toPdf($trimmed);
                if ($pdf->GetStringWidth($encoded) <= $maxWidth) {
                    return $encoded . $suffix;
                }
            }
            return $suffix;
        };

        $pdf = new \FPDF('P', 'mm', 'Letter');
        $leftMargin = 15;
        $rightMargin = 15;
        $topMargin = 15;
        $pdf->SetMargins($leftMargin, $topMargin, $rightMargin);
        $pdf->SetAutoPageBreak(true, 15);

        $logoPath = FCPATH . 'assets/images/alrahma_logo.png';
        if (!is_file($logoPath)) {
            $logoPath = FCPATH . 'images/alrahma_logo.png';
        }
        if (!is_file($logoPath)) {
            $logoPath = null;
        }
        $signaturePath = FCPATH . 'assets/images/signature.png';
        if (!is_file($signaturePath)) {
            $signaturePath = null;
        }

        $drawField = static function (
            \FPDF $pdf,
            float $x,
            float $y,
            float $width,
            string $label,
            string $value,
            callable $fitText,
            callable $toPdf
        ): void {
            $pdf->SetXY($x, $y);
            $pdf->SetFont('Arial', 'B', 11);
            $pdf->Write(5, $toPdf($label));
            $labelWidth = $pdf->GetStringWidth($toPdf($label));
            $pdf->SetFont('Arial', '', 11);
            $available = max(1, $width - $labelWidth - 2);
            $valueText = $fitText($pdf, $value, $available);
            $pdf->SetXY($x + $labelWidth + 2, $y);
            $pdf->Write(5, $valueText);
        };

        $competitionTitle = $normalize((string) ($competition['title'] ?? ''));
        $competitionLabel = $competitionTitle !== '' ? $competitionTitle : 'competition';
        if (stripos($competitionLabel, 'competition') === false) {
            $competitionLabel .= ' competition';
        }
        $eventDate = (new \DateTime('now'))->format('m/d/Y');

        $pageWidth = $pdf->GetPageWidth();
        $pageHeight = $pdf->GetPageHeight();
        $bottomMargin = 15;
        $blockHeight = ($pageHeight - $topMargin - $bottomMargin) / 2;

        $drawStudentBlock = static function (
            \FPDF $pdf,
            array $row,
            float $blockTop,
            float $pageWidth,
            float $leftMargin,
            float $rightMargin,
            ?string $logoPath,
            ?string $signaturePath,
            array $classMap,
            array $questionCounts,
            callable $formatScore,
            callable $formatOrdinal,
            callable $fitText,
            callable $toPdf,
            callable $normalize,
            string $competitionLabel,
            string $eventDate,
            callable $drawField
        ): void {
            $contentWidth = $pageWidth - $leftMargin - $rightMargin;

            $logoX = $leftMargin;
            $logoY = $blockTop + 5;
            $logoW = 22;
            if ($logoPath) {
                $pdf->Image($logoPath, $logoX, $logoY, $logoW);
            }

            $infoX = $logoX + $logoW + 6;
            $infoY = $blockTop + 8;
            $availableW = $pageWidth - $infoX - $rightMargin;
            $leftW = $availableW * 0.48;
            $midW = $availableW * 0.32;
            $rightW = $availableW - $leftW - $midW;

            $classId = (int) ($row['class_section_id'] ?? 0);
            $classLabel = (string) ($row['class_section_name'] ?? ($classMap[$classId] ?? ('Class ' . $classId)));
            if (strtolower(trim($classLabel)) === 'youth') {
                $classLabel = 'Youth';
            }
            $name = trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? ''));
            $studentLabel = $name !== '' ? $name : ('Student #' . ($row['student_id'] ?? ''));
            $scoreLabel = $formatScore($row['score'] ?? null, $questionCounts[$classId] ?? null);
            $placementLabel = $formatOrdinal($row['rank'] ?? null);

            $prizeValue = null;
            if (isset($row['prize_amount']) && $row['prize_amount'] !== '' && is_numeric($row['prize_amount'])) {
                $prizeValue = (float) $row['prize_amount'];
            }
            $prizeLabel = $prizeValue !== null ? ('$' . number_format($prizeValue, 2, '.', '')) : '-';

            $drawField($pdf, $infoX, $infoY, $leftW, 'Student Name: ', $studentLabel, $fitText, $toPdf);
            $drawField($pdf, $infoX + $leftW, $infoY, $midW + $rightW, 'Grade: ', $classLabel, $fitText, $toPdf);

            $line2Y = $infoY + 7;
            $drawField($pdf, $infoX, $line2Y, $leftW, 'Quiz Score: ', $scoreLabel, $fitText, $toPdf);
            $drawField($pdf, $infoX + $leftW, $line2Y, $midW, 'Final Placement: ', $placementLabel, $fitText, $toPdf);
            $drawField($pdf, $infoX + $leftW + $midW, $line2Y, $rightW, 'Prize: ', $prizeLabel, $fitText, $toPdf);

            $bodyY = $line2Y + 16;
            $pdf->SetXY($leftMargin, $bodyY);
            $pdf->SetFont('Arial', '', 11);
            $firstParagraph = $eventDate !== ''
                ? sprintf(
                    'Heartfelt congratulations on your exceptional performance in the %s held by Al Rahma Sunday School on %s!',
                    $competitionLabel,
                    $eventDate
                )
                : sprintf(
                    'Heartfelt congratulations on your exceptional performance in the %s held by Al Rahma Sunday School!',
                    $competitionLabel
                );
            $paragraphs = [
                $firstParagraph,
                'Your commitment to excellence in the competition reflects not only your knowledge but also your passion for the subject. May this achievement be a stepping stone to even greater successes in the future.',
                'Wishing you continued success and fulfillment in your academic journey.',
            ];
            foreach ($paragraphs as $paragraph) {
                $pdf->MultiCell($contentWidth, 6, $toPdf($normalize($paragraph)), 0, 'L');
                $pdf->Ln(2);
            }

            $pdf->Ln(2);
            $pdf->SetFont('Arial', '', 11);
            $pdf->Cell(0, 6, $toPdf('Warmest congratulations,'), 0, 1, 'L');
            $pdf->Cell(0, 6, $toPdf('- Al Rahma Principal'), 0, 1, 'L');

            if ($signaturePath) {
                $pdf->Ln(2);
                $sigX = $leftMargin + 2;
                $sigY = $pdf->GetY();
                $pdf->Image($signaturePath, $sigX, $sigY, 35);
            }
        };

        foreach (array_values($rows) as $index => $row) {
            if ($index % 2 === 0) {
                $pdf->AddPage();
            }
            $blockTop = $topMargin + ($blockHeight * ($index % 2));
            $drawStudentBlock(
                $pdf,
                $row,
                $blockTop,
                $pageWidth,
                $leftMargin,
                $rightMargin,
                $logoPath,
                $signaturePath,
                $classMap,
                $questionCounts,
                $formatScore,
                $formatOrdinal,
                $fitText,
                $toPdf,
                $normalize,
                $competitionLabel,
                $eventDate,
                $drawField
            );
        }

        if (ob_get_length()) {
            ob_end_clean();
        }
        $pdfContent = $pdf->Output('S');
        $filename = 'Competition_Winners_Recognition_' . (int) $id . '.pdf';
        return $this->response
            ->setHeader('Content-Type', 'application/pdf')
            ->setHeader('Content-Disposition', 'inline; filename="' . $filename . '"')
            ->setHeader('Cache-Control', 'private, max-age=0, must-revalidate')
            ->setHeader('Pragma', 'public')
            ->setHeader('Content-Length', (string) strlen($pdfContent))
            ->setBody($pdfContent);
    }

    public function publish($id)
    {
        $competitionModel = new CompetitionModel();
        $winnerModel      = new CompetitionWinnerModel();
        $scoreModel       = new CompetitionScoreModel();

        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }
        if (!empty($competition['is_locked'])) {
            return redirect()->back()->with('error', 'Competition is locked. Unlock it to publish winners.');
        }

        $rows = $scoreModel
            ->select('competition_scores.*')
            ->where('competition_id', $id)
            ->findAll();

        $rankedByClass = $this->groupScoresByClass($rows);
        $scoreCounts = $this->getScoreCountsByClass($rankedByClass);
        $classCounts = $this->getClassStudentCounts($competition['school_year'] ?? null);
        $settings = $this->getClassSettings((int) $id);
        $overrides = $settings['overrides'];
        $questionCounts = $settings['questions'];
        $prizeMap = $settings['prizes'];
        $winnersCountByClass = $this->getWinnersCountByClass($competition, $classCounts, $scoreCounts, $overrides);

        $winnerModel->where('competition_id', $id)->delete();

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

            $topWithPrizes = $this->assignPrizesByScore(
                $rowsForClass,
                $prizeMap[$classSectionId] ?? [],
                $limit,
                $questionCounts[$classSectionId] ?? null
            );
            foreach ($topWithPrizes as $row) {
                $winnerModel->insert([
                    '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'       => date('Y-m-d H:i:s'),
                ]);
            }
        }

        $competitionModel->update($id, [
            'is_published' => 1,
            'published_at' => date('Y-m-d H:i:s'),
        ]);

        return redirect()->to('/admin/competition-winners')->with('success', 'Winners published.');
    }

    public function lock($id)
    {
        $competitionModel = new CompetitionModel();
        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }

        if (!empty($competition['is_locked'])) {
            return redirect()->back()->with('success', 'Competition is already locked.');
        }

        $competitionModel->update($id, [
            'is_locked' => 1,
            'locked_at' => date('Y-m-d H:i:s'),
            'locked_by' => session('user_id') ?: null,
        ]);

        return redirect()->back()->with('success', 'Competition locked.');
    }

    public function unlock($id)
    {
        $competitionModel = new CompetitionModel();
        $competition = $competitionModel->find($id);
        if (!$competition) {
            return redirect()->to('/admin/competition-winners')->with('error', 'Competition not found.');
        }

        if (empty($competition['is_locked'])) {
            return redirect()->back()->with('success', 'Competition is already unlocked.');
        }

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

        return redirect()->back()->with('success', 'Competition unlocked.');
    }

    private 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 = $this->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;
    }

    private 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;
        }));
    }

    private function saveClassSettings(int $competitionId, array $overrides, array $questionCounts, array $prizes, ?int $limitClassId = null): void
    {
        $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 = $this->classWinnerModel
                ->where('competition_id', $competitionId)
                ->where('class_section_id', $classId)
                ->first();

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

            if ($overrideValue === null && $questionValue === null && empty($hasPrize)) {
                if ($existing) {
                    $this->classWinnerModel->delete($existing['id']);
                }
                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) {
                $this->classWinnerModel->update($existing['id'], $payload);
            } else {
                $this->classWinnerModel->insert($payload);
            }
        }
    }

    private function getClassSettings(int $competitionId): array
    {
        $rows = $this->classWinnerModel
            ->where('competition_id', $competitionId)
            ->findAll();

        $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 && array_key_exists('question_count', $row) && $row['question_count'] !== null) {
                $questions[$classId] = (int) $row['question_count'];
            }
            $prizes[$classId] = [
                1 => $this->castPrize($row['prize_1'] ?? null),
                2 => $this->castPrize($row['prize_2'] ?? null),
                3 => $this->castPrize($row['prize_3'] ?? null),
                4 => $this->castPrize($row['prize_4'] ?? null),
                5 => $this->castPrize($row['prize_5'] ?? null),
                6 => $this->castPrize($row['prize_6'] ?? null),
            ];
        }

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

    private function getQuestionCountsForCompetition(int $competitionId): array
    {
        if (!$this->hasQuestionCount || !$this->hasClassWinnerTable) {
            return [];
        }

        $qRows = $this->classWinnerModel
            ->select('class_section_id, question_count')
            ->where('competition_id', $competitionId)
            ->findAll();

        $questionCounts = [];
        foreach ($qRows as $row) {
            $classId = (int) ($row['class_section_id'] ?? 0);
            if ($classId > 0 && $row['question_count'] !== null) {
                $questionCounts[$classId] = (int) $row['question_count'];
            }
        }

        return $questionCounts;
    }

    private function currentCompetitionSchoolYear(): string
    {
        return $this->currentSchoolYearName((string) ($this->configModel->getConfig('school_year') ?? ''));
    }

    private function getClassSectionMap(): array
    {
        $sections = $this->classSectionModel
            ->select('class_section_id, class_section_name')
            ->orderBy('class_section_name', 'ASC')
            ->findAll();

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

        return $map;
    }

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

        $row = $this->classSectionModel
            ->select('class_section_name')
            ->where('class_section_id', $classSectionId)
            ->first();

        return $row['class_section_name'] ?? null;
    }

    private function getStudentsForCompetition(StudentModel $studentModel, array $competition, int $classSectionId): array
    {
        if ($classSectionId <= 0 || $this->classStudentTable === '') {
            return [];
        }

        $builder = $this->db->table('students s')
            ->select('s.id, s.school_id, s.firstname, s.lastname')
            ->join($this->classStudentTable . ' cs', 'cs.student_id = s.id', 'inner')
            ->where('cs.class_section_id', $classSectionId);

        $hasSchoolYear = $this->db->fieldExists('school_year', $this->classStudentTable);
        if ($hasSchoolYear && !empty($competition['school_year'])) {
            $builder->where('cs.school_year', $competition['school_year']);
        }

        $builder->distinct();
        $builder->orderBy('s.lastname', 'ASC')
            ->orderBy('s.firstname', 'ASC');

        return $builder->get()->getResultArray();
    }

    private function getClassStudentCounts(?string $schoolYear): array
    {
        if ($this->classStudentTable === '') {
            return [];
        }

        $builder = $this->db->table($this->classStudentTable)
            ->select('class_section_id, COUNT(*) AS total')
            ->where('class_section_id IS NOT NULL', null, false)
            ->groupBy('class_section_id');

        if ($schoolYear && $this->db->fieldExists('school_year', $this->classStudentTable)) {
            $builder->where('school_year', $schoolYear);
        }

        $rows = $builder->get()->getResultArray();

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

        return $map;
    }

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

        foreach ($this->winnerTiers 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;
    }

    private 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 = $this->winnersForCount((int) $baseCount);
            $minWinners = 3;
            $map[$classId] = $maxWinners >= $minWinners ? $maxWinners : $minWinners;
        }

        return $map;
    }

    private 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;
    }

    private function getScoreCountsByClass(array $rankedByClass): array
    {
        $counts = [];
        foreach ($rankedByClass as $classId => $rows) {
            $counts[(int) $classId] = count($rows);
        }
        return $counts;
    }

    private function getTopWinnersByClass(
        array $rankedByClass,
        array $winnersCountByClass,
        array $prizeMap,
        array $questionCounts,
        array $classCounts,
        array $scoreCounts
    ): array
    {
        $out = [];

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

            $out[$classSectionId] = $this->assignPrizesByScore(
                $rows,
                $prizeMap[$classSectionId] ?? [],
                $limit,
                $questionCounts[$classSectionId] ?? null
            );
        }

        return $out;
    }

    private 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;
    }

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

    private function getPrizeForRank(array $classPrizes, int $rank): float
    {
        $val = $classPrizes[$rank] ?? null;
        return $val !== null ? (float) $val : 0.0;
    }

    private 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 = $this->getPrizeForRank($classPrizes, 1);
            }
        }

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

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

        return $out;
    }
}
