<?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\ReportBundle\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 Chill\ReportBundle\Entity\Report;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface;

/**
 * @internal
 *
 * @coversNothing
 */
final class ReportVoterTest extends KernelTestCase
{
    use PrepareCenterTrait;

    use PrepareScopeTrait;

    use PrepareUserTrait;

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

    /**
     * @var \Chill\ReportBundle\Security\Authorization\ReportVoter
     */
    protected $voter;

    public static function setUpBeforeClass(): void {}

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

    /**
     * @dataProvider dataProvider
     *
     * @param type $expectedResult
     * @param type $action
     * @param type $message
     */
    public function testAccess(
        $expectedResult,
        Report $report,
        $action,
        $message,
        ?User $user = null,
    ) {
        $token = $this->prepareToken($user);
        $result = $this->voter->vote($token, $report, [$action]);
        $this->assertEquals($expectedResult, $result, $message);
    }

    public function dataProvider()
    {
        $centerA = $this->prepareCenter(1, 'center A');
        $centerB = $this->prepareCenter(2, 'center B');
        $scopeA = $this->prepareScope(1, 'scope default');
        $scopeB = $this->prepareScope(2, 'scope B');
        $scopeC = $this->prepareScope(3, 'scope C');

        $userA = $this->prepareUser([
            [
                'center' => $centerA,
                'permissionsGroup' => [
                    ['scope' => $scopeB, 'role' => 'CHILL_REPORT_SEE'],
                    ['scope' => $scopeA, 'role' => 'CHILL_REPORT_UPDATE'],
                ],
            ],
            [
                'center' => $centerB,
                'permissionsGroup' => [
                    ['scope' => $scopeA, 'role' => 'CHILL_REPORT_SEE'],
                ],
            ],
        ]);

        $reportA = (new Report())
            ->setScope($scopeA)
            ->setPerson($this->preparePerson($centerA));
        $reportB = (new Report())
            ->setScope($scopeB)
            ->setPerson($this->preparePerson($centerA));
        $reportC = (new Report())
            ->setScope($scopeC)
            ->setPerson($this->preparePerson($centerB));

        return [
            [
                VoterInterface::ACCESS_DENIED,
                $reportA,
                'CHILL_REPORT_SEE',
                'assert is denied to a null user',
                null,
            ],
            [
                VoterInterface::ACCESS_GRANTED,
                $reportA,
                'CHILL_REPORT_SEE',
                'assert access is granted to a user with inheritance UPDATE > SEE',
                $userA,
            ],
            [
                VoterInterface::ACCESS_GRANTED,
                $reportB,
                'CHILL_REPORT_SEE',
                'assert access is granted to a user without inheritance',
                $userA,
            ],
            [
                VoterInterface::ACCESS_DENIED,
                $reportC,
                'CHILL_REPORT_SEE',
                'assert access is denied to a report',
                $userA,
            ],
        ];
    }

    /**
     * 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
     *
     * @return \Symfony\Component\Security\Core\Authentication\Token\TokenInterface
     */
    protected function prepareToken(?User $user = null)
    {
        $token = $this->prophet->prophesize();
        $token
            ->willImplement('\\'.\Symfony\Component\Security\Core\Authentication\Token\TokenInterface::class);

        if (null === $user) {
            $token->getUser()->willReturn(null);
        } else {
            $token->getUser()->willReturn($user);
        }

        return $token->reveal();
    }
}
