<?php

namespace App\Domain\Training\Services;

use App\Domain\Participant\Models\Participant;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Events\EnrollmentCancelled;
use App\Domain\Training\Events\EnrollmentCompleted;
use App\Domain\Training\Events\EnrollmentCreated;
use App\Domain\Training\Events\WaitlistSpotAvailable;
use App\Domain\Training\Models\Enrollment;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Models\Waitlist;
use App\Models\User;
use Illuminate\Support\Facades\DB;

class EnrollmentService
{
    public function __construct(
        private readonly TrainingGroupService $groupService,
    ) {}

    public function enroll(Participant $participant, TrainingGroup $group, User $actor, ?array $options = []): Enrollment
    {
        return DB::transaction(function () use ($participant, $group, $actor, $options) {
            // Guard: participant status must allow enrollment
            $blockedStatuses = ['suspended', 'blacklisted', 'transferred'];
            if (in_array($participant->status->value, $blockedStatuses)) {
                throw new DomainException('لا يمكن تسجيل مشترك ' . $this->getStatusLabel($participant->status->value));
            }

            // Guard: check if already enrolled in THIS group
            $existingInGroup = Enrollment::where('participant_id', $participant->id)
                ->where('training_group_id', $group->id)
                ->whereIn('status', ['pending', 'active'])
                ->exists();
            if ($existingInGroup) {
                throw new DomainException('المشترك مسجل بالفعل في هذه المجموعة');
            }

            // Guard: one group per program rule
            $existingInProgram = Enrollment::where('participant_id', $participant->id)
                ->where('training_program_id', $group->training_program_id)
                ->whereIn('status', ['pending', 'active'])
                ->exists();
            if ($existingInProgram) {
                throw new DomainException('المشترك مسجل بالفعل في مجموعة أخرى من نفس البرنامج');
            }

            // Guard: group capacity
            if ($group->isFull()) {
                if ($group->program && $group->program->allow_waitlist) {
                    $this->addToWaitlist($participant, $group);
                    $position = Waitlist::where('training_group_id', $group->id)
                        ->whereNull('response')
                        ->max('position');
                    throw new DomainException('المجموعة ممتلئة. تمت إضافة المشترك لقائمة الانتظار (المركز: ' . $position . ')');
                }
                throw new DomainException('المجموعة ممتلئة ولا تقبل تسجيلات جديدة');
            }

            // Guard: group must accept enrollments
            $acceptingStatuses = ['forming', 'active'];
            if (!in_array($group->status->value, $acceptingStatuses)) {
                throw new DomainException('المجموعة لا تقبل تسجيلات حالياً');
            }

            $enrollment = Enrollment::create([
                'participant_id' => $participant->id,
                'training_group_id' => $group->id,
                'training_program_id' => $group->training_program_id,
                'enrollment_date' => now()->toDateString(),
                'start_date' => $options['start_date'] ?? $group->start_date ?? now()->toDateString(),
                'end_date' => $options['end_date'] ?? $group->end_date,
                'status' => 'active',
                'enrolled_by' => $actor->id,
                'invoice_id' => $options['invoice_id'] ?? null,
                'payment_status' => $options['payment_status'] ?? 'pending',
                'sessions_total' => $group->program?->total_sessions,
            ]);

            // Update group count
            $this->groupService->incrementCount($group);

            EnrollmentCreated::dispatch($enrollment, $actor);

            return $enrollment;
        });
    }

    /**
     * Enroll a participant in a program by finding (or auto-creating) an available group.
     */
    public function enrollInProgram(Participant $participant, TrainingProgram $program, User $actor, array $options = []): Enrollment
    {
        return DB::transaction(function () use ($participant, $program, $actor, $options) {
            // Guard: program must accept enrollments
            if (!$program->registration_open) {
                throw new DomainException('التسجيل في هذا البرنامج مغلق حالياً');
            }

            // Guard: participant not already enrolled in this program
            $existingInProgram = Enrollment::where('participant_id', $participant->id)
                ->where('training_program_id', $program->id)
                ->whereIn('status', ['pending', 'active'])
                ->exists();
            if ($existingInProgram) {
                throw new DomainException('المشترك مسجل بالفعل في هذا البرنامج');
            }

            $allowOverload = $options['allow_overload'] ?? false;

            $group = $this->findOrCreateGroup($program, $actor, $allowOverload);

            return $this->enroll($participant, $group, $actor, $options);
        });
    }

    /**
     * Find an available group in the program, or auto-create one.
     * Uses lockForUpdate to prevent race conditions on group selection.
     */
    private function findOrCreateGroup(TrainingProgram $program, User $actor, bool $allowOverload): TrainingGroup
    {
        // Find groups accepting enrollments (forming or active) with available capacity
        $availableGroup = TrainingGroup::where('training_program_id', $program->id)
            ->whereIn('status', ['forming', 'active'])
            ->lockForUpdate()
            ->get()
            ->filter(fn (TrainingGroup $g) => $g->hasAvailableSpots())
            ->sortByDesc(fn (TrainingGroup $g) => $g->max_capacity - $g->current_count)
            ->first();

        if ($availableGroup) {
            return $availableGroup;
        }

        // No group with capacity found — handle overload or auto-create
        if ($allowOverload) {
            // Overload: pick the least-full group (even though all are full)
            $leastFullGroup = TrainingGroup::where('training_program_id', $program->id)
                ->whereIn('status', ['forming', 'active', 'full'])
                ->lockForUpdate()
                ->orderByRaw('(max_capacity - current_count) DESC')
                ->first();

            if (!$leastFullGroup) {
                throw new DomainException('لا توجد مجموعات في هذا البرنامج يمكن التسجيل فيها');
            }

            return $leastFullGroup;
        }

        // Auto-create a new group
        return $this->autoCreateGroup($program, $actor);
    }

