<?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\MainBundle\Tests\Security\Authorization;

use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Security\Authorization\EntityWorkflowVoter;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\EntityWorkflowMarkingStore;
use Chill\MainBundle\Workflow\EventSubscriber\EntityWorkflowTransitionEventSubscriber;
use Chill\MainBundle\Workflow\Helper\DuplicateEntityWorkflowFinder;
use Chill\MainBundle\Workflow\WorkflowTransitionContextDTO;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Workflow\DefinitionBuilder;
use Symfony\Component\Workflow\Metadata\InMemoryMetadataStore;
use Symfony\Component\Workflow\Registry;
use Symfony\Component\Workflow\SupportStrategy\WorkflowSupportStrategyInterface;
use Symfony\Component\Workflow\Transition;
use Symfony\Component\Workflow\Workflow;
use Symfony\Component\Workflow\WorkflowInterface;

/**
 * @internal
 *
 * @coversNothing
 */
class EntityWorkflowVoterTest extends TestCase
{
    public function testVoteDeleteEntityWorkflowForInitialPlace(): void
    {
        $entityWorkflow = $this->buildEntityWorkflow();

        $voter = $this->buildVoter();
        $token = $this->buildToken();

        $actual = $voter->vote($token, $entityWorkflow, [EntityWorkflowVoter::DELETE]);

        self::assertEquals(Voter::ACCESS_GRANTED, $actual);
    }

    public function testVoteDeleteEntityWorkflowForSomeOherPlace(): void
    {
        $entityWorkflow = $this->buildEntityWorkflow();
        $registry = $this->buildRegistry();
        $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
        $dto = new WorkflowTransitionContextDTO($entityWorkflow);
        $dto->futureDestUsers = [new User()];
        $workflow->apply($entityWorkflow, 'move_to_in_between', ['context' => $dto, 'transition' => 'move_to_in_between', 'transitionAt' => new \DateTimeImmutable()]);

        assert('in_between' === $entityWorkflow->getStep(), 'we ensure that the workflow is well transitionned');

        $voter = $this->buildVoter();
        $token = $this->buildToken();

        $actual = $voter->vote($token, $entityWorkflow, [EntityWorkflowVoter::DELETE]);

        self::assertEquals(Voter::ACCESS_DENIED, $actual);
    }

    public function testVoteDeleteEntityWorkflowForFinalPositive(): void
    {
        $entityWorkflow = $this->buildEntityWorkflow();
        $registry = $this->buildRegistry();
        $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
        $dto = new WorkflowTransitionContextDTO($entityWorkflow);
        $dto->futureDestUsers = [new User()];
        $workflow->apply($entityWorkflow, 'move_to_final_positive', ['context' => $dto, 'transition' => 'move_to_final_positive', 'transitionAt' => new \DateTimeImmutable()]);

        assert('final_positive' === $entityWorkflow->getStep(), 'we ensure that the workflow is well transitionned');

        $voter = $this->buildVoter();
        $token = $this->buildToken();

        $actual = $voter->vote($token, $entityWorkflow, [EntityWorkflowVoter::DELETE]);

        self::assertEquals(Voter::ACCESS_DENIED, $actual);
    }

    public function testVoteDeleteEntityWorkflowForFinalNegative(): void
    {
        $entityWorkflow = $this->buildEntityWorkflow();
        $registry = $this->buildRegistry();
        $workflow = $registry->get($entityWorkflow, $entityWorkflow->getWorkflowName());
        $dto = new WorkflowTransitionContextDTO($entityWorkflow);
        $dto->futureDestUsers = [new User()];
        $workflow->apply($entityWorkflow, 'move_to_final_negative', ['context' => $dto, 'transition' => 'move_to_final_negative', 'transitionAt' => new \DateTimeImmutable()]);

        $voter = $this->buildVoter();
        $token = $this->buildToken();

        $actual = $voter->vote($token, $entityWorkflow, [EntityWorkflowVoter::DELETE]);

        self::assertEquals(Voter::ACCESS_GRANTED, $actual);
    }

    private function buildToken(): TokenInterface
    {
        return new UsernamePasswordToken($user = new User(), 'main', $user->getRoles());
    }

    private function buildVoter(): EntityWorkflowVoter
    {
        $manager = $this->createMock(EntityWorkflowManager::class);
        $security = $this->createMock(Security::class);
        $duplicateEntityWorkflowFind = $this->createMock(DuplicateEntityWorkflowFinder::class);

        return new EntityWorkflowVoter(
            $manager,
            $security,
            $duplicateEntityWorkflowFind,
            $this->buildRegistry()
        );
    }

    private function buildEntityWorkflow(): EntityWorkflow
    {
        $entityWorkflow = new EntityWorkflow();
        $entityWorkflow->setWorkflowName('dummy');
        $entityWorkflow->setRelatedEntityId(1)->setRelatedEntityClass(\stdClass::class);

        return $entityWorkflow;
    }

    private function buildRegistry(): Registry
    {
        $security = $this->createMock(Security::class);
        $security->method('getUser')->willReturn(new User());

        $builder = new DefinitionBuilder();
        $builder->addPlaces(['initial', 'in_between', 'final_positive', 'final_negative']);

        $metadataStore = new InMemoryMetadataStore(
            placesMetadata: [
                'final_positive' => [
                    'isFinal' => true,
                    'isFinalPositive' => true,
                ],
                'final_negative' => [
                    'isFinal' => true,
                    'isFinalPositive' => false,
                ],
            ]
        );

        $builder->setMetadataStore($metadataStore);

        $transitions = [
            new Transition('move_to_in_between', 'initial', 'in_between'),
            new Transition('move_to_final_positive', 'initial', 'final_positive'),
            new Transition('move_to_final_negative', 'initial', 'final_negative'),
        ];

        foreach ($transitions as $transition) {
            $builder->addTransition($transition);
        }

        $definition = $builder->build();

        $eventSubscriber = new EventDispatcher();
        $eventSubscriber->addSubscriber(
            new EntityWorkflowTransitionEventSubscriber(
                new NullLogger(),
                $security
            )
        );

        $registry = new Registry();
        $workflow = new Workflow($definition, new EntityWorkflowMarkingStore(), $eventSubscriber, name: 'dummy');

        $registry->addWorkflow($workflow, new class () implements WorkflowSupportStrategyInterface {
            public function supports(WorkflowInterface $workflow, $subject): bool
            {
                return true;
            }
        });

        return $registry;
    }
}
