<?php

declare(strict_types=1);

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

use HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken;
use NoahVet\Reef\Phpunit\Fixture\FixtureRepository;
use NoahVet\Reef\Phpunit\Fixture\PermissionFixture;
use NoahVet\Reef\Phpunit\Fixture\ReefTokenFixture;
use NoahVet\Reef\Phpunit\Security\Voter\MockedIAMVoter;
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\User\ReefOAuthUser;
use PHPUnit\Framework\TestCase;
use Symfony\Bundle\FrameworkBundle\Test\TestBrowserToken;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;

class MockedIAMVoterTest extends TestCase
{
    public function testVoteOnPermission(): void
    {
        $user = new ReefOAuthUser('toto', null);
        $permissions = [
            new PermissionFixture(
                Resource::fromString(
                    'reef:phpunit:ResourceType',
                    'id1',
                ),
                ['permission_read'],
            ),
        ];

        $tokenFixture = new ReefTokenFixture(
            'unit_token',
            $user,
            $permissions,
            false,
        );

        $repository = new FixtureRepository();
        $repository->addFixture($tokenFixture);

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

        $mapper
            ->method('map')
            ->with('resource1')
            ->willReturn(Resource::fromString('reef:phpunit:ResourceType', 'id1'))
        ;

        $voter = new MockedIAMVoter($mapper, $repository);

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

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                'resource1',
                ['permission_read'],
            ),
        );

        self::assertEquals(
            VoterInterface::ACCESS_DENIED,
            $voter->vote(
                $token,
                'resource1',
                ['permission_write'],
            ),
        );

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                Resource::fromString('reef:phpunit:ResourceType', 'id1'),
                ['permission_read'],
            ),
        );

        self::assertEquals(
            VoterInterface::ACCESS_DENIED,
            $voter->vote(
                $token,
                Resource::fromString('reef:phpunit:ResourceType', 'id2'),
                ['permission_read'],
            ),
        );

        $invalidToken = new ReefOAuthToken('main', 'invalid_token', null, new \DateTimeImmutable('+1 hour'));
        self::assertEquals(
            VoterInterface::ACCESS_DENIED,
            $voter->vote(
                $invalidToken,
                Resource::fromString('reef:phpunit:ResourceType', 'id1'),
                ['permission_read'],
            ),
        );
        /*
                $mapper
                    ->method('map')
                    ->with('resource2')
                    ->willReturn(null)
                ;

                self::assertEquals(
                    VoterInterface::ACCESS_ABSTAIN,
                    $voter->vote(
                        $token,
                        'resource2',
                        ['permission_read'],
                    ),
                );
        */
    }

    public function testVoteOnPermissionSuperAdmin(): void
    {
        $user = new ReefOAuthUser('toto', null);

        $tokenFixture = new ReefTokenFixture(
            'unit_token',
            $user,
            [],
            true,
        );

        $repository = new FixtureRepository();
        $repository->addFixture($tokenFixture);

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

        $mapper
            ->method('map')
            ->with('resource1')
            ->willReturn(Resource::fromString('reef:phpunit:ResourceType', 'id1'))
        ;

        $voter = new MockedIAMVoter($mapper, $repository);

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

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                'resource1',
                ['permission_read'],
            ),
        );

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                'resource1',
                ['permission_write'],
            ),
        );

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                Resource::fromString('reef:phpunit:ResourceType', 'id1'),
                ['permission_read'],
            ),
        );

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                Resource::fromString('reef:phpunit:ResourceType', 'id2'),
                ['permission_read'],
            ),
        );

        $invalidToken = new ReefOAuthToken('main', 'invalid_token', null, new \DateTimeImmutable('+1 hour'));
        self::assertEquals(
            VoterInterface::ACCESS_DENIED,
            $voter->vote(
                $invalidToken,
                Resource::fromString('reef:phpunit:ResourceType', 'id1'),
                ['permission_read'],
            ),
        );
    }

    public function testVoteNonReefToken(): void
    {
        $repository = new FixtureRepository();
        $mapper = $this->getMockBuilder(IAMResourceMapper::class)
            ->disableOriginalConstructor()
            ->getMock()
        ;

        $voter = new MockedIAMVoter($mapper, $repository);

        $token = new UsernamePasswordToken(
            new ReefOAuthUser('toto', null),
            'main',
        );

        self::assertEquals(
            VoterInterface::ACCESS_ABSTAIN,
            $voter->vote(
                $token,
                'resource2',
                ['permission_read'],
            ),
        );
    }

    public function testVoteSuperAdminGranted(): void
    {
        $repository = new FixtureRepository();
        $user = new ReefOAuthUser('toto', null);
        $permissions = [];

        $tokenFixture = new ReefTokenFixture(
            'unit_token',
            $user,
            $permissions,
            true,
        );

        $repository->addFixture($tokenFixture);

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

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

        $voter = new MockedIAMVoter($mapper, $repository);

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                'resource2',
                ['reef:iam:principals:superAdmins'],
            ),
        );
    }

    public function testVoteSuperAdminDenied(): void
    {
        $repository = new FixtureRepository();
        $user = new ReefOAuthUser('toto', null);
        $permissions = [];

        $tokenFixture = new ReefTokenFixture(
            'unit_token',
            $user,
            $permissions,
            false,
        );

        $repository->addFixture($tokenFixture);

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

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

        $voter = new MockedIAMVoter($mapper, $repository);

        self::assertEquals(
            VoterInterface::ACCESS_DENIED,
            $voter->vote(
                $token,
                'resource2',
                ['reef:iam:principals:superAdmins'],
            ),
        );
    }

    public function testNonMappableResource(): void
    {
        $user = new ReefOAuthUser('toto', null);
        $permissions = [
            new PermissionFixture(
                Resource::fromString(
                    'reef:phpunit:ResourceType',
                    'id1',
                ),
                ['permission_read'],
            ),
        ];

        $tokenFixture = new ReefTokenFixture(
            'unit_token',
            $user,
            $permissions,
            false,
        );

        $repository = new FixtureRepository();
        $repository->addFixture($tokenFixture);

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

        $mapper
            ->method('map')
            ->with('unkown_resource')
            ->willReturn(null)
        ;

        $voter = new MockedIAMVoter($mapper, $repository);

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

        self::assertEquals(
            VoterInterface::ACCESS_ABSTAIN,
            $voter->vote(
                $token,
                'unkown_resource',
                ['permission_read'],
            ),
        );
    }

    public function testHWIOAuthTokenGranted(): void
    {
        $repository = new FixtureRepository();
        $user = new ReefOAuthUser('toto', null);
        $permissions = [];

        $tokenFixture = new ReefTokenFixture(
            'unit_token',
            $user,
            $permissions,
            true,
        );

        $repository->addFixture($tokenFixture);

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

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

        $voter = new MockedIAMVoter($mapper, $repository);

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                'resource2',
                ['reef:iam:principals:superAdmins'],
            ),
        );
    }

    public function testGenericTokenGranted(): void
    {
        $repository = new FixtureRepository();
        $user = new ReefOAuthUser('toto', null);
        $permissions = [];

        $tokenFixture = new ReefTokenFixture(
            'unit_token',
            $user,
            $permissions,
            true,
        );

        $repository->addFixture($tokenFixture);

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

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

        $voter = new MockedIAMVoter($mapper, $repository);

        self::assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $voter->vote(
                $token,
                'resource2',
                ['reef:iam:principals:superAdmins'],
            ),
        );
    }
}
