<?php

namespace App\Services\Scores;

use App\Models\Configuration;
use App\Models\GradingLock;
use App\Models\MissingScoreOverride;
use App\Models\ScoreComment;
use App\Models\SemesterScore;
use App\Models\Student;
use App\Models\StudentClass;
use Illuminate\Support\Facades\DB;

class ScoreCommentService
{
    public function __construct(private ScoreTermService $term)
    {
    }

    public function list(?string $classSectionId, ?string $semester, ?string $schoolYear): array
    {
        $schoolYear = $this->term->schoolYear($schoolYear);
        $activeSemester = $this->term->semesterLabel($semester);
        $isAllSections = is_string($classSectionId) && strtolower(trim($classSectionId)) === 'allsections';
        $classSectionInt = $isAllSections ? null : (int) ($classSectionId ?? 0);

        $builder = DB::table('student_class as sc')
            ->select('s.id as student_id', 's.firstname', 's.lastname', 's.school_id')
            ->join('students as s', 's.id', '=', 'sc.student_id')
            ->where('sc.school_year', $schoolYear)
            ->orderBy('s.lastname')
            ->orderBy('s.firstname')
            ->distinct();

        if (!$isAllSections && $classSectionInt) {
            $builder->where('sc.class_section_id', $classSectionInt);
        }

        $students = $builder->get()->map(fn ($row) => (array) $row)->all();

        $comments = [];
        if (!empty($students)) {
            $studentIds = array_map(static fn ($r) => (int) $r['student_id'], $students);
            $rawComments = ScoreComment::query()
                ->whereIn('student_id', $studentIds)
                ->whereIn('score_type', ['midterm', 'final', 'ptap', 'attendance'])
                ->where('semester', $activeSemester)
                ->where('school_year', $schoolYear)
                ->when(!$isAllSections && $classSectionInt, function ($q) use ($classSectionInt) {
                    $q->where(function ($w) use ($classSectionInt) {
                        $w->where('class_section_id', $classSectionInt)
                          ->orWhereNull('class_section_id');
                    });
                })
                ->get();

            foreach ($rawComments as $comment) {
                $sid = (int) $comment->student_id;
                $typ = (string) $comment->score_type;
                $comments[$sid][$typ] = [
                    'comment' => $comment->comment,
                    'comment_review' => $comment->comment_review,
                    'commented_by' => $comment->commented_by,
                    'reviewed_by' => $comment->reviewed_by,
                ];
            }

            $attendanceScores = [];
            $scoreRows = SemesterScore::query()
                ->select(['student_id', 'attendance_score'])
                ->whereIn('student_id', $studentIds)
                ->where('semester', $activeSemester)
                ->where('school_year', $schoolYear)
                ->get();

            foreach ($scoreRows as $row) {
                $attendanceScores[(int) $row->student_id] = $row->attendance_score !== null
                    ? (float) $row->attendance_score
                    : null;
            }

            foreach ($students as $student) {
                $sid = (int) $student['student_id'];
                $hasComment = isset($comments[$sid]['attendance']['comment'])
                    && trim((string) $comments[$sid]['attendance']['comment']) !== '';
                if ($hasComment) {
                    continue;
                }
                if (!function_exists('attendance_comment_from_score')) {
                    continue;
                }
                $score = $attendanceScores[$sid] ?? null;
                if ($score === null) {
                    continue;
                }
                $auto = attendance_comment_from_score($score, $student['firstname'] ?? '');
                if ($auto !== null) {
                    $comments[$sid]['attendance'] = [
                        'comment' => $auto,
                        'comment_review' => $comments[$sid]['attendance']['comment_review'] ?? null,
                        'commented_by' => $comments[$sid]['attendance']['commented_by'] ?? null,
                        'reviewed_by' => $comments[$sid]['attendance']['reviewed_by'] ?? null,
                    ];
                }
            }
        }

        return [
            'students' => $students,
            'comments' => $comments,
            'semester' => $activeSemester,
            'schoolYear' => $schoolYear,
            'classSectionId' => $isAllSections ? 'allsections' : $classSectionInt,
            'scoresLocked' => (!$isAllSections && $classSectionInt)
                ? GradingLock::isLocked($classSectionInt, $activeSemester, $schoolYear)
                : false,
        ];
    }

