<?php

namespace App\Livewire\Groups;

use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingProgram;
use App\Domain\Training\Services\TrainingGroupService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('إدارة المجموعة')]
class GroupForm extends Component
{
    use UsesBranchScope;

    /**
     * Route-model bound, so BranchScope resolves it — another branch's group
     * 404s here rather than opening. Locked so the browser cannot swap the id
     * on a later round-trip and steer save() onto a different group.
     */
    #[Locked]
    public ?TrainingGroup $group = null;

    public bool $editing = false;

    public ?int $program_id = null;
    public ?int $branch_id = null;
    public string $name = '';
    public string $name_ar = '';
    public string $code = '';
    public ?int $head_trainer_id = null;
    public int $max_capacity = 20;
    public string $season = '';
    public ?string $start_date = null;
    public ?string $end_date = null;
    public string $notes = '';

    public function mount(?TrainingGroup $group = null): void
    {
        // The routes carry groups.create / groups.update, but a form that only
        // gates at the route gates in one of the five places it should.
        $this->authorize($group?->exists ? 'groups.update' : 'groups.create');

        if ($group && $group->exists) {
            $this->group = $group;
            $this->editing = true;
            $this->program_id = $group->training_program_id;
            $this->branch_id = $group->branch_id;
            $this->name = $group->name ?? '';
            $this->name_ar = $group->name_ar;
            $this->code = $group->code ?? '';
            $this->head_trainer_id = $group->head_trainer_id;
            $this->max_capacity = $group->max_capacity;
            $this->season = $group->season ?? '';
            $this->start_date = $group->start_date?->format('Y-m-d');
            $this->end_date = $group->end_date?->format('Y-m-d');
            $this->notes = $group->notes ?? '';
        }

        // A new group belongs to the branch being worked in. The picker only
        // appears at all for a user genuinely in all-branches mode, where there
        // is no single answer to take from the context.
        $this->branch_id ??= $this->getActiveBranchId();
    }

    public function updatedProgramId(): void
    {
        if ($this->program_id && !$this->editing) {
            // TrainingProgram is branch-scoped (shared), so this resolves to
            // this branch's programme or an academy-wide one, or to nothing.
            $program = TrainingProgram::find($this->program_id);

            // The branch is deliberately NOT copied from the programme any
            // more: an academy-wide programme carries branch_id NULL, and
            // copying that null would file the group under no branch at all —
            // invisible from every branch at once.
            if ($program && $program->max_participants) {
                $this->max_capacity = $program->max_participants;
            }
        }
    }

    /**
     * The branches this form may offer and accept.
     *
     * selectableBranches() is one entry unless the user is genuinely in
     * all-branches mode. The group's own branch is kept valid while editing
     * even if that branch has since been deactivated, so renaming an old group
     * is not blocked by a branch nobody can pick any more.
     */
    private function branchOptions(): Collection
    {
        $options = $this->selectableBranches();

        $current = $this->editing ? $this->group?->branch : null;

        if ($current && ! $options->contains('id', $current->id)) {
            $options = $options->push($current);
        }

        return $options->values();
    }

