<?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 AutoAdmin\Bundle\Content\Site\SiteContextHelper;
use AutoAdmin\Bundle\Content\Translation\LocaleResolver;
use Exception;
use ReflectionClass;
use Symfony\Component\PropertyAccess\PropertyAccessor;
use Symfony\Component\Routing\RouterInterface;

class ContentRouter
{
    public function __construct(
        protected ConfigManager $config,
        protected RouterInterface $router,
        protected LocaleResolver $localeResolver,
        protected AutoRouterManager $autoRouterManager,
        protected ContentResolver $resolver,
        protected SiteContextHelper $contextHelper,
    ) {
    }

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

        return '';
    }

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

        if ($action === 'chain') {
            return $this->chainGenerate($content, $parameters, $options);
        }

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

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

        if (count($this->contextHelper->getDomain()->getLocales()) <= 1) {
            unset($parameters['_locale']);
        }

        if (is_string($content)) {
            $found = null;
            if (!empty($parameters['slug'])) {
                $found = $this->resolver->resolveBySlug($content, $parameters['slug']);
            } elseif (!empty($parameters['slugAdmin'])) {
                $found = $this->resolver->resolveBySlugAdmin($content, $parameters['slugAdmin']);
                unset($parameters['slugAdmin']);// internal parameter, remove from url generation
            } elseif ($action === 'index') {
                // allow simple index action generation,
                // often not dependent on a loaded entity as we want a listing url
                $route = $content.'_'.$action;

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

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

            $content = $found;
        }

        if (is_object($content)) {
            $routeName = $this->getRouteName($content, $action, $options);
            // route ending with locale (route names: my_route_name.fr or my_route_name.en)
            if (preg_match('/(.+?)(\.([a-z]{2}))/', $routeName ?? '', $matches)) {
                $locale = $matches[3];
                if (!array_key_exists('_locale', $parameters)) {
                    $parameters['_locale'] = $locale;
                }
            }
            $url = $this->autoRouterManager->generate($this, $content, $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 getActionChainConfig($content): array
    {
        $contentConfig = $this->config->getContentConfigForSlug($this->config->guessSlug($content));

        return $contentConfig['action_chain'] ?? [];
    }

    private function chainGenerate($content, array $parameters = [], array $options = [])
    {
        $actionChain = $this->getActionChainConfig($content);

        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, string $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->localeResolver->resolveLocale($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];
    }

    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->localeResolver->resolveLocale($parameters['_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;
    }
}
