<?php

namespace App\Livewire\Portal;

use App\Domain\Compliance\Enums\ConsentType;
use App\Domain\Compliance\Services\ConsentService;
use App\Domain\Identity\Models\Branch;
use App\Domain\Identity\Services\PhoneVerificationService;
use App\Domain\Portal\Services\PortalRegistrationService;
use App\Domain\Shared\Exceptions\DomainException;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use Illuminate\Validation\ValidationException;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Locked;
use Livewire\Attributes\Title;
use Livewire\Component;

/**
 * Public signup: guardian details, phone verification, child, consent.
 *
 * Reachable only where an academy has turned self-registration on. The form is
 * four steps because the phone must be proven before anything is written, and
 * a member who abandons after step two must leave nothing behind.
 *
 * Note what is `#[Locked]` and what is not. The step counter and the verified
 * flag are server state — in Livewire v4 a plain public property is settable
 * from the browser, so an unlocked `$verified` would be a signup form with an
 * optional verification step.
 */
#[Layout('layouts.portal-public')]
#[Title('إنشاء حساب')]
class PortalSignUp extends Component
{
    #[Locked]
    public int $step = 1;

    #[Locked]
    public bool $codeSent = false;

    #[Locked]
    public bool $verified = false;

    public string $guardianName = '';

    public string $phone = '';

    public string $email = '';

    public string $password = '';

    public string $passwordConfirmation = '';

    public string $code = '';

    public string $childName = '';

    public string $childDateOfBirth = '';

    public string $childGender = '';

    public ?int $branchId = null;

    /** @var array<string, bool> */
    public array $consents = [];

    public function mount(): void
    {
        foreach (ConsentType::cases() as $type) {
            $this->consents[$type->value] = $type->isRequired() ? false : false;
        }
    }

    public function sendCode(PhoneVerificationService $verification): void
    {
        $this->validate([
            'guardianName' => ['required', 'string', 'min:3', 'max:120'],
            'phone' => ['required', 'string', 'min:10', 'max:20'],
            'email' => ['nullable', 'email', 'max:255'],
            'password' => ['required', 'string', 'min:8', 'max:200', 'same:passwordConfirmation'],
        ], [
            'guardianName.required' => __('الاسم مطلوب'),
            'guardianName.min' => __('الاسم قصير جداً'),
            'phone.required' => __('رقم الهاتف مطلوب'),
            'password.required' => __('كلمة المرور مطلوبة'),
            'password.min' => __('كلمة المرور يجب ألا تقل عن ٨ أحرف'),
            'password.same' => __('تأكيد كلمة المرور غير مطابق'),
        ]);

        try {
            $verification->send(app('current_academy')->id, $this->phone, 'registration', request()->ip());
        } catch (DomainException $e) {
            $this->addError('phone', $e->getMessage());

            return;
        }

        $this->codeSent = true;
        $this->step = 2;
    }

    public function verifyCode(PhoneVerificationService $verification): void
    {
        $this->validate(
            ['code' => ['required', 'digits:6']],
            ['code.required' => __('أدخل رمز التحقق'), 'code.digits' => __('الرمز مكوّن من ٦ أرقام')],
        );

        if (! $verification->verify(app('current_academy')->id, $this->phone, $this->code)) {
            // One message for every failure — wrong, expired, or too many
            // attempts. Telling them apart tells someone guessing which it was.
            $this->addError('code', __('الرمز غير صحيح أو انتهت صلاحيته'));

            return;
        }

        $this->verified = true;
        $this->step = 3;
    }

    /**
     * Step three: who the member is, and which branch they join.
     *
     * `branchId` is the one field here that decides an isolation boundary. It
     * used to be `nullable|integer`, which is no check at all on a guest route:
     * there is no branch context to correct a wrong value afterwards, so a
     * crafted payload filed a brand-new participant into a closed branch, a
     * soft-deleted one, or — on a shared installation — a branch belonging to a
     * different academy, and that participant then appeared on that branch's
     * rosters, dashboards and pickers.
     *
     * @return array<string, array<int, mixed>>
     */
    private function memberRules(): array
    {
        return [
            'childName' => ['required', 'string', 'min:3', 'max:120'],
            'childDateOfBirth' => ['required', 'date', 'before:today'],
            'childGender' => ['required', 'in:male,female'],
            'branchId' => [
                'nullable',
                'integer',
                Rule::exists('branches', 'id')
                    ->where('academy_id', app('current_academy')->id)
                    ->where('is_active', true)
                    ->whereNull('deleted_at'),
            ],
        ];
    }

    /** @return array<string, string> */
    private function memberMessages(): array
    {
        return [
            'childName.required' => __('اسم العضو مطلوب'),
            'childDateOfBirth.required' => __('تاريخ الميلاد مطلوب'),
            'childDateOfBirth.before' => __('تاريخ الميلاد يجب أن يكون في الماضي'),
            'childGender.required' => __('النوع مطلوب'),
            'branchId.exists' => __('الفرع المختار غير متاح'),
        ];
    }

    public function goToConsents(): void
    {
        $this->validate($this->memberRules(), $this->memberMessages());

        $this->step = 4;
    }

    public function submit(PortalRegistrationService $registration, ConsentService $consents): void
    {
        abort_unless($this->verified, 403);

        // Step three is validated again here rather than trusted from the
        // round-trip that ran it. `$step` is #[Locked], but `childName`,
        // `childDateOfBirth`, `childGender` and `branchId` are not — they are
        // plain public properties, which in Livewire v4 means the browser can
        // change them between goToConsents() passing and this call. Sending
        // the member back to step three is what makes the errors visible: the
        // consent step does not render those fields.
        try {
            $this->validate($this->memberRules(), $this->memberMessages());
        } catch (ValidationException $e) {
            $this->step = 3;

            throw $e;
        }

        foreach (ConsentType::required() as $type) {
            if (! ($this->consents[$type->value] ?? false)) {
                $this->addError('consents', __('لا يمكن إنشاء الحساب دون الموافقة على: ') . $type->label());

                return;
            }
        }

        try {
            ['user' => $user, 'participant' => $participant] = $registration->register([
                'guardian_name' => $this->guardianName,
                'phone' => $this->phone,
                'email' => $this->email ?: null,
                'password' => $this->password,
                'child_name' => $this->childName,
                'child_date_of_birth' => $this->childDateOfBirth,
                'child_gender' => $this->childGender,
                'branch_id' => $this->branchId,
            ], app('current_academy')->id, request()->ip());
        } catch (DomainException $e) {
            $this->addError('phone', $e->getMessage());

            return;
        }

        Auth::login($user, remember: true);
        request()->session()->regenerate();

        $consents->recordMany($this->consents, $user, $participant, request()->ip(), request()->userAgent());

        session()->flash('success', __('تم إنشاء حسابك. تسجيل العضو قيد المراجعة من الأكاديمية.'));

        $this->redirect(route('portal.home'), navigate: false);
    }

    public function render()
    {
        return view('livewire.portal.portal-sign-up', [
            'branches' => Branch::where('is_active', true)->get(),
            'consentTypes' => ConsentType::cases(),
        ]);
    }
}
