<?php

namespace App\Services\Grading;

use App\Models\AttendanceRecord;
use App\Models\CalendarEvent;
use App\Models\Configuration;
use App\Models\ClassSection;
use App\Models\GradingLock;
use App\Models\Student;
use App\Services\Scores\AttendanceCalculator;
use App\Services\Scores\SemesterScoreService;
use Illuminate\Support\Facades\DB;

class GradingOverviewService
{
    private AttendanceCalculator $attendanceCalculator;

    public function __construct(private SemesterScoreService $semesterScoreService)
    {
        $this->attendanceCalculator = new AttendanceCalculator(
            new AttendanceRecord(),
            new Configuration(),
            new CalendarEvent()
        );
    }

    public function overview(?int $classId, ?string $semester, ?string $schoolYear): array
    {
        $schoolYear = $schoolYear ?: (string) (Configuration::getConfig('school_year') ?? '');
        $configuredSemester = (string) (Configuration::getConfig('semester') ?? '');
        $semesterOptions = $this->getSemestersForSchoolYear($schoolYear, $configuredSemester);
        $semester = $this->resolveSemesterSelection($semester, $semesterOptions, $configuredSemester);

        $this->ensureParentReleaseKeyExists('Fall');
        $this->ensureParentReleaseKeyExists('Spring');
        $scoresReleased = $this->getParentScoresReleasedForSemester($semester);
        $scoresReleasedFall = $this->getParentScoresReleasedForSemester('Fall');
        $scoresReleasedSpring = $this->getParentScoresReleasedForSemester('Spring');

        if ($classId && $this->semesterScoreService) {
            $sectionIds = ClassSection::query()
                ->select('class_section_id')
                ->where('class_id', $classId)
                ->pluck('class_section_id')
                ->map(fn ($v) => (int) $v)
                ->all();
            foreach ($sectionIds as $sectionId) {
                $studentTeacherInfo = Student::getStudentInfoByClassSectionId(
                    $sectionId,
                    $semester,
                    $schoolYear
                );
                if (empty($studentTeacherInfo)) {
                    continue;
                }
                try {
                    $this->semesterScoreService->updateScoresForStudents($studentTeacherInfo, $semester, $schoolYear);
                } catch (\Throwable $e) {
                }
            }
        }

        $rows = $this->buildGradingRows($semester, $schoolYear);

        $counts = $this->loadScoreCounts($rows, $semester, $schoolYear);

        $sectionsNeedingRefresh = [];
        foreach ($rows as $row) {
            $sectionId = (int) ($row['section_id'] ?? 0);
            if ($sectionId <= 0) {
                continue;
            }
            if ($row['ss_ptap_score'] === null || $row['ss_semester_score'] === null) {
                $sectionsNeedingRefresh[$sectionId] = true;
            }
        }

        if (!empty($sectionsNeedingRefresh)) {
            foreach (array_keys($sectionsNeedingRefresh) as $sectionId) {
                $studentTeacherInfo = Student::getStudentInfoByClassSectionId(
                    $sectionId,
                    $semester,
                    $schoolYear
                );
                if (empty($studentTeacherInfo)) {
                    continue;
                }
                try {
                    $this->semesterScoreService->updateScoresForStudents($studentTeacherInfo, $semester, $schoolYear);
                } catch (\Throwable $e) {
                }
            }
            $rows = $this->buildGradingRows($semester, $schoolYear);
        }

        $grades = [];
        $studentsBySection = [];
        foreach ($rows as $row) {
            $sectionId = (int) ($row['section_id'] ?? 0);
            $classIdValue = (int) ($row['class_id'] ?? 0);
            $sectionName = (string) ($row['class_section_name'] ?? '');
            if ($sectionId <= 0 || $classIdValue <= 0) {
                continue;
            }

            if (!isset($grades[$classIdValue])) {
                $grades[$classIdValue] = [];
            }
            $exists = false;
            foreach ($grades[$classIdValue] as $section) {
                if ((int) $section['class_section_id'] === $sectionId) {
                    $exists = true;
                    break;
                }
            }
            if (!$exists) {
                $grades[$classIdValue][] = [
                    'class_section_id' => $sectionId,
                    'class_section_name' => $sectionName,
                ];
            }

            $studentId = (int) ($row['student_id'] ?? 0);
            if ($studentId <= 0) {
                continue;
            }

            $attendanceScore = $this->calculateAttendanceScoreForStudent(
                $studentId,
                $semester,
                $schoolYear,
                $sectionId
            );
            if ($attendanceScore === null) {
                $rawAttendance = $row['ss_attendance_score'] ?? null;
                if ($rawAttendance !== null && $rawAttendance !== '') {
                    $attendanceScore = round((float) $rawAttendance, 2);
                }
            }

            $studentRow = [
                'id' => $studentId,
                'school_id' => $row['school_id'] ?? null,
                'firstname' => $row['firstname'] ?? null,
                'lastname' => $row['lastname'] ?? null,
                'class_id' => $classIdValue,
                'ptap' => $this->normalizeScore($row['ss_ptap_score'] ?? null),
                'semester_score' => $this->normalizeScore($row['ss_semester_score'] ?? null),
                'attendance' => $attendanceScore,
                'homework_avg' => $this->normalizeCountedScore($row['ss_homework_avg'] ?? null, $counts['homework'] ?? [], $sectionId, $studentId),
                'project_avg' => $this->normalizeCountedScore($row['ss_project_avg'] ?? null, $counts['project'] ?? [], $sectionId, $studentId),
                'quiz_avg' => $this->normalizeCountedScore($row['ss_quiz_avg'] ?? null, $counts['quiz'] ?? [], $sectionId, $studentId),
                'participation' => $this->normalizeCountedScore($row['ss_participation_score'] ?? null, $counts['participation'] ?? [], $sectionId, $studentId),
                'midterm_exam' => $this->normalizeCountedScore($row['ss_midterm_exam_score'] ?? null, $counts['midterm'] ?? [], $sectionId, $studentId),
                'final_exam' => $this->normalizeScore($row['ss_final_exam_score'] ?? null),
                'matched_biz_csid' => $row['matched_biz_csid'] ?? null,
                'matched_pk_csid' => $row['matched_pk_csid'] ?? null,
                'placement_level' => $row['placement_level'] ?? null,
            ];

            $studentsBySection[$sectionId][] = $studentRow;
        }

        $scoreLocks = $this->loadScoreLocks($grades, $semester, $schoolYear);

        return [
            'grades' => $grades,
            'students_by_section' => $studentsBySection,
            'semester' => $semester,
            'school_year' => $schoolYear,
            'requested_class_id' => $classId,
            'semester_options' => $semesterOptions,
            'scores_released' => $scoresReleased,
            'scores_released_fall' => $scoresReleasedFall,
            'scores_released_spring' => $scoresReleasedSpring,
            'score_locks' => $scoreLocks,
        ];
    }

