<?php

declare(strict_types=1);

namespace CpCreation\VitiCore\Bridge\Symfony\Bundle\EventListener;

use CpCreation\VitiCore\Bridge\Symfony\Bundle\Context\GroupContext;
use CpCreation\VitiCore\Locale\Model\Locale;
use CpCreation\VitiCore\Locale\Repository\LocaleRepository;
use CpCreation\VitiCore\Media\Model\Image as ModelImage;
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\ProductImage;
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 CpCreation\VitiCore\Shop\Model\ShopConfig;
use CpCreation\VitiCore\Shop\Repository\ShopConfigRepository;
use Liip\ImagineBundle\Imagine\Cache\CacheManager;
use Presta\SitemapBundle\Event\SitemapPopulateEvent;
use Presta\SitemapBundle\Service\UrlContainerInterface;
use Presta\SitemapBundle\Sitemap\Url\GoogleImage;
use Presta\SitemapBundle\Sitemap\Url\GoogleImageUrlDecorator;
use Presta\SitemapBundle\Sitemap\Url\GoogleMultilangUrlDecorator;
use Presta\SitemapBundle\Sitemap\Url\Url;
use Presta\SitemapBundle\Sitemap\Url\UrlConcrete;
use Symfony\Component\EventDispatcher\Attribute\AsEventListener;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouterInterface;

#[AsEventListener(event: SitemapPopulateEvent::class, method: 'populate')]
final class SitemapPopulateListener
{
    private const STATIC_SECTION = 'static';
    private const CUVEE_SECTION = 'cuvees';
    private const CUVEE_ITEM_SECTION = 'cuvee_items';
    private const NEWS_SECTION = 'news';
    private const SHOP_CATEGORIES_SECTION = 'shop_categories';
    private const SHOP_PRODUCTS_SECTION = 'shop_products';
    private const PRODUCT_IMAGE_FILTER = 'relative_widen_800_filter';
    private const DEFAULT_LOCALE = 'fr';

    /**
     * @var array<string, true>|null
     */
    private ?array $enabledLocales = null;

    /**
     * @var list<string>
     */
    private const EXCLUDED_CANONICAL_ROUTES = [
        'admin_logout',
        'app_account',
        'app_account_order',
        'app_account_order_download',
        'app_forgot_password',
        'app_forgot_password_pending',
        'app_login',
        'app_logout',
        'app_register_confirm',
        'app_register_pending',
        'app_reset_password',
        'app_verify_account',
        'cgv',
        'cuvees',
        'legals',
        'private-policy',
        'setLocale',
        'shop_checkout',
        'shop_checkout_cart',
        'shop_checkout_success',
        'shop_product',
        'shop_purchase_order_download',
    ];

    /**
     * @var list<string>
     */
    private const EXCLUDED_ROUTE_PREFIXES = [
        'admin_',
        'api_',
        'fos_js_',
        'liip_',
        'payment_',
        'presta_',
        'swagger_',
        'ux_',
        '_',
    ];

    /**
     * @var list<string>
     */
    private const EXCLUDED_PATH_FRAGMENTS = [
        '/admincp',
        '/api',
        '/docs',
        '/js/',
        '/media/cache',
        '/paiement',
        '/request/',
    ];

    public function __construct(
        private readonly RouterInterface $router,
        private readonly UrlGeneratorInterface $urlGenerator,
        private readonly ProductCategoryRepository $productCategoryRepository,
        private readonly ProductRepository $productRepository,
        private readonly ShopConfigRepository $shopConfigRepository,
        private readonly CacheManager $cacheManager,
        private readonly LocaleRepository $localeRepository,
        private readonly NewsRepository $newsRepository,
    ) {}

