<?php

namespace App\Tests\Shared;

use App\Auth\Entity\User;
use App\Auth\Entity\UserActiveContext;
use App\Clinic\Entity\Clinic;
use App\Clinic\Entity\ClinicDoctorPermission;
use App\Doctor\Entity\Doctor;
use App\Secretary\Entity\DoctorSecretary;
use App\Shared\Context\EntityContext;
use App\Shared\Context\EntityContextResolver;
use App\Shared\Exception\AppException;
use App\Tests\ApiTestCase;

/**
 * ماتریس نقش‌ها: «این کاربر در کدام محیط کار می‌کند؟»
 *
 * قرارداد اجرایی فازهای بعدیِ نشانه‌گذاری tenant است — هر assert روی
 * toEntityPair() است، نه روی جزئیات داخلی رزولور.
 *
 * رزولور فقط محیط را تعیین می‌کند؛ مجوزها (ClinicDoctorPermission /
 * DoctorSecretary.permission) لایهٔ جداگانه‌اند و اینجا سنجیده نمی‌شوند.
 */
class EntityContextResolverTest extends ApiTestCase
{
    private function resolver(): EntityContextResolver
    {
        return static::getContainer()->get(EntityContextResolver::class);
    }

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

        return $doctor;
    }

    private function makeClinic(?User $owner = null): Clinic
    {
        $clinic = new Clinic($owner ?? $this->createUser(['ROLE_CLINIC']));
        $clinic->setName('کلینیک تست');
        $this->em->persist($clinic);
        $this->em->flush();

        return $clinic;
    }

    private function joinClinic(Clinic $clinic, Doctor $doctor): void
    {
        $clinic->getDoctors()->add($doctor);
        $this->em->persist(new ClinicDoctorPermission($clinic, $doctor));
        $this->em->flush();
    }

    private function setActiveContext(User $user, string $dbUuid, string $dbType): void
    {
        $this->em->persist(new UserActiveContext($user, $dbUuid, $dbType));
        $this->em->flush();
    }

    private function setClinicContext(User $user, Clinic $clinic): void
    {
        $this->setActiveContext($user, $clinic->getUuid(), EntityContext::TYPE_CLINIC);
    }

    private function setDoctorContext(User $user, Doctor $doctor): void
    {
        $this->setActiveContext($user, $doctor->getUuid(), EntityContext::TYPE_DOCTOR);
    }

    // ── سناریو ۱: پزشک مستقل ────────────────────────────────────────────────

    public function testIndependentDoctorAlwaysResolvesToOwnPractice(): void
    {
        $doctor = $this->makeDoctor();

        $context = $this->resolver()->resolve($doctor->getUser());

        self::assertSame(['doctor', $doctor->getId()], $context->toEntityPair());
    }

    // ── سناریو ۲: پزشکِ مستقلِ عضو کلینیک ───────────────────────────────────

    public function testClinicMemberDoctorResolvesToClinicWhenActiveContextIsClinic(): void
    {
        $doctor = $this->makeDoctor();
        $clinic = $this->makeClinic();
        $this->joinClinic($clinic, $doctor);
        $this->setClinicContext($doctor->getUser(), $clinic);

        $context = $this->resolver()->resolve($doctor->getUser());

        self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair());
    }

    public function testClinicMemberDoctorFallsBackToOwnPracticeWhenActiveContextIsSelf(): void
    {
        $doctor = $this->makeDoctor();
        $clinic = $this->makeClinic();
        $this->joinClinic($clinic, $doctor);
        $this->setDoctorContext($doctor->getUser(), $doctor);

        $context = $this->resolver()->resolve($doctor->getUser());

        self::assertSame(['doctor', $doctor->getId()], $context->toEntityPair());
    }

    /**
     * مجوزِ غیرفعال محیط را عوض نمی‌کند — عضویت در clinic_doctors تعیین‌کنندهٔ
     * «کجا»ست و ClinicDoctorPermission تعیین‌کنندهٔ «چه کاری». محدودسازی در
     * PatientRecordScopeResolver اتفاق می‌افتد، نه اینجا.
     */
    public function testInactivePermissionStillResolvesToClinicEnvironment(): void
    {
        $doctor = $this->makeDoctor();
        $clinic = $this->makeClinic();
        $this->joinClinic($clinic, $doctor);

        $permission = $this->em->getRepository(ClinicDoctorPermission::class)
            ->findOneBy(['clinic' => $clinic, 'doctor' => $doctor]);
        $permission->setActive(false);
        $this->em->flush();

        $this->setClinicContext($doctor->getUser(), $clinic);

        $context = $this->resolver()->resolve($doctor->getUser());

        self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair());
    }

    // ── سناریو ۳: پزشکی که هم عضو و هم مالک کلینیک است ──────────────────────

    public function testDoctorWhoOwnsClinicResolvesToClinicWithoutMembershipRow(): void
    {
        $user   = $this->createUser(['ROLE_DOCTOR', 'ROLE_CLINIC']);
        $doctor = $this->makeDoctor($user);
        $clinic = $this->makeClinic($user);
        $this->setClinicContext($user, $clinic);

        $context = $this->resolver()->resolve($user);

        self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair());
        self::assertTrue($this->resolver()->canActInClinic($user, $clinic));
    }

    public function testDoctorWhoOwnsClinicResolvesToOwnPracticeWhenActiveContextIsSelf(): void
    {
        $user   = $this->createUser(['ROLE_DOCTOR', 'ROLE_CLINIC']);
        $doctor = $this->makeDoctor($user);
        $this->makeClinic($user);
        $this->setDoctorContext($user, $doctor);

        $context = $this->resolver()->resolve($user);

        self::assertSame(['doctor', $doctor->getId()], $context->toEntityPair());
    }

    // ── سناریو ۴: مدیر/مالک کلینیک (غیرپزشک) ────────────────────────────────

    public function testClinicManagerAlwaysResolvesToClinic(): void
    {
        $clinic = $this->makeClinic();

        $context = $this->resolver()->resolve($clinic->getUser());

        self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair());
    }

    // ── سناریو ۵: منشی ──────────────────────────────────────────────────────

    public function testSecretaryWithoutActiveContextResolvesToUnknown(): void
    {
        $user = $this->createUser(['ROLE_SECRETARY']);

        $context = $this->resolver()->resolve($user);

        self::assertSame(EntityContext::TYPE_UNKNOWN, $context->type);
        self::assertFalse($context->isResolved());
    }

    public function testSecretaryWithActiveClinicRelationResolvesToThatClinic(): void
    {
        $doctor    = $this->makeDoctor();
        $clinic    = $this->makeClinic();
        $this->joinClinic($clinic, $doctor);
        $secretary = $this->createUser(['ROLE_SECRETARY']);

        $this->em->persist(new DoctorSecretary($doctor, $secretary, $clinic));
        $this->em->flush();
        $this->setClinicContext($secretary, $clinic);

        $context = $this->resolver()->resolve($secretary);

        self::assertSame(['clinic', $clinic->getId()], $context->toEntityPair());
    }

    public function testSecretaryWithActiveDoctorRelationResolvesToThatPractice(): void
    {
        $doctor    = $this->makeDoctor();
        $secretary = $this->createUser(['ROLE_SECRETARY']);

        $this->em->persist(new DoctorSecretary($doctor, $secretary));
        $this->em->flush();
        $this->setDoctorContext($secretary, $doctor);

        $context = $this->resolver()->resolve($secretary);

        self::assertSame(['doctor', $doctor->getId()], $context->toEntityPair());
    }

    /** رابطهٔ غیرفعال = پایان همکاری؛ محیط دیگر حل نمی‌شود. */
    public function testSecretaryWithInactiveRelationResolvesToUnknown(): void
    {
        $doctor    = $this->makeDoctor();
        $clinic    = $this->makeClinic();
        $this->joinClinic($clinic, $doctor);
        $secretary = $this->createUser(['ROLE_SECRETARY']);

        $relation = new DoctorSecretary($doctor, $secretary, $clinic);
        $relation->setActive(false);
        $this->em->persist($relation);
        $this->em->flush();
        $this->setClinicContext($secretary, $clinic);

        $context = $this->resolver()->resolve($secretary);

        self::assertFalse($context->isResolved());
    }

    // ── مسیر clinic_uuid صریح ───────────────────────────────────────────────

    public function testExplicitClinicUuidForNonMemberThrowsAccessDenied(): void
    {
        $doctor = $this->makeDoctor();
        $clinic = $this->makeClinic();

        $this->expectException(AppException::class);
        $this->resolver()->resolve($doctor->getUser(), $clinic->getUuid());
    }

    public function testTryResolveReturnsNullInsteadOfThrowingForNonMember(): void
    {
        $doctor = $this->makeDoctor();
        $clinic = $this->makeClinic();

        self::assertNull($this->resolver()->tryResolve($doctor->getUser(), $clinic->getUuid()));
    }

    public function testExplicitClinicUuidWinsOverStoredActiveContext(): void
    {
        $doctor  = $this->makeDoctor();
        $clinicA = $this->makeClinic();
        $clinicB = $this->makeClinic();
        $this->joinClinic($clinicA, $doctor);
        $this->joinClinic($clinicB, $doctor);
        $this->setClinicContext($doctor->getUser(), $clinicA);

        $context = $this->resolver()->resolve($doctor->getUser(), $clinicB->getUuid());

        self::assertSame(['clinic', $clinicB->getId()], $context->toEntityPair());
    }
}
