<?php

namespace App\Tests\Unit\Service\Access;

use App\Entity\Access\Access;
use App\Entity\Organization\Organization;
use App\Repository\Access\AccessRepository;
use App\Service\Access\Utils as AccessUtils;
use App\Service\ServiceIterator\OptionalsRolesIterator;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Role\RoleHierarchy;

class UtilsTest extends TestCase
{
    private MockObject | RoleHierarchy $roleHierarchy;
    private MockObject | OptionalsRolesIterator $optionalsRolesIterator;
    private MockObject | AccessRepository $accessRepository;

    public function setUp():void
    {
        $this->roleHierarchy = $this->getMockBuilder(RoleHierarchy::class)->disableOriginalConstructor()->getMock();
        $this->optionalsRolesIterator = $this->getMockBuilder(OptionalsRolesIterator::class)->disableOriginalConstructor()->getMock();
        $this->accessRepository = $this->getMockBuilder(AccessRepository::class)->disableOriginalConstructor()->getMock();
    }

    /**
     * @see Utils::filterAccesses()
     */
    public function testFilterAccesses(): void
    {
        $accessUtils = $this->getMockBuilder(AccessUtils::class)
            ->setConstructorArgs([$this->roleHierarchy,  $this->accessRepository, $this->optionalsRolesIterator])
            ->setMethodsExcept(['filterAccesses'])
            ->getMock();

        $access1 = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
        $access1->method('getId')->willReturn(1);

        $access2 = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
        $access2->method('getId')->willReturn(2);

        $access3 = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
        $access3->method('getId')->willReturn(3);

        $accesses = [$access1, $access2, $access3];

        $result = $accessUtils->filterAccesses($accesses, $access2);

        $this->assertEqualsCanonicalizing(
            [$access1->getId(), $access3->getId()],
            array_map(static function ($access) { return $access->getId(); }, $result)
        );
    }

    /**
     * @see Utils::hasRoles()
     */
    public function testHasRoles(): void
    {
        $accessUtils = $this->getMockBuilder(AccessUtils::class)
            ->setConstructorArgs([$this->roleHierarchy,  $this->accessRepository, $this->optionalsRolesIterator])
            ->setMethodsExcept(['hasRoles'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();

        $accessUtils->method('getAllRoles')->with($access)->willReturn(['ROLE_A']);

        $this->assertTrue($accessUtils->hasRoles($access, 'ROLE_A'));
        $this->assertFalse($accessUtils->hasRoles($access, 'ROLE_B'));
    }

    /**
     * @see Utils::getAllRoles()
     */
    public function testGetAllRoles(): void
    {
        $accessUtils = $this->getMockBuilder(AccessUtils::class)
            ->setConstructorArgs([$this->roleHierarchy,  $this->accessRepository, $this->optionalsRolesIterator])
            ->setMethodsExcept(['getAllRoles'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
        $access->method('getRoles')->willReturn(['ROLE_A']);

        $this->roleHierarchy->method('getReachableRoleNames')->willReturn(["ROLE_A", "ROLE_B"]);
        $this->optionalsRolesIterator->method('getOptionalsRoles')->willReturn(["ROLE_OPT"]);

        $this->assertEquals(['ROLE_A', 'ROLE_B'], $accessUtils->getAllRoles($access));
    }

    /**
     * @see Utils::findAdminFor()
     */
    public function testFindAdminFor(): void
    {
        $accessUtils = $this->getMockBuilder(AccessUtils::class)
            ->setConstructorArgs([$this->roleHierarchy,  $this->accessRepository, $this->optionalsRolesIterator])
            ->setMethodsExcept(['findAdminFor'])
            ->getMock();

        $access = $this->getMockBuilder(Access::class)->disableOriginalConstructor()->getMock();
        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();

        $this->accessRepository->method('findOneBy')->with(['adminAccess' => true, 'organization' => $organization])->willReturn($access);

        $this->assertNotEmpty($accessUtils->findAdminFor($organization));
    }

    /**
     * @see Utils::findAdminFor()
     */
    public function testFindAdminForNotFound(): void
    {
        $accessUtils = $this->getMockBuilder(AccessUtils::class)
            ->setConstructorArgs([$this->roleHierarchy,  $this->accessRepository, $this->optionalsRolesIterator])
            ->setMethodsExcept(['findAdminFor'])
            ->getMock();

        $organization = $this->getMockBuilder(Organization::class)->disableOriginalConstructor()->getMock();

        $this->accessRepository->method('findOneBy')->with(['adminAccess' => true, 'organization' => $organization])->willReturn(null);

        $this->assertNull($accessUtils->findAdminFor($organization));
    }
}