    public function populate(SitemapPopulateEvent $event): void
    {
        $section = $event->getSection();
        $urls = $event->getUrlContainer();

        if (\in_array($section, [null, self::STATIC_SECTION], true)) {
            $this->registerStaticUrls($urls);
        }

        if (\in_array($section, [null, self::CUVEE_SECTION], true)) {
            $this->registerCuveeUrls($urls);
        }

        if (\in_array($section, [null, self::CUVEE_ITEM_SECTION], true)) {
            $this->registerCuveeItemUrls($urls);
        }

        if (\in_array($section, [null, self::NEWS_SECTION], true)) {
            $this->registerNewsUrls($urls);
        }

        if (\in_array($section, [null, self::SHOP_CATEGORIES_SECTION], true)) {
            $this->registerShopCategoryUrls($urls);
        }

        if (\in_array($section, [null, self::SHOP_PRODUCTS_SECTION], true)) {
            $this->registerShopProductUrls($urls);
        }
    }

    private function registerStaticUrls(UrlContainerInterface $urls): void
    {
        $localizedUrlGroups = [];

        foreach ($this->router->getRouteCollection()->all() as $routeName => $route) {
            if (!$this->isIndexableStaticRoute($routeName, $route)) {
                continue;
            }

            $localizedRoute = $this->resolveLocalizedRoute($routeName, $route);
            $location = $this->urlGenerator->generate($routeName, [], UrlGeneratorInterface::ABSOLUTE_URL);

            if (null === $localizedRoute) {
                $urls->addUrl(new UrlConcrete($location), self::STATIC_SECTION);

                continue;
            }

            if (!$this->isLocaleEnabled($localizedRoute['locale'])) {
                continue;
            }

            $localizedUrlGroups[$localizedRoute['canonical']][$localizedRoute['locale']] = $location;
        }

        foreach ($localizedUrlGroups as $localizedUrls) {
            $url = $this->createUrlWithAlternates($localizedUrls);
            if (null === $url) {
                continue;
            }

            $urls->addUrl($url, self::STATIC_SECTION);
        }
    }

    private function registerShopCategoryUrls(UrlContainerInterface $urls): void
    {
        if (!$this->areShopCategoryPagesEnabled()) {
            return;
        }

        $categories = $this->productCategoryRepository->findOnlyShop(GroupContext::$groupId);
        $localizedRoutes = $this->findLocalizedRoutes('shop');

        $shopIndexUrl = $this->createUrlWithAlternates($this->buildLocalizedUrls($localizedRoutes));
        if (null !== $shopIndexUrl) {
            $urls->addUrl($shopIndexUrl, self::SHOP_CATEGORIES_SECTION);
        }

        foreach ($categories as $category) {
            $categoryUrl = $this->createUrlWithAlternates(
                $this->buildLocalizedUrls(
                    $localizedRoutes,
                    function (string $locale) use ($category): ?array {
                        $slug = $this->resolveCategorySlug($category, $locale);

                        return null !== $slug ? ['slug' => $slug] : null;
                    }
                )
            );

            if (null === $categoryUrl) {
                continue;
            }

            $urls->addUrl($categoryUrl, self::SHOP_CATEGORIES_SECTION);
        }
    }

    private function registerShopProductUrls(UrlContainerInterface $urls): void
    {
        if (!$this->areShopProductPagesEnabled()) {
            return;
        }

        $products = $this->productRepository->findOnlyShop(GroupContext::$groupId);
        $localizedRoutes = $this->findLocalizedRoutes('shop_product');

        foreach ($products as $product) {
            if (!$this->isIndexableProduct($product)) {
                continue;
            }

            $category = $product->getCategory();
            if (!$category instanceof ProductCategory) {
                continue;
            }

            $decoratedUrl = $this->decorateProductUrl(
                $this->buildLocalizedUrls(
                    $localizedRoutes,
                    function (string $locale) use ($category, $product): ?array {
                        $categorySlug = $this->resolveCategorySlug($category, $locale);
                        $productSlug = $this->resolveProductSlug($product, $locale);

                        if (null === $categorySlug || null === $productSlug) {
                            return null;
                        }

                        return [
                            'categorySlug' => $categorySlug,
                            'slug' => $productSlug,
                        ];
                    }
                ),
                $product,
                true
            );

            if (null === $decoratedUrl) {
                continue;
            }

            $urls->addUrl($decoratedUrl, self::SHOP_PRODUCTS_SECTION);
        }
    }

