<?php

namespace App\Tests\Staff;

use App\Auth\Repository\UserRepository;
use App\Doctor\Entity\Doctor;
use App\Shared\Constant\ErrorCodes;
use App\Shared\Exception\AppException;
use App\Staff\Entity\ClinicStaff;
use App\Staff\Repository\ClinicStaffRepository;
use App\Staff\Service\StaffAccountService;
use App\Tests\ApiTestCase;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;

/**
 * ساخت/اتصال/قطع حساب کاربری پرسنل: یک ردیف ClinicStaff به یک User با
 * ROLE_STAFF وصل می‌شود بدون اینکه کاربر موجود آسیب ببیند.
 */
class StaffAccountServiceTest extends ApiTestCase
{
    /** سرویس تک‌مصرفه است و کانتینر inline‌اش می‌کند، پس با وابستگی‌های واقعی ساخته می‌شود. */
    private function service(): StaffAccountService
    {
        return new StaffAccountService(
            static::getContainer()->get(UserRepository::class),
            static::getContainer()->get(ClinicStaffRepository::class),
            static::getContainer()->get(UserPasswordHasherInterface::class),
        );
    }

    private function newStaff(int $entityId, string $name = 'زهرا احمدی'): ClinicStaff
    {
        $staff = new ClinicStaff('doctor', $entityId, $name);
        $this->em->persist($staff);
        $this->em->flush();

        return $staff;
    }

    private function newDoctorOwner(): Doctor
    {
        $owner  = $this->createUser(['ROLE_DOCTOR']);
        $doctor = new Doctor($owner, 'دکتر تست');
        $this->em->persist($doctor);
        $this->em->flush();

        return $doctor;
    }

    public function testCreatesNewUserWithStaffRole(): void
    {
        $doctor = $this->newDoctorOwner();
        $staff  = $this->newStaff($doctor->getId());
        $mobile = '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT);

        $user = $this->service()->attachAccount($staff, $mobile, 'Staff@1234', $doctor->getUser());

        self::assertContains('ROLE_STAFF', $user->getRoles());
        self::assertSame($mobile, $user->getMobileNumber());
        self::assertSame('زهرا احمدی', $user->getRealName());
        self::assertTrue($staff->hasAccount());
        self::assertSame($user->getId(), $staff->getUser()?->getId());
        self::assertSame($mobile, $staff->getPhone());

        $hasher = static::getContainer()->get(UserPasswordHasherInterface::class);
        self::assertTrue($hasher->isPasswordValid($user, 'Staff@1234'));
    }

    /** کاربر موجود فقط نقش می‌گیرد؛ رمز و نامش پاک نمی‌شود. */
    public function testReusesExistingUserAndKeepsPassword(): void
    {
        $doctor   = $this->newDoctorOwner();
        $existing = $this->createUser(['ROLE_USER']);
        $existing->setRealName('نام قبلی');
        $hasher = static::getContainer()->get(UserPasswordHasherInterface::class);
        $existing->setPasswordHash($hasher->hashPassword($existing, 'Old@12345'));
        $this->em->flush();

        $staff = $this->newStaff($doctor->getId());
        $user  = $this->service()->attachAccount($staff, $existing->getMobileNumber(), null, $doctor->getUser());

        self::assertSame($existing->getId(), $user->getId());
        self::assertContains('ROLE_STAFF', $user->getRoles());
        self::assertContains('ROLE_USER', $user->getRoles());
        self::assertSame('نام قبلی', $user->getRealName());
        self::assertTrue($hasher->isPasswordValid($user, 'Old@12345'));
    }

    public function testRejectsInvalidMobile(): void
    {
        $doctor = $this->newDoctorOwner();
        $staff  = $this->newStaff($doctor->getId());

        $this->expectException(AppException::class);
        $this->expectExceptionMessage(ErrorCodes::message(ErrorCodes::ERR_STAFF_MOBILE_INVALID));

        $this->service()->attachAccount($staff, '12345', 'Staff@1234', $doctor->getUser());
    }

    public function testRejectsOwnerMobile(): void
    {
        $doctor = $this->newDoctorOwner();
        $staff  = $this->newStaff($doctor->getId());

        try {
            $this->service()->attachAccount($staff, $doctor->getUser()->getMobileNumber(), null, $doctor->getUser());
            self::fail('owner mobile must be rejected');
        } catch (AppException $e) {
            self::assertSame(ErrorCodes::ERR_STAFF_MOBILE_INVALID, $e->getErrorCode());
            self::assertSame(422, $e->getHttpStatus());
        }
    }

    /** مرزی: همان شماره، همان محیط، ردیف دوم → 409. */
    public function testRejectsDuplicateMobileInSameEntity(): void
    {
        $doctor = $this->newDoctorOwner();
        $first  = $this->newStaff($doctor->getId(), 'پرسنل اول');
        $second = $this->newStaff($doctor->getId(), 'پرسنل دوم');
        $mobile = '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT);

        $this->service()->attachAccount($first, $mobile, null, $doctor->getUser());

        try {
            $this->service()->attachAccount($second, $mobile, null, $doctor->getUser());
            self::fail('duplicate mobile in the same entity must be rejected');
        } catch (AppException $e) {
            self::assertSame(ErrorCodes::ERR_STAFF_MOBILE_TAKEN, $e->getErrorCode());
            self::assertSame(409, $e->getHttpStatus());
        }
    }

    /** ارقام فارسی از هر کلاینتی بیاید، شمارهٔ ذخیره‌شده لاتین است. */
    public function testNormalizesPersianDigits(): void
    {
        $doctor = $this->newDoctorOwner();
        $staff  = $this->newStaff($doctor->getId());

        $user = $this->service()->attachAccount($staff, '۰۹۱۲۳۴۵۶۷۸۹', null, $doctor->getUser());

        self::assertSame('09123456789', $user->getMobileNumber());
    }

    /** تغییر شماره یعنی تغییر نام‌کاربری ورود؛ ردیف پرسنل همان می‌ماند. */
    public function testReattachWithNewMobileMovesTheAccount(): void
    {
        $doctor = $this->newDoctorOwner();
        $staff  = $this->newStaff($doctor->getId());
        $first  = '0912' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT);
        $second = '0913' . str_pad((string) random_int(0, 9_999_999), 7, '0', STR_PAD_LEFT);

        $this->service()->attachAccount($staff, $first, null, $doctor->getUser());
        $user = $this->service()->attachAccount($staff, $second, null, $doctor->getUser());

        self::assertSame($second, $user->getMobileNumber());
        self::assertSame($second, $staff->getPhone());
        self::assertSame($user->getId(), $staff->getUser()?->getId());
    }
}