    /**
     * Auto-create a new group for the program with sequential naming.
     */
    private function autoCreateGroup(TrainingProgram $program, User $actor): TrainingGroup
    {
        $existingCount = TrainingGroup::where('training_program_id', $program->id)->count();
        $sequenceNumber = $existingCount + 1;

        $programName = $program->name_ar ?: $program->name;
        $groupNameAr = "مجموعة {$programName} {$sequenceNumber}";
        $groupName = $program->name ? "Group {$program->name} {$sequenceNumber}" : $groupNameAr;

        return $this->groupService->create([
            'academy_id' => $program->academy_id,
            'training_program_id' => $program->id,
            'branch_id' => $program->branch_id,
            'name' => $groupName,
            'name_ar' => $groupNameAr,
            'max_capacity' => $program->max_participants,
            'current_count' => 0,
            'waitlist_count' => 0,
            'start_date' => $program->program_start_date ?? now()->toDateString(),
            'end_date' => $program->program_end_date,
        ], $actor);
    }

    public function cancel(Enrollment $enrollment, string $reason, User $actor): Enrollment
    {
        return DB::transaction(function () use ($enrollment, $reason, $actor) {
            if (!in_array($enrollment->status->value, ['pending', 'active'])) {
                throw new DomainException('لا يمكن إلغاء هذا التسجيل');
            }

            $enrollment->update([
                'status' => 'cancelled',
                'withdrawal_date' => now()->toDateString(),
                'withdrawal_reason' => $reason,
            ]);

            // Decrement group count
            $group = $enrollment->group;
            $this->groupService->decrementCount($group);

            EnrollmentCancelled::dispatch($enrollment, $actor);

            return $enrollment->fresh();
        });
    }

    public function complete(Enrollment $enrollment, ?User $actor = null): Enrollment
    {
        return DB::transaction(function () use ($enrollment, $actor) {
            if ($enrollment->status->value !== 'active') {
                throw new DomainException('يمكن إكمال التسجيلات النشطة فقط');
            }

            // Check attendance requirement
            $program = $enrollment->program;
            if ($program && $program->attendance_requirement_percent) {
                $percentage = $enrollment->attendance_percentage ?? 0;
                if ($percentage < $program->attendance_requirement_percent) {
                    throw new DomainException("نسبة الحضور ({$percentage}%) أقل من المطلوب ({$program->attendance_requirement_percent}%)");
                }
            }

            $enrollment->update([
                'status' => 'completed',
                'end_date' => now()->toDateString(),
                'completion_percentage' => 100,
            ]);

            if ($actor) {
                EnrollmentCompleted::dispatch($enrollment, $actor);
            }

            return $enrollment->fresh();
        });
    }

    public function transfer(Enrollment $enrollment, TrainingGroup $toGroup, User $actor): Enrollment
    {
        return DB::transaction(function () use ($enrollment, $toGroup, $actor) {
            if ($enrollment->status->value !== 'active') {
                throw new DomainException('يمكن نقل التسجيلات النشطة فقط');
            }

            if ($toGroup->isFull()) {
                throw new DomainException('المجموعة المنقول إليها ممتلئة');
            }

            // Cancel old enrollment
            $enrollment->update([
                'status' => 'cancelled',
                'withdrawal_date' => now()->toDateString(),
                'withdrawal_reason' => 'تحويل إلى مجموعة أخرى',
                'transferred_to_id' => $toGroup->id,
            ]);

            $this->groupService->decrementCount($enrollment->group);

            // Create new enrollment in target group
            $newEnrollment = Enrollment::create([
                'participant_id' => $enrollment->participant_id,
                'training_group_id' => $toGroup->id,
                'training_program_id' => $toGroup->training_program_id,
                'enrollment_date' => now()->toDateString(),
                'start_date' => now()->toDateString(),
                'end_date' => $toGroup->end_date,
                'status' => 'active',
                'enrolled_by' => $actor->id,
                'payment_status' => $enrollment->payment_status->value,
                'sessions_attended' => $enrollment->sessions_attended,
                'sessions_total' => $enrollment->sessions_total,
            ]);

            $this->groupService->incrementCount($toGroup);

            // Process waitlist on source group
            $this->processWaitlist($enrollment->group);

            return $newEnrollment;
        });
    }

    private function addToWaitlist(Participant $participant, TrainingGroup $group): void
    {
        $existsOnWaitlist = Waitlist::where('participant_id', $participant->id)
            ->where('training_group_id', $group->id)
            ->whereNull('response')
            ->exists();

        if ($existsOnWaitlist) {
            throw new DomainException('المشترك موجود بالفعل في قائمة الانتظار');
        }

        $position = Waitlist::where('training_group_id', $group->id)
            ->whereNull('response')
            ->max('position') ?? 0;

        Waitlist::create([
            'academy_id' => $group->academy_id,
            'participant_id' => $participant->id,
            'training_group_id' => $group->id,
            'position' => $position + 1,
            'requested_at' => now(),
        ]);

        $group->increment('waitlist_count');
    }

    public function processWaitlist(TrainingGroup $group): void
    {
        if (!$group->hasAvailableSpots()) {
            return;
        }

        $next = Waitlist::where('training_group_id', $group->id)
            ->whereNull('response')
            ->orderBy('position')
            ->first();

        if ($next) {
            $next->update([
                'notified_at' => now(),
                'expires_at' => now()->addHours(24),
            ]);
            WaitlistSpotAvailable::dispatch($group, $next);
        }
    }

    private function getStatusLabel(string $status): string
    {
        return match ($status) {
            'suspended' => 'موقوف',
            'blacklisted' => 'محظور',
            'transferred' => 'محول',
            default => $status,
        };
    }
}
