<?php


namespace AutoAdmin\Bundle\Admin\EventListener;


use AutoAdmin\Bundle\Admin\Configuration\ConfigManager;
use AutoAdmin\Bundle\Admin\Exception\ForbiddenActionException;
use AutoAdmin\Bundle\Admin\Exception\UnknownActionException;
use AutoAdmin\Bundle\Admin\Exception\UnknownEntityException;
use AutoAdmin\Bundle\Admin\Helper\AccessHelper;
use AutoAdmin\Bundle\Core\Util\CaseConverter;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;
use Symfony\Component\HttpKernel\Event\ControllerEvent;

class CustomControllerListener
{
    private ConfigManager $configManager;
    private ControllerResolverInterface $resolver;
    private AccessHelper $accessHelper;
    private CaseConverter $caseConverter;

    public function __construct(
        ConfigManager               $configManager,
        ControllerResolverInterface $resolver,
        AccessHelper                $accessHelper,
        CaseConverter               $caseConverter
    )
    {
        $this->configManager = $configManager;
        $this->resolver = $resolver;
        $this->accessHelper = $accessHelper;
        $this->caseConverter = $caseConverter;
    }

    /**
     * Exchange default admin controller by custom entity admin controller.
     *
     * @throws UnknownEntityException
     */
    public function onKernelController(ControllerEvent $event): void
    {
        $request = $event->getRequest();
        $routeName = $request->get('_route');
        if ($routeName !== 'aa_admin_entity' && $routeName !== 'aa_admin_controller') {
            return;
        }

        $controllerCallable = null;
        if (
            $routeName === 'aa_admin_entity'
            && ($slug = $request->get('entitySlug'))
            && ($action = $request->get('action'))
        ) {
            if ($slug && $action) {
                $entityConfig = $this->configManager->getEntityConfigBySlug($slug);
                $actionConfig = $this->configManager->getActionConfig($entityConfig['class'], $action);
                $method = $actionConfig['method'] ?? $action;
                if ($entityConfig !== null && isset($entityConfig['controller'])) {
                    $controller = $entityConfig['controller'];
                    $controllerCallable = $this->getControllerCallable($request, $controller, $method);
                }
            }
        } elseif (
            $routeName === 'aa_admin_controller'
            && ($slug = $request->get('controller'))
            && ($action = $request->get('method'))
        ) {
            $controller = $this->configManager->getControllerConfigBySlug($slug);

            if (empty($controller)) {
                throw new UnknownActionException($slug, $action);
            }

            if (!$this->accessHelper->isActionAllowed($controller['class'], $action)) {

                throw new ForbiddenActionException($controller['class'] . '::' . $action);
            }
            $methodName = $this->caseConverter->kebabToCamelCase($action);
            $controllerCallable = $this->getControllerCallable($request, $controller['class'], $methodName);
        }

        if ($controllerCallable) {
            $event->setController($controllerCallable);

            // cf. https://github.com/EasyCorp/EasyAdminBundle/blob/1cec5e83f9457f192c0f859313189457ed04c718/src/EventListener/AdminContextListener.php
            // this makes Symfony believe that another controller is being executed
            // (e.g. this is needed for the autowiring of controller action arguments)
            $event->getRequest()->attributes->set('_controller', $controllerCallable);
        }
    }

    protected function getControllerCallable($request, $controllerFqcn, $method)
    {
        $newRequest = $request->duplicate(null, null, ['_controller' => [$controllerFqcn, $method]]);

        return $this->resolver->getController($newRequest);
    }
}

