<?php

declare(strict_types=1);

namespace NoahVet\Reef\Test\A_Unit\Security\Voter;

use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
use NoahVet\Reef\Factory\ClientFactory;
use NoahVet\Reef\Jane\Client;
use NoahVet\Reef\Jane\Model\PermissionGrant;
use NoahVet\Reef\Jane\Model\PermissionGrantList;
use NoahVet\Reef\Security\Authentication\BearerClientHMacComputer;
use NoahVet\Reef\Security\Authentication\Token\ReefOAuthToken;
use NoahVet\Reef\Security\IAM\Mapper\IAMResourceMapper;
use NoahVet\Reef\Security\IAM\Model\Resource;
use NoahVet\Reef\Security\IAM\Model\ResourceType;
use NoahVet\Reef\Security\Voter\IAMVoter;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;
use Symfony\Component\Security\Core\User\UserInterface;

class IAMVoterTest extends TestCase
{
    public function testVoteReefToken(): void
    {
        $clientFactory = $this->getMockBuilder(ClientFactory::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $client = $this->getMockBuilder(Client::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $permissionGrant = new PermissionGrant();
        $permissionGrant->setDenied([]);
        $permissionGrant->setAllowed(['reef:bsmApi:veterinaryEstablishment:perm:readPublic']);

        $client
            ->method('getDetailedPermissionsResourceItem')
            ->withAnyParameters()
            ->willReturn($permissionGrant)
        ;

        $clientFactory
            ->method('create')
            ->with('NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA')
            ->willReturn($client)
        ;

        $resourceMapper = $this->getMockBuilder(IAMResourceMapper::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $voter = new IAMVoter(
            new BearerClientHMacComputer(
                'o8k80s8gzowtwcw8gkg84os0ko80ccw0w8cwk7ktaxgo8og8k',
                '44kkk8kc0ectkockc80k4o9q2z5g5sogsoc0wokk40g44so8g',
            ),
            $resourceMapper,
            $clientFactory,
        );

        $token = new ReefOAuthToken(
            'main',
            'NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA',
            null,
            new \DateTimeImmutable('+1 hour'),
        );

        $resource = Resource::fromString(
            'reef:bsmApi:veterinaryEstablishment',
            'toto',
        );

        $granted = $voter->vote(
            $token,
            $resource,
            ['reef:bsmApi:veterinaryEstablishment:perm:readPublic'],
        );

        self::assertEquals(VoterInterface::ACCESS_GRANTED, $granted);

        $denied = $voter->vote(
            $token,
            $resource,
            ['reef:bsmApi:veterinaryEstablishment:perm:write'],
        );

        self::assertEquals(VoterInterface::ACCESS_DENIED, $denied);
    }

    public function testVoteHWIOauthToken(): void
    {
        $clientFactory = $this->getMockBuilder(ClientFactory::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $client = $this->getMockBuilder(Client::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $permissionGrant = new PermissionGrant();
        $permissionGrant->setDenied([]);
        $permissionGrant->setAllowed(['reef:bsmApi:veterinaryEstablishment:perm:readPublic']);

        $client
            ->method('getDetailedPermissionsResourceItem')
            ->withAnyParameters()
            ->willReturn($permissionGrant)
        ;

        $clientFactory
            ->method('create')
            ->with('NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA')
            ->willReturn($client)
        ;

        $resourceMapper = $this->getMockBuilder(IAMResourceMapper::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $voter = new IAMVoter(
            new BearerClientHMacComputer(
                'o8k80s8gzowtwcw8gkg84os0ko80ccw0w8cwk7ktaxgo8og8k',
                '44kkk8kc0ectkockc80k4o9q2z5g5sogsoc0wokk40g44so8g',
            ),
            $resourceMapper,
            $clientFactory,
        );

        $token = new OAuthToken(
            'NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA',
            ['ROLE_USER'],
        );

        $resource = Resource::fromString(
            'reef:bsmApi:veterinaryEstablishment',
            'toto',
        );

        $granted = $voter->vote(
            $token,
            $resource,
            ['reef:bsmApi:veterinaryEstablishment:perm:readPublic'],
        );

        self::assertEquals(VoterInterface::ACCESS_GRANTED, $granted);

        $denied = $voter->vote(
            $token,
            $resource,
            ['reef:bsmApi:veterinaryEstablishment:perm:write'],
        );

        self::assertEquals(VoterInterface::ACCESS_DENIED, $denied);
    }

    public function testVoteNonOAuthToken(): void
    {
        $clientFactory = $this->getMockBuilder(ClientFactory::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $resourceMapper = $this->getMockBuilder(IAMResourceMapper::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $voter = new IAMVoter(
            new BearerClientHMacComputer(
                'o8k80s8gzowtwcw8gkg84os0ko80ccw0w8cwk7ktaxgo8og8k',
                '44kkk8kc0ectkockc80k4o9q2z5g5sogsoc0wokk40g44so8g',
            ),
            $resourceMapper,
            $clientFactory,
        );

        $user = $this->getMockBuilder(UserInterface::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $token = new UsernamePasswordToken(
            $user,
            'main',
            ['ROLE_USER'],
        );

        $resource = Resource::fromString(
            'reef:bsmApi:veterinaryEstablishment',
            'toto',
        );

        $granted = $voter->vote(
            $token,
            $resource,
            ['reef:bsmApi:veterinaryEstablishment:perm:readPublic'],
        );

        self::assertEquals(VoterInterface::ACCESS_ABSTAIN, $granted);
    }

    public function testVoteSuperAdminDenied(): void
    {
        $clientFactory = $this->getMockBuilder(ClientFactory::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $client = $this->getMockBuilder(Client::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $resourceMapper = $this->getMockBuilder(IAMResourceMapper::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $voter = new IAMVoter(
            new BearerClientHMacComputer(
                'o8k80s8gzowtwcw8gkg84os0ko80ccw0w8cwk7ktaxgo8og8k',
                '44kkk8kc0ectkockc80k4o9q2z5g5sogsoc0wokk40g44so8g',
            ),
            $resourceMapper,
            $clientFactory,
        );

        $token = new OAuthToken(
            'NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA',
            ['ROLE_USER'],
        );

        $userInfo = $this->createMock(\NoahVet\Reef\Jane\Model\UserinfoGetResponse200::class);
        $userInfo->method('getGroups')
            ->willReturn([''])
        ;

        $client
            ->method('userinfo')
            ->willReturn($userInfo)
        ;

        $clientFactory
            ->expects($this->once())
            ->method('create')
            ->with('NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA')
            ->willReturn($client)
        ;

        $granted = $voter->vote(
            $token,
            null,
            ['reef:iam:principals:superAdmins'],
        );

        self::assertEquals(VoterInterface::ACCESS_DENIED, $granted);
    }

    public function testVoteSuperAdminGranted(): void
    {
        $clientFactory = $this->getMockBuilder(ClientFactory::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $client = $this->getMockBuilder(Client::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $resourceMapper = $this->getMockBuilder(IAMResourceMapper::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $voter = new IAMVoter(
            new BearerClientHMacComputer(
                'o8k80s8gzowtwcw8gkg84os0ko80ccw0w8cwk7ktaxgo8og8k',
                '44kkk8kc0ectkockc80k4o9q2z5g5sogsoc0wokk40g44so8g',
            ),
            $resourceMapper,
            $clientFactory,
        );

        $token = new OAuthToken(
            'NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA',
            ['ROLE_USER'],
        );

        $userInfo = $this->createMock(\NoahVet\Reef\Jane\Model\UserinfoGetResponse200::class);
        $userInfo->method('getGroups')
            ->willReturn(['reef:iam:principals:superAdmins'])
        ;

        $client
            ->method('userinfo')
            ->willReturn($userInfo)
        ;

        $clientFactory
            ->expects($this->once())
            ->method('create')
            ->with('NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA')
            ->willReturn($client)
        ;

        $granted = $voter->vote(
            $token,
            null,
            ['reef:iam:principals:superAdmins'],
        );

        self::assertEquals(VoterInterface::ACCESS_GRANTED, $granted);
    }

    public function testVoteResourceType(): void
    {
        $clientFactory = $this->createMock(ClientFactory::class);

        $client = $this->createMock(Client::class);

        $resourceMapper = $this->getMockBuilder(IAMResourceMapper::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $voter = new IAMVoter(
            new BearerClientHMacComputer(
                'o8k80s8gzowtwcw8gkg84os0ko80ccw0w8cwk7ktaxgo8og8k',
                '44kkk8kc0ectkockc80k4o9q2z5g5sogsoc0wokk40g44so8g',
            ),
            $resourceMapper,
            $clientFactory,
        );

        $token = new OAuthToken(
            'NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA',
            ['ROLE_USER'],
        );

        $permissionGrant = new PermissionGrantList();
        $permissionGrant->setResources([
            'reef:iam:principal:uuid1' => (new PermissionGrant())->setAllowed(['reef:iam:principal:perm:write']),
        ]);
        $permissionGrant->setGroups([
            'reef:iam:principals:*' => (new PermissionGrant())->setAllowed(['reef:iam:principal:perm:read']),
        ]);

        $client
            ->method('getDetailedPermissionsResourceTypeItem')
            ->willReturn($permissionGrant)
        ;

        $clientFactory
            ->expects($this->once())
            ->method('create')
            ->with('NDAyMzc2YTQ0ODljYzhjNjJlYTc0ZjFhZjlhMmQ2NzkxYTJiNDgxMDQ3OWZkMjJlZDY5Yjc5NmM5NTZlMTcxOA')
            ->willReturn($client)
        ;

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                ResourceType::fromString('reef:iam:principal'),
                ['reef:iam:principal:perm:read'],
            ),
        );

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                ResourceType::fromString('reef:iam:principal'),
                ['reef:iam:principal:perm:write'],
            ),
        );

        self::assertEquals(
            VoterInterface::ACCESS_DENIED,
            $voter->vote(
                $token,
                ResourceType::fromString('reef:iam:principal'),
                ['reef:iam:principal:perm:delete'],
            ),
        );
    }
}
