<?php

namespace App\Tests\Appointment;

use App\Appointment\Entity\Appointment;
use App\Auth\Entity\User;
use App\Doctor\Entity\Doctor;
use App\Shared\Constant\ErrorCodes;
use App\Tests\ApiTestCase;
use App\UserProfile\Entity\UserProfile;

/**
 * POST /api/v1/my/appointment must identify the patient by national code.
 * National code is stored on the patient's UserProfile (profiles.national_code,
 * unique), so the patient User — and thus the case-file — is resolved by that
 * profile first. One person keeps a single record even when booked under
 * several mobiles.
 */
class AppointmentNationalCodeTest extends ApiTestCase
{
    /** @return array{0: User, 1: Doctor} */
    private function doctor(): array
    {
        $owner  = $this->createUser(['ROLE_DOCTOR']);
        $doctor = new Doctor($owner, 'دکتر');
        $this->em->persist($doctor);
        $this->em->flush();

        return [$owner, $doctor];
    }

    private function nationalCode(): string
    {
        return '00' . str_pad((string) random_int(0, 99_999_999), 8, '0', STR_PAD_LEFT);
    }

    private function mobile(): string
    {
        return '09' . str_pad((string) random_int(0, 999_999_999), 9, '0', STR_PAD_LEFT);
    }

    private function body(string $doctorUuid, string $mobile, string $nationalCode): array
    {
        $start = time() + 86_400 + random_int(0, 3_600) * 100;

        return [
            'doctor_uuid'           => $doctorUuid,
            'slot_start'            => $start,
            'slot_end'              => $start + 1_800,
            'patient_mobile'        => $mobile,
            'patient_name'          => 'بیمار تست',
            'patient_national_code' => $nationalCode,
        ];
    }

    private function profileByNationalCode(string $nc): ?UserProfile
    {
        return $this->em->getRepository(UserProfile::class)->findOneBy(['nationalCode' => $nc]);
    }

    public function testStoresNationalCodeOnProfile(): void
    {
        [$owner, $doctor] = $this->doctor();
        $nc     = $this->nationalCode();
        $mobile = $this->mobile();

        $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $mobile, $nc));

        self::assertSame(201, $this->responseCode());
        $profile = $this->profileByNationalCode($nc);
        self::assertNotNull($profile);
        self::assertSame($mobile, $profile->getUser()->getMobileNumber());
    }

    public function testSameNationalCodeDifferentMobileReusesSinglePatient(): void
    {
        [$owner, $doctor] = $this->doctor();
        $nc = $this->nationalCode();

        // First booking under mobile A creates the patient + profile.
        $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $this->mobile(), $nc));
        self::assertSame(201, $this->responseCode());

        // Second booking under a *different* mobile but the same national code
        // must resolve to the same patient — the case-file stays unique.
        $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $this->mobile(), $nc));
        self::assertSame(201, $this->responseCode());

        $profiles = $this->em->getRepository(UserProfile::class)->findBy(['nationalCode' => $nc]);
        self::assertCount(1, $profiles);
    }

    /**
     * The reported bug: a patient already exists whose national code sits on
     * their profile. Booking under a brand-new mobile with that same national
     * code must attach to the existing patient, not stamp the code onto a new
     * user.
     */
    public function testExistingProfileNationalCodeIsReusedNotDuplicated(): void
    {
        [$owner, $doctor] = $this->doctor();
        $nc = $this->nationalCode();

        // Existing patient with the national code on their profile.
        $existing = $this->createUser(['ROLE_USER'], $this->mobile());
        $profile  = new UserProfile($existing);
        $profile->setNationalCode($nc);
        $this->em->persist($profile);
        $this->em->flush();

        // Book under a different mobile but the same national code.
        $res = $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $this->mobile(), $nc));
        self::assertSame(201, $this->responseCode());

        // No duplicate profile, and the appointment is attached to the existing user.
        $profiles = $this->em->getRepository(UserProfile::class)->findBy(['nationalCode' => $nc]);
        self::assertCount(1, $profiles);

        $appointment = $this->em->getRepository(Appointment::class)->findOneBy(['uuid' => $res['data']['uuid']]);
        self::assertSame($existing->getId(), $appointment->getUser()->getId());
    }

    public function testMissingNationalCodeIs422(): void
    {
        [$owner, $doctor] = $this->doctor();
        $body = $this->body($doctor->getUuid(), $this->mobile(), $this->nationalCode());
        unset($body['patient_national_code']);

        $this->authJson('POST', '/api/v1/my/appointment', $owner, $body);

        self::assertSame(422, $this->responseCode());
    }

    public function testInvalidNationalCodeIs422(): void
    {
        [$owner, $doctor] = $this->doctor();

        $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $this->mobile(), '123'));

        self::assertSame(422, $this->responseCode());
    }

    public function testMobileBelongingToAnotherNationalCodeIsRejected(): void
    {
        [$owner, $doctor] = $this->doctor();
        $mobile = $this->mobile();

        // First booking binds this mobile to national code A.
        $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $mobile, $this->nationalCode()));
        self::assertSame(201, $this->responseCode());

        // Same mobile, a *different* national code → identity conflict.
        $res = $this->authJson('POST', '/api/v1/my/appointment', $owner, $this->body($doctor->getUuid(), $mobile, $this->nationalCode()));
        self::assertSame(422, $this->responseCode());
        self::assertSame(ErrorCodes::ERR_PROFILE_MOBILE_TAKEN, $res['errors'][0]['code']);
    }
}
