<?php
namespace Backend\BaseBundle\EventListener;

use Backend\BaseBundle\Service\FunctionCheckerServiceInterface;
use JMS\DiExtraBundle\Annotation as DI;
use JMS\DiExtraBundle\Annotation\Inject;
use JMS\DiExtraBundle\Annotation\InjectParams;
use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Backend\BaseBundle\Controller\BaseController;
use Backend\BaseBundle\Guesser\ControllerNameGuesser;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;

/**
 * @DI\Service
 */
class ControllerActionListener
{
    /** @var  ExpressionLanguage */
    protected $language;

    /** @var  TokenStorageInterface */
    protected $tokenStorage;

    /** @var  FunctionCheckerServiceInterface */
    protected $functionChecker;

    /**
     * @InjectParams({
     *    "functionChecker" = @Inject("site.function.checker"),
     *    "language" = @Inject("security.expression_language"),
     *    "tokenStorage" = @Inject("security.token_storage")
     * })
     */
    public function injectService(FunctionCheckerServiceInterface $functionChecker, ExpressionLanguage $language, TokenStorageInterface $tokenStorage)
    {
        $this->functionChecker = $functionChecker;
        $this->language = $language;
        $this->tokenStorage = $tokenStorage;
    }

    /**
     * @DI\Observe("kernel.controller")
     */
    public function onKernelController(FilterControllerEvent $event)
    {
        $controller = $event->getController();

        if(!($controller[0] instanceof BaseController)){
            return;
        }

        $config = $controller[0]->getNormalizeConfig();
        $this->checkActionExist($config, get_class($controller[0]), $controller[1]);
        $this->checkRole($config, $controller[1]);
    }

    protected function extractAction($actionName)
    {
        if(preg_match('/^(\w+?)(Action)?$/i', $actionName, $match)){
            return strtolower($match[1]);
        }
        return false;
    }

    protected function hasAction($config, $controllerName, $action)
    {

        $controllerNameGuesser = new ControllerNameGuesser($controllerName);
        return isset($config['route']) &&
            isset($config['route'][$action]) &&
            $controllerNameGuesser->getActionRouteName($action) == $config['route'][$action]
            ;
    }

    protected function isFunctionEnabled($controllerName)
    {
        $controllerNameGuesser = new ControllerNameGuesser($controllerName);
        return $this->functionChecker->isBundleEnabled($controllerNameGuesser->getBundleName());
    }

    protected function checkActionExist($config, $controllerName, $actionName)
    {
        $action = $this->extractAction($actionName);

        if(!$this->isFunctionEnabled($controllerName)){
            throw new NotFoundHttpException();
        }

        if(!in_array($action, $controllerName::INTERNAL_ACTION)){
            return;
        }

        if(!$this->hasAction($config, $controllerName, $action)){
            throw new NotFoundHttpException();
        }
    }

    protected function checkRole($config, $actionName)
    {
        if(!$this->hasRoleOrSuperAdmin($config, $this->extractAction($actionName))){
            throw new AccessDeniedHttpException();
        }
    }

    /**
     * @param array $config
     * @param string $action
     * @return bool
     */
    protected function hasRoleOrSuperAdmin($config, $action)
    {
        if(!isset($config['security'])){
            return true;
        }

        if(!isset($config['security'][$action])){
            return true;
        }

        $role = $config['security'][$action];

        if(!($token = $this->tokenStorage->getToken())){
            return false;
        }

        if(!($user = $token->getUser())){
            return false;
        }

        if(!$this->language->evaluate("has_role_or_superadmin('$role')", array(
            'user' => $user,
            'roles' => $user->getRoles(),
        ))){
            return false;
        }

        return true;
    }
}