<?php

namespace App\Services\Scores;

use App\Interfaces\ScoreCalculatorInterface;
use App\Models\Configuration;
use App\Models\FinalExam;
use App\Models\MidtermExam;
use App\Models\Participation;
use App\Models\SemesterScore;
use App\Services\Scores\AttendanceCalculator;
use App\Services\Scores\HomeworkCalculator;
use App\Services\Scores\ProjectCalculator;
use App\Services\Scores\QuizCalculator;
use App\Models\AttendanceRecord;
use App\Models\CalendarEvent;
use App\Models\Homework;
use App\Models\Project;
use RuntimeException;

class SemesterScoreService
{
    /** @var array<string, ScoreCalculatorInterface> */
    private array $scoreCalculators = [];

    private string $semester;
    private string $schoolYear;
    private ?int $actorId;

    public function __construct(
        ?AttendanceCalculator $attendanceCalculator = null,
        ?HomeworkCalculator $homeworkCalculator = null,
        ?QuizCalculator $quizCalculator = null,
        ?ProjectCalculator $projectCalculator = null
    ) {
        $this->semester = (string) (Configuration::getConfig('semester') ?? '');
        $this->schoolYear = (string) (Configuration::getConfig('school_year') ?? '');
        $this->actorId = (int) (auth()->id() ?? 0) ?: null;

        $attendanceCalculator = $attendanceCalculator ?? new AttendanceCalculator(
            new AttendanceRecord(),
            new Configuration(),
            new CalendarEvent()
        );
        $homeworkCalculator = $homeworkCalculator ?? new HomeworkCalculator(new Homework());
        $quizCalculator = $quizCalculator ?? new QuizCalculator();
        $projectCalculator = $projectCalculator ?? new ProjectCalculator(new Project());

        $this->scoreCalculators = [
            AttendanceCalculator::class => $attendanceCalculator,
            HomeworkCalculator::class => $homeworkCalculator,
            QuizCalculator::class => $quizCalculator,
            ProjectCalculator::class => $projectCalculator,
        ];
    }

    public function updateScoresForStudents(array $students, ?string $semester = null, ?string $schoolYear = null): void
    {
        $semester = $semester ?? $this->semester;
        $schoolYear = $schoolYear ?? $this->schoolYear;

        foreach ($students as $student) {
            $this->updateStudentScores($student, $semester, $schoolYear);
        }
    }

    public function updateStudentScores(array $student, string $semester, string $schoolYear): void
    {
        foreach (['student_id', 'school_id', 'class_section_id'] as $field) {
            if (!isset($student[$field])) {
                throw new RuntimeException("Missing required student field: {$field}");
            }
        }

        $updatedBy = isset($student['updated_by'])
            ? (int) $student['updated_by']
            : ($this->actorId ?? null);

        $scoreData = $this->calculateAllScores(
            (int) $student['student_id'],
            $semester,
            $schoolYear,
            (int) ($student['class_section_id'] ?? 0) ?: null
        );

        $this->saveStudentScores(
            (int) $student['student_id'],
            (string) $student['school_id'],
            (int) $student['class_section_id'],
            $updatedBy,
            $semester,
            $schoolYear,
            $scoreData
        );
    }

    private function calculateAllScores(int $studentId, string $semester, string $schoolYear, ?int $classSectionId = null): array
    {
        $scoreData = [
            'updated_at' => now(),
        ];

        foreach ($this->scoreCalculators as $calculator) {
            try {
                $scoreData = array_merge(
                    $scoreData,
                    $calculator->calculate($studentId, $semester, $schoolYear, $classSectionId)
                );
            } catch (\Throwable $e) {
                // ignore calculator errors to keep scoring pipeline running
            }
        }

        if (!isset($scoreData['participation_score'])) {
            $participationScore = Participation::getParticipationScore($studentId, $semester, $schoolYear, $classSectionId);
            $scoreData['participation_score'] = $participationScore !== null ? (float) $participationScore : null;
        }

        $scoreData['ptap_score'] = $this->calculatePTAPScore($scoreData);
        $midfinal = $this->calculateSemesterScore($scoreData, $semester, $studentId, $schoolYear, $classSectionId);

        return array_merge($scoreData, [
            'semester_score' => $midfinal['semester_score'],
            'midterm_exam_score' => $midfinal['midterm_score'],
            'final_exam_score' => $midfinal['final_score'],
            'participation_score' => $scoreData['participation_score'],
        ]);
    }

