<?php

namespace App\Services\Grading;

use App\Models\BelowSixtyDecision;
use App\Models\CurrentFlag;
use App\Models\ParentMeetingSchedule;
use App\Models\ScoreComment;
use App\Models\Student;
use App\Models\StudentDecision;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Event;
use RuntimeException;

class GradingBelowSixtyService
{
    public function listRows(string $schoolYear, string $semester): array
    {
        $semesterKey = strtolower(trim($semester));
        $rows = DB::table('semester_scores as ss')
            ->select([
                's.id as student_id',
                's.school_id',
                's.firstname',
                's.lastname',
                'cs.class_section_name',
                'ss.homework_avg',
                'ss.project_avg',
                'ss.participation_score',
                DB::raw('COALESCE(ss.test_avg, ss.quiz_avg) as test_avg'),
                'ss.ptap_score',
                'ss.attendance_score',
                'ss.midterm_exam_score',
                'ss.final_exam_score',
                'ss.semester_score',
            ])
            ->join('students as s', 's.id', '=', 'ss.student_id')
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id')
            ->where('s.is_active', 1)
            ->where('ss.school_year', $schoolYear)
            ->whereRaw('LOWER(TRIM(ss.semester)) = ?', [$semesterKey])
            ->whereNotNull('ss.semester_score')
            ->where('ss.semester_score', '<', 60)
            ->orderBy('s.lastname', 'ASC')
            ->orderBy('s.firstname', 'ASC')
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();

        if (empty($rows)) {
            return [];
        }

        $studentIds = array_values(array_unique(array_map(
            static fn ($r) => (int) ($r['student_id'] ?? 0),
            $rows
        )));
        $studentIds = array_values(array_filter($studentIds, static fn ($id) => $id > 0));

        $commentMap = [];
        if (!empty($studentIds)) {
            $commentRows = ScoreComment::query()
                ->select('student_id', 'comment', 'created_at')
                ->where('score_type', 'general')
                ->where('school_year', $schoolYear)
                ->whereRaw('LOWER(TRIM(semester)) = ?', [$semesterKey])
                ->whereIn('student_id', $studentIds)
                ->orderBy('created_at', 'DESC')
                ->get();
            foreach ($commentRows as $row) {
                $sid = (int) $row->student_id;
                if ($sid > 0 && !isset($commentMap[$sid])) {
                    $commentMap[$sid] = (string) ($row->comment ?? '');
                }
            }
        }

        $statusMap = [];
        if (!empty($studentIds)) {
            $flagRows = CurrentFlag::query()
                ->select('student_id', 'flag_state')
                ->where('flag', 'grade')
                ->where('school_year', $schoolYear)
                ->whereRaw('LOWER(TRIM(semester)) = ?', [$semesterKey])
                ->whereIn('student_id', $studentIds)
                ->get();
            foreach ($flagRows as $row) {
                $sid = (int) $row->student_id;
                if ($sid > 0) {
                    $statusMap[$sid] = (string) ($row->flag_state ?? '');
                }
            }
        }

        foreach ($rows as &$row) {
            $sid = (int) ($row['student_id'] ?? 0);
            $row['comment'] = $commentMap[$sid] ?? '';
            $flagState = strtolower(trim((string) ($statusMap[$sid] ?? '')));
            $row['status'] = ($flagState === 'closed' || $flagState === 'canceled') ? 'Closed' : 'Open';
        }
        unset($row);

        return $rows;
    }

    public function emailContext(int $studentId, string $schoolYear, string $semester): array
    {
        $row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester);
        if (empty($row)) {
            throw new RuntimeException('Student record not found for the selected term.');
        }

