<?php

namespace App\Services\Admin\Manage;

use App\Enums\RegistrationStatus;
use App\Enums\StudentStatus;
use App\Models\AcademicTerm;
use App\Models\ClassEnrollment;
use App\Models\Course;
use App\Models\CourseClass;
use App\Models\CourseRegistration;
use App\Models\CourseRegistrationSubmission;
use App\Models\Department;
use App\Models\Student;
use App\Services\Admin\Master\AcademicTermService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class CourseRegistrationService
{
    public function __construct(
        private readonly AcademicTermService $academicTermService,
    ) {}

    /**
     * @return Collection<int, Department>
     */
    public function departmentSummary(): Collection
    {
        return Department::query()
            ->select('id', 'name')
            ->orderBy('name')
            ->get();
    }

    /**
     * @param  array<int, int>|null  $ledDepartmentIds  Restricts results to a kaprodi's own department(s).
     */
    public function paginated(int $perPage = 25, string $search = '', ?int $departmentId = null, ?int $advisorLecturerId = null, ?array $ledDepartmentIds = null): LengthAwarePaginator
    {
        $paginator = Student::query()
            ->select(['students.id', 'students.user_id', 'students.student_number', 'students.department_id'])
            ->join('departments', 'departments.id', '=', 'students.department_id')
            ->join('users', 'users.id', '=', 'students.user_id')
            ->join('user_profiles', 'user_profiles.user_id', '=', 'users.id')
            ->where('students.status', StudentStatus::Active)
            ->when($ledDepartmentIds !== null, fn ($q) => $q->whereIn('students.department_id', $ledDepartmentIds))
            ->when($departmentId, fn ($q) => $q->where('students.department_id', $departmentId))
            ->when($advisorLecturerId, fn ($q) => $q->where('students.academic_advisor_id', $advisorLecturerId))
            ->when($search, fn ($q) => $q->where('students.student_number', 'like', "%{$search}%")
                ->orWhere('user_profiles.full_name', 'like', "%{$search}%"))
            ->with(['user.profile', 'department:id,name'])
            ->orderBy('departments.name')
            ->orderBy('user_profiles.full_name')
            ->paginate($perPage);

        return $paginator->through(fn (Student $student) => [
            'student_id' => $student->id,
            'student' => $student,
        ]);
    }

    public function findOrCreateForStudent(Student $student, AcademicTerm $academicTerm, ?int $semesterNumber): CourseRegistrationSubmission
    {
        return CourseRegistrationSubmission::firstOrCreate(
            [
                'student_id' => $student->id,
                'academic_term_id' => $academicTerm->id,
                'semester_number' => $semesterNumber,
            ],
            [
                'status' => RegistrationStatus::Submitted,
                'signed_at' => now(),
            ],
        );
    }

    public function findOrCreateForSemester(Student $student, string $semester): CourseRegistrationSubmission
    {
        $semesterNumber = CourseRegistrationSubmission::parseSemesterKey($semester);
        $activeTerm = $this->academicTermService->getActive();

        abort_if($activeTerm === null, 422);

        return $this->findOrCreateForStudent($student, $activeTerm, $semesterNumber);
    }

    /**
     * @return array<string, mixed>
     */
    public function buildSubmissionPayload(Student $student, AcademicTerm $activeTerm, ?int $semesterNumber, Collection $departmentCourses, Collection $allCourseClasses): array
    {
        $submission = $this->findOrCreateForStudent($student, $activeTerm, $semesterNumber);

        $data = $this->withDetails($submission);
        $payload = $data->toArray();

        $registeredCourseClassIds = $data->courseRegistrations->pluck('course_class_id')->all();
        $courseClassesByCourseId = $allCourseClasses->keyBy('course_id');

        $payload['has_registrations'] = ! empty($registeredCourseClassIds);
        $payload['course_registrations'] = $departmentCourses
            ->filter(fn (Course $course) => $course->semester_number === $semesterNumber)
            ->map(function (Course $course) use ($courseClassesByCourseId, $registeredCourseClassIds) {
                $courseClass = $courseClassesByCourseId->get($course->id);

                return [
                    'id' => $courseClass?->id ?? -$course->id,
                    'course' => $course,
                    'course_class' => $courseClass ? ['id' => $courseClass->id] : null,
                    'is_registered' => $courseClass !== null && in_array($courseClass->id, $registeredCourseClassIds, true),
                ];
            })
            ->values();

        return $payload;
    }

    /**
     * The department's full course curriculum, independent of which classes
     * have been opened for the current academic term.
     *
     * @return Collection<int, Course>
     */
    public function departmentCourses(int $departmentId): Collection
    {
        return Course::query()
            ->where('department_id', $departmentId)
            ->orderBy('semester_number')
            ->orderBy('name')
            ->get(['id', 'code', 'name', 'credits', 'semester_number']);
    }

    public function withDetails(CourseRegistrationSubmission $submission): CourseRegistrationSubmission
    {
        return $submission->load([
            'student.user.profile',
            'student.department.currentLeader.lecturer.user.profile',
            'student.academicAdvisor.user.profile',
            'academicTerm:id,academic_year,semester,start_date,end_date',
            'reviewer.profile',
            'courseRegistrations.courseClass.course',
            'logs' => fn ($q) => $q->oldest(),
            'logs.actor.profile',
        ]);
    }

    /**
     * @return Collection<int, CourseClass>
     */
    public function availableCourseClasses(int $departmentId, int $academicTermId): Collection
    {
        return CourseClass::query()
            ->where('academic_term_id', $academicTermId)
            ->whereHas('course', fn ($q) => $q->where('department_id', $departmentId))
            ->with('course:id,code,name,credits,semester_number')
            ->get();
    }

    /**
     * @param  array<int, int>  $openSemesters
     * @return array<int, int>
     */
    public function selectableCourseClassIds(int $departmentId, int $academicTermId, ?int $semesterNumber, array $openSemesters, ?int $studentCurrentSemester): array
    {
        if ($semesterNumber === null || $semesterNumber !== $studentCurrentSemester || ! in_array($semesterNumber, $openSemesters, true)) {
            return [];
        }

        return CourseClass::query()
            ->where('academic_term_id', $academicTermId)
            ->whereHas('course', fn ($q) => $q->where('department_id', $departmentId)->where('semester_number', $semesterNumber))
            ->pluck('id')
            ->all();
    }

    /**
     * @param  array<int, int>  $courseClassIds
     */
    public function saveRegistrations(
        CourseRegistrationSubmission $submission,
        array $courseClassIds,
        int $academicTermId,
        int $studentId,
        ?UploadedFile $signature,
    ): CourseRegistrationSubmission {
        abort_if($submission->status === RegistrationStatus::Approved, 403);

        return DB::transaction(function () use ($submission, $courseClassIds, $academicTermId, $studentId, $signature) {
            $submission->courseRegistrations()->delete();

            foreach ($courseClassIds as $courseClassId) {
                CourseRegistration::create([
                    'student_id' => $studentId,
                    'academic_term_id' => $academicTermId,
                    'course_class_id' => $courseClassId,
                    'submission_id' => $submission->id,
                ]);
            }

            $submission->update([
                'status' => RegistrationStatus::Submitted,
                'rejection_reason' => null,
                'signed_at' => now(),
            ]);

            if ($signature) {
                $submission->addMedia($signature)->toMediaCollection('signature');
            }

            $submission->logs()->create([
                'status' => RegistrationStatus::Submitted,
                'actor_id' => auth()->id(),
            ]);

            return $submission;
        });
    }

    public function signAsKaprodi(CourseRegistrationSubmission $submission, UploadedFile $signature): CourseRegistrationSubmission
    {
        $this->assertSignable($submission);

        $submission->addMedia($signature)->toMediaCollection('kaprodi_signature');

        return $submission;
    }

    public function signAsAdvisor(CourseRegistrationSubmission $submission, UploadedFile $signature): CourseRegistrationSubmission
    {
        $this->assertSignable($submission);

        $submission->addMedia($signature)->toMediaCollection('advisor_signature');

        return $submission;
    }

    private function assertSignable(CourseRegistrationSubmission $submission): void
    {
        abort_unless($submission->status === RegistrationStatus::Submitted, 403);
        abort_unless($submission->courseRegistrations()->exists(), 403);
    }

    public function approve(CourseRegistrationSubmission $submission): CourseRegistrationSubmission
    {
        abort_unless($submission->status === RegistrationStatus::Submitted, 403);
        abort_if($submission->kaprodi_signature_url === null, 422, 'Ketua Program Studi belum menandatangani KRS ini.');
        abort_if($submission->advisor_signature_url === null, 422, 'Silakan tanda tangani KRS ini terlebih dahulu.');

        return DB::transaction(function () use ($submission) {
            $submission->update([
                'status' => RegistrationStatus::Approved,
                'rejection_reason' => null,
                'reviewed_by' => auth()->id(),
                'reviewed_at' => now(),
            ]);

            $submission->logs()->create([
                'status' => RegistrationStatus::Approved,
                'actor_id' => auth()->id(),
            ]);

            foreach ($submission->courseRegistrations as $registration) {
                ClassEnrollment::firstOrCreate(
                    [
                        'course_class_id' => $registration->course_class_id,
                        'student_id' => $registration->student_id,
                    ],
                    [
                        'enrolled_at' => now(),
                    ],
                );
            }

            return $submission;
        });
    }

    public function reject(CourseRegistrationSubmission $submission, string $reason): CourseRegistrationSubmission
    {
        abort_unless($submission->status === RegistrationStatus::Submitted, 403);

        return DB::transaction(function () use ($submission, $reason) {
            $submission->update([
                'status' => RegistrationStatus::Rejected,
                'rejection_reason' => $reason,
                'reviewed_by' => auth()->id(),
                'reviewed_at' => now(),
            ]);

            $submission->logs()->create([
                'status' => RegistrationStatus::Rejected,
                'reason' => $reason,
                'actor_id' => auth()->id(),
            ]);

            return $submission;
        });
    }
}