    private function buildGradingRows(string $semester, string $schoolYear): array
    {
        $semLower = strtolower(trim($semester));

        return DB::table('student_class as sc')
            ->select([
                'cs.id as section_pk',
                'cs.class_section_id as section_id',
                'cs.class_id as class_id',
                'cs.class_section_name',
                's.id as student_id',
                's.school_id',
                's.firstname',
                's.lastname',
                'pl.level as placement_level',
                DB::raw('COALESCE(ss_b.ptap_score, ss_p.ptap_score) as ss_ptap_score'),
                DB::raw('COALESCE(ss_b.semester_score, ss_p.semester_score) as ss_semester_score'),
                DB::raw('COALESCE(ss_b.attendance_score, ss_p.attendance_score) as ss_attendance_score'),
                DB::raw('COALESCE(ss_b.homework_avg, ss_p.homework_avg) as ss_homework_avg'),
                DB::raw('COALESCE(ss_b.project_avg, ss_p.project_avg) as ss_project_avg'),
                DB::raw('COALESCE(ss_b.quiz_avg, ss_p.quiz_avg) as ss_quiz_avg'),
                DB::raw('COALESCE(ss_b.participation_score, ss_p.participation_score) as ss_participation_score'),
                DB::raw('COALESCE(ss_b.midterm_exam_score, ss_p.midterm_exam_score) as ss_midterm_exam_score'),
                DB::raw('COALESCE(ss_b.final_exam_score, ss_p.final_exam_score) as ss_final_exam_score'),
                'ss_b.class_section_id as matched_biz_csid',
                'ss_p.class_section_id as matched_pk_csid',
            ])
            ->leftJoin('classSection as cs', 'cs.class_section_id', '=', 'sc.class_section_id')
            ->join('students as s', 's.id', '=', 'sc.student_id')
            ->leftJoin('placement_levels as pl', function ($join) use ($schoolYear) {
                $join->on('pl.student_id', '=', 's.id')
                    ->where('pl.school_year', '=', $schoolYear);
            })
            ->leftJoin('semester_scores as ss_b', function ($join) use ($semLower, $schoolYear) {
                $join->on('ss_b.student_id', '=', 's.id')
                    ->on('ss_b.class_section_id', '=', 'sc.class_section_id')
                    ->whereRaw('LOWER(TRIM(ss_b.semester)) = ?', [$semLower])
                    ->where('ss_b.school_year', '=', $schoolYear);
            })
            ->leftJoin('semester_scores as ss_p', function ($join) use ($semLower, $schoolYear) {
                $join->on('ss_p.student_id', '=', 's.id')
                    ->on('ss_p.class_section_id', '=', 'cs.id')
                    ->whereRaw('LOWER(TRIM(ss_p.semester)) = ?', [$semLower])
                    ->where('ss_p.school_year', '=', $schoolYear);
            })
            ->where('sc.school_year', $schoolYear)
            ->where('s.is_active', 1)
            ->orderBy('cs.class_id', 'ASC')
            ->orderBy('cs.class_section_name', 'ASC')
            ->orderBy('s.lastname', 'ASC')
            ->orderBy('s.firstname', 'ASC')
            ->get()
            ->map(fn ($row) => (array) $row)
            ->all();
    }

