<?php
declare(strict_types=1);

namespace App\Security\Voter;

use ApiPlatform\Metadata\Resource\Factory\ResourceMetadataCollectionFactoryInterface;
use App\Entity\Access\Access;
use App\Entity\Organization\Organization;
use App\Service\Security\Module;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\Security\Core\Authentication\Token\NullToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authorization\Voter\Voter;

/**
 * Class ModuleVoter : permet d'assurer que la resource appelée est comprise dans l'un des modules de la structure
 * @package App\Security
 */
class ModuleVoter extends Voter
{
    const HAVING_MODULE = 'IS_HAVING_MODULE';

    public function __construct(private Module $module, private ResourceMetadataCollectionFactoryInterface $resourceMetadataFactory)
    { }

    protected function supports(string $attribute, $subject): bool
    {
        if (!in_array($attribute, [self::HAVING_MODULE])) {
            return false;
        }
        return true;
    }

    /**
     * @param string $attribute
     * @param mixed $subject
     * @param TokenInterface $token
     * @return bool
     * @throws \ApiPlatform\Exception\ResourceClassNotFoundException
     */
    protected function voteOnAttribute(string $attribute, $subject, TokenInterface $token): bool
    {
        if (!$subject->attributes->get('_api_resource_class')) {
            throw new AccessDeniedHttpException(sprintf('Missing resource class'));
        }
        if ($token instanceof NullToken) {
            return false;
        }

        $resourceMetadata = $this->resourceMetadataFactory->create($subject->attributes->get('_api_resource_class'));
        $module = $this->module->getModuleByResourceName($resourceMetadata->getOperation()->getShortName());

        //Check if there is a module for this entity : eq configuration problem
        if (null === $module) {
            throw new AccessDeniedHttpException(sprintf('There are no module for the entity (%s) !', $resourceMetadata->getOperation()->getShortName()));
        }

        /** @var Access $currentAccess */
        $currentAccess = $token->getUser();
        /** @var Organization $organization */
        $organization = $currentAccess->getOrganization();

        if (!$this->isOrganizationHaveThisModule($organization, $module)) {
            throw new AccessDeniedHttpException(
                sprintf("The organization doesn't have the module '%s'", $module)
            );
        }
        return true;
    }

    /**
     * Test si l'organisation possède le module parmis les modules possédés via le produit souscrit, les options souscrites
     * ou les modules possédées via des conditions particulières (isCmf par exemple)
     *
     * @param Organization $organization
     * @param string $module
     * @return bool
     */
    private function isOrganizationHaveThisModule(Organization $organization, string $module): bool
    {
        $organizationModules = $this->module->getOrganizationModules($organization);
        return in_array($module, $organizationModules);
    }
}
