<?php

namespace App\Livewire\Groups;

use App\Domain\HR\Models\Trainer;
use App\Domain\HR\Services\TrainerWorkloadService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\Activity;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingGroupService;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('إنشاء مجموعة تدريبية')]
class CreateGroupWizard extends Component
{
    use UsesBranchScope;

    public int $currentStep = 1;
    public int $totalSteps = 4;
    public bool $completed = false;

    // Step 1: Program Selection
    public ?int $programId = null;
    public ?int $activityFilter = null;

    // Step 2: Group Info
    public string $nameAr = '';
    public string $name = '';
    public string $code = '';
    public string $season = '';
    public string $startDate = '';
    public string $endDate = '';
    public string $notes = '';

    // Step 3: Capacity & Trainer
    public string $maxCapacity = '';
    public ?int $headTrainerId = null;

    public function mount(): void
    {
        $this->authorize('groups.create');
        $this->startDate = now()->toDateString();
    }

    public function getStepLabels(): array
    {
        return [
            1 => 'البرنامج',
            2 => 'بيانات المجموعة',
            3 => 'السعة والمدرب',
            4 => 'مراجعة',
        ];
    }

    public function updatedActivityFilter(): void
    {
        $this->programId = null;
    }

    /**
     * The coaches this branch may put in charge of a group.
     *
     * No branch clause of its own: Trainer carries BranchScope in shared mode,
     * so this is the branch's coaches plus the academy-wide ones. Filtering
     * through employees on top of that would be redundant and would also drop
     * every freelance coach, who has no employee record at all.
     */
    private function branchTrainers(): Collection
    {
        return Trainer::with('employee.person', 'employee:id,user_id')
            ->where('status', 'active')
            ->get();
    }

    /**
     * The user ids the step-3 picker actually offers — the set head trainer is
     * validated against, so the answer cannot be wider than the question.
     */
    private function branchTrainerUserIds(): array
    {
        return $this->branchTrainers()
            ->map(fn ($t) => $t->employee?->user_id)
            ->filter()
            ->values()
            ->all();
    }

    /**
     * exists: runs a raw table query, so it never sees BranchScope and would
     * happily accept another branch's programme id posted straight into the
     * public $programId. The branch clause has to be written out here.
     *
     * A group may only be opened on a programme this branch actually runs.
     * In all-branches mode there is no branch to compare against and the rule
     * stays open.
     */
    private function programExistsRule(): \Illuminate\Validation\Rules\Exists
    {
        $rule = Rule::exists('training_programs', 'id');
        $branchId = $this->getActiveBranchId();

        if ($branchId !== null) {
            $rule->where(fn ($q) => $q->where('branch_id', $branchId));
        }

        return $rule;
    }

    public function rulesForStep(int $step): array
    {
        return match ($step) {
            1 => [
                'programId' => ['required', $this->programExistsRule()],
            ],
            2 => [
                'nameAr' => 'required|string|max:255',
                'code' => 'required|string|max:30',
                'startDate' => 'required|date',
                'endDate' => 'nullable|date|after:startDate',
            ],
            3 => [
                'maxCapacity' => 'required|integer|min:1|max:500',
                'headTrainerId' => ['nullable', 'integer', Rule::in($this->branchTrainerUserIds())],
            ],
            default => [],
        };
    }

    public function messagesForStep(int $step): array
    {
        return match ($step) {
            1 => [
                'programId.required' => 'يجب اختيار البرنامج التدريبي',
                'programId.exists' => 'البرنامج المختار غير موجود',
            ],
            2 => [
                'nameAr.required' => 'اسم المجموعة بالعربي مطلوب',
                'code.required' => 'كود المجموعة مطلوب',
                'code.max' => 'كود المجموعة لا يتجاوز 30 حرف',
                'startDate.required' => 'تاريخ البدء مطلوب',
                'startDate.date' => 'تاريخ البدء غير صالح',
                'endDate.after' => 'تاريخ الانتهاء يجب أن يكون بعد تاريخ البدء',
            ],
            3 => [
                'maxCapacity.required' => 'السعة القصوى مطلوبة',
                'maxCapacity.integer' => 'السعة القصوى يجب أن تكون رقم صحيح',
                'maxCapacity.min' => 'السعة القصوى يجب أن تكون 1 على الأقل',
                'headTrainerId.in' => 'المدرب المختار غير متاح في هذا الفرع',
            ],
            default => [],
        };
    }

    public function nextStep(): void
    {
        $this->validate(
            $this->rulesForStep($this->currentStep),
            $this->messagesForStep($this->currentStep)
        );

        if ($this->currentStep < $this->totalSteps) {
            $this->currentStep++;
        }
    }

    public function previousStep(): void
    {
        if ($this->currentStep > 1) {
            $this->currentStep--;
        }
    }

    public function goToStep(int $step): void
    {
        if ($step < $this->currentStep) {
            $this->currentStep = $step;
        }
    }

    public function confirm(): void
    {
        $this->authorize('groups.create');

        // Every step is re-validated here, not just the one on screen. A
        // Livewire action is callable from the browser at any time, so a client
        // that never walked through nextStep() would otherwise write a
        // programme and a head trainer nobody ever checked.
        foreach ([1, 2, 3] as $step) {
            $this->validate($this->rulesForStep($step), $this->messagesForStep($step));
        }

        try {
            DB::transaction(function () {
                $program = TrainingProgram::findOrFail($this->programId);

                $data = [
                    'academy_id' => app('current_academy')->id,
                    'training_program_id' => $this->programId,
                    // The group belongs to the branch it is being created in.
                    // Taking it from the programme instead would file the group
                    // under no branch at all whenever the programme is an
                    // academy-wide (branch_id NULL) catalogue row.
                    'branch_id' => $this->getActiveBranchId() ?? $program->branch_id,
                    'name_ar' => $this->nameAr,
                    'name' => $this->name ?: null,
                    'code' => $this->code,
                    'season' => $this->season ?: null,
                    'start_date' => $this->startDate,
                    'end_date' => $this->endDate ?: null,
                    'notes' => $this->notes ?: null,
                    'max_capacity' => (int) $this->maxCapacity,
                    'head_trainer_id' => $this->headTrainerId,
                ];

                app(TrainingGroupService::class)->create($data, auth()->user());
            });

            $this->completed = true;
            session()->flash('success', 'تم إنشاء المجموعة التدريبية بنجاح');
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    public function render()
    {
        // No branch clause on the programmes: TrainingProgram carries
        // BranchScope in shared mode, so this is already this branch's
        // programmes plus the academy-wide ones.
        $programs = TrainingProgram::query()
            ->when($this->activityFilter, fn ($q) => $q->where('activity_id', $this->activityFilter))
            ->whereIn('status', ['draft', 'active'])
            ->orderBy('name_ar')
            ->get(['id', 'name_ar', 'name', 'activity_id', 'max_participants']);

        $trainers = $this->branchTrainers();

        $trainerUserIds = $trainers->map(fn ($t) => $t->employee?->user_id)->filter()->values()->toArray();
        $workloads = app(TrainerWorkloadService::class)->getWorkloadsForUsers($trainerUserIds);

        return view('livewire.groups.create-group-wizard', [
            'stepLabels' => $this->getStepLabels(),
            'activities' => Activity::orderBy('name_ar')->get(['id', 'name_ar']),
            'programs' => $programs,
            'trainers' => $trainers,
            'trainerWorkloads' => $workloads,
            'selectedProgram' => $this->programId ? TrainingProgram::with('activity', 'branch')->find($this->programId) : null,
        ]);
    }
}
