<?php

namespace App\Services\Scores;

use App\Interfaces\ScoreCalculatorInterface;
use App\Models\AttendanceRecord;
use App\Models\CalendarEvent;
use App\Models\Configuration;
use RuntimeException;

class AttendanceCalculator implements ScoreCalculatorInterface
{
    public function __construct(
        private AttendanceRecord $attendanceModel,
        private Configuration $configModel,
        private CalendarEvent $calendarModel
    ) {
    }

    public function calculate(int $studentId, string $semester, string $schoolYear, ?int $classSectionId = null): array
    {
        $absences = AttendanceRecord::getTotalAbsences($studentId, $semester, $schoolYear);

        $totalDays = null;
        $normSemester = $this->normalizeSemester($semester);
        if ($schoolYear !== '' && $normSemester !== '') {
            $semRange = $this->getSemesterRange($schoolYear, $normSemester);
            if ($semRange) {
                try {
                    $events = CalendarEvent::getEventsBySchoolYearAndSemester($schoolYear, $normSemester);
                    $noSchoolSundays = 0;
                    foreach ($events as $event) {
                        if (!empty($event['no_school'])) {
                            $date = new \DateTime($event['date']);
                            if ($date->format('N') == 7) {
                                if ($date->format('Y-m-d') >= $semRange[0] && $date->format('Y-m-d') <= $semRange[1]) {
                                    $noSchoolSundays++;
                                }
                            }
                        }
                    }
                    $totalDays = $this->countSundays($semRange[0], $semRange[1]) - $noSchoolSundays;
                } catch (\Throwable $e) {
                    $totalDays = null;
                }
            }
        }

        if ($totalDays === null) {
            $totalDays = $this->getConfiguredSemesterLength($semester);
        } else {
            $configuredDays = $this->getConfiguredSemesterLength($semester);
            if ($configuredDays !== null) {
                $totalDays = $configuredDays;
            }
        }

        if ($totalDays === null || $totalDays < 0) {
            throw new RuntimeException("Invalid attendance data: could not determine total days for student {$studentId}.");
        }

        if ($totalDays == 0) {
            return ['attendance_score' => 100.0];
        }

        $attendanceRatio = ($totalDays + 1 - $absences) / $totalDays;
        $score = min(100, max(0, $attendanceRatio * 100));

        return ['attendance_score' => round($score, 2)];
    }

    protected function normalizeSemester(?string $input): string
    {
        $s = strtolower(trim((string) $input));
        if ($s === 'fall' || str_contains($s, '1')) return 'fall';
        if ($s === 'spring' || str_contains($s, '2')) return 'spring';
        return '';
    }

    protected function getSemesterRange(string $schoolYear, string $semester): ?array
    {
        $norm = $this->normalizeSemester($semester);
        if ($norm === '' || !preg_match('/^(\d{4})-(\d{4})$/', trim($schoolYear), $m)) {
            if ($norm === '' || !preg_match('/^(\d{4})/', trim($schoolYear), $m)) {
                return null;
            }
            $y1 = (int) $m[1];
            $y2 = $y1 + 1;
        } else {
            $y1 = (int) $m[1];
            $y2 = (int) $m[2];
        }

        $fallStartCfg = (string) (Configuration::getConfig('fall_semester_start') ?? '');
        $fallEndCfg = (string) (Configuration::getConfig('fall_end_date') ?? '');
        $springStartCfg = (string) (Configuration::getConfig('spring_semester_start') ?? '');
        $springEndCfg = (string) (Configuration::getConfig('last_school_day') ?? '');

        if ($norm === 'fall') {
            $start = ($fallStartCfg !== '') ? sprintf('%04d-%s', $y1, date('m-d', strtotime($fallStartCfg))) : "{$y1}-09-01";
            $end = ($fallEndCfg !== '') ? sprintf('%04d-%s', $y2, date('m-d', strtotime($fallEndCfg))) : "{$y2}-01-15";
            return [$start, $end];
        }

        if ($norm === 'spring') {
            $start = ($springStartCfg !== '') ? sprintf('%04d-%s', $y2, date('m-d', strtotime($springStartCfg))) : "{$y2}-01-16";
            $end = ($springEndCfg !== '') ? sprintf('%04d-%s', $y2, date('m-d', strtotime($springEndCfg))) : "{$y2}-06-30";
            return [$start, $end];
        }

        return null;
    }

    private function countSundays(string $startDate, string $endDate): int
    {
        $start = new \DateTime($startDate);
        $end = new \DateTime($endDate);
        $sundays = 0;
        $interval = new \DateInterval('P1D');
        $period = new \DatePeriod($start, $interval, $end->modify('+1 day'));

        foreach ($period as $day) {
            if ($day->format('N') == 7) {
                $sundays++;
            }
        }
        return $sundays;
    }

    private function getConfiguredSemesterLength(string $semester): ?int
    {
        $norm = $this->normalizeSemester($semester);
        if ($norm === '') {
            return null;
        }

        $configKey = $norm === 'fall' ? 'total_semester1_days' : 'total_semester2_days';
        $value = Configuration::getConfig($configKey);
        if (!is_numeric($value)) {
            return null;
        }

        $length = (int) $value;
        return $length > 0 ? $length : null;
    }
}