    private function registerCuveeUrls(UrlContainerInterface $urls): void
    {
        $products = $this->productRepository->findOnlyCuvee();
        $localizedRoutes = $this->findLocalizedRoutes('cuvees');
        $url = $this->createUrlWithAlternates($this->buildLocalizedUrls($localizedRoutes));

        if (null === $url) {
            return;
        }

        $decoratedUrl = new GoogleImageUrlDecorator($url);

        foreach ($products as $product) {
            if (!$this->isIndexableCuvee($product)) {
                continue;
            }

            $this->addImageToUrl(
                $decoratedUrl,
                $product->getImage(),
                $this->buildProductImageTitle($product)
            );
        }

        $urls->addUrl($decoratedUrl, self::CUVEE_SECTION);
    }

    private function registerCuveeItemUrls(UrlContainerInterface $urls): void
    {
        $products = $this->productRepository->findOnlyCuvee();
        $localizedRoutes = $this->findLocalizedRoutes('cuveeItem');

        foreach ($products as $product) {
            if (!$this->isIndexableCuvee($product)) {
                continue;
            }

            $decoratedUrl = $this->decorateProductUrl(
                $this->buildLocalizedUrls(
                    $localizedRoutes,
                    function (string $locale) use ($product): ?array {
                        $productSlug = $this->resolveProductSlug($product, $locale);

                        return null !== $productSlug ? ['slug' => $productSlug] : null;
                    }
                ),
                $product,
                true
            );

            if (null === $decoratedUrl) {
                continue;
            }

            $urls->addUrl($decoratedUrl, self::CUVEE_ITEM_SECTION);
        }
    }

    private function registerNewsUrls(UrlContainerInterface $urls): void
    {
        $localizedRoutes = $this->findLocalizedRoutes('news_category');

        foreach ($this->newsRepository->findAvailableAndPublish() as $news) {
            if (!$news instanceof News) {
                continue;
            }

            $url = $this->createUrlWithAlternates(
                $this->buildLocalizedUrls(
                    $localizedRoutes,
                    function (string $locale) use ($news): ?array {
                        $slug = $this->resolveNewsSlug($news, $locale);

                        return null !== $slug ? ['slug' => $slug] : null;
                    }
                )
            );

            if (null === $url) {
                continue;
            }

            $urls->addUrl($url, self::NEWS_SECTION);
        }
    }

    private function isIndexableStaticRoute(string $routeName, Route $route): bool
    {
        if ($this->isExcludedRoute($routeName, $route)) {
            return false;
        }

        $methods = $route->getMethods();
        if ([] !== $methods && !\in_array('GET', $methods, true) && !\in_array('HEAD', $methods, true)) {
            return false;
        }

        return [] === $route->compile()->getVariables();
    }

    private function isExcludedRoute(string $routeName, Route $route): bool
    {
        if (false === $route->getOption('sitemap')) {
            return true;
        }

        if ($this->matchesExcludedName($routeName)) {
            return true;
        }

        $canonicalRoute = $route->getDefault('_canonical_route');
        if (\is_string($canonicalRoute) && $this->matchesExcludedName($canonicalRoute)) {
            return true;
        }

        $path = $route->getPath();
        foreach (self::EXCLUDED_PATH_FRAGMENTS as $fragment) {
            if (str_contains($path, $fragment)) {
                return true;
            }
        }

        return false;
    }

