<?php


namespace AutoAdmin\Bundle\Content\Routing;


use AutoAdmin\Bundle\Content\Configuration\ConfigManager;
use AutoAdmin\Bundle\Content\Entity\Page;
use AutoAdmin\Bundle\Content\Model\ContentInterface;
use AutoAdmin\Bundle\Content\Model\TranslatableInterface;
use AutoAdmin\Bundle\Content\Resolver\ContentResolver;
use AutoAdmin\Bundle\Content\Routing\Exception\ContentRoutingFailedException;
use AutoAdmin\Bundle\Content\Routing\Exception\NullContentRoutingException;
use AutoAdmin\Bundle\Content\Routing\Exception\UnresolvableContentException;
use Exception;
use ReflectionClass;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Routing\RouterInterface;

class ContentRouter
{
    private ConfigManager $config;
    private RouterInterface $router;
    private ?RequestStack $requestStack;
    private AutoRouterManager $autoRouterManager;
    private ContentResolver $resolver;

    public function __construct(
        ConfigManager $config,
        RouterInterface $router,
        RequestStack $requestStack,
        AutoRouterManager $autoRouterManager,
        ContentResolver $resolver
    ) {
        $this->config = $config;
        $this->router = $router;
        $this->requestStack = $requestStack;
        $this->autoRouterManager = $autoRouterManager;
        $this->resolver = $resolver;
    }

    public function generateSafe($content, $action = 'view', array $parameters = [], array $options = []): ?string
    {
        try {
            return $this->generate($content, $action, $parameters, $options);
        } catch (Exception $e) {
            // silenced
        }

        return '';
    }

    public function generate($content, $action = 'view', array $parameters = [], array $options = []): ?string
    {
        if ($action === 'chain') {
            return $this->chainGenerate($content, $action, $parameters, $options);
        }

        return $this->doGenerate($content, $action, $parameters, $options);
    }

    public function doGenerate($content, $action = 'view', array $parameters = [], array $options = [])
    {
        if (
            $content === null
            || ($content instanceof TranslatableInterface && empty($content->getCurrentTranslation()))
        ) {
            throw new NullContentRoutingException($content);
        }

        if (is_string($content)) {
            if (!empty($parameters['slug'])) {
                $found = $this->resolver->resolveBySlug($content, $parameters['slug']);
            } elseif ($parameters['slugAdmin']) {
                $found = $this->resolver->resolveBySlugAdmin($content, $parameters['slugAdmin']);
                unset($parameters['slugAdmin']);// internal parameter, remove from url generation
            }

            if ($found === null) {
                throw new UnresolvableContentException($content);
            }

            $content = $found;
        }

        if (is_object($content)) {
            $routeName = $this->getRouteName($content, $action, $options);
            $url = $this->autoRouterManager->generate(
                $this,
                is_object($content) ? $content : null,
                $routeName,
                $parameters,
                $options
            );
        } else {
            throw new ContentRoutingFailedException(gettype($content));
        }

        if (($scheme = $options['scheme'] ?? 'https') && !empty($host = $options['host'] ?? null)) {
            $url = $scheme.'://'.$host.$url;
        }

        return $url;
    }

    private function chainGenerate($content, $action = 'chain', array $parameters = [], array $options = [])
    {
        $contentConfig = $this->config->getContentConfigForSlug($this->config->guessSlug($content));
        $actionChain = $contentConfig['action_chain'] ?? [];

        if (empty($actionChain)) {
            // no config, return 'view' action url
            return $this->doGenerate($content, 'view', $parameters, $options);
        }

        $errors = [];
        $url = '';
        foreach ($actionChain as $chainedAction) {
            try {
                $url = $this->generate($content, $chainedAction, $parameters, $options);
                break;
            } catch (Exception $e) {
                $errors[] = $e;
            }
        }

        // if last action generates errors, throw it
        // chained actions should always have at least 1 working route,
        // so an offset of minimum 1 between errors and actions
        if (count($errors) == count($actionChain)) {
            throw  $errors[count($errors) - 1];
        }

        return $url;
    }

    public function getRouteName($content, $action = 'view', array $options = []): ?string
    {
        $contentSlug = $this->config->guessSlug($content);
        list($contentSlug, $action) = $this->filterSpecialRoutes($content, $contentSlug, $action);

        $routeName = $contentSlug.'_'.$action;
        $routes = $this->router->getRouteCollection();
        $route = $routes->get($routeName);
        if ($route === null) {
            $locale = $this->resolveCurrentLocale($options['locale'] ?? null);
            $routeName .= '.'.$locale;
            $route = $routes->get($routeName);
        }

        if ($route === null) {
            return null;
        }

        return $routeName;
    }

    protected function filterSpecialRoutes($content, $contentSlug, $routeAction): array
    {
        // specific case: if we are handling the homepage, use "index" action instead of "view" action
        if (is_object($content)) {
            $contentClassRefl = new ReflectionClass($content);
            /** @var Page $content */
            if ($contentSlug === 'page' && $contentClassRefl->isSubclassOf(Page::class) && $content->isHomepage()) {
                $routeAction = 'index';
            }
        }

        return [$contentSlug, $routeAction];
    }

    protected function resolveCurrentLocale($locale = null): string
    {
        return (string)(
            $locale ??
            $this->requestStack->getMainRequest()->get('_admin_locale') ??
            $this->requestStack->getMainRequest()->getLocale()
        );
    }

    public function buildRouteParams($content, $action = 'view', array $options = []): array
    {
        $contentSlug = $this->config->guessSlug($content);
        list($contentSlug, $action) = $this->filterSpecialRoutes($content, $contentSlug, $action);
        $routeName = $this->getRouteName($content, $action, $options);

        $parameters = [];
        $parameters = $this->addImplicitParameters($routeName, $parameters);
        if ($content instanceof ContentInterface) {
            $parameters = $this->addAutoFilledParameters($routeName, $parameters, $content);
        }

        return $parameters;
    }

    protected function addImplicitParameters(string $routeName, array $parameters = []): array
    {
        // auto-add locale if parameter is defined in route
        $route = $this->router->getRouteCollection()->get($routeName);
        if ($route) {
            $routePath = $route->getPath();
            if (stripos($routePath, '{_locale}') !== false) {
                $parameters['_locale'] = $this->resolveCurrentLocale();
            }

//            if (!array_key_exists('slug', $parameters) && stripos($routePath, '{slug}') !== false) {
//                if(!array_key_exists('slug', $parameters)) {
////                    if () {
//
////                    }
//                $parameters['slug'] = $this->resolveCurrentLocale($options['locale'] ?? null);
//                }
//            }
        }

        return $parameters;
    }

    protected function addAutoFilledParameters(string $routeName, array $parameters, ContentInterface $content): array
    {
        $routeConfig = $this->config->getRoute($routeName);
        $propertyAccessor = new PropertyAccessor();
        foreach ($routeConfig['autofill_parameters'] ?? [] as $key => $propertyPath) {
            $propertyPath = str_replace('_tr.', 'currentTranslation.', $propertyPath);
            $parameters[$key] = $propertyAccessor->getValue($content, $propertyPath);
        }

        return $parameters;
    }

    public function getRouter(): RouterInterface
    {
        return $this->router;
    }

    public function generateFromAdminSlug($slug)
    {
        $routeName = $this->config->getAdminRouteName($slug);
        $route = $this->router->getRouteCollection()->get($routeName);
        if ($route === null) {
            return null;
        }

        return $this->router->generate($routeName);
    }
}
