<?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\Workflow\EventSubscriber;

use Chill\MainBundle\Entity\User;
use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Security\Authorization\EntityWorkflowTransitionVoter;
use Chill\MainBundle\Templating\Entity\UserRender;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Workflow\Event\GuardEvent;
use Symfony\Component\Workflow\TransitionBlocker;

/**
 * Prevent apply a transition on an entity workflow.
 *
 * This apply logic and rules to decide if a transition can be applyed.
 *
 * Those rules are:
 *
 * - if the transition is system-only or is allowed for user;
 * - if the user is present in the dest users for a workflow;
 * - or if the user have permission to apply all the transitions
 */
class EntityWorkflowGuardTransition implements EventSubscriberInterface
{
    public function __construct(
        private readonly UserRender $userRender,
        private readonly Security $security,
    ) {}

    public static function getSubscribedEvents(): array
    {
        return [
            'workflow.guard' => [
                ['guardEntityWorkflow', 0],
            ],
        ];
    }

    public function guardEntityWorkflow(GuardEvent $event)
    {
        if (!$event->getSubject() instanceof EntityWorkflow) {
            return;
        }

        /** @var EntityWorkflow $entityWorkflow */
        $entityWorkflow = $event->getSubject();

        if ($entityWorkflow->isFinal()) {
            $event->addTransitionBlocker(
                new TransitionBlocker(
                    'workflow.The workflow is finalized',
                    'd6306280-7535-11ec-a40d-1f7bee26e2c0'
                )
            );

            return;
        }

        $user = $this->security->getUser();
        $metadata = $event->getWorkflow()->getMetadataStore()->getTransitionMetadata($event->getTransition());
        $systemTransitions = explode('+', $metadata['transitionGuard'] ?? 'only-dest');

        if (null === $user) {
            if (in_array('system', $systemTransitions, true)) {
                // it is safe to apply this transition
                return;
            }

            $event->addTransitionBlocker(
                new TransitionBlocker(
                    'workflow.Transition is not allowed for system',
                    'd9e39a18-704c-11ef-b235-8fe0619caee7'
                )
            );

            return;
        }

        if (!$user instanceof User) {
            $event->addTransitionBlocker(
                new TransitionBlocker(
                    'workflow.Only regular user can apply a transition',
                    '04fb4f76-7c0e-11ef-afc3-877bad7b0fe7'
                )
            );

            return;
        }

        // for users
        if (!in_array('only-dest', $systemTransitions, true)) {
            $event->addTransitionBlocker(
                new TransitionBlocker(
                    'workflow.Only system can apply this transition',
                    '5b6b95e0-704d-11ef-a5a9-4b6fc11a8eeb'
                )
            );
        }

        if (
            !$entityWorkflow->getCurrentStep()->getAllDestUser()->contains($user)
        ) {
            if ($event->getMarking()->has('initial')) {
                return;
            }

            if ($this->security->isGranted(EntityWorkflowTransitionVoter::APPLY_ALL_TRANSITIONS, $entityWorkflow->getCurrentStep())) {
                return;
            }

            // we give a second chance, searching for the presence of the user within userGroups
            foreach ($entityWorkflow->getCurrentStep()->getDestUserGroups() as $userGroup) {
                if ($userGroup->contains($user)) {
                    return;
                }
            }

            $event->addTransitionBlocker(new TransitionBlocker(
                'workflow.You are not allowed to apply a transition on this workflow. Only those users are allowed: %users%',
                'f3eeb57c-7532-11ec-9495-e7942a2ac7bc',
                [
                    '%users%' => implode(
                        ', ',
                        $entityWorkflow->getCurrentStep()->getAllDestUser()->map(fn (User $u) => $this->userRender->renderString($u, []))->toArray()
                    ),
                ]
            ));
        }
    }
}
