<?php

namespace App\Tests\Patient;

use App\Appointment\Entity\Appointment;
use App\Auth\Entity\User;
use App\Auth\Repository\UserActiveContextRepository;
use App\Clinic\Entity\Clinic;
use App\Clinic\Repository\ClinicDoctorPermissionRepository;
use App\Doctor\Entity\Doctor;
use App\Patient\Entity\PatientRecord;
use App\Secretary\Entity\DoctorSecretary;
use App\Shared\Context\EntityContext;
use App\Tests\ApiTestCase;

/**
 * پروندهٔ کلینیکی per-بیمار است (یکتایی clinic+user) و مدیر کلینیک همه را می‌بیند.
 * پزشکِ عضو هم باید پرونده‌های بیمارانِ خودش در همان کلینیک را ببیند — رابطه از
 * نوبت‌های همان پزشک در همان کلینیک می‌آید، نه از ستونی روی پرونده.
 *
 * با غیرفعال شدن پزشک، دسترسی‌اش قطع می‌شود ولی پرونده‌ها دست‌نخورده می‌مانند.
 */
class ClinicRecordAccessTest extends ApiTestCase
{
    private function makeDoctor(string $name = 'دکتر تست'): Doctor
    {
        $user   = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
        $doctor = new Doctor($user, $name);
        $doctor->setMobileNumber($user->getMobileNumber());
        $this->em->persist($doctor);
        $this->em->flush();

        return $doctor;
    }

    /** @return array{0: User, 1: Clinic} */
    private function makeClinicWith(Doctor ...$doctors): array
    {
        $owner  = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
        $clinic = new Clinic($owner);
        $clinic->setName('کلینیک تست');
        foreach ($doctors as $d) {
            $clinic->getDoctors()->add($d);
        }
        $this->em->persist($clinic);
        $this->em->flush();

        return [$owner, $clinic];
    }

    private function activeContext(User $user, string $dbUuid, string $dbType): void
    {
        static::getContainer()->get(UserActiveContextRepository::class)->upsert($user, $dbUuid, $dbType);
    }

    /** پروندهٔ کلینیکی بیمار + نوبتی که او را به این پزشک وصل می‌کند. */
    private function makeClinicRecordFor(Clinic $clinic, Doctor $doctor, ?User $patient = null): PatientRecord
    {
        $patient ??= $this->createUser();

        $record = new PatientRecord('clinic', $clinic->getId(), $patient, 'system', $clinic->getId());
        $this->em->persist($record);

        $start = strtotime('+60 days') + random_int(0, 500_000) * 7;
        $appointment = $this->newAppointment($doctor, $patient, $start, $start + 900, $clinic);
        $this->em->persist($appointment);

        $this->em->flush();

        return $record;
    }

    private function uuidsFromList(array $response): array
    {
        return array_map(fn(array $row) => $row['uuid'], $response['data'] ?? []);
    }

    // ── پزشک عضو ─────────────────────────────────────────────────────────────

    public function testMemberDoctorSeesOwnPatientsClinicRecord(): void
    {
        $doctor     = $this->makeDoctor();
        [, $clinic] = $this->makeClinicWith($doctor);
        $record     = $this->makeClinicRecordFor($clinic, $doctor);

        $this->activeContext($doctor->getUser(), $clinic->getUuid(), EntityContext::TYPE_CLINIC);

        $res = $this->authJson('GET', '/api/v1/patients?limit=50', $doctor->getUser());

        self::assertSame(200, $this->responseCode());
        self::assertContains($record->getUuid(), $this->uuidsFromList($res));
    }

    public function testMemberDoctorCannotSeeAnotherDoctorsClinicRecord(): void
    {
        $mine       = $this->makeDoctor('پزشک من');
        $theirs     = $this->makeDoctor('پزشک دیگر');
        [, $clinic] = $this->makeClinicWith($mine, $theirs);
        $foreign    = $this->makeClinicRecordFor($clinic, $theirs);

        $this->activeContext($mine->getUser(), $clinic->getUuid(), EntityContext::TYPE_CLINIC);

        $res = $this->authJson('GET', '/api/v1/patients?limit=50', $mine->getUser());
        self::assertNotContains($foreign->getUuid(), $this->uuidsFromList($res));

        $this->authJson('GET', "/api/v1/patient/{$foreign->getUuid()}", $mine->getUser());
        self::assertSame(404, $this->responseCode(), 'پروندهٔ بیمارِ پزشک دیگر برای او وجود ندارد');
    }

    public function testMemberDoctorCanOpenAndManageOwnPatientRecord(): void
    {
        $doctor     = $this->makeDoctor();
        [, $clinic] = $this->makeClinicWith($doctor);
        $record     = $this->makeClinicRecordFor($clinic, $doctor);

        $this->activeContext($doctor->getUser(), $clinic->getUuid(), EntityContext::TYPE_CLINIC);

        $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $doctor->getUser());
        self::assertSame(200, $this->responseCode());