    private function calculatePTAPScore(array $scores): ?float
    {
        $avgHomework = $scores['homework_avg'] ?? null;
        $avgQuiz = $scores['quiz_avg'] ?? null;
        $avgProject = $scores['project_avg'] ?? null;
        $avgParticipation = $scores['participation_score'] ?? null;

        $hasTests = $avgQuiz !== null;
        $hasProjects = $avgProject !== null;

        if (!$hasTests && !$hasProjects) {
            if ($avgHomework === null || $avgParticipation === null) {
                return null;
            }
            return round(($avgHomework * 0.5) + ($avgParticipation * 0.5), 1);
        }

        if (!$hasProjects) {
            if ($avgHomework === null || $avgParticipation === null || $avgQuiz === null) {
                return null;
            }
            return round(($avgHomework * 0.4) + ($avgParticipation * 0.4) + ($avgQuiz * 0.2), 1);
        }

        if (!$hasTests) {
            if ($avgHomework === null || $avgParticipation === null || $avgProject === null) {
                return null;
            }
            return round(($avgHomework * 0.34) + ($avgParticipation * 0.33) + ($avgProject * 0.33), 1);
        }

        if ($avgHomework === null || $avgParticipation === null || $avgProject === null || $avgQuiz === null) {
            return null;
        }

        return round(
            ($avgHomework * 0.3) + ($avgParticipation * 0.3) + ($avgProject * 0.3) + ($avgQuiz * 0.1),
            1
        );
    }

    private function calculateSemesterScore(array $scores, string $semester, int $studentId, string $schoolYear, ?int $classSectionId): array
    {
        $ptap = $scores['ptap_score'] ?? null;
        $attendance = $scores['attendance_score'] ?? null;

        $out = [
            'midterm_score' => null,
            'final_score' => null,
            'semester_score' => null,
        ];

        if (strcasecmp($semester, 'Fall') === 0) {
            $mid = $scores['midterm_exam_score'] ?? MidtermExam::getMidtermExamScore($studentId, $semester, $schoolYear, $classSectionId);
            $out['midterm_score'] = $mid !== null ? round((float) $mid, 1) : null;
            if ($ptap !== null && $attendance !== null && $out['midterm_score'] !== null) {
                $out['semester_score'] = round(($attendance * 0.2) + ($ptap * 0.2) + ($out['midterm_score'] * 0.6), 1);
            }
            return $out;
        }

        if (strcasecmp($semester, 'Spring') === 0) {
            $fin = $scores['final_exam_score'] ?? FinalExam::getFinalExamScore($studentId, $semester, $schoolYear, $classSectionId);
            $out['final_score'] = $fin !== null ? round((float) $fin, 1) : null;
            if ($ptap !== null && $attendance !== null && $out['final_score'] !== null) {
                $out['semester_score'] = round(($attendance * 0.2) + ($ptap * 0.2) + ($out['final_score'] * 0.6), 1);
            }
            return $out;
        }

        return $out;
    }

    private function saveStudentScores(
        int $studentId,
        string $schoolId,
        int $classSectionId,
        ?int $updatedBy,
        string $semester,
        string $schoolYear,
        array $scoreData
    ): void {
        $payload = array_merge([
            'student_id' => $studentId,
            'school_id' => $schoolId,
            'class_section_id' => $classSectionId,
            'semester' => $semester,
            'school_year' => $schoolYear,
            'updated_at' => now(),
        ], $scoreData);

        if ($updatedBy !== null) {
            $payload['updated_by'] = $updatedBy;
        }

        SemesterScore::upsertScore($payload);
    }
}
