<?php
declare(strict_types=1);

namespace Autogedal\Manufacturers\Service;

use Autogedal\Manufacturers\Model\Config as ManufacturersConfig;
use Autogedal\Manufacturers\Model\ResourceModel\ManufacturerPage as ManufacturerPageResource;
use Autogedal\Manufacturers\Model\ResourceModel\ManufacturerPage\CollectionFactory as ManufacturerPageCollectionFactory;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Cms\Api\PageRepositoryInterface;
use Magento\Framework\App\CacheInterface;
use Magento\Widget\Helper\Conditions as ConditionsHelper;
use Psr\Log\LoggerInterface;

class FeaturedProductsUpdater
{
    private const WIDGET_PATTERN = '/{{widget\s.*?}}/s';
    private const WIDGET_TEMPLATE = 'Autogedal_Manufacturers::widget/product_list.phtml';
    private const CONDITIONS_PATTERN = '/\sconditions_encoded="([^"]+)"/';
    private const SKU_LIST_OPERATOR = '()';
    private const ANOWAVE_EC_CACHE_TAG = 'EC';

    public function __construct(
        private readonly FeaturedSkuProvider $featuredSkuProvider,
        private readonly ConditionsHelper $conditionsHelper,
        private readonly ManufacturerPageCollectionFactory $manufacturerPageCollectionFactory,
        private readonly ManufacturerPageResource $manufacturerPageResource,
        private readonly PageRepositoryInterface $pageRepository,
        private readonly ManufacturersConfig $manufacturersConfig,
        private readonly CacheInterface $cache,
        private readonly LoggerInterface $logger
    ) {
    }

    public function updatePages(): int
    {
        $updatedPages = 0;

        foreach ($this->manufacturerPageCollectionFactory->create() as $manufacturerPage) {
            $optionId = (int)$manufacturerPage->getOptionId();
            if (!$this->manufacturersConfig->isManufacturerActive($optionId)) {
                continue;
            }

            foreach ($this->manufacturerPageResource->getCmsPageIds($optionId) as $cmsPageId) {
                try {
                    $updatedPages += (int)$this->updatePage($cmsPageId, $optionId);
                } catch (\Throwable $exception) {
                    $this->logger->error(
                        sprintf(
                            'Autogedal_Manufacturers: Could not update the featured products of CMS page %d: %s',
                            $cmsPageId,
                            $exception->getMessage()
                        ),
                        ['exception' => $exception]
                    );
                }
            }
        }

        return $updatedPages;
    }

    public function updateContent(string $content, int $optionId): string
    {
        return preg_replace_callback(
            self::WIDGET_PATTERN,
            fn (array $matches): string => $this->updateWidget($matches[0], $optionId),
            $content
        ) ?? $content;
    }

    private function updatePage(int $cmsPageId, int $optionId): bool
    {
        $cmsPage = $this->pageRepository->getById($cmsPageId);
        $content = (string)$cmsPage->getContent();
        $updatedContent = $this->updateContent($content, $optionId);
        if ($updatedContent === $content) {
            return false;
        }

        $cmsPage->setContent($updatedContent);
        $this->cache->clean([self::ANOWAVE_EC_CACHE_TAG]);
        $this->pageRepository->save($cmsPage);

        return true;
    }

    private function updateWidget(string $widget, int $optionId): string
    {
        if (!str_contains($widget, self::WIDGET_TEMPLATE)
            || !preg_match(self::CONDITIONS_PATTERN, $widget, $matches)
        ) {
            return $widget;
        }

        $conditions = $this->conditionsHelper->decode(htmlspecialchars_decode($matches[1]));
        $skuListKeys = array_keys(array_filter($conditions, $this->isSkuList(...)));
        if (!$skuListKeys) {
            return $widget;
        }

        $skus = implode(', ', $this->featuredSkuProvider->getSkus($optionId));
        $updatedConditions = $conditions;
        foreach ($skuListKeys as $key) {
            $updatedConditions[$key]['value'] = $skus;
        }

        return $updatedConditions === $conditions
            ? $widget
            : str_replace($matches[1], $this->conditionsHelper->encode($updatedConditions), $widget);
    }

    private function isSkuList(mixed $condition): bool
    {
        return is_array($condition)
            && ($condition['attribute'] ?? null) === ProductInterface::SKU
            && ($condition['operator'] ?? null) === self::SKU_LIST_OPERATOR;
    }
}