    private function loadScoreCounts(array $rows, string $semester, string $schoolYear): array
    {
        $sectionIds = [];
        $studentIds = [];
        foreach ($rows as $row) {
            $studentId = (int) ($row['student_id'] ?? 0);
            $sectionId = (int) ($row['section_id'] ?? 0);
            if ($studentId > 0) {
                $studentIds[$studentId] = true;
            }
            if ($sectionId > 0) {
                $sectionIds[$sectionId] = true;
            }
        }

        $sectionIds = array_keys($sectionIds);
        $studentIds = array_keys($studentIds);
        if (empty($sectionIds) || empty($studentIds)) {
            return [
                'quiz' => [],
                'homework' => [],
                'project' => [],
                'participation' => [],
                'midterm' => [],
            ];
        }

        return [
            'quiz' => $this->countScores('quiz', $sectionIds, $studentIds, $semester, $schoolYear),
            'homework' => $this->countScores('homework', $sectionIds, $studentIds, $semester, $schoolYear),
            'project' => $this->countScores('project', $sectionIds, $studentIds, $semester, $schoolYear),
            'participation' => $this->countScores('participation', $sectionIds, $studentIds, $semester, $schoolYear),
            'midterm' => $this->countScores('midterm_exam', $sectionIds, $studentIds, $semester, $schoolYear),
        ];
    }

    private function countScores(string $table, array $sectionIds, array $studentIds, string $semester, string $schoolYear): array
    {
        $rows = DB::table($table)
            ->select('student_id', 'class_section_id', DB::raw('COUNT(*) as cnt'))
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->whereIn('class_section_id', $sectionIds)
            ->whereIn('student_id', $studentIds)
            ->whereNotNull('score')
            ->groupBy('student_id', 'class_section_id')
            ->get();

        $counts = [];
        foreach ($rows as $row) {
            $counts[(int) $row->class_section_id][(int) $row->student_id] = (int) $row->cnt;
        }

        return $counts;
    }

