<?php

namespace KupShop\I18nBundle\Util\Locale;

use KupShop\KupShopBundle\Context\ContextManager;
use KupShop\KupShopBundle\Context\LanguageContext;
use KupShop\KupShopBundle\Util\HTTPUtil;
use KupShop\KupShopBundle\Util\System\ControllerUtil;
use KupShop\KupShopBundle\Views\RouteAwareResponderInterface;
use KupShop\LocalePrefixBundle\Util\LocalePrefixUtil;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\Routing\RouterInterface;

class LanguageAwareUrlGenerator
{
    public function __construct(
        private readonly RouterInterface $router,
        private readonly LanguageContext $languageContext,
        private readonly ControllerUtil $controllerUtil,
        private readonly ContextManager $contextManager,
        private readonly RequestStack $requestStack,
        private readonly ?LocalePrefixUtil $localePrefixUtil = null,
    ) {
    }

    public function generateInLanguage(string $language, string $path = '/'): string
    {
        $request = $this->createRequest($path);

        return $this->withMainRequest($request, function () use ($request, $language) {
            $fallback = true;

            if ($match = $this->controllerUtil->getControllerByRequest($request)) {
                if ($url['path'] = $this->getCorrectUrlFromController($request, $match, $language)) {
                    return HTTPUtil::http_build_url($url);
                }

                $fallback = false;
                try {
                    $url['path'] = $pathInLanguage = $this->router->generateInLanguage($language, $match['_route'], $match['_route_params']);
                    $url = HTTPUtil::http_build_url($url);
                    if (!$url || empty($pathInLanguage)) {
                        $fallback = true;
                    }
                } catch (\Exception) {
                    $fallback = true;
                }
            }

            if ($fallback) {
                $url = $this->router->generateInLanguage($language, 'home');
            }

            $this->languageContext->activate($language);

            return $url;
        });
    }

    private function getCorrectUrlFromController(Request $request, array $match, string $language): ?string
    {
        $controller = $this->controllerUtil->instantiateController($match['_controller']);
        $controllerReturn = clone $this->controllerUtil->callController($request, $controller);

        if (!($controllerReturn instanceof RouteAwareResponderInterface)) {
            return null;
        }

        try {
            $url = $this->contextManager->activateContexts(
                [LanguageContext::class => $language],
                fn (): ?string => $controllerReturn->getCorrectUrl(),
            );
        } catch (NotFoundHttpException) {
            $url = null;
        }

        return $url === null ? $this->router->generateInLanguage($language, 'home') : $url;
    }

    private function createRequest(string $uri): Request
    {
        $request = Request::create($uri);
        $request->attributes->set('path', ltrim(parse_url($uri)['path'] ?? '/', '/'));

        if (findModule(\Modules::LOCALE_PREFIX)) {
            $this->localePrefixUtil->setRequestAttribute($request);
        }

        if ($this->requestStack->getMainRequest()->hasSession()) {
            $request->setSession(
                $this->requestStack->getMainRequest()->getSession()
            );
        }

        return $request;
    }

    private function withMainRequest(Request $request, callable $fn): mixed
    {
        $prevRequests = [];
        while ($this->requestStack->getCurrentRequest()) {
            // no-op: intentionally discarding all requests so request in args will be main request
            $prevRequests[] = $this->requestStack->pop();
        }
        // reverse, so last pop request is first
        $prevRequests = array_reverse($prevRequests);

        // push arg request to requests so it becomes main request
        $this->requestStack->push($request);

        $result = $fn();

        // remove arg request
        $this->requestStack->pop();

        // restore prev requests
        foreach ($prevRequests as $prevRequest) {
            $this->requestStack->push($prevRequest);
        }

        return $result;
    }
}
