<?php

declare(strict_types=1);

namespace NoahVet\Reef\Test\A_Unit\Factory\RequestContext;

use Mockery\Mock;
use NoahVet\Reef\Bridge\Reef\IAM\Client\ClientAdapter;
use NoahVet\Reef\Factory\RequestContext\PermissionApplier;
use NoahVet\Reef\Security\Authentication\Token\ReefOAuthToken;
use NoahVet\Reef\Security\IAM\Model\ResourceType;
use NoahVet\Reef\Security\ResourceType\ResourceCollectionPermission;
use NoahVet\Reef\Security\ResourceType\ResourceCollectionPermissionFetcherInterface;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;

class PermissionApplierTest extends TestCase
{
    private PermissionApplier $permissionApplier;

    /**
     * @var ClientAdapter&MockObject
     */
    private ClientAdapter $clientAdapterMock;

    /**
     * @var ResourceCollectionPermissionFetcherInterface&MockObject
     */
    private ResourceCollectionPermissionFetcherInterface $permissionFetcherMock;

    /**
     * @var ReefOAuthToken&Mock
     */
    private ReefOAuthToken $tokenMock;

    protected function setUp(): void
    {
        $this->clientAdapterMock = $this->createMock(ClientAdapter::class);
        $this->permissionFetcherMock = $this->createMock(ResourceCollectionPermissionFetcherInterface::class);
        $this->tokenMock = $this->createMock(ReefOAuthToken::class);

        $this->clientAdapterMock
            ->method('getToken')
            ->willReturn($this->tokenMock)
        ;
        $this->tokenMock
            ->method('getBearerToken')
            ->willReturn('bearerToken123')
        ;

        $this->permissionApplier = new PermissionApplier(
            $this->clientAdapterMock,
            $this->permissionFetcherMock,
            'reefAppName',
        );
    }

    public function testApplyReturnsEmptyIfWhitelistDoesNotContainPermissions(): void
    {
        $request = $this->createMock(Request::class);
        $whitelist = [];

        $result = $this->permissionApplier->apply($request, $whitelist);

        $this->assertSame([], $result);
    }

    /**
     * CAS 1 : Toutes les ressources ont l'autorisation perm1, aucun filtrage nécessaire.
     */
    public function testApplyWithOnePermissionAndAllAllowed(): void
    {
        $request = $this->createMock(Request::class);
        $whitelist = [
            'permissions' => [
                [
                    'resourceName' => 'resource1',
                    'permissionSlug' => 'perm1',
                    'mappedKey' => 'key1',
                ],
            ],
        ];

        $permissionsMock = $this->createMock(ResourceCollectionPermission::class);
        $this->permissionFetcherMock
            ->expects($this->once())
            ->method('fetch')
            ->with(
                $this->callback(fn (ResourceType $type) => 'reefAppName:resource1' === $type->getSlug()),
                true,
                'bearerToken123',
            )
            ->willReturn($permissionsMock)
        ;

        $permissionsMock
            ->expects($this->once())
            ->method('getAllPermissions')
            ->willReturn(['perm1'])
        ;

        $permissionsMock
            ->expects($this->once())
            ->method('getResourcePermissions')
            ->willReturn([
                'resourceA' => ['perm1'],
                'resourceB' => ['perm2'],
            ])
        ;

        $result = $this->permissionApplier->apply($request, $whitelist);

        $this->assertSame(['key1' => null], $result);
    }

    /**
     * CAS 1 : Toutes les ressources ont l'autorisation perm1, seule 1 resource à l'autorisation perm2.
     */
    public function testApplyWithTwoPermissionsAndOneAllowed(): void
    {
        $request = $this->createMock(Request::class);
        $whitelist = [
            'permissions' => [
                [
                    'resourceName' => 'resource1',
                    'permissionSlug' => ['perm1', 'perm2'],
                    'mappedKey' => 'key1',
                ],
            ],
        ];

        $permissionsMock = $this->createMock(ResourceCollectionPermission::class);
        $this->permissionFetcherMock
            ->expects($this->once())
            ->method('fetch')
            ->with(
                $this->callback(fn (ResourceType $type) => 'reefAppName:resource1' === $type->getSlug()),
                true,
                'bearerToken123',
            )
            ->willReturn($permissionsMock)
        ;

        $permissionsMock
            ->expects($this->once())
            ->method('getAllPermissions')
            ->willReturn(['perm1'])
        ;

        $permissionsMock
            ->expects($this->once())
            ->method('getResourcePermissions')
            ->willReturn([
                'resourceA' => ['perm1'],
                'resourceB' => ['perm2'],
            ])
        ;

        $result = $this->permissionApplier->apply($request, $whitelist);

        $this->assertSame(['key1' => ['resourceB']], $result);
    }

    /**
     * CAS 3 : Aucune ressource autorisée.
     */
    public function testApplyWithTwoPermissionsAndNoneAllowed(): void
    {
        $request = $this->createMock(Request::class);
        $whitelist = [
            'permissions' => [
                [
                    'resourceName' => 'resource1',
                    'permissionSlug' => ['perm1', 'perm2', 'perm3'],
                    'mappedKey' => 'key1',
                ],
            ],
        ];

        $permissionsMock = $this->createMock(ResourceCollectionPermission::class);
        $this->permissionFetcherMock
            ->expects($this->once())
            ->method('fetch')
            ->with(
                $this->callback(fn (ResourceType $type) => 'reefAppName:resource1' === $type->getSlug()),
                true,
                'bearerToken123',
            )
            ->willReturn($permissionsMock)
        ;

        $permissionsMock
            ->expects($this->once())
            ->method('getAllPermissions')
            ->willReturn(['perm1'])
        ;

        $permissionsMock
            ->expects($this->once())
            ->method('getResourcePermissions')
            ->willReturn([
                'resourceA' => ['perm1'],
                'resourceB' => ['perm2'],
            ])
        ;

        $result = $this->permissionApplier->apply($request, $whitelist);

        $this->assertSame(['key1' => []], $result);
    }
}