    private function loadScoreLocks(array $grades, string $semester, string $schoolYear): array
    {
        $sectionIds = [];
        foreach ($grades as $sections) {
            foreach ($sections as $section) {
                $sid = (int) ($section['class_section_id'] ?? 0);
                if ($sid > 0) {
                    $sectionIds[$sid] = true;
                }
            }
        }
        $sectionIds = array_keys($sectionIds);
        if (empty($sectionIds)) {
            return [];
        }

        $locks = GradingLock::query()
            ->whereIn('class_section_id', $sectionIds)
            ->where('semester', $semester)
            ->where('school_year', $schoolYear)
            ->get();

        $map = [];
        foreach ($locks as $lock) {
            $map[(int) $lock->class_section_id] = (bool) $lock->is_locked;
        }

        return $map;
    }

    private function calculateAttendanceScoreForStudent(int $studentId, string $semester, string $schoolYear, ?int $classSectionId = null): ?float
    {
        try {
            $result = $this->attendanceCalculator->calculate($studentId, $semester, $schoolYear, $classSectionId);
            $score = $result['attendance_score'] ?? null;
            if ($score === null || $score === '') {
                return null;
            }
            return round((float) $score, 2);
        } catch (\Throwable $e) {
        }

        return null;
    }

    private function normalizeScore($value): ?float
    {
        if ($value === null || $value === '') {
            return null;
        }
        return round((float) $value, 2);
    }

    private function normalizeCountedScore($value, array $countMap, int $sectionId, int $studentId): ?float
    {
        $score = $this->normalizeScore($value);
        if ($score !== null && (float) $score === 0.0) {
            $count = (int) ($countMap[$sectionId][$studentId] ?? 0);
            if ($count === 0) {
                return null;
            }
        }
        return $score;
    }

    private function resolveSemesterSelection(?string $requestedSemester, array $semesterOptions, ?string $fallbackSemester): string
    {
        if ($requestedSemester !== null && $requestedSemester !== '') {
            foreach ($semesterOptions as $option) {
                if (strcasecmp($option, $requestedSemester) === 0) {
                    return $option;
                }
            }
            return $requestedSemester;
        }
        if ($fallbackSemester !== null && $fallbackSemester !== '') {
            foreach ($semesterOptions as $option) {
                if (strcasecmp($option, $fallbackSemester) === 0) {
                    return $option;
                }
            }
            return $fallbackSemester;
        }
        return $semesterOptions[0] ?? '';
    }

    private function getSemestersForSchoolYear(string $schoolYear, ?string $fallbackSemester = null): array
    {
        $rows = DB::table('semester_scores')
            ->select(DB::raw('DISTINCT semester'))
            ->whereNotNull('semester')
            ->where('semester', '!=', '')
            ->where('school_year', $schoolYear)
            ->orderBy('semester', 'ASC')
            ->get();

        $semesters = [];
        foreach ($rows as $row) {
            $value = trim((string) $row->semester);
            if ($value !== '') {
                $semesters[] = $value;
            }
        }

        if (empty($semesters)) {
            $semesters = ['Fall', 'Spring'];
        }

        if ($fallbackSemester !== null && $fallbackSemester !== '' && !in_array($fallbackSemester, $semesters, true)) {
            array_unshift($semesters, $fallbackSemester);
        }

        return array_values(array_unique($semesters));
    }

    private function getParentReleaseKey(string $semester): ?string
    {
        $norm = strtolower(trim($semester));
        if ($norm === 'fall') {
            return 'parent_scores_released_fall';
        }
        if ($norm === 'spring') {
            return 'parent_scores_released_spring';
        }
        return null;
    }

    private function getParentScoresReleasedForSemester(string $semester): bool
    {
        $key = $this->getParentReleaseKey($semester);
        $raw = $key ? Configuration::getConfig($key) : null;
        $raw = (string) ($raw ?? '');
        return in_array(strtolower(trim($raw)), ['1', 'true', 'yes', 'y', 'on'], true);
    }

    private function ensureParentReleaseKeyExists(string $semester): void
    {
        $key = $this->getParentReleaseKey($semester);
        if (!$key) {
            return;
        }

        if (Configuration::getConfigValueByKey($key) === null) {
            Configuration::setConfigValueByKey($key, '0');
        }
    }
}