    private function matchesExcludedName(string $routeName): bool
    {
        if (\in_array($routeName, self::EXCLUDED_CANONICAL_ROUTES, true)) {
            return true;
        }

        foreach (self::EXCLUDED_ROUTE_PREFIXES as $prefix) {
            if (str_starts_with($routeName, $prefix)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return list<array{name: string, locale: string}>
     */
    private function findLocalizedRoutes(string $canonicalRoute): array
    {
        $localizedRoutes = [];

        foreach ($this->router->getRouteCollection()->all() as $routeName => $route) {
            $locale = $route->getDefault('_locale');
            if (!\is_string($locale) || '' === $locale) {
                continue;
            }

            if (!$this->isLocaleEnabled($locale)) {
                continue;
            }

            $routeCanonicalName = $route->getDefault('_canonical_route');
            if (
                $routeName !== $canonicalRoute
                && $routeCanonicalName !== $canonicalRoute
                && $routeName !== $canonicalRoute . '.' . $locale
            ) {
                continue;
            }

            $localizedRoutes[] = [
                'name' => $routeName,
                'locale' => $locale,
            ];
        }

        return $localizedRoutes;
    }

    private function isIndexableProduct(Product $product): bool
    {
        if ($product->isDisabled() || $product->isDeleted() || !$product->isShop()) {
            return false;
        }

        $category = $product->getCategory();

        return $category instanceof ProductCategory && $category->isPageShop();
    }

    private function areShopCategoryPagesEnabled(): bool
    {
        $config = $this->shopConfigRepository->findOne();

        return $config instanceof ShopConfig && $config->isShowCategoryMenuFront();
    }

    private function areShopProductPagesEnabled(): bool
    {
        $config = $this->shopConfigRepository->findOne();

        return $config instanceof ShopConfig && $config->isEnableProductPageFront();
    }

    private function isIndexableCuvee(Product $product): bool
    {
        if ($product->isDisabled() || $product->isDeleted() || !$product->isCuvee()) {
            return false;
        }

        $category = $product->getCategory();

        return $category instanceof ProductCategory && $category->isPageCuvee();
    }

    private function resolveCategorySlug(ProductCategory $category, string $locale): ?string
    {
        $translation = $category->translate($locale, false);
        if (!$translation instanceof ProductCategoryTranslation) {
            return null;
        }

        $slug = $translation->getSlug();

        return \is_string($slug) && '' !== $slug ? $slug : null;
    }

    private function resolveProductSlug(Product $product, string $locale): ?string
    {
        $translation = $product->translate($locale, false);
        if (!$translation instanceof ProductTranslation) {
            return null;
        }

        $slug = $translation->getSlug();

        return \is_string($slug) && '' !== $slug ? $slug : null;
    }

    private function resolveNewsSlug(News $news, string $locale): ?string
    {
        $translation = $news->translate($locale, false);
        if (!$translation instanceof NewsTranslation) {
            return null;
        }

        $slug = $translation->getSlug();

        return \is_string($slug) && '' !== $slug ? $slug : null;
    }

    /**
     * @param array<string, string> $localizedUrls
     */
    private function decorateProductUrl(array $localizedUrls, Product $product, bool $includeGalleryImages = false): ?GoogleImageUrlDecorator
    {
        $url = $this->createUrlWithAlternates($localizedUrls);
        if (null === $url) {
            return null;
        }

        $decoratedUrl = new GoogleImageUrlDecorator($url);
        $title = $this->buildProductImageTitle($product);

        $this->addImageToUrl($decoratedUrl, $product->getImage(), $title);
        $this->addImageToUrl($decoratedUrl, $product->getSecondImage(), $title);

        if ($includeGalleryImages) {
            foreach ($product->getImages() as $image) {
                if (!$image instanceof ProductImage) {
                    continue;
                }

                $this->addImageToUrl($decoratedUrl, $image->getImage(), $title);
            }
        }

        return $decoratedUrl;
    }

    /**
     * @param list<array{name: string, locale: string}> $localizedRoutes
     * @param callable(string): (array<string, mixed>|null) $parametersResolver
     *
     * @return array<string, string>
     */
    private function buildLocalizedUrls(array $localizedRoutes, ?callable $parametersResolver = null): array
    {
        $localizedUrls = [];

        foreach ($localizedRoutes as $localizedRoute) {
            $parameters = null !== $parametersResolver ? $parametersResolver($localizedRoute['locale']) : [];
            if (null === $parameters) {
                continue;
            }

            $localizedUrls[$localizedRoute['locale']] = $this->urlGenerator->generate(
                $localizedRoute['name'],
                $parameters,
                UrlGeneratorInterface::ABSOLUTE_URL
            );
        }

        return $localizedUrls;
    }

    /**
     * @param array<string, string> $localizedUrls
     */
    private function createUrlWithAlternates(array $localizedUrls): ?Url
    {
        $canonicalLocale = $this->resolveCanonicalLocale($localizedUrls);
        if (null === $canonicalLocale) {
            return null;
        }

        $url = new UrlConcrete($localizedUrls[$canonicalLocale]);

        if (\count($localizedUrls) <= 1) {
            return $url;
        }

        $decoratedUrl = new GoogleMultilangUrlDecorator($url);
        foreach ($localizedUrls as $locale => $location) {
            $decoratedUrl->addLink($location, $locale);
        }

        return $decoratedUrl;
    }

    /**
     * @param array<string, string> $localizedUrls
     */
    private function resolveCanonicalLocale(array $localizedUrls): ?string
    {
        if ([] === $localizedUrls) {
            return null;
        }

        if (isset($localizedUrls[self::DEFAULT_LOCALE])) {
            return self::DEFAULT_LOCALE;
        }

        $firstLocale = array_key_first($localizedUrls);

        return \is_string($firstLocale) ? $firstLocale : null;
    }

    private function isLocaleEnabled(string $locale): bool
    {
        return isset($this->getEnabledLocales()[$locale]);
    }

    /**
     * @return array<string, true>
     */
    private function getEnabledLocales(): array
    {
        if (null !== $this->enabledLocales) {
            return $this->enabledLocales;
        }

        $enabledLocales = [
            self::DEFAULT_LOCALE => true,
        ];

        foreach ($this->localeRepository->findBy(['disabled' => 0]) as $locale) {
            if (!$locale instanceof Locale) {
                continue;
            }

            $code = $locale->getLocale();
            if (\is_string($code) && '' !== $code) {
                $enabledLocales[$code] = true;
            }
        }

        return $this->enabledLocales = $enabledLocales;
    }

    /**
     * @return array{canonical: string, locale: string}|null
     */
    private function resolveLocalizedRoute(string $routeName, Route $route): ?array
    {
        $locale = $route->getDefault('_locale');
        if (!\is_string($locale) || '' === $locale) {
            return null;
        }

        $canonicalRoute = $route->getDefault('_canonical_route');
        if (!\is_string($canonicalRoute) || '' === $canonicalRoute) {
            $suffix = '.' . $locale;
            $canonicalRoute = str_ends_with($routeName, $suffix)
                ? substr($routeName, 0, -\strlen($suffix))
                : $routeName;
        }

        return [
            'canonical' => $canonicalRoute,
            'locale' => $locale,
        ];
    }

    private function addImageToUrl(GoogleImageUrlDecorator $decoratedUrl, ?ModelImage $image, string $title): void
    {
        $path = $image?->getPath();
        if (null === $path || '' === $path) {
            return;
        }

        $imagePath = $this->cacheManager->getBrowserPath(
            $path,
            self::PRODUCT_IMAGE_FILTER,
            [],
            null,
            UrlGeneratorInterface::ABSOLUTE_URL
        );

        $caption = $image->getTitle() ?? $title;
        $decoratedUrl->addImage(new GoogleImage($imagePath, $caption, null, $title));
    }

    private function buildProductImageTitle(Product $product): string
    {
        return 'CP Création - ' . $product->getFullName();
    }
}
