<?php

namespace App\Livewire\HR;

use App\Domain\HR\Enums\EmploymentType;
use App\Domain\HR\Enums\SalaryFrequency;
use App\Domain\HR\Services\EmployeeService;
use App\Domain\Identity\Models\Person;
use App\Domain\Identity\Models\Role;
use App\Domain\Identity\Services\PersonService;
use App\Domain\Shared\Exceptions\DomainException;
use App\Domain\Shared\Traits\UsesBranchScope;
use App\Models\User;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Component;

#[Layout('layouts.app')]
#[Title('إضافة موظف جديد')]
class CreateEmployeeWizard extends Component
{
    use UsesBranchScope;

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

    // Step 1: Person
    public ?int $personId = null;
    public string $personSearch = '';
    public bool $createNewPerson = false;
    public string $personNameAr = '';
    public string $personName = '';
    public string $personPhone = '';
    public string $personNationalId = '';
    public string $personDateOfBirth = '';
    public string $personGender = '';

    // Step 2: Employment
    public string $department = '';
    public string $position = '';
    public string $employmentType = '';
    public string $startDate = '';
    public string $endDate = '';

    // Step 3: Branch & Manager
    public ?int $branchId = null;
    public ?int $managerId = null;

    // Step 4: Salary
    public string $salaryAmount = '';
    public string $salaryFrequency = '';
    public string $workingHoursPerWeek = '';

    // Step 5: User Account
    public bool $createUserAccount = true;
    public string $userEmail = '';
    public string $userPassword = '';
    public string $userPasswordConfirmation = '';
    public ?int $userRoleId = null;

    public function mount(): void
    {
        $this->authorize('employees.create');
        $this->startDate = now()->toDateString();
        // The wizard files the employee — and their user account — into this
        // branch. Default it to the one the operator is working in; only an
        // all-branches user is offered a choice.
        $this->branchId = $this->getActiveBranchId();
    }

    public function getStepLabels(): array
    {
        return [
            1 => 'بيانات الشخص',
            2 => 'بيانات التوظيف',
            3 => 'الفرع والمسؤول',
            4 => 'الراتب',
            5 => 'حساب المستخدم',
            6 => 'مراجعة',
        ];
    }

    public function getPersonResultsProperty(): \Illuminate\Support\Collection
    {
        if (strlen($this->personSearch) < 2) {
            return collect();
        }

        return Person::where(function ($q) {
            $q->where('name_ar', 'ilike', "%{$this->personSearch}%")
              ->orWhere('name', 'ilike', "%{$this->personSearch}%")
              ->orWhere('phone', 'like', "%{$this->personSearch}%")
              ->orWhere('national_id', 'like', "%{$this->personSearch}%");
        })->limit(10)->get(['id', 'name', 'name_ar', 'phone', 'national_id']);
    }

    public function selectPerson(int $id): void
    {
        $person = Person::findOrFail($id);
        $this->personId = $person->id;
        $this->personNameAr = $person->name_ar ?? '';
        $this->personName = $person->name ?? '';
        $this->personPhone = $person->phone ?? '';
        $this->personNationalId = $person->national_id ?? '';
        $this->personDateOfBirth = $person->date_of_birth?->toDateString() ?? '';
        $this->personGender = $person->gender ?? '';
        $this->createNewPerson = false;
        $this->personSearch = '';
    }

    public function toggleCreateNew(): void
    {
        $this->createNewPerson = !$this->createNewPerson;
        if ($this->createNewPerson) {
            $this->personId = null;
        }
    }