        $this->authJson('POST', "/api/v1/patient/{$record->getUuid()}/note", $doctor->getUser(), [
            'body' => 'یادداشت پزشک عضو',
        ]);
        self::assertSame(201, $this->responseCode(), 'پزشک عضو فعال پرونده را مدیریت هم می‌کند');
    }

    public function testDoctorInPersonalContextSeesOnlyOwnOfficeRecords(): void
    {
        $doctor       = $this->makeDoctor();
        [, $clinic]   = $this->makeClinicWith($doctor);
        $clinicRecord = $this->makeClinicRecordFor($clinic, $doctor);

        // بدون محیط فعالِ کلینیک ⇒ مطب شخصی.
        $this->activeContext($doctor->getUser(), $doctor->getUuid(), EntityContext::TYPE_DOCTOR);

        $res = $this->authJson('GET', '/api/v1/patients?limit=50', $doctor->getUser());

        self::assertSame(200, $this->responseCode());
        self::assertNotContains($clinicRecord->getUuid(), $this->uuidsFromList($res));
    }

    // ── پایان همکاری ─────────────────────────────────────────────────────────

    public function testDeactivatedDoctorLosesClinicRecordsButOwnerKeepsThem(): void
    {
        $doctor          = $this->makeDoctor();
        [$owner, $clinic] = $this->makeClinicWith($doctor);
        $record          = $this->makeClinicRecordFor($clinic, $doctor);

        $this->activeContext($doctor->getUser(), $clinic->getUuid(), EntityContext::TYPE_CLINIC);

        $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $doctor->getUser());
        self::assertSame(200, $this->responseCode(), 'پزشک فعال دسترسی دارد');

        $permissions = static::getContainer()->get(ClinicDoctorPermissionRepository::class);
        $permissions->getOrCreate($clinic, $doctor)->setActive(false);
        $this->em->flush();

        $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $doctor->getUser());
        self::assertSame(404, $this->responseCode(), 'بعد از پایان همکاری، دسترسی قطع می‌شود');

        $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $owner);
        self::assertSame(200, $this->responseCode(), 'مدیر کلینیک دسترسی کامل دارد');

        $this->em->clear();
        self::assertNotNull(
            $this->em->getRepository(PatientRecord::class)->find($record->getId()),
            'پرونده حذف یا منتقل نمی‌شود',
        );
    }

    // ── مدیر کلینیک ──────────────────────────────────────────────────────────

    public function testClinicOwnerSeesEveryDoctorsRecords(): void
    {
        $first            = $this->makeDoctor('پزشک اول');
        $second           = $this->makeDoctor('پزشک دوم');
        [$owner, $clinic] = $this->makeClinicWith($first, $second);
        $firstRecord      = $this->makeClinicRecordFor($clinic, $first);
        $secondRecord     = $this->makeClinicRecordFor($clinic, $second);

        $res   = $this->authJson('GET', '/api/v1/patients?limit=50', $owner);
        $uuids = $this->uuidsFromList($res);

        self::assertSame(200, $this->responseCode());
        self::assertContains($firstRecord->getUuid(), $uuids);
        self::assertContains($secondRecord->getUuid(), $uuids);
    }

    // ── منشی ─────────────────────────────────────────────────────────────────

    public function testClinicSecretaryIsLimitedToAssignedDoctors(): void
    {
        $mine       = $this->makeDoctor('پزشک من');
        $theirs     = $this->makeDoctor('پزشک دیگر');
        [, $clinic] = $this->makeClinicWith($mine, $theirs);
        $ownRecord  = $this->makeClinicRecordFor($clinic, $mine);
        $foreign    = $this->makeClinicRecordFor($clinic, $theirs);

        $secretaryUser = $this->createUser(['ROLE_USER', 'ROLE_SECRETARY']);
        $relation      = new DoctorSecretary($mine, $secretaryUser, $clinic);
        $this->em->persist($relation);
        $this->em->flush();

        $this->activeContext($secretaryUser, $clinic->getUuid(), EntityContext::TYPE_CLINIC);

        $res   = $this->authJson('GET', '/api/v1/patients?limit=50', $secretaryUser);
        $uuids = $this->uuidsFromList($res);

        self::assertSame(200, $this->responseCode());
        self::assertContains($ownRecord->getUuid(), $uuids);
        self::assertNotContains($foreign->getUuid(), $uuids);
    }

    // ── قطعی‌کردن نوبت کلینیکی، دیده‌شده توسط هر دو نقش ───────────────────────

    public function testConfirmedClinicAppointmentRecordIsVisibleToBothRoles(): void
    {
        $doctor           = $this->makeDoctor();
        [$owner, $clinic] = $this->makeClinicWith($doctor);
        $patient          = $this->createUser();

        $start       = strtotime('+70 days') + random_int(0, 500_000) * 7;
        $appointment = $this->newAppointment($doctor, $patient, $start, $start + 900, $clinic);
        $appointment->setVisitPriceRials(3_000_000);
        $this->em->persist($appointment);
        $this->em->flush();

        // محیط فعال را قبل از confirm ست کن: آن درخواست EntityManager را پاک می‌کند.
        $this->activeContext($doctor->getUser(), $clinic->getUuid(), EntityContext::TYPE_CLINIC);

        $this->authJson('POST', "/api/v1/appointment/{$appointment->getUuid()}/confirm", $owner, [
            'version' => $appointment->getVersion(),
        ]);
        self::assertSame(200, $this->responseCode());

        $record = $this->em->getRepository(PatientRecord::class)->findOneBy([
            'entityType' => 'clinic',
            'entityId'   => $clinic->getId(),
            'user'       => $patient,
        ]);
        self::assertNotNull($record);

        $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $owner);
        self::assertSame(200, $this->responseCode(), 'مدیر کلینیک');

        $this->authJson('GET', "/api/v1/patient/{$record->getUuid()}", $doctor->getUser());
        self::assertSame(200, $this->responseCode(), 'پزشکِ همان نوبت');
    }
}
