<?php

declare(strict_types=1);

/*
 * Chill is a software for social workers
 *
 * For the full copyright and license information, please view
 * the LICENSE file that was distributed with this source code.
 */

namespace Chill\PersonBundle\Tests\Security\Authorization;

use Chill\MainBundle\Entity\Center;
use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Test\PrepareCenterTrait;
use Chill\MainBundle\Test\PrepareScopeTrait;
use Chill\MainBundle\Test\PrepareUserTrait;
use Chill\PersonBundle\Entity\Person;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;

/**
 * Test PersonVoter.
 *
 * @internal
 *
 * @coversNothing
 */
final class PersonVoterTest extends KernelTestCase
{
    use PrepareCenterTrait;

    use PrepareScopeTrait;

    use PrepareUserTrait;

    /**
     * @var \Prophecy\Prophet
     */
    protected $prophet;

    /**
     * @var \Chill\PersonBundle\Security\Authorization\PersonVoter
     */
    protected $voter;

    protected function setUp(): void
    {
        self::bootKernel();
        $this->voter = self::getContainer()
            ->get('chill.person.security.authorization.person');
        $this->prophet = new \Prophecy\Prophet();
    }

    public function testNullUser()
    {
        $token = $this->prepareToken();
        $center = $this->prepareCenter(1, 'center');
        $person = $this->preparePerson($center);

        $this->assertEquals(
            VoterInterface::ACCESS_DENIED,
            $this->voter->vote($token, $person, ['CHILL_PERSON_SEE']),
            'assert that a null user is not allowed to see'
        );
    }

    /**
     * test a user with sufficient right may see the person.
     */
    public function testUserAllowed()
    {
        $center = $this->prepareCenter(1, 'center');
        $scope = $this->prepareScope(1, 'default');
        $token = $this->prepareToken([
            [
                'center' => $center, 'permissionsGroup' => [
                    ['scope' => $scope, 'role' => 'CHILL_PERSON_SEE'],
                ],
            ],
        ]);
        $person = $this->preparePerson($center);

        $this->assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $this->voter->vote($token, $person, ['CHILL_PERSON_SEE']),
            'assert that a user with correct rights may is granted access'
        );
    }

    /**
     * test a user with sufficient right may see the person.
     * hierarchy between role is required.
     */
    public function testUserAllowedWithInheritance()
    {
        $center = $this->prepareCenter(1, 'center');
        $scope = $this->prepareScope(1, 'default');
        $token = $this->prepareToken([
            [
                'center' => $center, 'permissionsGroup' => [
                    ['scope' => $scope, 'role' => 'CHILL_PERSON_UPDATE'],
                ],
            ],
        ]);
        $person = $this->preparePerson($center);
        $this->assertEquals(
            VoterInterface::ACCESS_GRANTED,
            $this->voter->vote($token, $person, ['CHILL_PERSON_SEE']),
            'assert that a user with correct role is granted on inherited roles'
        );
    }

    public function testUserCanNotReachCenter()
    {
        $centerA = $this->prepareCenter(1, 'centera');
        $centerB = $this->prepareCenter(2, 'centerb');
        $scope = $this->prepareScope(1, 'default');
        $token = $this->prepareToken([
            [
                'center' => $centerA, 'permissionsGroup' => [
                    ['scope' => $scope, 'role' => 'CHILL_PERSON_UPDATE'],
                ],
            ],
        ]);
        $person = $this->preparePerson($centerB);

        $this->assertEquals(
            VoterInterface::ACCESS_DENIED,
            $this->voter->vote($token, $person, ['CHILL_PERSON_UPDATE']),
            'assert that a user with right not in the good center has access denied'
        );
    }

    /**
     * prepare a person.
     *
     * The only properties set is the center, others properties are ignored.
     *
     * @return Person
     */
    protected function preparePerson(Center $center)
    {
        return (new Person())
            ->setCenter($center);
    }

    /**
     * prepare a token interface with correct rights.
     *
     * if $permissions = null, user will be null (no user associated with token
     *
     * @param array $permissions an array of permissions, with key 'center' for the center and 'permissions' for an array of permissions
     *
     * @return \Symfony\Component\Security\Core\Authentication\Token\TokenInterface
     */
    protected function prepareToken(?array $permissions = null)
    {
        $token = $this->prophet->prophesize();
        $token
            ->willImplement('\\'.\Symfony\Component\Security\Core\Authentication\Token\TokenInterface::class);

        if (null === $permissions) {
            $token->getUser()->willReturn(null);
        } else {
            $token->getUser()->willReturn($this->prepareUser($permissions));
        }

        return $token->reveal();
    }
}