    public function rulesForStep(int $step): array
    {
        return match ($step) {
            1 => $this->createNewPerson ? [
                'personNameAr' => 'required|string|max:255',
                'personPhone' => 'required|string|max:20',
                'personGender' => 'required|in:male,female',
            ] : [
                'personId' => 'required|exists:people,id',
            ],
            2 => [
                'department' => 'required|string|max:100',
                'position' => 'required|string|max:100',
                'employmentType' => 'required|in:' . implode(',', array_column(EmploymentType::cases(), 'value')),
                'startDate' => 'required|date',
                'endDate' => 'nullable|date|after:startDate',
            ],
            3 => [
                // exists:branches,id is a raw query and would accept any branch
                // in the academy, dropdown or no dropdown. Only a branch the
                // user may actually pick counts.
                'branchId' => ['required', Rule::in($this->selectableBranches()->pluck('id')->all())],
                // managerId had no rule at all, so any employee id in the
                // academy was accepted — including a manager at another branch.
                // employees is shared-scoped, so the manager may be this
                // branch's or academy-wide, and nobody else's.
                'managerId' => ['nullable', Rule::exists('employees', 'id')->where(
                    fn ($q) => $q
                        ->where('academy_id', app('current_academy')->id)
                        ->whereNull('deleted_at')
                        ->where(fn ($w) => $w
                            ->where('branch_id', $this->selectedBranchId())
                            ->orWhereNull('branch_id'))
                )],
            ],
            4 => [
                'salaryAmount' => 'required|numeric|min:0',
                'salaryFrequency' => 'required|in:' . implode(',', array_column(SalaryFrequency::cases(), 'value')),
                'workingHoursPerWeek' => 'nullable|numeric|min:1|max:168',
            ],
            5 => $this->createUserAccount ? [
                'userEmail' => 'required|email|max:255|unique:users,email',
                'userPassword' => 'required|string|min:8',
                'userPasswordConfirmation' => 'required|same:userPassword',
                // Same shape of hole as the branch one: exists:roles,id took any
                // role in the academy, including one above the creator's own
                // level, while the dropdown showed only the ones below it.
                'userRoleId' => ['required', Rule::in($this->assignableRoles()->pluck('id')->all())],
            ] : [],
            default => [],
        };
    }

    public function messagesForStep(int $step): array
    {
        return match ($step) {
            1 => [
                'personId.required' => 'يجب اختيار شخص من القائمة',
                'personId.exists' => 'الشخص المختار غير موجود',
                'personNameAr.required' => 'الاسم بالعربي مطلوب',
                'personPhone.required' => 'رقم الهاتف مطلوب',
                'personGender.required' => 'الجنس مطلوب',
                'personGender.in' => 'قيمة الجنس غير صالحة',
            ],
            2 => [
                'department.required' => 'القسم مطلوب',
                'position.required' => 'المسمى الوظيفي مطلوب',
                'employmentType.required' => 'نوع التوظيف مطلوب',
                'employmentType.in' => 'نوع التوظيف غير صالح',
                'startDate.required' => 'تاريخ البدء مطلوب',
                'startDate.date' => 'تاريخ البدء غير صالح',
                'endDate.after' => 'تاريخ الانتهاء يجب أن يكون بعد تاريخ البدء',
            ],
            3 => [
                'branchId.required' => 'يجب اختيار الفرع',
                'branchId.in' => 'الفرع المختار غير متاح',
                'managerId.exists' => 'المسؤول المختار غير متاح في هذا الفرع',
            ],
            4 => [
                'salaryAmount.required' => 'مبلغ الراتب مطلوب',
                'salaryAmount.numeric' => 'مبلغ الراتب يجب أن يكون رقم',
                'salaryAmount.min' => 'مبلغ الراتب لا يمكن أن يكون سالب',
                'salaryFrequency.required' => 'دورية الراتب مطلوبة',
                'salaryFrequency.in' => 'دورية الراتب غير صالحة',
                'workingHoursPerWeek.numeric' => 'ساعات العمل يجب أن تكون رقم',
                'workingHoursPerWeek.min' => 'ساعات العمل يجب أن تكون أكثر من صفر',
                'workingHoursPerWeek.max' => 'ساعات العمل لا يمكن أن تتجاوز 168 ساعة',
            ],
            5 => [
                'userEmail.required' => 'البريد الإلكتروني مطلوب',
                'userEmail.email' => 'صيغة البريد الإلكتروني غير صحيحة',
                'userEmail.unique' => 'البريد الإلكتروني مستخدم بالفعل',
                'userPassword.required' => 'كلمة المرور مطلوبة',
                'userPassword.min' => 'كلمة المرور يجب أن تكون 8 أحرف على الأقل',
                'userPasswordConfirmation.required' => 'تأكيد كلمة المرور مطلوب',
                'userPasswordConfirmation.same' => 'تأكيد كلمة المرور غير متطابق',
                'userRoleId.required' => 'يجب اختيار الصلاحيات',
                'userRoleId.in' => 'الصلاحيات المختارة غير متاحة',
            ],
            default => [],
        };
    }

