<?php

namespace App\Tests\Shared;

use App\ClinicService\Entity\ServiceItem;
use App\ClinicService\Entity\ServiceSection;
use App\Clinic\Entity\Clinic;
use App\Doctor\Entity\Doctor;
use App\Shared\Context\EntityContext;
use App\Shared\Tenant\TenantOwnershipChecker;
use App\Staff\Entity\ClinicStaff;
use App\Tests\ApiTestCase;

/**
 * تنها محافظِ موجودیت‌هایی که uuidشان از خودِ درخواست می‌آید و TenantFilter
 * پوششان نمی‌دهد — یا چون فرزند aggregateاند یا چون کاربر محیطی انتخاب نکرده.
 */
class TenantOwnershipCheckerTest extends ApiTestCase
{
    private function checker(): TenantOwnershipChecker
    {
        return static::getContainer()->get(TenantOwnershipChecker::class);
    }

    private function makeDoctor(): Doctor
    {
        $doctor = new Doctor($this->createUser(['ROLE_DOCTOR']), 'دکتر مالکیت');
        $this->em->persist($doctor);
        $this->em->flush();

        return $doctor;
    }

    private function serviceFor(Doctor $doctor): ServiceItem
    {
        $section = new ServiceSection('doctor', $doctor->getId(), 'بخش');
        $this->em->persist($section);
        $item = new ServiceItem($section, 'سرویس', 0);
        $this->em->persist($item);
        $this->em->flush();

        return $item;
    }

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

        self::assertTrue(
            $this->checker()->belongsTo(EntityContext::forDoctor($doctor), $this->serviceFor($doctor)),
        );
    }

    public function testEntityOfAnotherEnvironmentDoesNot(): void
    {
        $doctor  = $this->makeDoctor();
        $foreign = $this->serviceFor($this->makeDoctor());

        self::assertFalse($this->checker()->belongsTo(EntityContext::forDoctor($doctor), $foreign));
    }

    /** فرزند aggregate جفتش را از ریشه به ارث می‌برد — ServiceItem از ServiceSection. */
    public function testAggregateChildIsComparedThroughItsRoot(): void
    {
        $doctor = $this->makeDoctor();
        $item   = $this->serviceFor($doctor);

        self::assertSame('doctor', $item->getEntityType());
        self::assertSame($doctor->getId(), $item->getEntityId());
    }

    /** ❌ null یعنی «پیدا نشد» و همان‌قدر رد می‌شود که یک موجودیت بیگانه. */
    public function testNullIsNeverOwned(): void
    {
        $doctor = $this->makeDoctor();

        self::assertFalse($this->checker()->belongsTo(EntityContext::forDoctor($doctor), null));
        self::assertFalse($this->checker()->belongsToPair('doctor', $doctor->getId(), null));
    }

    /** ❌ محیط حل‌نشده هیچ‌چیز را مالک نیست. */
    public function testUnresolvedContextOwnsNothing(): void
    {
        $item = $this->serviceFor($this->makeDoctor());

        self::assertFalse($this->checker()->belongsTo(EntityContext::unknown(), $item));
    }

    /**
     * ❌ موجودیتی که جفت محیطش را expose نمی‌کند باید بلند خطا بدهد، نه اینکه
     * بی‌صدا false برگرداند — سکوت اینجا یعنی گاردِ همیشه-بسته که باگ می‌سازد.
     */
    public function testEntityWithoutATenantPairThrows(): void
    {
        $doctor = $this->makeDoctor();

        $this->expectException(\InvalidArgumentException::class);
        $this->checker()->belongsTo(EntityContext::forDoctor($doctor), new \stdClass());
    }

    /** ⚠️ یک بیگانه در فهرست، کل فهرست را رد می‌کند. */
    public function testOneForeignEntityRejectsTheWholeList(): void
    {
        $doctor  = $this->makeDoctor();
        $mine    = $this->serviceFor($doctor);
        $foreign = $this->serviceFor($this->makeDoctor());
        $context = EntityContext::forDoctor($doctor);

        self::assertTrue($this->checker()->allBelongTo($context, [$mine]));
        self::assertFalse($this->checker()->allBelongTo($context, [$mine, $foreign]));
        self::assertTrue($this->checker()->allBelongTo($context, []), 'فهرست خالی مانعی ندارد');
    }

    /** نوعِ محیط هم بخشی از هویت است: کلینیک ۵ با پزشک ۵ یکی نیست. */
    public function testTypeIsPartOfTheIdentityNotJustTheId(): void
    {
        $doctor = $this->makeDoctor();
        $item   = $this->serviceFor($doctor);

        $clinic = new Clinic($this->createUser(['ROLE_CLINIC']));
        $clinic->setName('کلینیک');
        $this->em->persist($clinic);
        $this->em->flush();

        self::assertFalse(
            $this->checker()->belongsToPair('clinic', $doctor->getId(), $item),
            'همان شناسه ولی نوع دیگر نباید مالک شمرده شود',
        );
    }

    /** موجودیت‌هایی که خودشان جفت دارند (نه از ریشه) هم با همین checker سنجیده می‌شوند. */
    public function testEntitiesCarryingTheirOwnPairWorkToo(): void
    {
        $doctor = $this->makeDoctor();
        $staff  = new ClinicStaff('doctor', $doctor->getId(), 'پرسنل');
        $this->em->persist($staff);
        $this->em->flush();

        self::assertTrue($this->checker()->belongsTo(EntityContext::forDoctor($doctor), $staff));
        self::assertFalse($this->checker()->belongsToPair('doctor', $doctor->getId() + 1, $staff));
    }
}
