<?php

namespace App\Domain\Training\Services;

use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Training\Events\SessionCancelled;
use App\Domain\Training\Events\SessionCreated;
use App\Domain\Training\Models\Holiday;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingSession;
use App\Models\User;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Support\Facades\DB;

class SessionGeneratorService
{
    public function generate(TrainingGroup $group, Carbon $from, Carbon $to): int
    {
        return DB::transaction(function () use ($group, $from, $to) {
            $schedules = $group->schedules()
                ->active()
                ->effectiveOn($from)
                ->get();

            if ($schedules->isEmpty()) {
                return 0;
            }

            // Get holidays for this academy/branch
            $holidays = Holiday::where('academy_id', $group->academy_id)
                ->where(function ($q) use ($group) {
                    $q->whereNull('branch_id')
                      ->orWhere('branch_id', $group->branch_id);
                })
                ->affectsTraining()
                ->whereBetween('date', [$from->toDateString(), $to->toDateString()])
                ->pluck('date')
                ->map(fn ($d) => $d->toDateString())
                ->toArray();

            $sessionsCreated = 0;
            $lastSessionNumber = $group->sessions()->max('session_number') ?? 0;

            foreach ($schedules as $schedule) {
                $period = CarbonPeriod::create($from, $to);

                foreach ($period as $date) {
                    // Skip if not the right day of week
                    if ($date->dayOfWeek !== $schedule->day_of_week) {
                        continue;
                    }

                    // Skip holidays
                    if (in_array($date->toDateString(), $holidays)) {
                        continue;
                    }

                    // Skip if schedule not effective on this date
                    if ($date->lt($schedule->effective_from)) {
                        continue;
                    }
                    if ($schedule->effective_until && $date->gt($schedule->effective_until)) {
                        continue;
                    }

                    // Skip if session already exists for this group+date+time
                    $exists = TrainingSession::where('training_group_id', $group->id)
                        ->where('session_date', $date->toDateString())
                        ->where('start_time', $schedule->start_time)
                        ->exists();
                    if ($exists) {
                        continue;
                    }

                    $lastSessionNumber++;

                    $session = TrainingSession::create([
                        'academy_id' => $group->academy_id,
                        'training_group_id' => $group->id,
                        'schedule_id' => $schedule->id,
                        'facility_id' => $schedule->facility_id,
                        'session_date' => $date->toDateString(),
                        'start_time' => $schedule->start_time,
                        'end_time' => $schedule->end_time,
                        'trainer_id' => $schedule->trainer_id ?? $group->head_trainer_id,
                        'assistant_trainer_id' => $schedule->assistant_trainer_id,
                        'session_number' => $lastSessionNumber,
                        'status' => 'scheduled',
                        'expected_participants' => $group->current_count,
                    ]);

                    SessionCreated::dispatch($session);
                    $sessionsCreated++;
                }
            }

            return $sessionsCreated;
        });
    }

    public function generateUpcoming(int $daysAhead = 7): int
    {
        $total = 0;
        $from = now()->startOfDay();
        $to = now()->addDays($daysAhead)->endOfDay();

        $groups = TrainingGroup::whereIn('status', ['active', 'forming'])
            ->with('schedules')
            ->get();

        foreach ($groups as $group) {
            $total += $this->generate($group, $from, $to);
        }

        return $total;
    }

    public function cancelSession(TrainingSession $session, string $reason, User $actor): TrainingSession
    {
        return DB::transaction(function () use ($session, $reason, $actor) {
            if ($session->status->value !== 'scheduled') {
                throw new DomainException('لا يمكن إلغاء حصة غير مجدولة');
            }

            $session->update([
                'status' => 'cancelled',
                'cancelled_reason' => $reason,
                'cancelled_by' => $actor->id,
            ]);

            SessionCancelled::dispatch($session, $reason, $actor);

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

    public function rescheduleSession(TrainingSession $session, string $newDate, string $newStartTime, string $newEndTime, User $actor): TrainingSession
    {
        return DB::transaction(function () use ($session, $newDate, $newStartTime, $newEndTime, $actor) {
            if ($session->status->value !== 'scheduled') {
                throw new DomainException('لا يمكن إعادة جدولة حصة غير مجدولة');
            }

            // Create new session
            $newSession = TrainingSession::create([
                'academy_id' => $session->academy_id,
                'training_group_id' => $session->training_group_id,
                'schedule_id' => $session->schedule_id,
                'facility_id' => $session->facility_id,
                'session_date' => $newDate,
                'start_time' => $newStartTime,
                'end_time' => $newEndTime,
                'trainer_id' => $session->trainer_id,
                'assistant_trainer_id' => $session->assistant_trainer_id,
                'session_number' => $session->session_number,
                'topic' => $session->topic,
                'objectives' => $session->objectives,
                'status' => 'scheduled',
                'expected_participants' => $session->expected_participants,
            ]);

            // Mark original as rescheduled
            $session->update([
                'status' => 'rescheduled',
                'rescheduled_to_id' => $newSession->id,
                'cancelled_reason' => 'تم إعادة الجدولة',
                'cancelled_by' => $actor->id,
            ]);

            SessionCreated::dispatch($newSession);

            return $newSession;
        });
    }

    public function substituteTrainer(TrainingSession $session, int $newTrainerId, string $reason): TrainingSession
    {
        return DB::transaction(function () use ($session, $newTrainerId, $reason) {
            $session->update([
                'trainer_id' => $newTrainerId,
                'substitute_reason' => $reason,
            ]);
            return $session->fresh();
        });
    }
}
