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

use Chill\MainBundle\Entity\Workflow\EntityWorkflow;
use Chill\MainBundle\Workflow\EntityWorkflowManager;
use Chill\MainBundle\Workflow\Helper\DuplicateEntityWorkflowFinder;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Workflow\Registry;

class EntityWorkflowVoter extends Voter
{
    final public const CREATE = 'CHILL_MAIN_WORKFLOW_CREATE';

    final public const DELETE = 'CHILL_MAIN_WORKFLOW_DELETE';

    final public const SEE = 'CHILL_MAIN_WORKFLOW_SEE';

    final public const SHOW_ENTITY_LINK = 'CHILL_MAIN_WORKFLOW_LINK_SHOW';

    public function __construct(
        private readonly EntityWorkflowManager $manager,
        private readonly Security $security,
        private readonly DuplicateEntityWorkflowFinder $duplicateEntityWorkflowFinder,
        private readonly Registry $registry,
    ) {}

    protected function supports($attribute, $subject)
    {
        return $subject instanceof EntityWorkflow && \in_array($attribute, self::getRoles(), true);
    }

    /**
     * @param EntityWorkflow $subject
     */
    protected function voteOnAttribute($attribute, $subject, TokenInterface $token): bool
    {
        switch ($attribute) {
            case self::CREATE:
                if (false === $this->voteOnAttribute(self::SEE, $subject, $token)) {
                    return false;
                }

                if ($this->duplicateEntityWorkflowFinder->hasDuplicateOpenedOrFinalPositiveEntityWorkflow($subject)) {
                    return false;
                }

                return true;
            case self::SEE:
                $handler = $this->manager->getHandler($subject);

                $entityAttribute = $handler->getRoleShow($subject);

                if (null === $entityAttribute) {
                    return true;
                }

                $relatedEntity = $handler->getRelatedEntity($subject);

                if (null === $relatedEntity) {
                    return true;
                }

                if ($this->security->isGranted($entityAttribute, $relatedEntity)) {
                    return true;
                }

                foreach ($subject->getSteps() as $step) {
                    if ($step->getAllDestUser()->contains($token->getUser())) {
                        return true;
                    }
                }

                return false;

            case self::DELETE:
                if ('initial' === $subject->getStep()) {
                    return true;
                }

                if (!$subject->isFinal()) {
                    return false;
                }

                // the entity workflow is finalized. We check if the final is not positive. If yes, we can
                // delete the entity
                $workflow = $this->registry->get($subject, $subject->getWorkflowName());
                foreach ($workflow->getMarking($subject)->getPlaces() as $place => $key) {
                    $metadata = $workflow->getMetadataStore()->getPlaceMetadata($place);
                    if (false === ($metadata['isFinalPositive'] ?? true)) {
                        return true;
                    }
                }

                return false;

            case self::SHOW_ENTITY_LINK:
                if ('initial' === $subject->getStep()) {
                    return false;
                }

                $currentStep = $subject->getCurrentStepChained();

                if ($currentStep->isFinal()) {
                    return false;
                }

                return $currentStep->getPrevious()->getTransitionBy() === $this->security->getUser();

            default:
                throw new \UnexpectedValueException("attribute {$attribute} not supported");
        }
    }

    private static function getRoles(): array
    {
        return [
            self::SEE,
            self::CREATE,
            self::DELETE,
            self::SHOW_ENTITY_LINK,
        ];
    }
}
