<?php

namespace App\Livewire\Assignments;

use App\Domain\Scheduling\Enums\AssignmentScope;
use App\Domain\Scheduling\Services\AssignmentService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Domain\Training\Models\TrainingGroup;
use App\Domain\Training\Models\TrainingSession;
use App\Models\User;
use Illuminate\Support\Collection;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\Exists;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('إنشاء تكليف')]
class AssignmentForm extends Component
{
    use UsesBranchScope;

    public string $user_id = '';
    public string $assignable_type = 'group';
    public string $assignable_id = '';
    public string $scope = 'full';
    public string $role_label = '';
    public string $start_date = '';
    public ?string $end_date = null;
    public ?string $start_time = null;
    public ?string $end_time = null;
    public bool $is_primary = false;
    public string $notes = '';
    public array $schedule_days = [];

    public function mount(): void
    {
        $this->authorize('assignments.create');
        $this->start_date = now()->format('Y-m-d');
    }

    public function updatedAssignableType(): void
    {
        $this->assignable_id = '';
    }

    /**
     * Users who may be assigned 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 user with no pivot branch is not pinned anywhere and works for
     * every branch; hard-filtering those would empty the picker in every
     * academy that never filled the pivot in.
     */
    private function branchUsers(): Collection
    {
        $branchId = $this->getActiveBranchId();

        return User::query()
            ->where('status', 'active')
            ->when($branchId !== null, fn ($q) => $q->where(fn ($w) => $w
                ->whereHas('roles', fn ($r) => $r->where('role_user.branch_id', $branchId))
                ->orWhereDoesntHave('roles', fn ($r) => $r->whereNotNull('role_user.branch_id'))))
            ->orderBy('name_ar')
            ->get(['id', 'name_ar', 'name']);
    }

    /**
     * The posted assignable id, checked against the branch it must live in.
     *
     * exists: runs a raw table query, so BranchScope does not reach it and it
     * would accept any group or session id in the academy — and assignable_id
     * is settable from the browser. save() resolves the model through Eloquent,
     * which is scoped, so an id from another branch would 404 anyway; the rule
     * is what turns that into an Arabic validation message instead.
     */
    private function assignableExistsRule(): Exists
    {
        $rule = $this->assignable_type === 'session'
            ? Rule::exists('training_sessions', 'id')
            : Rule::exists('training_groups', 'id')->whereNull('deleted_at');

        $branchId = $this->getActiveBranchId();

        if ($branchId !== null) {
            // Both tables are strictly scoped — a null branch there is a bug,
            // not "shared with every branch" — so no orWhereNull.
            $rule->where('branch_id', $branchId);
        }

        return $rule;
    }

    public function rules(): array
    {
        return [
            // Not exists:users,id — that accepts every user in the academy,
            // including staff pinned to another branch.
            'user_id' => ['required', Rule::in($this->branchUsers()->pluck('id')->all())],
            'assignable_type' => 'required|in:group,session',
            'assignable_id' => ['required', 'integer', 'min:1', $this->assignableExistsRule()],
            'scope' => 'required|in:full,partial,supervising,assisting,observing',
            'role_label' => 'nullable|string|max:100',
            'start_date' => 'required|date',
            'end_date' => 'nullable|date|after_or_equal:start_date',
            'start_time' => 'nullable|date_format:H:i',
            'end_time' => 'nullable|date_format:H:i|after:start_time',
            'is_primary' => 'boolean',
            'notes' => 'nullable|string|max:1000',
            'schedule_days' => 'nullable|array',
            'schedule_days.*' => 'integer|between:0,6',
        ];
    }