        $studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));
        $subject = $this->buildBelowSixtySubject($studentName, $semester, $schoolYear);
        $parentName = $this->fetchBelowSixtyParentName($studentId);

        $scores = [
            'homework_avg' => $row['homework_avg'] ?? null,
            'project_avg' => $row['project_avg'] ?? null,
            'participation_score' => $row['participation_score'] ?? null,
            'test_avg' => $row['test_avg'] ?? null,
            'ptap_score' => $row['ptap_score'] ?? null,
            'attendance_score' => $row['attendance_score'] ?? null,
            'midterm_exam_score' => $row['midterm_exam_score'] ?? null,
            'semester_score' => $row['semester_score'] ?? null,
        ];

        return [
            'student_id' => $studentId,
            'student_name' => $studentName !== '' ? $studentName : 'Student',
            'parent_name' => $parentName,
            'class_section_name' => $row['class_section_name'] ?? '',
            'semester' => $semester,
            'school_year' => $schoolYear,
            'scores' => $scores,
            'comment' => $row['comment'] ?? '',
            'subject' => $subject,
        ];
    }

    public function sendEmail(array $payload): void
    {
        Event::dispatch('below60.email', [$payload]);
    }

    public function updateStatus(int $studentId, string $semester, string $schoolYear, string $status, string $note, ?int $userId): void
    {
        $status = ucfirst(strtolower($status));
        if (!in_array($status, ['Open', 'Closed'], true)) {
            throw new RuntimeException('Invalid status.');
        }

        $semesterKey = strtolower(trim($semester));
        $existing = CurrentFlag::query()
            ->where('student_id', $studentId)
            ->where('flag', 'grade')
            ->where('school_year', $schoolYear)
            ->whereRaw('LOWER(TRIM(semester)) = ?', [$semesterKey])
            ->first();

        $now = now();

        if ($existing) {
            $data = [
                'flag_state' => $status,
                'flag_datetime' => $now,
                'updated_at' => $now,
            ];
            if ($status === 'Open') {
                $data['updated_by_open'] = $userId;
                if ($note !== '') {
                    $prev = (string) ($existing->open_description ?? '');
                    $data['open_description'] = trim($prev . PHP_EOL . $note);
                }
            } else {
                $data['updated_by_closed'] = $userId;
                if ($note !== '') {
                    $prev = (string) ($existing->close_description ?? '');
                    $data['close_description'] = trim($prev . PHP_EOL . $note);
                }
            }
            $existing->update($data);
            return;
        }

        $row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester);
        $studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));
        $grade = (string) ($row['class_section_name'] ?? '');
        $data = [
            'student_id' => $studentId,
            'student_name' => $studentName !== '' ? $studentName : 'Student',
            'grade' => $grade,
            'flag' => 'grade',
            'flag_datetime' => $now,
            'flag_state' => $status,
            'semester' => $semester,
            'school_year' => $schoolYear,
            'updated_at' => $now,
        ];
        if ($status === 'Open') {
            $data['updated_by_open'] = $userId;
            if ($note !== '') {
                $data['open_description'] = $note;
            }
        } else {
            $data['updated_by_closed'] = $userId;
            if ($note !== '') {
                $data['close_description'] = $note;
            }
        }

        CurrentFlag::query()->create($data);
    }

    public function meetingContext(int $studentId, string $schoolYear, string $semester): array
    {
        $context = $this->fetchBelowSixtyMeetingContext($studentId, $schoolYear, $semester);
        if (empty($context)) {
            throw new RuntimeException('Student record not found for the selected term.');
        }

        return $context + [
            'student_id' => $studentId,
            'semester' => $semester,
            'school_year' => $schoolYear,
        ];
    }

    public function saveMeeting(array $payload, ?int $userId): void
    {
        $studentId = (int) ($payload['student_id'] ?? 0);
        $semester = (string) ($payload['semester'] ?? '');
        $schoolYear = (string) ($payload['school_year'] ?? '');
        $date = trim((string) ($payload['date'] ?? ''));
        $time = trim((string) ($payload['time'] ?? ''));
        $notes = trim((string) ($payload['notes'] ?? ''));

        if ($studentId <= 0 || $semester === '' || $schoolYear === '' || $date === '') {
            throw new RuntimeException('Missing required fields.');
        }

        $context = $this->fetchBelowSixtyMeetingContext($studentId, $schoolYear, $semester);
        if (empty($context)) {
            throw new RuntimeException('Student record not found for the selected term.');
        }

        ParentMeetingSchedule::query()->create([
            'student_id' => $studentId,
            'parent_user_id' => $context['parent_user_id'] ?? null,
            'parent_name' => $context['parent_name'],
            'student_name' => $context['student_name'],
            'class_section_name' => $context['class_section_name'],
            'date' => $date,
            'time' => $time !== '' ? $time : null,
            'notes' => $notes !== '' ? $notes : null,
            'semester' => $semester,
            'school_year' => $schoolYear,
            'status' => 'scheduled',
            'created_by' => $userId,
        ]);
    }

    public function listDecisionRows(string $schoolYear): array
    {
        $scoreRows = DB::table('semester_scores as ss')
            ->select([
                's.id as student_id',
                's.school_id',
                's.firstname',
                's.lastname',
                's.age',
                'ss.class_section_id',
                'cs.class_section_name',
                DB::raw('LOWER(TRIM(ss.semester)) as sem_key'),
                'ss.semester_score',
            ])
            ->join('students as s', 's.id', '=', 'ss.student_id')
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id')
            ->where('s.is_active', 1)
            ->where('ss.school_year', $schoolYear)
            ->whereRaw('LOWER(TRIM(ss.semester)) IN (?, ?)', ['fall', 'spring'])
            ->whereNotNull('ss.semester_score')
            ->orderBy('cs.class_section_name', 'ASC')
            ->orderBy('s.lastname', 'ASC')
            ->orderBy('s.firstname', 'ASC')
            ->orderByDesc('ss.updated_at')
            ->orderByDesc('ss.id')
            ->get();

        if ($scoreRows->isEmpty()) {
            return [];
        }

        $studentMap = [];
        foreach ($scoreRows as $row) {
            $studentId = (int) ($row->student_id ?? 0);
            if ($studentId <= 0) {
                continue;
            }

            if (!isset($studentMap[$studentId])) {
                $studentMap[$studentId] = [
                    'student_id' => $studentId,
                    'school_id' => $row->school_id,
                    'firstname' => $row->firstname,
                    'lastname' => $row->lastname,
                    'age' => $row->age,
                    'class_section_id' => (int) ($row->class_section_id ?? 0),
                    'class_section_name' => $row->class_section_name,
                    'fall_score' => null,
                    'spring_score' => null,
                ];
            }

            $semesterKey = strtolower(trim((string) ($row->sem_key ?? '')));
            $score = is_numeric($row->semester_score ?? null) ? (float) $row->semester_score : null;
            if ($score === null) {
                continue;
            }

            if ($semesterKey === 'fall' && $studentMap[$studentId]['fall_score'] === null) {
                $studentMap[$studentId]['fall_score'] = $score;
            }

            if ($semesterKey === 'spring' && $studentMap[$studentId]['spring_score'] === null) {
                $studentMap[$studentId]['spring_score'] = $score;
            }
        }

        $studentIds = array_keys($studentMap);
        if (empty($studentIds)) {
            return [];
        }

        $belowDecisionMap = BelowSixtyDecision::query()
            ->where('school_year', $schoolYear)
            ->where('semester', 'year')
            ->whereIn('student_id', $studentIds)
            ->get()
            ->keyBy(static fn (BelowSixtyDecision $row) => (int) $row->student_id);

        $studentDecisionMap = StudentDecision::query()
            ->where('school_year', $schoolYear)
            ->whereIn('student_id', $studentIds)
            ->get()
            ->keyBy(static fn (StudentDecision $row) => (int) $row->student_id);

        $certificateRows = DB::table('certificate_records')
            ->select('student_id', 'certificate_number', 'issued_at')
            ->where('school_year', $schoolYear)
            ->whereIn('student_id', $studentIds)
            ->orderByDesc('issued_at')
            ->get();

        $certificateMap = [];
        foreach ($certificateRows as $row) {
            $studentId = (int) ($row->student_id ?? 0);
            if ($studentId > 0 && !isset($certificateMap[$studentId])) {
                $certificateMap[$studentId] = (string) ($row->certificate_number ?? '');
            }
        }

        $rows = [];
        foreach ($studentMap as $studentId => $info) {
            $fall = $info['fall_score'];
            $spring = $info['spring_score'];

            if ($fall === null || $spring === null) {
                continue;
            }

            $yearScore = round(($fall + $spring) / 2, 2);
            if ($yearScore >= 60) {
                continue;
            }

            $belowDecision = $belowDecisionMap->get($studentId);
            $studentDecision = $studentDecisionMap->get($studentId);

            if ($studentDecision !== null && is_numeric($studentDecision->year_score)) {
                $yearScore = round((float) $studentDecision->year_score, 2);
            }

            $rows[] = [
                'student_id' => $studentId,
                'school_id' => $info['school_id'],
                'firstname' => $info['firstname'],
                'lastname' => $info['lastname'],
                'age' => $info['age'],
                'class_section_id' => $info['class_section_id'],
                'class_section_name' => $info['class_section_name'],
                'fall_score' => $fall,
                'spring_score' => $spring,
                'year_score' => $yearScore,
                'decision' => (string) ($belowDecision?->decision ?? ''),
                'decision_notes' => (string) ($belowDecision?->notes ?? ''),
                'consolidated_decision' => $studentDecision?->decision,
                'certificate_number' => $certificateMap[$studentId] ?? '',
            ];
        }

        return $rows;
    }

    public function saveDecision(int $studentId, string $schoolYear, string $decision, string $notes, ?int $userId): array
    {
        if ($studentId <= 0 || trim($schoolYear) === '') {
            throw new RuntimeException('Missing student or school year.');
        }

        $decision = trim($decision);
        $notes = trim($notes);

        $belowDecision = BelowSixtyDecision::query()->firstOrNew([
            'student_id' => $studentId,
            'semester' => 'year',
            'school_year' => $schoolYear,
        ]);
        $belowDecision->decision = $decision !== '' ? $decision : null;
        $belowDecision->notes = $notes !== '' ? $notes : null;
        $belowDecision->decided_by = $userId;
        $belowDecision->save();

        $scoreRows = DB::table('semester_scores as ss')
            ->select([
                DB::raw('LOWER(TRIM(ss.semester)) as sem_key'),
                'ss.semester_score',
                'cs.class_section_name',
            ])
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id')
            ->where('ss.student_id', $studentId)
            ->where('ss.school_year', $schoolYear)
            ->whereRaw('LOWER(TRIM(ss.semester)) IN (?, ?)', ['fall', 'spring'])
            ->whereNotNull('ss.semester_score')
            ->orderByDesc('ss.updated_at')
            ->orderByDesc('ss.id')
            ->get();

        $fallScore = null;
        $springScore = null;
        $classSectionName = null;
        foreach ($scoreRows as $row) {
            $semesterKey = strtolower(trim((string) ($row->sem_key ?? '')));
            $score = is_numeric($row->semester_score ?? null) ? (float) $row->semester_score : null;
            if ($classSectionName === null && trim((string) ($row->class_section_name ?? '')) !== '') {
                $classSectionName = (string) $row->class_section_name;
            }
            if ($score === null) {
                continue;
            }
            if ($semesterKey === 'fall' && $fallScore === null) {
                $fallScore = $score;
            }
            if ($semesterKey === 'spring' && $springScore === null) {
                $springScore = $score;
            }
        }

        if ($classSectionName === null) {
            $enrollment = DB::table('student_class as sc')
                ->select('cs.class_section_name')
                ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
                ->where('sc.student_id', $studentId)
                ->where('sc.school_year', $schoolYear)
                ->orderByDesc('sc.id')
                ->first();
            $classSectionName = $enrollment?->class_section_name;
        }

        $yearScore = null;
        if ($fallScore !== null && $springScore !== null) {
            $yearScore = round(($fallScore + $springScore) / 2, 2);
        } elseif ($fallScore !== null) {
            $yearScore = round($fallScore, 2);
        } elseif ($springScore !== null) {
            $yearScore = round($springScore, 2);
        }

        $studentDecision = StudentDecision::query()->firstOrNew([
            'student_id' => $studentId,
            'school_year' => $schoolYear,
        ]);
        $studentDecision->class_section_name = $classSectionName;
        $studentDecision->year_score = $yearScore;
        $studentDecision->decision = $decision !== '' ? $decision : null;
        $studentDecision->source = $decision !== '' ? 'manual' : 'pending';
        $studentDecision->notes = $notes !== '' ? $notes : null;
        $studentDecision->generated_by = $userId;
        $studentDecision->save();

        return [
            'student_id' => $studentId,
            'school_year' => $schoolYear,
            'decision' => $decision,
            'notes' => $notes,
            'year_score' => $yearScore,
            'class_section_name' => $classSectionName,
        ];
    }

    public function studentDecisionDetails(int $studentId, string $schoolYear): array
    {
        if ($studentId <= 0 || trim($schoolYear) === '') {
            throw new RuntimeException('Missing student or school year.');
        }

        return [
            'semesters' => $this->fetchAllSemestersForStudent($studentId, $schoolYear),
        ];
    }

    public function decisionEmailContext(int $studentId, string $schoolYear): array
    {
        $context = $this->getBelowSixtyDecisionEmailContext($studentId, $schoolYear);
        if (empty($context['student'])) {
            throw new RuntimeException('Student not found.');
        }

        $decision = trim((string) ($context['decision_row']['decision'] ?? ''));
        if ($decision === '') {
            throw new RuntimeException('No saved decision found for this student. Save a decision first.');
        }

        $subject = 'Whole Year Academic Decision — '
            . (string) ($context['student_name'] ?? 'Student')
            . ' (' . $schoolYear . ')';

        return [
            'student_id' => $studentId,
            'student_name' => (string) ($context['student_name'] ?? 'Student'),
            'class_section_name' => (string) ($context['class_section_name'] ?? ''),
            'school_year' => $schoolYear,
            'semester' => 'year',
            'decision' => $decision,
            'subject' => $subject,
            'html' => $this->buildDecisionEmailHtml($context),
            'year_score' => $context['year_score'] ?? null,
        ];
    }

    public function sendDecisionEmail(int $studentId, string $schoolYear, ?string $subject, ?string $html): void
    {
        $context = $this->getBelowSixtyDecisionEmailContext($studentId, $schoolYear);
        if (empty($context['student'])) {
            throw new RuntimeException('Student not found.');
        }

        $decision = trim((string) ($context['decision_row']['decision'] ?? ''));
        if ($decision === '') {
            throw new RuntimeException('No saved decision found for this student.');
        }

        $resolvedSubject = trim((string) $subject);
        if ($resolvedSubject === '') {
            $resolvedSubject = 'Whole Year Academic Decision — '
                . (string) ($context['student_name'] ?? 'Student')
                . ' (' . $schoolYear . ')';
        }

        $payload = [
            'student_id' => $studentId,
            'student_name' => (string) ($context['student_name'] ?? 'Student'),
            'class_section_name' => (string) ($context['class_section_name'] ?? ''),
            'semester' => 'year',
            'school_year' => $schoolYear,
            'decision' => $decision,
            'notes' => (string) ($context['decision_row']['notes'] ?? ''),
            'subject' => $resolvedSubject,
            'scores' => [
                'fall_score' => $context['fall_score'] ?? null,
                'spring_score' => $context['spring_score'] ?? null,
                'year_score' => $context['year_score'] ?? null,
            ],
            'all_semesters' => $context['all_semesters'] ?? [],
        ];

        $resolvedHtml = trim((string) $html);
        if ($resolvedHtml !== '') {
            $payload['html'] = $resolvedHtml;
        } else {
            $payload['html'] = $this->buildDecisionEmailHtml($context);
        }

        $this->sendEmail($payload);
    }

    public function listAllDecisions(string $schoolYear): array
    {
        $saved = StudentDecision::query()
            ->where('school_year', $schoolYear)
            ->get();

        $savedMap = [];
        foreach ($saved as $row) {
            $studentId = (int) $row->student_id;
            if ($studentId > 0) {
                $savedMap[$studentId] = $row;
            }
        }

        $allScoreRows = DB::table('semester_scores as ss')
            ->select([
                's.id as student_id',
                's.school_id',
                's.firstname',
                's.lastname',
                's.gender',
                'ss.class_section_id',
                'cs.class_section_name',
                DB::raw('LOWER(TRIM(ss.semester)) as sem_key'),
                'ss.semester_score',
            ])
            ->join('students as s', 's.id', '=', 'ss.student_id')
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id')
            ->where('s.is_active', 1)
            ->where('ss.school_year', $schoolYear)
            ->whereRaw('LOWER(TRIM(ss.semester)) IN (?, ?)', ['fall', 'spring'])
            ->whereNotNull('ss.semester_score')
            ->orderBy('cs.class_section_name')
            ->orderBy('s.lastname')
            ->orderBy('s.firstname')
            ->get();

        $studentMap = [];
        foreach ($allScoreRows as $row) {
            $studentId = (int) ($row->student_id ?? 0);
            if ($studentId <= 0) {
                continue;
            }

            if (!isset($studentMap[$studentId])) {
                $studentMap[$studentId] = [
                    'school_id' => $row->school_id,
                    'firstname' => $row->firstname,
                    'lastname' => $row->lastname,
                    'gender' => $row->gender,
                    'class_section_id' => (int) ($row->class_section_id ?? 0),
                    'class_section_name' => $row->class_section_name,
                    'fall_score' => null,
                    'spring_score' => null,
                ];
            }

            $semesterKey = strtolower(trim((string) ($row->sem_key ?? '')));
            $score = is_numeric($row->semester_score ?? null) ? (float) $row->semester_score : null;
            if ($semesterKey === 'fall') {
                $studentMap[$studentId]['fall_score'] = $score;
            } elseif ($semesterKey === 'spring') {
                $studentMap[$studentId]['spring_score'] = $score;
            }
        }

        $belowRows = BelowSixtyDecision::query()
            ->where('school_year', $schoolYear)
            ->where('semester', 'year')
            ->get();

        $belowMap = [];
        foreach ($belowRows as $row) {
            $studentId = (int) ($row->student_id ?? 0);
            if ($studentId <= 0) {
                continue;
            }
            if (!isset($belowMap[$studentId]) || trim((string) ($belowMap[$studentId]->decision ?? '')) === '') {
                $belowMap[$studentId] = $row;
            }
        }

        $rows = [];
        foreach ($studentMap as $studentId => $info) {
            $fall = $info['fall_score'];
            $spring = $info['spring_score'];

            if ($fall !== null && $spring !== null) {
                $yearScore = round(($fall + $spring) / 2, 2);
            } elseif ($fall !== null) {
                $yearScore = round((float) $fall, 2);
            } elseif ($spring !== null) {
                $yearScore = round((float) $spring, 2);
            } else {
                $yearScore = null;
            }

            if (isset($savedMap[$studentId])) {
                $savedRow = $savedMap[$studentId];
                $decision = trim((string) ($savedRow->decision ?? ''));
                $source = trim((string) ($savedRow->source ?? 'pending'));
                $notes = (string) ($savedRow->notes ?? '');
                if (is_numeric($savedRow->year_score ?? null)) {
                    $yearScore = round((float) $savedRow->year_score, 2);
                }
            } elseif ($yearScore !== null && $yearScore >= 60) {
                $decision = 'Pass';
                $source = 'auto';
                $notes = '';
            } elseif ($yearScore !== null && isset($belowMap[$studentId])) {
                $decision = trim((string) ($belowMap[$studentId]->decision ?? ''));
                $source = $decision !== '' ? 'manual' : 'pending';
                $notes = (string) ($belowMap[$studentId]->notes ?? '');
            } else {
                $decision = '';
                $source = 'pending';
                $notes = '';
            }

            $rows[] = [
                'student_id' => $studentId,
                'school_id' => $info['school_id'],
                'firstname' => $info['firstname'],
                'lastname' => $info['lastname'],
                'gender' => $info['gender'] ?? '',
                'class_section_id' => (int) ($info['class_section_id'] ?? 0),
                'class_section_name' => $info['class_section_name'],
                'fall_score' => $fall,
                'spring_score' => $spring,
                'year_score' => $yearScore,
                'decision' => $decision,
                'source' => $source,
                'notes' => $notes,
                'saved' => isset($savedMap[$studentId]),
                'is_trophy' => false,
            ];
        }

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

        foreach ($rowsByClass as $classIndexes) {
            $scores = [];
            foreach ($classIndexes as $rowIndex) {
                $yearScore = $rows[$rowIndex]['year_score'] ?? null;
                if (is_numeric($yearScore)) {
                    $scores[] = (float) $yearScore;
                }
            }

            $thresholdInfo = $this->calculateTrophyThreshold($scores, 75.0);
            $threshold = $thresholdInfo['threshold'];
            if ($threshold === null) {
                continue;
            }

            foreach ($classIndexes as $rowIndex) {
                $yearScore = $rows[$rowIndex]['year_score'] ?? null;
                $rows[$rowIndex]['is_trophy'] = is_numeric($yearScore) && (float) $yearScore >= $threshold;
            }
        }

        return [
            'rows' => $rows,
            'generated' => !empty($savedMap),
            'school_year' => $schoolYear,
        ];
    }

    public function generateAllDecisions(string $schoolYear, ?int $userId): int
    {
        $allScoreRows = DB::table('semester_scores as ss')
            ->select([
                's.id as student_id',
                's.firstname',
                's.lastname',
                'cs.class_section_name',
                DB::raw('LOWER(TRIM(ss.semester)) as sem_key'),
                'ss.semester_score',
            ])
            ->join('students as s', 's.id', '=', 'ss.student_id')
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id')
            ->where('s.is_active', 1)
            ->where('ss.school_year', $schoolYear)
            ->whereRaw('LOWER(TRIM(ss.semester)) IN (?, ?)', ['fall', 'spring'])
            ->whereNotNull('ss.semester_score')
            ->get();

        if ($allScoreRows->isEmpty()) {
            throw new RuntimeException('No semester scores found for this school year.');
        }

        $studentMap = [];
        foreach ($allScoreRows as $row) {
            $studentId = (int) ($row->student_id ?? 0);
            if ($studentId <= 0) {
                continue;
            }

            if (!isset($studentMap[$studentId])) {
                $studentMap[$studentId] = [
                    'firstname' => $row->firstname,
                    'lastname' => $row->lastname,
                    'class_section_name' => $row->class_section_name,
                    'fall_score' => null,
                    'spring_score' => null,
                ];
            }

            $semesterKey = strtolower(trim((string) ($row->sem_key ?? '')));
            $score = is_numeric($row->semester_score ?? null) ? (float) $row->semester_score : null;
            if ($semesterKey === 'fall') {
                $studentMap[$studentId]['fall_score'] = $score;
            } elseif ($semesterKey === 'spring') {
                $studentMap[$studentId]['spring_score'] = $score;
            }
        }

        $belowRows = BelowSixtyDecision::query()
            ->where('school_year', $schoolYear)
            ->where('semester', 'year')
            ->get();
        $belowMap = [];
        foreach ($belowRows as $row) {
            $studentId = (int) ($row->student_id ?? 0);
            if ($studentId <= 0) {
                continue;
            }
            if (!isset($belowMap[$studentId]) || trim((string) ($belowMap[$studentId]->decision ?? '')) === '') {
                $belowMap[$studentId] = $row;
            }
        }

        $existingRows = StudentDecision::query()
            ->where('school_year', $schoolYear)
            ->get();
        $existingMap = [];
        foreach ($existingRows as $row) {
            $studentId = (int) ($row->student_id ?? 0);
            if ($studentId > 0) {
                $existingMap[$studentId] = $row;
            }
        }

        $savedCount = 0;
        foreach ($studentMap as $studentId => $info) {
            $fall = $info['fall_score'];
            $spring = $info['spring_score'];
            if ($fall !== null && $spring !== null) {
                $yearScore = round(($fall + $spring) / 2, 2);
            } elseif ($fall !== null) {
                $yearScore = round((float) $fall, 2);
            } elseif ($spring !== null) {
                $yearScore = round((float) $spring, 2);
            } else {
                continue;
            }

            if ($yearScore >= 60) {
                $decision = 'Pass';
                $source = 'auto';
                $notes = null;
            } elseif (isset($belowMap[$studentId]) && trim((string) ($belowMap[$studentId]->decision ?? '')) !== '') {
                $decision = trim((string) $belowMap[$studentId]->decision);
                $source = 'manual';
                $notes = trim((string) ($belowMap[$studentId]->notes ?? ''));
                $notes = $notes !== '' ? $notes : null;
            } else {
                $decision = null;
                $source = 'pending';
                $notes = null;
            }

            $payload = [
                'student_id' => $studentId,
                'school_year' => $schoolYear,
                'class_section_name' => $info['class_section_name'] ?? null,
                'year_score' => $yearScore,
                'decision' => $decision,
                'source' => $source,
                'notes' => $notes,
                'generated_by' => $userId,
            ];

            if (isset($existingMap[$studentId])) {
                $existingMap[$studentId]->fill($payload)->save();
            } else {
                StudentDecision::query()->create($payload);
            }

            $savedCount++;
        }

        return $savedCount;
    }

    private function fetchBelowSixtyEmailRow(int $studentId, string $schoolYear, string $semester): array
    {
        $semesterKey = strtolower(trim($semester));
        $row = DB::table('semester_scores as ss')
            ->select([
                's.id as student_id',
                's.firstname',
                's.lastname',
                'cs.class_section_name',
                'ss.homework_avg',
                'ss.project_avg',
                'ss.participation_score',
                DB::raw('COALESCE(ss.test_avg, ss.quiz_avg) as test_avg'),
                'ss.ptap_score',
                'ss.attendance_score',
                'ss.midterm_exam_score',
                'ss.semester_score',
            ])
            ->join('students as s', 's.id', '=', 'ss.student_id')
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id')
            ->where('ss.school_year', $schoolYear)
            ->where('ss.student_id', $studentId)
            ->where('s.is_active', 1)
            ->whereRaw('LOWER(TRIM(ss.semester)) = ?', [$semesterKey])
            ->first();

        if (!$row) {
            return [];
        }

        $commentRow = ScoreComment::query()
            ->select('comment')
            ->where('score_type', 'general')
            ->where('school_year', $schoolYear)
            ->whereRaw('LOWER(TRIM(semester)) = ?', [$semesterKey])
            ->where('student_id', $studentId)
            ->orderBy('created_at', 'DESC')
            ->first();

        $row = (array) $row;
        $row['comment'] = (string) ($commentRow?->comment ?? '');

        return $row;
    }

    private function fetchAllSemestersForStudent(int $studentId, string $schoolYear): array
    {
        $rows = DB::table('semester_scores as ss')
            ->select([
                'ss.semester',
                'cs.class_section_name',
                'ss.homework_avg',
                'ss.project_avg',
                'ss.participation_score',
                DB::raw('COALESCE(ss.test_avg, ss.quiz_avg) as test_avg'),
                'ss.ptap_score',
                'ss.attendance_score',
                'ss.midterm_exam_score',
                'ss.final_exam_score',
                'ss.semester_score',
            ])
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id')
            ->where('ss.student_id', $studentId)
            ->where('ss.school_year', $schoolYear)
            ->orderBy('ss.semester')
            ->get();

        $semesters = [];
        foreach ($rows as $row) {
            $semester = ucfirst(strtolower(trim((string) ($row->semester ?? ''))));
            $semesters[$semester] = [
                'semester' => $semester,
                'class_section_name' => $row->class_section_name,
                'homework_avg' => $row->homework_avg,
                'project_avg' => $row->project_avg,
                'participation_score' => $row->participation_score,
                'test_avg' => $row->test_avg,
                'ptap_score' => $row->ptap_score,
                'attendance_score' => $row->attendance_score,
                'midterm_exam_score' => $row->midterm_exam_score,
                'final_exam_score' => $row->final_exam_score,
                'semester_score' => $row->semester_score,
                'comments' => [],
            ];
        }

        if (!empty($semesters)) {
            $commentRows = DB::table('score_comments')
                ->select('semester', 'score_type', 'comment', 'created_at')
                ->where('student_id', $studentId)
                ->where('school_year', $schoolYear)
                ->whereNotNull('comment')
                ->where('comment', '!=', '')
                ->orderBy('semester')
                ->orderBy('score_type')
                ->orderByDesc('created_at')
                ->get();

            foreach ($commentRows as $row) {
                $semester = ucfirst(strtolower(trim((string) ($row->semester ?? ''))));
                $type = strtolower(trim((string) ($row->score_type ?? 'general')));
                if (isset($semesters[$semester]) && !isset($semesters[$semester]['comments'][$type])) {
                    $semesters[$semester]['comments'][$type] = (string) ($row->comment ?? '');
                }
            }
        }

        return array_values($semesters);
    }

    private function fetchBelowSixtyParentName(int $studentId): string
    {
        $parentName = 'Parent/Guardian';
        try {
            $rows = DB::select(
                "SELECT u.firstname, u.lastname
                   FROM family_students fs
                   JOIN family_guardians fg ON fg.family_id = fs.family_id
                   JOIN users u ON u.id = fg.user_id
                  WHERE fs.student_id = ?
                  ORDER BY fg.is_primary DESC, u.lastname, u.firstname
                  LIMIT 1",
                [$studentId]
            );
            if (!empty($rows[0])) {
                $candidate = trim((string) ($rows[0]->firstname ?? '') . ' ' . (string) ($rows[0]->lastname ?? ''));
                if ($candidate !== '') {
                    $parentName = $candidate;
                }
            }
        } catch (\Throwable $e) {
        }
        return $parentName;
    }

    private function getBelowSixtyDecisionEmailContext(int $studentId, string $schoolYear): array
    {
        $student = DB::table('students as s')
            ->select(['s.id', 's.firstname', 's.lastname', 's.is_active'])
            ->where('s.id', $studentId)
            ->first();

        if ($student === null) {
            return [
                'student' => null,
                'decision_row' => null,
                'student_name' => '',
                'class_section_name' => '',
                'fall_score' => null,
                'spring_score' => null,
                'year_score' => null,
                'all_semesters' => [],
            ];
        }

        $studentName = trim((string) ($student->firstname ?? '') . ' ' . (string) ($student->lastname ?? ''));
        if ($studentName === '') {
            $studentName = 'Student';
        }

        $decisionRow = BelowSixtyDecision::query()
            ->where('student_id', $studentId)
            ->where('school_year', $schoolYear)
            ->where('semester', 'year')
            ->first();

        $scoreRows = DB::table('semester_scores as ss')
            ->select([
                DB::raw('LOWER(TRIM(ss.semester)) as sem_key'),
                'ss.semester',
                'ss.semester_score',
                'ss.class_section_id',
                'cs.class_section_name',
            ])
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'ss.class_section_id')
            ->where('ss.student_id', $studentId)
            ->where('ss.school_year', $schoolYear)
            ->whereRaw('LOWER(TRIM(ss.semester)) IN (?, ?)', ['fall', 'spring'])
            ->whereNotNull('ss.semester_score')
            ->orderByDesc('ss.updated_at')
            ->orderByDesc('ss.id')
            ->get();

        $fallScore = null;
        $springScore = null;
        $classSectionName = '';
        foreach ($scoreRows as $row) {
            $semesterKey = strtolower(trim((string) ($row->sem_key ?? '')));
            $score = is_numeric($row->semester_score ?? null) ? (float) $row->semester_score : null;
            if ($classSectionName === '' && trim((string) ($row->class_section_name ?? '')) !== '') {
                $classSectionName = (string) $row->class_section_name;
            }
            if ($score === null) {
                continue;
            }
            if ($semesterKey === 'fall' && $fallScore === null) {
                $fallScore = $score;
            }
            if ($semesterKey === 'spring' && $springScore === null) {
                $springScore = $score;
            }
        }

        if ($classSectionName === '') {
            $enrollment = DB::table('student_class as sc')
                ->select('cs.class_section_name')
                ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
                ->where('sc.student_id', $studentId)
                ->where('sc.school_year', $schoolYear)
                ->orderByDesc('sc.id')
                ->first();
            $classSectionName = (string) ($enrollment?->class_section_name ?? '');
        }

        $yearScore = null;
        if ($fallScore !== null && $springScore !== null) {
            $yearScore = round(($fallScore + $springScore) / 2, 2);
        } elseif ($fallScore !== null) {
            $yearScore = round($fallScore, 2);
        } elseif ($springScore !== null) {
            $yearScore = round($springScore, 2);
        }

        return [
            'student' => (array) $student,
            'decision_row' => $decisionRow?->toArray(),
            'student_name' => $studentName,
            'class_section_name' => $classSectionName,
            'school_year' => $schoolYear,
            'fall_score' => $fallScore,
            'spring_score' => $springScore,
            'year_score' => $yearScore,
            'all_semesters' => $this->fetchAllSemestersForStudent($studentId, $schoolYear),
        ];
    }

    private function buildDecisionEmailHtml(array $context): string
    {
        $studentName = (string) ($context['student_name'] ?? 'Student');
        $schoolYear = (string) ($context['school_year'] ?? '');
        $classSectionName = (string) ($context['class_section_name'] ?? '');
        $decision = trim((string) ($context['decision_row']['decision'] ?? ''));
        $notes = trim((string) ($context['decision_row']['notes'] ?? ''));
        $fallScore = $context['fall_score'] ?? null;
        $springScore = $context['spring_score'] ?? null;
        $yearScore = $context['year_score'] ?? null;

        $fallText = $fallScore !== null ? number_format((float) $fallScore, 2) : 'N/A';
        $springText = $springScore !== null ? number_format((float) $springScore, 2) : 'N/A';
        $yearText = $yearScore !== null ? number_format((float) $yearScore, 2) : 'N/A';

        $html = '
            <p>Dear Parent/Guardian,</p>
            <p>
                This message is regarding <strong>' . e($studentName) . '</strong>
                for the <strong>' . e($schoolYear) . '</strong> school year.
            </p>
            <p>
                Class: <strong>' . e($classSectionName !== '' ? $classSectionName : 'N/A') . '</strong><br>
                Fall Score: <strong>' . e($fallText) . '</strong><br>
                Spring Score: <strong>' . e($springText) . '</strong><br>
                Whole-Year Score: <strong>' . e($yearText) . '</strong><br>
                Decision: <strong>' . e($decision) . '</strong>
            </p>';

        if ($notes !== '') {
            $html .= '
            <p>
                <strong>Decision Notes:</strong><br>
                ' . nl2br(e($notes)) . '
            </p>';
        }

        $html .= $this->buildDecisionEmailDetailsHtml($context['all_semesters'] ?? []);

        $html .= '
            <p>Please contact the school administration if you have any questions.</p>
            <p>Regards,<br>Al Rahma Sunday School</p>';

        return $html;
    }

    private function calculateTrophyThreshold(array $scores, float $percentile = 75.0): array
    {
        $scores = array_values(array_filter(
            $scores,
            static fn ($value): bool => is_numeric($value) && $value !== null
        ));
        $scores = array_map('floatval', $scores);
        sort($scores);

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

        $minWinners = 3;
        $maxWinners = max($minWinners, (int) floor($count * (1 - $percentile / 100)));

        $threshold = $this->empiricalTrophyPercentile($scores, $percentile);
        $winners = $this->countScoresAtOrAbove($scores, $threshold);

        if ($winners < $minWinners) {
            $target = min($minWinners, $count);
            $descending = array_reverse($scores);
            $threshold = $descending[$target - 1];
            $winners = $this->countScoresAtOrAbove($scores, $threshold);

            return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'min3_reduced'];
        }

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

        $result = $this->capTrophyThresholdByRank($scores, $maxWinners);
        if ($result['winners'] < $minWinners) {
            $target = min($minWinners, $count);
            $descending = array_reverse($scores);
            $threshold = $descending[$target - 1];
            $winners = $this->countScoresAtOrAbove($scores, $threshold);

            return ['threshold' => $threshold, 'winners' => $winners, 'method' => 'min3_after_cap'];
        }

        return $result;
    }

    private function capTrophyThresholdByRank(array $sortedScores, int $max): array
    {
        $descending = array_reverse($sortedScores);
        $threshold = $descending[$max - 1];
        $winners = $this->countScoresAtOrAbove($sortedScores, $threshold);

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

        $uniqueHigherScores = array_values(array_unique(array_filter(
            $sortedScores,
            static fn ($score): bool => $score > $threshold
        )));
        sort($uniqueHigherScores);

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

        return ['threshold' => $sortedScores[0], 'winners' => count($sortedScores), 'method' => 'all_equal'];
    }

    private function empiricalTrophyPercentile(array $sortedScores, float $percentile): float
    {
        $count = count($sortedScores);
        if ($count === 0) {
            return 0.0;
        }

        if ($count === 1) {
            return (float) $sortedScores[0];
        }

        $rank = ($percentile / 100) * ($count - 1);
        $lowerIndex = (int) floor($rank);
        $upperIndex = (int) ceil($rank);
        $weight = $rank - $lowerIndex;

        $lower = (float) $sortedScores[$lowerIndex];
        $upper = (float) $sortedScores[$upperIndex];

        return $lower + (($upper - $lower) * $weight);
    }

    private function countScoresAtOrAbove(array $scores, float $threshold): int
    {
        return count(array_filter(
            $scores,
            static fn ($score): bool => is_numeric($score) && (float) $score >= $threshold
        ));
    }

    private function buildDecisionEmailDetailsHtml(array $semesters): string
    {
        if (empty($semesters)) {
            return '
                <p><strong>Detailed Scores and Comments</strong></p>
                <p>No detailed score data found.</p>';
        }

        $scoreLabels = [
            'homework_avg' => 'Homework Avg',
            'project_avg' => 'Project Avg',
            'participation_score' => 'Participation',
            'test_avg' => 'Test Avg',
            'ptap_score' => 'PTAP Score',
            'attendance_score' => 'Attendance',
            'midterm_exam_score' => 'Midterm Score',
            'final_exam_score' => 'Final Exam',
            'semester_score' => 'Semester Score',
        ];

        $commentTypeLabels = [
            'general' => 'General',
            'attendance' => 'Attendance',
            'attendance_comment' => 'Attendance',
            'midterm' => 'Midterm',
            'final' => 'Final Exam',
            'ptap' => 'PTAP',
        ];

        $html = '
            <hr>
            <p><strong>Detailed Scores and Comments</strong></p>';

        foreach ($semesters as $semester) {
            $semesterName = trim((string) ($semester['semester'] ?? ''));
            if ($semesterName === '') {
                $semesterName = 'Semester';
            }

            $classSectionName = trim((string) ($semester['class_section_name'] ?? ''));
            $html .= '
                <p style="margin-top:16px;margin-bottom:6px;">
                    <strong>' . e($semesterName) . ' Semester</strong>';
            if ($classSectionName !== '') {
                $html .= ' — ' . e($classSectionName);
            }
            $html .= '
                </p>
                <table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%;margin-bottom:10px;">
                    <thead>
                        <tr>
                            <th align="left" style="background:#f2f2f2;">Item</th>
                            <th align="right" style="background:#f2f2f2;">Score</th>
                        </tr>
                    </thead>
                    <tbody>';

            $hasScoreRow = false;
            foreach ($scoreLabels as $key => $label) {
                if (!array_key_exists($key, $semester)) {
                    continue;
                }
                $value = $semester[$key];
                if ($value === null || $value === '') {
                    continue;
                }
                $scoreText = is_numeric($value) ? number_format((float) $value, 2) : (string) $value;
                $fontWeight = $key === 'semester_score' ? 'font-weight:bold;' : '';
                $html .= '
                    <tr>
                        <td>' . e($label) . '</td>
                        <td align="right" style="' . $fontWeight . '">' . e($scoreText) . '</td>
                    </tr>';
                $hasScoreRow = true;
            }

            if (!$hasScoreRow) {
                $html .= '
                    <tr>
                        <td colspan="2">No scores recorded.</td>
                    </tr>';
            }

            $html .= '
                    </tbody>
                </table>';

            $comments = is_array($semester['comments'] ?? null) ? $semester['comments'] : [];
            if (!empty($comments)) {
                $deduped = [];
                $seen = [];
                foreach ($comments as $type => $text) {
                    $text = trim((string) $text);
                    if ($text === '') {
                        continue;
                    }
                    $label = $commentTypeLabels[$type] ?? (string) $type;
                    $hash = $label . '|' . $text;
                    if (isset($seen[$hash])) {
                        continue;
                    }
                    $seen[$hash] = true;
                    $deduped[] = ['label' => $label, 'text' => $text];
                }

                if (!empty($deduped)) {
                    $html .= '<p style="margin:8px 0 4px;"><strong>Comments</strong></p>';
                    foreach ($deduped as $comment) {
                        $html .= '
                            <p style="margin:4px 0 8px;padding:8px;background:#f8f9fa;border-left:4px solid #999;">
                                <strong>' . e($comment['label']) . ':</strong><br>
                                ' . nl2br(e($comment['text'])) . '
                            </p>';
                    }
                }
            }
        }

        return $html;
    }

    private function buildBelowSixtySubject(string $studentName, string $semester, string $schoolYear): string
    {
        $subject = 'Student Performance Alert';
        if ($studentName !== '') {
            $subject .= ' — ' . $studentName;
        }
        if ($semester !== '' || $schoolYear !== '') {
            $subject .= ' (' . trim($semester . ' ' . $schoolYear) . ')';
        }
        return $subject;
    }

    private function fetchBelowSixtyMeetingContext(int $studentId, string $schoolYear, string $semester): array
    {
        $row = $this->fetchBelowSixtyEmailRow($studentId, $schoolYear, $semester);
        if (empty($row)) {
            return [];
        }

        $parentName = 'Parent/Guardian';
        $parentUserId = null;
        try {
            $pRows = DB::select(
                "SELECT u.id AS user_id, u.firstname, u.lastname
                   FROM family_students fs
                   JOIN family_guardians fg ON fg.family_id = fs.family_id
                   JOIN users u ON u.id = fg.user_id
                  WHERE fs.student_id = ?
                  ORDER BY fg.is_primary DESC, u.lastname, u.firstname
                  LIMIT 1",
                [$studentId]
            );
            if (!empty($pRows[0])) {
                $parentUserId = (int) ($pRows[0]->user_id ?? 0) ?: null;
                $parentName = trim((string) ($pRows[0]->firstname ?? '') . ' ' . (string) ($pRows[0]->lastname ?? ''));
                if ($parentName === '') {
                    $parentName = 'Parent/Guardian';
                }
            }
        } catch (\Throwable $e) {
        }

        if ($parentUserId === null) {
            try {
                $srow = DB::selectOne(
                    "SELECT s.parent_id, u.firstname, u.lastname
                       FROM students s
                       LEFT JOIN users u ON u.id = s.parent_id
                      WHERE s.id = ?
                      LIMIT 1",
                    [$studentId]
                );
                if (!empty($srow)) {
                    $parentUserId = (int) ($srow->parent_id ?? 0) ?: null;
                    $fallbackName = trim((string) ($srow->firstname ?? '') . ' ' . (string) ($srow->lastname ?? ''));
                    if ($fallbackName !== '') {
                        $parentName = $fallbackName;
                    }
                }
            } catch (\Throwable $e) {
            }
        }

        $studentName = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));

        return [
            'student_name' => $studentName !== '' ? $studentName : 'Student',
            'parent_name' => $parentName,
            'parent_user_id' => $parentUserId,
            'class_section_name' => (string) ($row['class_section_name'] ?? ''),
        ];
    }
}
