<?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\Twig;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Twig\Components\Shop\ProductNavigation;
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\ProductRepository;
use CpCreation\VitiCore\Shop\Model\ShopConfig;
use CpCreation\VitiCore\Shop\Repository\ShopConfigRepository;
use PHPUnit\Framework\TestCase;
use Symfony\UX\TwigComponent\ComponentAttributes;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
use Twig\Runtime\EscaperRuntime;
use Twig\TwigFilter;
use Twig\TwigFunction;

final class ProductNavigationTest extends TestCase
{
    public function testCategoryMenuVisibilityControlsTheRenderedBackLink(): void
    {
        $current = $this->createProduct('current');
        $other = $this->createProduct('other');

        $withCategoryMenu = $this->createComponent([$current, $other], true);
        $withCategoryMenu->product = $current;
        self::assertStringContainsString('href="/boutique/category"', $this->render($withCategoryMenu));

        $withoutCategoryMenu = $this->createComponent([$current, $other], false);
        $withoutCategoryMenu->product = $current;
        $html = $this->render($withoutCategoryMenu);
        self::assertStringContainsString('href="/boutique"', $html);
        self::assertStringNotContainsString('href="/boutique/category"', $html);
    }

    public function testItLoadsNavigableProductsOnceAndResolvesNeighbors(): void
    {
        $first = $this->createProduct('first');
        $current = $this->createProduct('current');
        $last = $this->createProduct('last');
        $invalid = new Product();

        $repository = $this->createMock(ProductRepository::class);
        $repository->expects(self::once())
            ->method('findOnlyShop')
            ->willReturn([$first, $invalid, $current, $last]);

        $component = new ProductNavigation($repository, $this->createStub(ShopConfigRepository::class));
        $component->product = $current;

        self::assertSame([$first, $current, $last], $component->getProducts());
        self::assertSame(1, $component->getCurrentIndex());
        self::assertSame($first, $component->getPreviousProduct());
        self::assertSame($last, $component->getNextProduct());
    }

    public function testItDoesNotExposeNeighborsWhenCurrentProductIsAbsent(): void
    {
        $repository = $this->createMock(ProductRepository::class);
        $repository->method('findOnlyShop')
            ->willReturn([$this->createProduct('first'), $this->createProduct('last')]);

        $component = new ProductNavigation($repository, $this->createStub(ShopConfigRepository::class));
        $component->product = $this->createProduct('absent');

        self::assertSame(0, $component->getCurrentIndex());
        self::assertNull($component->getPreviousProduct());
        self::assertNull($component->getNextProduct());
    }

    private function createProduct(string $slug): Product
    {
        $category = new ProductCategory();
        $category->setCurrentLocale('fr');
        $categoryTranslation = $category->translate('fr');
        self::assertInstanceOf(ProductCategoryTranslation::class, $categoryTranslation);
        $categoryTranslation->setSlug('category');

        $product = new Product();
        $product->setCurrentLocale('fr');
        $productTranslation = $product->translate('fr');
        self::assertInstanceOf(ProductTranslation::class, $productTranslation);
        $productTranslation->setSlug($slug);
        $product->setCategory($category);

        return $product;
    }

    /**
     * @param Product[] $products
     */
    private function createComponent(array $products, bool $showCategoryMenu): ProductNavigation
    {
        $productRepository = $this->createMock(ProductRepository::class);
        $productRepository->expects(self::once())
            ->method('findOnlyShop')
            ->willReturn($products);

        $config = new ShopConfig();
        $config->setShowCategoryMenuFront($showCategoryMenu);
        $configRepository = $this->createMock(ShopConfigRepository::class);
        $configRepository->expects(self::once())
            ->method('findOne')
            ->willReturn($config);

        return new ProductNavigation($productRepository, $configRepository);
    }

    private function render(ProductNavigation $component): string
    {
        $componentsDirectory = \dirname(__DIR__, 2).'/src/Bridge/Symfony/Bundle/Resources/views/components';
        $twig = new Environment(new FilesystemLoader($componentsDirectory), ['autoescape' => false]);
        $twig->addFilter(new TwigFilter('trans', static fn (string $key): string => $key));
        $twig->addFunction(new TwigFunction(
            'path',
            static function (string $route, array $parameters = []): string {
                if ('shop' === $route) {
                    return isset($parameters['slug']) ? '/boutique/'.$parameters['slug'] : '/boutique';
                }

                return '/boutique/'.$parameters['categorySlug'].'/'.$parameters['slug'];
            },
        ));

        return $twig->render('shop/ProductNavigation.html.twig', [
            'attributes' => new ComponentAttributes([], new EscaperRuntime()),
            'product' => $component->product,
            'this' => $component,
        ]);
    }
}
