<?php

namespace AutoAdmin\Bundle\Admin\Helper;

use AutoAdmin\Bundle\Admin\Configuration\ConfigManager;
use AutoAdmin\Bundle\Admin\Exception\UnknownActionException;
use AutoAdmin\Bundle\Admin\Model\AdministratorInterface;
use AutoAdmin\Bundle\Content\Site\SiteContextHelper;
use Exception;
use RuntimeException;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;

class AccessHelper
{
    private ConfigManager $configManager;
    private EntityHelper $entityHelper;
    private AuthorizationCheckerInterface $authorizationChecker;
    private TokenStorageInterface $tokenStorage;
    private SiteContextHelper $siteContextHelper;
    private RequestStack $requestStack;

    public function __construct(
        ConfigManager                 $configManager,
        EntityHelper                  $entityHelper,
        AuthorizationCheckerInterface $authorizationChecker,
        TokenStorageInterface         $tokenStorage,
        RequestStack                  $requestStack,
        SiteContextHelper             $siteContextHelper
    )
    {
        $this->configManager = $configManager;
        $this->entityHelper = $entityHelper;
        $this->authorizationChecker = $authorizationChecker;
        $this->tokenStorage = $tokenStorage;
        $this->requestStack = $requestStack;
        $this->siteContextHelper = $siteContextHelper;
    }

    public function isActionAllowed(string $classFqcn, $action): bool
    {
        $controllerConfig = [];
        if (array_key_exists($classFqcn, $this->configManager->getConfig('controllers'))) {
            try {
                $controllerConfig = $this->configManager->getControllerConfig($classFqcn);
            } catch (Exception $e) {
            }
        }

        $entityConfig = [];
        if (array_key_exists($classFqcn, $this->configManager->getConfig('entities'))) {
            try {
                $entityConfig = $this->configManager->getEntityConfigByClass($classFqcn);
            } catch (Exception $e) {
            }
        }
        $actionsConfig = array_merge_recursive(
            $controllerConfig['actions'] ?? [],
            $entityConfig['actions'] ?? [],
        );

        // convert action to array if string provided
        if (is_string($action)) {
            if (!empty($actionsConfig[$action])) {
                $action = $actionsConfig[$action];
            } else {
                throw new UnknownActionException($classFqcn, $action);
            }
        }

        $roles = $action['roles'];
        /** @var AdministratorInterface $user */
        $user = $this->tokenStorage->getToken()->getUser();
        if (
            // super admin can view/edit any entity
        !$this->authorizationChecker->isGranted('ROLE_SUPER_ADMIN', $user)
        ) {
            // todo: enforce user limits on actions
            $currentSite = $this->siteContextHelper->getCurrentContext()->getSite();
            if (!$user->getAdministratorSites()->contains($currentSite)) {
                // administrator is not allowed to edit this site, forbid it
                return false;
            }
        }

        // we want to keep this working with symfony default security config:
        // access_decision_manager:
        //      strategy: affirmative
        // cf. https://github.com/symfony/symfony-docs/issues/8355
        // so if array, all roles are required to pass

        if (is_string($roles)) {
            $canAccess = $this->authorizationChecker->isGranted($roles, $user);
        } elseif (is_array($roles)) {
            $canAccess = $this->isGranted($roles);
        } else {
            throw new RuntimeException('Roles index must be string or array. "' . gettype($roles) . '" encountered');
        }

        return $canAccess;
    }

    public function isGranted(array $roles): bool
    {
        $user = $this->tokenStorage->getToken()->getUser();
        if (!$user instanceof AdministratorInterface || empty($roles)) {
            return false;
        }

        foreach ($roles as $role) {
            $roleAccess = $this->authorizationChecker->isGranted($role, $user);
            if (!isset($canAccess) || $canAccess === null) {
                $canAccess = $roleAccess;
            } else {
                $canAccess = $canAccess && $roleAccess;
            }
        }

        if (!isset($canAccess) || $canAccess === null) {
            $canAccess = false;
        }

        return $canAccess;
    }
}