    public function save(int $classSectionId, string $semester, string $schoolYear, array $comments, array $missingOk, int $teacherId): array
    {
        if (GradingLock::isLocked($classSectionId, $semester, $schoolYear)) {
            throw new \RuntimeException('Scores are locked for this class.');
        }

        $studentIds = array_map('intval', array_keys($comments));
        $studentNames = $this->getStudentFirstNames($studentIds);

        $isFall = strtolower($semester) === 'fall';
        $isSpring = strtolower($semester) === 'spring';
        $commentTypes = ['ptap_comment'];
        if ($isFall) {
            $commentTypes[] = 'midterm_comment';
        }
        if ($isSpring) {
            $commentTypes[] = 'final_comment';
        }

        foreach ($commentTypes as $type) {
            $checkedItems = [];
            foreach ($missingOk as $studentId => $types) {
                if (!is_array($types) || empty($types[$type])) {
                    continue;
                }
                $checkedItems[] = [
                    'student_id' => (int) $studentId,
                    'item_index' => null,
                ];
            }
            MissingScoreOverride::replaceOverrides(
                $classSectionId,
                $semester,
                $schoolYear,
                $type,
                $studentIds,
                null,
                $checkedItems,
                $teacherId,
                true
            );
        }

        $errors = [];
        foreach ($comments as $studentId => $commentTypes) {
            foreach ($commentTypes as $scoreType => $comment) {
                $normalizedScoreType = strtolower((string) $scoreType);
                $trimmedComment = trim((string) $comment);
                if ($trimmedComment === '') {
                    continue;
                }
                $validationError = $this->validateComment($trimmedComment, (int) $studentId, $normalizedScoreType, $studentNames);
                if ($validationError !== null) {
                    $errors[] = $validationError;
                    continue;
                }

                $existing = ScoreComment::query()
                    ->where('student_id', $studentId)
                    ->where('score_type', $normalizedScoreType)
                    ->where('semester', $semester)
                    ->where('school_year', $schoolYear)
                    ->where(function ($q) use ($classSectionId) {
                        $q->where('class_section_id', $classSectionId)
                          ->orWhereNull('class_section_id');
                    })
                    ->first();

                $data = [
                    'student_id' => $studentId,
                    'class_section_id' => $classSectionId,
                    'score_type' => $normalizedScoreType,
                    'semester' => $semester,
                    'school_year' => $schoolYear,
                    'comment' => $trimmedComment,
                    'commented_by' => $teacherId,
                    'created_at' => now(),
                ];

                if ($existing) {
                    $existing->update($data);
                } else {
                    ScoreComment::query()->create($data);
                }
            }
        }

        return ['errors' => $errors];
    }

    public function update(int $classSectionId, string $semester, string $schoolYear, array $comments, array $reviews, int $teacherId): array
    {
        if (GradingLock::isLocked($classSectionId, $semester, $schoolYear)) {
            throw new \RuntimeException('Scores are locked for this class.');
        }

        $studentIds = array_unique(array_map('intval', array_merge(array_keys($comments), array_keys($reviews))));
        $studentNames = $this->getStudentFirstNames($studentIds);
        $isReviewer = $this->isReviewer();

        $errors = [];
        foreach ($comments as $studentId => $types) {
            foreach ($types as $scoreType => $commentText) {
                $normalizedScoreType = strtolower((string) $scoreType);
                $commentText = trim((string) $commentText);

                $reviewText = isset($reviews[$studentId][$scoreType])
                    ? trim((string) $reviews[$studentId][$scoreType])
                    : '';

                if ($commentText === '' && $reviewText === '') {
                    continue;
                }

                if ($commentText !== '') {
                    $validationError = $this->validateComment($commentText, (int) $studentId, $normalizedScoreType, $studentNames);
                    if ($validationError !== null) {
                        $errors[] = $validationError;
                        continue;
                    }
                }

                $existing = ScoreComment::query()
                    ->where('student_id', $studentId)
                    ->where('score_type', $normalizedScoreType)
                    ->where('semester', $semester)
                    ->where('school_year', $schoolYear)
                    ->where(function ($q) use ($classSectionId) {
                        $q->where('class_section_id', $classSectionId)
                          ->orWhereNull('class_section_id');
                    })
                    ->first();

                $data = [
                    'comment' => $commentText === '' ? null : $commentText,
                    'commented_by' => $teacherId,
                    'class_section_id' => $classSectionId,
                ];

                if ($isReviewer && $reviewText !== '') {
                    $data['comment_review'] = $reviewText;
                    $data['reviewed_by'] = $teacherId;
                }

                if ($existing) {
                    $existing->update($data);
                } else {
                    ScoreComment::query()->create(array_merge([
                        'student_id' => $studentId,
                        'score_type' => $normalizedScoreType,
                        'semester' => $semester,
                        'school_year' => $schoolYear,
                        'class_section_id' => $classSectionId,
                    ], $data));
                }
            }
        }

        return ['errors' => $errors];
    }

    private function getStudentFirstNames(array $studentIds): array
    {
        $ids = array_values(array_filter(array_unique(array_map('intval', $studentIds)), static fn ($id) => $id > 0));
        if (empty($ids)) {
            return [];
        }

        $rows = Student::query()
            ->select(['id', 'firstname'])
            ->whereIn('id', $ids)
            ->get();

        $names = [];
        foreach ($rows as $row) {
            $names[(int) $row->id] = trim((string) $row->firstname);
        }

        return $names;
    }

    private function validateComment(string $comment, int $studentId, string $scoreType, array $studentNames): ?string
    {
        $trimmed = trim($comment);
        if ($trimmed === '') {
            return null;
        }

        $firstName = $studentNames[$studentId] ?? '';
        if ($firstName === '') {
            return "Missing student first name for student ID {$studentId}.";
        }

        $length = mb_strlen($trimmed, 'UTF-8');
        if ($length < 100) {
            return "{$firstName}'s {$scoreType} comment must be at least 100 characters.";
        }
        if ($length > 400) {
            return "{$firstName}'s {$scoreType} comment must be at most 400 characters.";
        }

        $pattern = '/^' . preg_quote($firstName, '/') . '\\b/i';
        if (!preg_match($pattern, $trimmed)) {
            return "{$firstName}'s {$scoreType} comment must start with the student's first name.";
        }

        return null;
    }

    private function isReviewer(): bool
    {
        $currentUserRole = session()->get('role');
        $reviewerRoles = (string) (Configuration::getConfig('comment_reviewer') ?? '');
        if ($reviewerRoles === '') {
            return false;
        }

        $allowedRoles = array_map('trim', explode(',', $reviewerRoles));
        return in_array($currentUserRole, $allowedRoles, true);
    }
}
