<?php

namespace App\Services\Admin\Manage;

use App\Enums\RegistrationStatus;
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\Student;
use App\Models\TuitionInvoice;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

class CourseRegistrationService
{
    public function paginated(int $perPage = 25, string $search = '', ?string $status = null, ?int $academicTermId = null): LengthAwarePaginator
    {
        return CourseRegistrationSubmission::query()
            ->select(['id', 'student_id', 'academic_term_id', 'status', 'signed_at', 'rejection_reason', 'reviewed_by', 'reviewed_at'])
            ->with([
                'student.user.profile',
                'student.department',
                'academicTerm:id,name,semester,start_date,end_date',
                'reviewer.profile',
                'courseRegistrations.courseClass.course:id,code,name',
                'logs' => fn ($q) => $q->latest(),
                'logs.actor.profile',
            ])
            ->when($search, fn ($q) => $q->whereHas('student', fn ($q) => $q->where('student_number', 'like', "%{$search}%")
                ->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))))
            ->when($status, fn ($q) => $q->where('status', $status))
            ->when($academicTermId, fn ($q) => $q->where('academic_term_id', $academicTermId))
            ->latest()
            ->paginate($perPage);
    }

    /**
     * Admin-created registration: additively attach course classes to a
     * student's submission for a term, without disturbing an existing
     * submission's review status.
     *
     * @param  array{student_id: int, academic_term_id: int, course_class_ids: array<int, int>}  $data
     */
    public function create(array $data): CourseRegistrationSubmission
    {
        return DB::transaction(function () use ($data) {
            $submission = CourseRegistrationSubmission::firstOrCreate(
                [
                    'student_id' => $data['student_id'],
                    'academic_term_id' => $data['academic_term_id'],
                ],
                [
                    'status' => RegistrationStatus::Submitted,
                    'signed_at' => now(),
                ],
            );

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

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

            return $submission;
        });
    }

    public function approve(CourseRegistrationSubmission $submission): CourseRegistrationSubmission
    {
        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
    {
        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;
        });
    }

    /**
     * @return Collection<int, array{course: Course, course_class: ?CourseClass}>
     */
    public function availableCourseClasses(Student $student, AcademicTerm $term): Collection
    {
        $courses = Course::query()
            ->where('department_id', $student->department_id)
            ->where('semester_number', $student->current_semester)
            ->orderBy('name')
            ->get(['id', 'code', 'name', 'credits']);

        $classes = CourseClass::query()
            ->whereIn('course_id', $courses->pluck('id'))
            ->where('academic_term_id', $term->id)
            ->with('lecturer.user.profile')
            ->get()
            ->keyBy('course_id');

        return $courses->map(fn (Course $course) => [
            'course' => $course,
            'course_class' => $classes->get($course->id),
        ])->values();
    }

    /**
     * @return array{has_invoice: bool, is_paid: bool, amount_due: ?float, paid_total: ?float}
     */
    public function paymentStatus(Student $student, AcademicTerm $term): array
    {
        $invoice = TuitionInvoice::query()
            ->where('student_id', $student->id)
            ->where('academic_term_id', $term->id)
            ->withSum('payments as paid_total', 'amount_paid')
            ->first();

        if (! $invoice) {
            return [
                'has_invoice' => false,
                'is_paid' => false,
                'amount_due' => null,
                'paid_total' => null,
            ];
        }

        $paidTotal = (float) ($invoice->paid_total ?? 0);
        $amountDue = (float) $invoice->amount_due;

        return [
            'has_invoice' => true,
            'is_paid' => $paidTotal >= $amountDue,
            'amount_due' => $amountDue,
            'paid_total' => $paidTotal,
        ];
    }

    public function currentSubmission(Student $student, AcademicTerm $term): ?CourseRegistrationSubmission
    {
        return CourseRegistrationSubmission::query()
            ->where('student_id', $student->id)
            ->where('academic_term_id', $term->id)
            ->with([
                'courseRegistrations.courseClass.course',
                'courseRegistrations.courseClass.lecturer.user.profile',
                'logs' => fn ($q) => $q->latest(),
                'logs.actor.profile',
            ])
            ->first();
    }

    /**
     * @return Collection<int, CourseRegistrationSubmission>
     */
    public function submissionsFor(Student $student): Collection
    {
        return CourseRegistrationSubmission::query()
            ->where('student_id', $student->id)
            ->withCount('courseRegistrations')
            ->with('academicTerm:id,name,semester,start_date,end_date')
            ->latest()
            ->get();
    }

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

    public function canSubmit(?CourseRegistrationSubmission $submission): bool
    {
        return $submission === null || $submission->status === RegistrationStatus::Rejected;
    }

    /**
     * @return array<int, int>
     */
    public function allowedCourseClassIds(Student $student, AcademicTerm $term): array
    {
        $courseIds = Course::query()
            ->where('department_id', $student->department_id)
            ->where('semester_number', $student->current_semester)
            ->pluck('id');

        return CourseClass::query()
            ->whereIn('course_id', $courseIds)
            ->where('academic_term_id', $term->id)
            ->pluck('id')
            ->all();
    }

    /**
     * Student self-service sign-off: replaces the submission's course
     * selection wholesale and attaches the captured signature.
     *
     * @param  array<int, int>  $courseClassIds
     */
    public function sign(Student $student, AcademicTerm $term, array $courseClassIds, UploadedFile $signatureFile): CourseRegistrationSubmission
    {
        return DB::transaction(function () use ($student, $term, $courseClassIds, $signatureFile) {
            $submission = CourseRegistrationSubmission::updateOrCreate(
                ['student_id' => $student->id, 'academic_term_id' => $term->id],
                [
                    'status' => RegistrationStatus::Submitted,
                    'signed_at' => now(),
                    'rejection_reason' => null,
                    'reviewed_by' => null,
                    'reviewed_at' => null,
                ],
            );

            $submission->courseRegistrations()->delete();

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

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

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

            return $submission;
        });
    }
}
