<?php

/*
 * This file is part of the VITICORE2 package.
 *
 * (c) CP Creation <web@cpcreation.fr>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace CpCreation\VitiCore\Tests\LocalizedRouting;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\LocalizedRouting\LocalizedPageRouteResolver;
use CpCreation\VitiCore\Bridge\Symfony\Bundle\Provider\LocaleProvider;
use CpCreation\VitiCore\News\Model\News;
use CpCreation\VitiCore\News\Model\Translation\NewsTranslation;
use CpCreation\VitiCore\News\Repository\NewsRepository;
use CpCreation\VitiCore\Product\Model\Product;
use CpCreation\VitiCore\Product\Model\ProductCategory;
use CpCreation\VitiCore\Product\Model\Translation\ProductCategoryTranslation;
use CpCreation\VitiCore\Product\Model\Translation\ProductTranslation;
use CpCreation\VitiCore\Product\Repository\ProductCategoryRepository;
use CpCreation\VitiCore\Product\Repository\ProductRepository;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Generator\UrlGenerator;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\RouterInterface;

final class LocalizedPageRouteResolverTest extends TestCase
{
    public function testItIntersectsEnabledLocalesWithTheRealRouteVariants(): void
    {
        $routes = new RouteCollection();
        $this->addLocalizedRoute($routes, 'home', 'fr', '/la-maison');
        $this->addLocalizedRoute($routes, 'home', 'en', '/en/house');
        $this->addLocalizedRoute($routes, 'home', 'de', '/de/haus');

        $resolver = $this->createResolver($routes, ['fr', 'en', 'de', 'ru', 'en']);
        $localizedRoutes = $resolver->resolve('home.en', currentLocale: 'en');

        self::assertSame(['fr', 'en', 'de'], array_keys($localizedRoutes->routes));
        self::assertSame('/en/house', $localizedRoutes->getCurrent()?->path);
        self::assertSame('https://example.test/la-maison', $localizedRoutes->getDefault()?->absoluteUrl);
        self::assertSame('de_DE', $localizedRoutes->routes['de']->openGraphLocale);
        self::assertArrayNotHasKey('ru', $localizedRoutes->routes);
    }

    public function testItTranslatesACategorySlugWithoutCreatingAMissingTranslation(): void
    {
        $routes = new RouteCollection();
        $this->addLocalizedRoute($routes, 'shop', 'fr', '/boutique/{slug}', ['slug' => null]);
        $this->addLocalizedRoute($routes, 'shop', 'en', '/en/shop/{slug}', ['slug' => null]);
        $this->addLocalizedRoute($routes, 'shop', 'de', '/de/shop/{slug}', ['slug' => null]);

        $category = new ProductCategory();
        $this->addCategoryTranslation($category, 'fr', 'champagne');
        $this->addCategoryTranslation($category, 'en', 'champagne-1');

        $categoryRepository = $this->createMock(ProductCategoryRepository::class);
        $categoryRepository->expects(self::once())
            ->method('findOneBySlug')
            ->with('champagne')
            ->willReturn($category);

        $resolver = $this->createResolver(
            $routes,
            ['fr', 'en', 'de'],
            $categoryRepository,
        );
        $localizedRoutes = $resolver->resolve('shop', ['slug' => 'champagne'], 'fr');

        self::assertSame('/boutique/champagne', $localizedRoutes->routes['fr']->path);
        self::assertSame('/en/shop/champagne-1', $localizedRoutes->routes['en']->path);
        self::assertArrayNotHasKey('de', $localizedRoutes->routes);
        self::assertCount(0, $category->getNewTranslations());
    }

    public function testItTranslatesBothProductRouteSlugsAndSkipsAnIncompleteLocale(): void
    {
        $routes = new RouteCollection();
        foreach (['fr', 'en', 'de'] as $locale) {
            $prefix = 'fr' === $locale ? '/boutique' : '/'.$locale.'/shop';
            $this->addLocalizedRoute(
                $routes,
                'shop_product',
                $locale,
                $prefix.'/{categorySlug}/{slug}',
            );
        }

        $category = new ProductCategory();
        $this->addCategoryTranslation($category, 'fr', 'champagne');
        $this->addCategoryTranslation($category, 'en', 'champagne-1');

        $product = new Product();
        $product->setCategory($category);
        $this->addProductTranslation($product, 'fr', 'la-seduisante-brut-1');
        $this->addProductTranslation($product, 'en', 'la-seduisante-brut');
        $this->addProductTranslation($product, 'de', 'die-verfuehrerin');

        $productRepository = $this->createMock(ProductRepository::class);
        $productRepository->expects(self::once())
            ->method('findOneBySlug')
            ->with('la-seduisante-brut-1')
            ->willReturn($product);

        $resolver = $this->createResolver(
            $routes,
            ['fr', 'en', 'de'],
            productRepository: $productRepository,
        );
        $localizedRoutes = $resolver->resolve('shop_product', [
            'categorySlug' => 'champagne',
            'slug' => 'la-seduisante-brut-1',
        ], 'fr');

        self::assertSame(
            '/en/shop/champagne-1/la-seduisante-brut',
            $localizedRoutes->routes['en']->path,
        );
        self::assertArrayNotHasKey('de', $localizedRoutes->routes);
        self::assertCount(0, $category->getNewTranslations());
        self::assertCount(0, $product->getNewTranslations());
    }

    public function testCurrentResolutionIsSharedByEveryCallerDuringTheRequest(): void
    {
        $routes = new RouteCollection();
        $this->addLocalizedRoute($routes, 'homepage', 'fr', '/');
        $this->addLocalizedRoute($routes, 'homepage', 'en', '/en');

        $request = Request::create('/en');
        $request->setLocale('en');
        $request->attributes->set('_route', 'homepage.en');
        $request->attributes->set('_canonical_route', 'homepage');
        $request->attributes->set('_route_params', ['_locale' => 'en']);
        $requestStack = new RequestStack();
        $requestStack->push($request);

        $router = $this->createRouter($routes);
        $router->expects(self::once())->method('getRouteCollection');
        $resolver = $this->createResolver(
            $routes,
            ['fr', 'en'],
            requestStack: $requestStack,
            router: $router,
        );

        $first = $resolver->current();
        $second = $resolver->current();

        self::assertSame($first, $second);
        self::assertSame('/en', $second->getCurrent()?->path);
    }

    public function testItTranslatesANewsSlug(): void
    {
        $routes = new RouteCollection();
        $this->addLocalizedRoute($routes, 'news_category', 'fr', '/les-actualites/{slug}');
        $this->addLocalizedRoute($routes, 'news_category', 'en', '/en/the-news/{slug}');

        $news = new News();
        $this->addNewsTranslation($news, 'fr', 'vendanges-2026');
        $this->addNewsTranslation($news, 'en', 'harvest-2026');

        $newsRepository = $this->createMock(NewsRepository::class);
        $newsRepository->expects(self::once())
            ->method('findOneAvailableBySlug')
            ->with('vendanges-2026')
            ->willReturn($news);

        $resolver = $this->createResolver(
            $routes,
            ['fr', 'en'],
            newsRepository: $newsRepository,
        );
        $localizedRoutes = $resolver->resolve('news_category', ['slug' => 'vendanges-2026'], 'fr');

        self::assertSame('/les-actualites/vendanges-2026', $localizedRoutes->routes['fr']->path);
        self::assertSame('/en/the-news/harvest-2026', $localizedRoutes->routes['en']->path);
        self::assertCount(0, $news->getNewTranslations());
    }

    public function testItTranslatesAProductSlugForAHostDefinedLocalizedRoute(): void
    {
        $routes = new RouteCollection();
        $this->addLocalizedRoute(
            $routes,
            'cuvee_detail',
            'fr',
            '/les-cuvees/{slug}',
            options: ['viti_localized_product_slug' => 'slug'],
        );
        $this->addLocalizedRoute(
            $routes,
            'cuvee_detail',
            'en',
            '/en/cuvees/{slug}',
            options: ['viti_localized_product_slug' => 'slug'],
        );

        $product = new Product();
        $this->addProductTranslation($product, 'fr', 'limpatiente');
        $this->addProductTranslation($product, 'en', 'the-impatient');
        $productRepository = $this->createMock(ProductRepository::class);
        $productRepository->expects(self::once())
            ->method('findOneBySlug')
            ->with('limpatiente')
            ->willReturn($product);

        $resolver = $this->createResolver(
            $routes,
            ['fr', 'en'],
            productRepository: $productRepository,
        );
        $localizedRoutes = $resolver->resolve('cuvee_detail.fr', ['slug' => 'limpatiente'], 'fr');

        self::assertSame('/les-cuvees/limpatiente', $localizedRoutes->routes['fr']->path);
        self::assertSame('/en/cuvees/the-impatient', $localizedRoutes->routes['en']->path);
    }

    /**
     * @param list<string> $locales
     */
    private function createResolver(
        RouteCollection $routes,
        array $locales,
        ?ProductCategoryRepository $categoryRepository = null,
        ?ProductRepository $productRepository = null,
        ?NewsRepository $newsRepository = null,
        ?RequestStack $requestStack = null,
        ?RouterInterface $router = null,
    ): LocalizedPageRouteResolver {
        $localeProvider = $this->createMock(LocaleProvider::class);
        $localeProvider->method('getLocales')->willReturn($locales);
        $localeProvider->method('getDefaultLocale')->willReturn('fr');

        return new LocalizedPageRouteResolver(
            $requestStack ?? new RequestStack(),
            $router ?? $this->createRouter($routes),
            $localeProvider,
            $newsRepository ?? $this->createStub(NewsRepository::class),
            $categoryRepository ?? $this->createStub(ProductCategoryRepository::class),
            $productRepository ?? $this->createStub(ProductRepository::class),
        );
    }

    /** @return RouterInterface&MockObject */
    private function createRouter(RouteCollection $routes): RouterInterface
    {
        $generator = new UrlGenerator(
            $routes,
            new RequestContext('', 'GET', 'example.test', 'https', 80, 443),
        );
        $router = $this->createMock(RouterInterface::class);
        $router->method('getRouteCollection')->willReturn($routes);
        $router->method('generate')->willReturnCallback(
            static fn (string $name, array $parameters = [], int $referenceType = 1): string => $generator->generate(
                $name,
                $parameters,
                $referenceType,
            ),
        );

        return $router;
    }

    /**
     * @param array<string, mixed> $defaults
     */
    private function addLocalizedRoute(
        RouteCollection $routes,
        string $canonicalRoute,
        string $locale,
        string $path,
        array $defaults = [],
        array $options = [],
    ): void {
        $routes->add($canonicalRoute.'.'.$locale, new Route($path, [
            ...$defaults,
            '_canonical_route' => $canonicalRoute,
            '_locale' => $locale,
        ], options: $options));
    }

    private function addCategoryTranslation(ProductCategory $category, string $locale, string $slug): void
    {
        $translation = new ProductCategoryTranslation();
        $translation->setLocale($locale);
        $translation->setName($slug);
        $translation->setSlug($slug);
        $category->addTranslation($translation);
    }

    private function addProductTranslation(Product $product, string $locale, string $slug): void
    {
        $translation = new ProductTranslation();
        $translation->setLocale($locale);
        $translation->setName($slug);
        $translation->setDescription($slug);
        $translation->setSlug($slug);
        $product->addTranslation($translation);
    }

    private function addNewsTranslation(News $news, string $locale, string $slug): void
    {
        $translation = new NewsTranslation();
        $translation->setLocale($locale);
        $translation->setTitle($slug);
        $translation->setText($slug);
        $translation->setSlug($slug);
        $news->addTranslation($translation);
    }
}
