<?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\Product\Repository;

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 Doctrine\Common\EventManager;
use Doctrine\DBAL\DriverManager;
use Doctrine\DBAL\Types\Type;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Events;
use Doctrine\ORM\ORMSetup;
use Doctrine\ORM\Tools\SchemaTool;
use Doctrine\Persistence\ManagerRegistry;
use Knp\DoctrineBehaviors\Contract\Provider\LocaleProviderInterface;
use Knp\DoctrineBehaviors\EventSubscriber\TranslatableEventSubscriber;
use PHPUnit\Framework\TestCase;
use Ramsey\Uuid\Doctrine\UuidType;

final class LocalizedSlugRepositoryTest extends TestCase
{
    public function testSlugLookupsLoadEveryTranslationAfterTheEntityManagerWasCleared(): void
    {
        if (!Type::hasType('uuid')) {
            Type::addType('uuid', UuidType::class);
        }

        $configuration = ORMSetup::createAttributeMetadataConfiguration([
            \dirname(__DIR__, 3).'/src/Product/Model',
        ], true);

        $localeProvider = $this->createMock(LocaleProviderInterface::class);
        $localeProvider->method('provideCurrentLocale')->willReturn('fr');
        $localeProvider->method('provideFallbackLocale')->willReturn('en');

        $eventManager = new EventManager();
        $eventManager->addEventListener([
            Events::loadClassMetadata,
            Events::postLoad,
            Events::prePersist,
        ], new TranslatableEventSubscriber($localeProvider, 'LAZY', 'LAZY'));

        $connection = DriverManager::getConnection([
            'driver' => 'pdo_sqlite',
            'memory' => true,
        ], $configuration, $eventManager);
        $entityManager = new EntityManager($connection, $configuration);

        (new SchemaTool($entityManager))->createSchema([
            $entityManager->getClassMetadata(Product::class),
            $entityManager->getClassMetadata(ProductTranslation::class),
            $entityManager->getClassMetadata(ProductCategory::class),
            $entityManager->getClassMetadata(ProductCategoryTranslation::class),
        ]);

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

        $product = new Product();
        $product->setCategory($category);
        $product->setPosition(0);
        $this->addProductTranslation($product, 'fr', 'La Séduisante', 'la-seduisante-brut-1');
        $this->addProductTranslation($product, 'en', 'The Seductive', 'la-seduisante-brut');

        $entityManager->persist($category);
        $entityManager->persist($product);
        $entityManager->flush();
        $entityManager->clear();

        $registry = $this->createMock(ManagerRegistry::class);
        $registry->method('getManagerForClass')->willReturn($entityManager);

        $loadedProduct = (new ProductRepository($registry))->findOneBySlug('la-seduisante-brut-1');
        self::assertInstanceOf(Product::class, $loadedProduct);
        self::assertSame(['en', 'fr'], $this->translationLocales($loadedProduct->getTranslations()->toArray()));
        $englishProductTranslation = $loadedProduct->getTranslations()->get('en');
        self::assertInstanceOf(ProductTranslation::class, $englishProductTranslation);
        self::assertSame('la-seduisante-brut', $englishProductTranslation->getSlug());

        $entityManager->clear();

        $loadedCategory = (new ProductCategoryRepository($registry))->findOneBySlug('champagne');
        self::assertInstanceOf(ProductCategory::class, $loadedCategory);
        self::assertSame(['en', 'fr'], $this->translationLocales($loadedCategory->getTranslations()->toArray()));
        $englishCategoryTranslation = $loadedCategory->getTranslations()->get('en');
        self::assertInstanceOf(ProductCategoryTranslation::class, $englishCategoryTranslation);
        self::assertSame('champagne-1', $englishCategoryTranslation->getSlug());
    }

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

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

    /**
     * @param object[] $translations
     *
     * @return string[]
     */
    private function translationLocales(array $translations): array
    {
        $locales = array_map(
            static fn (object $translation): string => (string) $translation->getLocale(),
            $translations,
        );
        sort($locales);

        return $locales;
    }
}