    public function messages(): array
    {
        return [
            'user_id.required' => 'يجب اختيار المستخدم',
            'user_id.in' => 'المستخدم المحدد غير متاح في هذا الفرع',
            'assignable_type.required' => 'يجب اختيار نوع التكليف',
            'assignable_type.in' => 'نوع التكليف غير صالح',
            'assignable_id.required' => 'يجب اختيار المجموعة أو الحصة',
            'assignable_id.integer' => 'اختيار غير صالح',
            'assignable_id.exists' => 'الاختيار غير متاح في هذا الفرع',
            'scope.required' => 'نطاق التكليف مطلوب',
            'scope.in' => 'نطاق التكليف غير صالح',
            'role_label.max' => 'المسمى الوظيفي يجب ألا يتجاوز 100 حرف',
            'start_date.required' => 'تاريخ البداية مطلوب',
            'start_date.date' => 'تاريخ البداية غير صالح',
            'end_date.date' => 'تاريخ النهاية غير صالح',
            'end_date.after_or_equal' => 'تاريخ النهاية يجب أن يكون بعد أو يساوي تاريخ البداية',
            'start_time.date_format' => 'وقت البداية يجب أن يكون بصيغة ساعة:دقيقة',
            'end_time.date_format' => 'وقت النهاية يجب أن يكون بصيغة ساعة:دقيقة',
            'end_time.after' => 'وقت النهاية يجب أن يكون بعد وقت البداية',
            'notes.max' => 'الملاحظات يجب ألا تتجاوز 1000 حرف',
            'schedule_days.array' => 'أيام الجدول غير صالحة',
            'schedule_days.*.between' => 'يوم غير صالح',
        ];
    }

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

        try {
            // Every lookup here reads through the global scopes: another
            // branch's user, group or session cannot be resolved at all.
            $user = User::findOrFail($this->user_id);

            if ($this->assignable_type === 'group') {
                $assignable = TrainingGroup::findOrFail($this->assignable_id);
            } else {
                $assignable = TrainingSession::findOrFail($this->assignable_id);
            }

            $service->assign($user, $assignable, [
                'scope' => $this->scope,
                'role_label' => $this->role_label ?: null,
                'start_date' => $this->start_date,
                'end_date' => $this->end_date ?: null,
                'start_time' => $this->start_time ?: null,
                'end_time' => $this->end_time ?: null,
                'is_primary' => $this->is_primary,
                'notes' => $this->notes ?: null,
                'schedule_days' => $this->schedule_days,
            ], auth()->user());

            session()->flash('success', __('تم إنشاء التكليف بنجاح'));
            $this->redirect(route('assignments.list'), navigate: true);
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    public function render()
    {
        $assignableOptions = [];

        // No branch clause: TrainingGroup and TrainingSession both carry
        // BranchScope, so these are this branch's rows and nothing else. The
        // ->when($branchId, ...) that used to sit here read a variable that was
        // never defined, so it filtered nothing at all.
        if ($this->assignable_type === 'group') {
            $assignableOptions = TrainingGroup::query()
                ->orderBy('name_ar')
                ->get(['id', 'name_ar', 'name'])
                ->mapWithKeys(fn ($g) => [$g->id => $g->name_ar])
                ->toArray();
        } elseif ($this->assignable_type === 'session') {
            $assignableOptions = TrainingSession::with('group')
                ->orderByDesc('session_date')
                ->limit(100)
                ->get()
                ->mapWithKeys(fn ($s) => [
                    $s->id => ($s->group?->name_ar ?? __('بدون مجموعة'))
                        . ' - ' . ($s->session_date?->format('Y-m-d') ?? '—'),
                ])
                ->toArray();
        }

        return view('livewire.assignments.assignment-form', [
            'users' => $this->branchUsers(),
            'scopeOptions' => collect(AssignmentScope::cases())->mapWithKeys(fn ($s) => [$s->value => $s->label()]),
            'assignableOptions' => $assignableOptions,
            'dayLabels' => [
                0 => __('الأحد'),
                1 => __('الإثنين'),
                2 => __('الثلاثاء'),
                3 => __('الأربعاء'),
                4 => __('الخميس'),
                5 => __('الجمعة'),
                6 => __('السبت'),
            ],
        ]);
    }
}