    /**
     * exists: runs a raw table query and never sees BranchScope, so it would
     * accept any programme in the academy posted straight into the public
     * $program_id. A group may only be opened on a programme this branch
     * actually runs.
     */
    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;
    }

    /**
     * Users who may run a group in this branch.
     *
     * users has no branch_id — a user's branch lives on the role_user pivot —
     * so BranchScope cannot reach this query and the filter has to be written
     * out. A null pivot branch means the user is not pinned anywhere and works
     * for every branch, the same reading Trainer uses for its shared rows;
     * hard-filtering those would empty the picker in every academy that never
     * filled the pivot in.
     */
    private function branchTrainers(): Collection
    {
        $branchId = $this->getActiveBranchId();

        return User::query()
            ->where('status', 'active')
            ->whereHas('roles', function ($q) use ($branchId) {
                $q->whereIn('slug', ['trainer', 'head_trainer']);

                if ($branchId !== null) {
                    $q->where(fn ($w) => $w->where('role_user.branch_id', $branchId)
                        ->orWhereNull('role_user.branch_id'));
                }
            })
            ->orderBy('name_ar')
            ->get(['id', 'name_ar', 'name']);
    }

    /**
     * What the head-trainer <select> offers, and the exact set the posted value
     * is validated against.
     *
     * The saved head trainer is kept in the list even when they no longer match
     * — assigned before the roles narrowed, or since moved — so editing the
     * name of an old group does not force a change of coach. It is read from
     * the persisted group, never from the browser-settable property, or the
     * validation below would just be echoing whatever was posted.
     */
    private function trainerOptions(): Collection
    {
        $options = $this->branchTrainers();

        $saved = $this->editing ? $this->group?->head_trainer_id : null;

        if ($saved && ! $options->contains('id', $saved)) {
            $current = User::find($saved);

            if ($current) {
                $options = $options->push($current)->values();
            }
        }

        return $options;
    }

    public function rules(): array
    {
        return [
            'program_id' => ['required', $this->programExistsRule()],
            // Not exists:branches,id — that accepts every branch in the
            // academy. branchOptions() is one entry unless the user is
            // genuinely in all-branches mode.
            //
            // Required, not nullable: training_groups is strict-scoped, so a
            // null branch is not "every branch", it is a group invisible from
            // every branch at once. Nullable let an all-branches user pick the
            // blank option and, on an edit, blank out a branch the group
            // already had.
            'branch_id' => ['required', Rule::in($this->branchOptions()->pluck('id')->map(fn ($id) => (int) $id)->all())],
            'name' => 'required|string|max:255',
            'name_ar' => 'required|string|max:255',
            'code' => 'nullable|string|max:10',
            'head_trainer_id' => ['nullable', Rule::in($this->trainerOptions()->pluck('id')->all())],
            'max_capacity' => 'required|integer|min:1|max:500',
            'season' => 'nullable|string|max:50',
            'start_date' => 'nullable|date',
            'end_date' => 'nullable|date|after_or_equal:start_date',
            'notes' => 'nullable|string|max:1000',
        ];
    }

    public function messages(): array
    {
        return [
            'program_id.required' => 'البرنامج مطلوب',
            'program_id.exists' => 'البرنامج المحدد غير موجود',
            'branch_id.required' => 'الفرع مطلوب',
            'branch_id.in' => 'الفرع المحدد غير متاح',
            'name.required' => 'اسم المجموعة بالإنجليزية مطلوب',
            'name.max' => 'اسم المجموعة بالإنجليزية يجب ألا يتجاوز 255 حرف',
            'name_ar.required' => 'اسم المجموعة بالعربية مطلوب',
            'name_ar.max' => 'اسم المجموعة بالعربية يجب ألا يتجاوز 255 حرف',
            'code.max' => 'الكود يجب ألا يتجاوز 50 حرف',
            'head_trainer_id.in' => 'المدرب المحدد غير متاح في هذا الفرع',
            'max_capacity.required' => 'الحد الأقصى للسعة مطلوب',
            'max_capacity.min' => 'الحد الأقصى للسعة يجب أن يكون 1 على الأقل',
            'max_capacity.max' => 'الحد الأقصى للسعة يجب ألا يتجاوز 500',
            'end_date.after_or_equal' => 'تاريخ الانتهاء يجب أن يكون بعد تاريخ البداية',
            'notes.max' => 'الملاحظات يجب ألا تتجاوز 1000 حرف',
        ];
    }

    public function save(TrainingGroupService $service): void
    {
        $this->validate();

        try {
            $data = [
                'training_program_id' => $this->program_id,
                'branch_id' => $this->branch_id,
                'name' => $this->name,
                'name_ar' => $this->name_ar,
                'code' => $this->code ?: null,
                'head_trainer_id' => $this->head_trainer_id,
                'max_capacity' => $this->max_capacity,
                'season' => $this->season ?: null,
                'start_date' => $this->start_date ?: null,
                'end_date' => $this->end_date ?: null,
                'notes' => $this->notes ?: null,
            ];

            if ($this->editing) {
                $service->update($this->group, $data);
                session()->flash('success', __('تم تحديث المجموعة بنجاح'));
            } else {
                $service->create($data, auth()->user());
                session()->flash('success', __('تم إنشاء المجموعة بنجاح'));
            }

            $this->redirect(route('groups.list'), navigate: true);
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    public function render()
    {
        return view('livewire.groups.group-form', [
            // No branch clause: TrainingProgram carries BranchScope in shared
            // mode, so this is this branch's programmes plus academy-wide ones.
            'programs' => TrainingProgram::orderBy('name_ar')->get(['id', 'name_ar']),
            'branches' => $this->branchOptions(),
            'trainers' => $this->trainerOptions(),
        ]);
    }
}
