<?php

namespace App\Tests\Clinic;

use App\Auth\Entity\User;
use App\Clinic\Entity\Clinic;
use App\Clinic\Entity\ClinicDoctorPermission;
use App\Clinic\Repository\ClinicDoctorPermissionRepository;
use App\Doctor\Entity\Doctor;
use App\Tests\ApiTestCase;

/**
 * Per-doctor permissions inside a clinic: owner-only management, deep-merge
 * semantics, lazy provisioning for pre-existing members, and context exposure.
 */
class ClinicDoctorPermissionTest extends ApiTestCase
{
    private function createClinicWithDoctor(): array
    {
        $owner  = $this->createUser(['ROLE_USER', 'ROLE_CLINIC']);
        $clinic = new Clinic($owner);
        $clinic->setName('کلینیک تست');

        $docUser = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
        $doctor  = new Doctor($docUser, 'دکتر عضو');
        $doctor->setMobileNumber($docUser->getMobileNumber());

        $this->em->persist($doctor);
        $clinic->getDoctors()->add($doctor);
        $this->em->persist($clinic);
        $this->em->flush();

        return [$owner, $clinic, $doctor, $docUser];
    }

    private function permRepo(): ClinicDoctorPermissionRepository
    {
        return static::getContainer()->get(ClinicDoctorPermissionRepository::class);
    }

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

        $res = $this->authJson('GET', "/api/v1/admin/clinic/{$clinic->getUuid()}/doctor/{$doctor->getUuid()}/permissions", $owner);

        self::assertSame(200, $this->responseCode());
        self::assertTrue($res['data']['active']);
        self::assertSame(
            ClinicDoctorPermission::DEFAULT_PERMISSIONS['resources'],
            $res['data']['permissions']['resources'],
            'a member added before this feature gets defaults on first read',
        );
    }

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

        $res = $this->authJson(
            'PATCH',
            "/api/v1/admin/clinic/{$clinic->getUuid()}/doctor/{$doctor->getUuid()}/permissions",
            $owner,
            ['permissions' => ['resources' => ['payments' => ['create' => true]]]],
        );

        self::assertSame(200, $this->responseCode());
        $resources = $res['data']['permissions']['resources'];
        self::assertTrue($resources['payments']['create']);
        self::assertFalse($resources['payments']['delete'], 'untouched actions keep their value');
        self::assertTrue($resources['appointments']['view'], 'untouched resources keep their value');
    }

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

        $res = $this->authJson(
            'PATCH',
            "/api/v1/admin/clinic/{$clinic->getUuid()}/doctor/{$doctor->getUuid()}/permissions",
            $owner,
            ['permissions' => ['resources' => ['bogus' => ['view' => true], 'payments' => ['fly' => true]]]],
        );

        self::assertSame(200, $this->responseCode());
        self::assertArrayNotHasKey('bogus', $res['data']['permissions']['resources']);
        self::assertArrayNotHasKey('fly', $res['data']['permissions']['resources']['payments']);
    }

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

        $this->authJson(
            'PATCH',
            "/api/v1/admin/clinic/{$clinic->getUuid()}/doctor/{$doctor->getUuid()}/permissions",
            $owner,
            ['active' => false],
        );
        self::assertSame(200, $this->responseCode());

        $this->em->clear();
        $perm = $this->permRepo()->findOneFor(
            $this->em->getRepository(Clinic::class)->find($clinic->getId()),
            $this->em->getRepository(Doctor::class)->find($doctor->getId()),
        );
        self::assertFalse($perm->isActive());
        self::assertFalse($perm->can('appointments', 'view'), 'inactive membership grants nothing');
    }

    public function testMemberDoctorCannotEditOwnPermissions(): void
    {
        [, $clinic, $doctor, $docUser] = $this->createClinicWithDoctor();

        $this->authJson(
            'PATCH',
            "/api/v1/admin/clinic/{$clinic->getUuid()}/doctor/{$doctor->getUuid()}/permissions",
            $docUser,
            ['permissions' => ['resources' => ['payments' => ['delete' => true]]]],
        );

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

    public function testDoctorOfAnotherClinicIsNotFound(): void
    {
        [$owner, $clinic] = $this->createClinicWithDoctor();

        $strangerUser = $this->createUser(['ROLE_USER', 'ROLE_DOCTOR']);
        $stranger     = new Doctor($strangerUser, 'دکتر بیرونی');
        $this->em->persist($stranger);
        $this->em->flush();

        $this->authJson('GET', "/api/v1/admin/clinic/{$clinic->getUuid()}/doctor/{$stranger->getUuid()}/permissions", $owner);

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

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

        $checker = static::getContainer()->get(\App\Clinic\Security\ClinicDoctorPermissionChecker::class);
        $perm    = $this->permRepo()->getOrCreate($clinic, $doctor);
        $perm->setActive(false);
        $this->em->flush();

        self::assertTrue($checker->can($owner, $clinic, 'clinic_info', 'update'));
    }

    public function testMemberContextCarriesPermissionsAndOwnPracticeDoesNot(): void
    {
        [, $clinic, $doctor, $docUser] = $this->createClinicWithDoctor();

        $res = $this->authJson('GET', '/oauth/userinfo', $docUser);
        self::assertSame(200, $this->responseCode());

        $contexts = $res['data']['available_contexts'];
        $personal = array_values(array_filter($contexts, fn($c) => $c['type'] === 'doctor'));
        $member   = array_values(array_filter($contexts, fn($c) => $c['type'] === 'clinic'));

        self::assertNotEmpty($personal);
        self::assertNotEmpty($member);
        self::assertNull($personal[0]['permissions'] ?? null, 'own practice is unrestricted');
        self::assertSame(
            ClinicDoctorPermission::DEFAULT_PERMISSIONS['resources'],
            $member[0]['permissions']['resources'],
        );
    }

    public function testDetachingDoctorRemovesPermissionRow(): void
    {
        [$owner, $clinic, $doctor] = $this->createClinicWithDoctor();
        $this->permRepo()->getOrCreate($clinic, $doctor);

        $this->authJson('DELETE', "/api/v1/admin/clinic/{$clinic->getUuid()}/doctor/{$doctor->getUuid()}", $owner);
        self::assertSame(200, $this->responseCode());

        $this->em->clear();
        $reloadedClinic = $this->em->getRepository(Clinic::class)->find($clinic->getId());
        $reloadedDoctor = $this->em->getRepository(Doctor::class)->find($doctor->getId());
        self::assertNull($this->permRepo()->findOneFor($reloadedClinic, $reloadedDoctor));
    }
}