    public function nextStep(): void
    {
        // Guarded: step 5 has no rules at all when the operator skips the user
        // account, and Livewire's validate() throws MissingRulesException on an
        // empty ruleset rather than passing it.
        $rules = $this->rulesForStep($this->currentStep);

        if (! empty($rules)) {
            $this->validate($rules, $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
    {
        // currentStep is a plain public property, so the browser can set it to
        // 6 and call confirm() without nextStep() ever running — which is how
        // branchId, managerId and userRoleId reached the write unvalidated.
        // Re-validate every step here, where the record is actually created.
        foreach ([1, 2, 3, 4, 5] as $step) {
            $rules = $this->rulesForStep($step);

            if (! empty($rules)) {
                $this->validate($rules, $this->messagesForStep($step));
            }
        }

        try {
            DB::transaction(function () {
                $person = $this->resolveOrCreatePerson();

                $data = [
                    'academy_id' => app('current_academy')->id,
                    'person_id' => $person->id,
                    'department' => $this->department,
                    'position' => $this->position,
                    'employment_type' => $this->employmentType,
                    'start_date' => $this->startDate,
                    'end_date' => $this->endDate ?: null,
                    'branch_id' => $this->branchId,
                    'manager_id' => $this->managerId,
                    'salary_amount' => (int) round((float) $this->salaryAmount * 100),
                    'salary_frequency' => $this->salaryFrequency,
                    'working_hours_per_week' => $this->workingHoursPerWeek ?: null,
                ];

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

                if ($this->createUserAccount) {
                    $user = User::create([
                        'academy_id' => app('current_academy')->id,
                        'name' => $this->personName ?: $this->personNameAr,
                        'name_ar' => $this->personNameAr,
                        'email' => $this->userEmail,
                        'password' => Hash::make($this->userPassword),
                        'role_id' => $this->userRoleId,
                        'person_id' => $person->id,
                        'status' => 'active',
                    ]);

                    app(PersonService::class)->linkUser($person, $user);
                }
            });

            $this->completed = true;
            session()->flash('success', 'تم إضافة الموظف بنجاح');
        } catch (DomainException $e) {
            session()->flash('error', $e->getMessage());
        }
    }

    private function resolveOrCreatePerson(): Person
    {
        if ($this->personId) {
            return Person::findOrFail($this->personId);
        }

        return Person::create([
            'academy_id' => app('current_academy')->id,
            'name_ar' => $this->personNameAr,
            'name' => $this->personName ?: null,
            'phone' => $this->personPhone,
            'national_id' => $this->personNationalId ?: null,
            'date_of_birth' => $this->personDateOfBirth ?: null,
            'gender' => $this->personGender,
            'created_by' => auth()->id(),
        ]);
    }

    /**
     * The roles this operator may hand out — never one at or above their own
     * level. Both the picker and its validation rule read from here, so the two
     * cannot drift apart.
     */
    private function assignableRoles(): \Illuminate\Support\Collection
    {
        $query = Role::orderBy('level', 'desc');

        if (! auth()->user()->is_super_admin) {
            $currentUserLevel = auth()->user()->primaryRole?->level ?? 0;
            $query->where('level', '<', $currentUserLevel);
        }

        return $query->get(['id', 'name_ar', 'slug', 'level']);
    }

    /**
     * The branch the wizard is filing into, once it has been checked against
     * what this operator may actually pick.
     *
     * $branchId is a plain public property, so the value arriving from the
     * browser is a request, not a fact.
     */
    private function selectedBranchId(): ?int
    {
        return $this->branchId && $this->selectableBranches()->contains('id', $this->branchId)
            ? (int) $this->branchId
            : null;
    }

    /**
     * Employees who may be picked as this one's manager: the target branch's
     * staff plus the academy-wide ones.
     */
    private function managerCandidates(): \Illuminate\Support\Collection
    {
        $branchId = $this->selectedBranchId();

        if (! $branchId) {
            return collect();
        }

        return \App\Domain\HR\Models\Employee::query()
            ->where(fn ($q) => $q->where('employees.branch_id', $branchId)
                ->orWhereNull('employees.branch_id'))
            ->with('person')
            ->get();
    }

    public function render()
    {
        return view('livewire.hr.create-employee-wizard', [
            'stepLabels' => $this->getStepLabels(),
            'branches' => $this->selectableBranches(),
            // The manager picker. Narrowed by hand rather than with forBranch()
            // — that helper lifts BranchScope and then demands branch_id = :id,
            // which on a shared table hides every academy-wide manager, the
            // opposite of what this list needs. And the branch is read through
            // selectedBranchId(), because $branchId is a public property: a
            // hand-set ?branchId= would otherwise list another branch's staff.
            'employees' => $this->managerCandidates(),
            'employmentTypes' => EmploymentType::cases(),
            'salaryFrequencies' => SalaryFrequency::cases(),
            'roles' => $this->assignableRoles(),
        ]);
    }
}
