<?php

namespace App\Staff\Service;

use App\Auth\Entity\User;
use App\Auth\Repository\UserRepository;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;
use App\Shared\Util\PersianText;
use App\Staff\Entity\ClinicStaff;
use App\Staff\Repository\ClinicStaffRepository;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

/**
 * تنها نقطهٔ ساخت/اتصال/قطع حساب کاربری پرسنل.
 *
 * پرسنل مثل منشی یک `User` است که با شمارهٔ موبایل وارد پنل می‌شود؛ تفاوتش این
 * است که رابطهٔ او با محیط، همان ردیف `ClinicStaff` است (نه یک جدول واسط جدا).
 * قرینهٔ {@see \App\Secretary\Service\SecretaryService::resolveSecretaryUser()}.
 */
class StaffAccountService
{
    public function __construct(
        private readonly UserRepository              $userRepo,
        private readonly ClinicStaffRepository       $staffRepo,
        private readonly UserPasswordHasherInterface $hasher,
    ) {}

    /**
     * حساب ورود پرسنل را می‌سازد یا به کاربر موجودِ همان موبایل وصل می‌کند و
     * ROLE_STAFF می‌دهد. شمارهٔ نرمال‌شده روی خود ردیف پرسنل هم ذخیره می‌شود تا
     * «شمارهٔ تماس» و «نام کاربری ورود» یکی بمانند.
     *
     * @param User $owner کاربرِ مالکِ محیط (پزشک/کلینیک) که این پرسنل را ثبت می‌کند
     *
     * @throws AppException ERR_STAFF_MOBILE_INVALID | ERR_STAFF_MOBILE_TAKEN
     */
    public function attachAccount(ClinicStaff $staff, ?string $mobile, ?string $password, User $owner): User
    {
        $mobile = $this->normalizeMobile($mobile);

        if ($mobile === $owner->getMobileNumber()) {
            throw new AppException(
                ErrorCodes::ERR_STAFF_MOBILE_INVALID,
                'شمارهٔ مالک نمی‌تواند به‌عنوان پرسنل ثبت شود',
                422,
                'phone',
            );
        }

        $duplicate = $this->staffRepo->findByEntityAndPhone($staff->getEntityType(), $staff->getEntityId(), $mobile);
        if ($duplicate !== null && $duplicate->getId() !== $staff->getId()) {
            throw new AppException(ErrorCodes::ERR_STAFF_MOBILE_TAKEN, null, 409, 'phone');
        }

        $user = $this->userRepo->findByMobile($mobile) ?? new User($mobile);

        // رمز خالی روی کاربر موجود، رمز فعلی‌اش را پاک نمی‌کند؛ کاربر تازه‌ساخته هم
        // بدون رمز می‌ماند و باید از «فراموشی رمز» استفاده کند.
        if ($password !== null && $password !== '') {
            $user->setPasswordHash($this->hasher->hashPassword($user, $password));
        }
        if ($user->getRealName() === null || $user->getRealName() === '') {
            $user->setRealName($staff->getFullName());
        }
        $user->addRole('ROLE_STAFF');
        $this->userRepo->save($user);

        $staff->setUser($user)->setPhone($mobile);
        $this->staffRepo->save($staff);

        return $user;
    }

    private function normalizeMobile(?string $mobile): string
    {
        $normalized = preg_replace('/\D+/', '', PersianText::digits((string) $mobile)) ?? '';

        if (!preg_match('/^09\d{9}$/', $normalized)) {
            throw new AppException(ErrorCodes::ERR_STAFF_MOBILE_INVALID, null, 422, 'phone');
        }

        return $normalized;
    }
}
