<?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\Discount\Entity\DiscountRule;
use App\Doctor\Entity\Doctor;
use App\Shared\Context\EntityContext;
use App\Tests\ApiTestCase;

/**
 * پس از یکسان‌سازی املای tenant، هر نقش باید فقط دادهٔ محیط خودش را ببیند.
 *
 * روی discount-rules سنجیده می‌شود چون تنها جدولی بود که با owner_type نوشته شده
 * بود و تا فاز ۳ از هر فیلتر مبتنی بر entity_type جا می‌ماند.
 */
class TenantIsolationMatrixTest extends ApiTestCase
{
    private const LIST_URL = '/api/v1/admin/discount-rules';

    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;
    }

    /** عضویت به‌علاوهٔ مجوز discounts.view که در DEFAULT_PERMISSIONS خاموش است. */
    private function joinClinic(Clinic $clinic, Doctor $doctor): void
    {
        $clinic->getDoctors()->add($doctor);

        $permission = new ClinicDoctorPermission($clinic, $doctor);
        $permission->mergePermissions(['resources' => ['discounts' => ['view' => true]]]);
        $this->em->persist($permission);
        $this->em->flush();
    }

    private function setClinicContext(User $user, Clinic $clinic): void
    {
        $this->em->persist(new UserActiveContext($user, $clinic->getUuid(), EntityContext::TYPE_CLINIC));
        $this->em->flush();
    }

    private function rule(string $entityType, int $entityId, string $name): DiscountRule
    {
        $rule = new DiscountRule($entityType, $entityId, $name, DiscountRule::TYPE_INVOICE_AMOUNT);
        $this->em->persist($rule);
        $this->em->flush();

        return $rule;
    }

    /**
     * پاسخ این endpoint با success(['data' => …]) ساخته می‌شود، پس یک لایه
     * تودرتوی اضافه دارد — همان دام double-nesting که در CLAUDE.md ثبت شده.
     *
     * @return string[]
     */
    private function listedNames(User $user): array
    {
        $res = $this->authJson('GET', self::LIST_URL, $user);
        self::assertSame(200, $this->responseCode());

        return array_column($res['data']['data'] ?? [], 'name');
    }

    public function testIndependentDoctorSeesOnlyItsOwnRules(): void
    {
        $mine    = $this->makeDoctor();
        $other   = $this->makeDoctor();
        $clinic  = $this->makeClinic();

        $this->rule('doctor', $mine->getId(), 'مالِ من');
        $this->rule('doctor', $other->getId(), 'مالِ پزشک دیگر');
        $this->rule('clinic', $clinic->getId(), 'مالِ کلینیک');

        $names = $this->listedNames($mine->getUser());

        self::assertContains('مالِ من', $names);
        self::assertNotContains('مالِ پزشک دیگر', $names);
        self::assertNotContains('مالِ کلینیک', $names);
    }

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

        $this->rule('clinic', $clinic->getId(), 'کلینیک خودم');
        $this->rule('clinic', $otherClinic->getId(), 'کلینیک دیگر');
        $this->rule('doctor', $doctor->getId(), 'مطب یک پزشک');

        $names = $this->listedNames($clinic->getUser());

        self::assertContains('کلینیک خودم', $names);
        self::assertNotContains('کلینیک دیگر', $names);
        self::assertNotContains('مطب یک پزشک', $names);
    }

    /** پزشکِ عضو در محیط کلینیک، قوانین کلینیک را می‌بیند نه مطب شخصی‌اش. */
    public function testMemberDoctorInClinicContextSeesClinicRules(): void
    {
        $doctor = $this->makeDoctor();
        $clinic = $this->makeClinic();
        $this->joinClinic($clinic, $doctor);
        $this->setClinicContext($doctor->getUser(), $clinic);

        $this->rule('clinic', $clinic->getId(), 'قانون کلینیک');
        $this->rule('doctor', $doctor->getId(), 'قانون مطب شخصی');

        $names = $this->listedNames($doctor->getUser());

        self::assertContains('قانون کلینیک', $names);
        self::assertNotContains('قانون مطب شخصی', $names);
    }

    /** همان پزشک بیرون از محیط کلینیک، فقط مطب شخصی. */
    public function testMemberDoctorOutsideClinicContextSeesOwnPracticeRules(): void
    {
        $doctor = $this->makeDoctor();
        $clinic = $this->makeClinic();
        $this->joinClinic($clinic, $doctor);

        $this->rule('clinic', $clinic->getId(), 'قانون کلینیک');
        $this->rule('doctor', $doctor->getId(), 'قانون مطب شخصی');

        $names = $this->listedNames($doctor->getUser());

        self::assertContains('قانون مطب شخصی', $names);
        self::assertNotContains('قانون کلینیک', $names);
    }

    /** پزشکی که مالک کلینیک هم هست: محیط فعال تعیین‌کننده است، نه نقش. */
    public function testDoctorWhoOwnsClinicSeesClinicRulesInClinicContext(): void
    {
        $user   = $this->createUser(['ROLE_DOCTOR', 'ROLE_CLINIC']);
        $doctor = $this->makeDoctor($user);
        $clinic = $this->makeClinic($user);
        $this->joinClinic($clinic, $doctor);
        $this->setClinicContext($user, $clinic);

        $this->rule('clinic', $clinic->getId(), 'قانون کلینیکِ خودش');
        $this->rule('doctor', $doctor->getId(), 'قانون مطبِ خودش');

        $names = $this->listedNames($user);

        self::assertContains('قانون کلینیکِ خودش', $names);
        self::assertNotContains('قانون مطبِ خودش', $names);
    }

    /** ❌ بدون توکن، لیست اصلاً باز نمی‌شود. */
    public function testAnonymousCannotListRules(): void
    {
        $this->client->request('GET', self::LIST_URL);

        self::assertSame(401, $this->client->getResponse()->getStatusCode());
    }

    /** ⚠️ محیط حل‌نشده: کاربر بدون نقش پنل، نه خطای ۵۰۰ می‌گیرد نه دادهٔ کسی را. */
    public function testUserWithoutAPanelRoleGetsNoRules(): void
    {
        $doctor = $this->makeDoctor();
        $this->rule('doctor', $doctor->getId(), 'قانون یک پزشک');

        $stranger = $this->createUser(['ROLE_USER']);
        $this->authJson('GET', self::LIST_URL, $stranger);

        self::assertContains($this->responseCode(), [403, 404], 'دسترسی رد شود، نه خطای سرور');
    }
}
