<?php
/**
 * @module  Autogedal_SeoCanonical
 * @author  Autogedal
 * @licence OSL 3.0
 */

namespace Autogedal\SeoCanonical\Cron;

use Autogedal\SeoCanonical\Helper\Config;
use Autogedal\SeoCanonical\Logger\Logger;
use Amasty\ShopbyPage\Model\ResourceModel\Page\CollectionFactory as PageCollectionFactory;
use Amasty\ShopbySeo\Helper\Data as SeoHelper;
use Amasty\ShopbySeo\Helper\Url as UrlHelper;
use Magento\Catalog\Model\CategoryRepository;
use Magento\Catalog\Model\Layer\Resolver as LayerResolver;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory as ProductCollectionFactory;
use Magento\Eav\Api\AttributeRepositoryInterface;
use Magento\Framework\App\Area;
use Magento\Framework\App\State as AppState;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\UrlInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\UrlRewrite\Model\UrlFinderInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;

class UpdateCanonicalUrls
{
    /**
     * @var Config
     */
    protected $configHelper;

    /**
     * @var Logger
     */
    protected $logger;

    /**
     * @var PageCollectionFactory
     */
    protected $pageCollectionFactory;

    /**
     * @var AttributeRepositoryInterface
     */
    protected $attributeRepository;

    /**
     * @var ProductCollectionFactory
     */
    protected $productCollectionFactory;

    /**
     * @var LayerResolver
     */
    protected $layerResolver;

    /**
     * @var UrlHelper
     */
    protected $urlHelper;

    /**
     * @var Json
     */
    protected $jsonSerializer;

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var CategoryRepository
     */
    protected $categoryRepository;

    /**
     * @var SeoHelper
     */
    protected $seoHelper;

    /**
     * @var AppState
     */
    protected $appState;

    /**
     * @var UrlFinderInterface
     */
    protected $urlFinder;

    /**
     * @param Config $configHelper
     * @param Logger $logger
     * @param PageCollectionFactory $pageCollectionFactory
     * @param AttributeRepositoryInterface $attributeRepository
     * @param ProductCollectionFactory $productCollectionFactory
     * @param LayerResolver $layerResolver
     * @param UrlHelper $urlHelper
     * @param Json $jsonSerializer
     * @param StoreManagerInterface $storeManager
     * @param CategoryRepository $categoryRepository
     * @param SeoHelper $seoHelper
     * @param AppState $appState
     * @param UrlFinderInterface $urlFinder
     */
    public function __construct(
        Config $configHelper,
        Logger $logger,
        PageCollectionFactory $pageCollectionFactory,
        AttributeRepositoryInterface $attributeRepository,
        ProductCollectionFactory $productCollectionFactory,
        LayerResolver $layerResolver,
        UrlHelper $urlHelper,
        Json $jsonSerializer,
        StoreManagerInterface $storeManager,
        CategoryRepository $categoryRepository,
        SeoHelper $seoHelper,
        AppState $appState,
        UrlFinderInterface $urlFinder
    ) {
        $this->configHelper = $configHelper;
        $this->logger = $logger;
        $this->pageCollectionFactory = $pageCollectionFactory;
        $this->attributeRepository = $attributeRepository;
        $this->productCollectionFactory = $productCollectionFactory;
        $this->layerResolver = $layerResolver;
        $this->urlHelper = $urlHelper;
        $this->jsonSerializer = $jsonSerializer;
        $this->storeManager = $storeManager;
        $this->categoryRepository = $categoryRepository;
        $this->seoHelper = $seoHelper;
        $this->appState = $appState;
        $this->urlFinder = $urlFinder;
    }

    /**
     * Execute cron job to update canonical URLs
     *
     * @return void
     */
    public function execute()
    {
        $this->logger->info('SEO Canonical URL Update Cron Started');

        if (!$this->configHelper->isEnabled()) {
            $this->logger->info('Module is disabled. Exiting.');
            return;
        }

        try {
            $this->appState->emulateAreaCode(Area::AREA_FRONTEND, function () {
                $defaultStore = $this->storeManager->getDefaultStoreView();
                $this->storeManager->setCurrentStore($defaultStore->getId());
                $this->processAllCategories();
            });
        } catch (\Exception $e) {
            $this->logger->error('Error during cron execution: ' . $e->getMessage());
        }
    }

    /**
     * Process all category configurations
     *
     * @return void
     */
    protected function processAllCategories()
    {
        $configurations = $this->configHelper->getCategoryConfigurations();

        if (empty($configurations)) {
            $this->logger->info('No category configurations found. Exiting.');
            return;
        }

        $totalUpdated = 0;
        $totalProcessed = 0;

        foreach ($configurations as $config) {
            try {
                $this->logger->info(sprintf(
                    'Processing category ID: %d with Marca: %d, Model: %d, Caroserie: %d',
                    $config['category_id'],
                    $config['marca_attribute_id'],
                    $config['model_attribute_id'],
                    $config['caroserie_attribute_id']
                ));

                $updated = $this->processCategory($config);
                $totalUpdated += $updated;
                $totalProcessed++;

                $this->logger->info(sprintf(
                    'Category ID %d processed. URLs updated: %d',
                    $config['category_id'],
                    $updated
                ));
            } catch (\Exception $e) {
                $this->logger->error(sprintf(
                    'Error processing category ID %d: %s',
                    $config['category_id'],
                    $e->getMessage()
                ));
            }
        }

        $this->logger->info(sprintf(
            'SEO Canonical URL Update Cron Completed. Categories processed: %d, Total URLs updated: %d',
            $totalProcessed,
            $totalUpdated
        ));
    }

    /**
     * Process a single category configuration
     *
     * @param array $config
     * @return int Number of URLs updated
     */
    protected function processCategory($config)
    {
        $categoryId = $config['category_id'];
        $marcaAttributeId = $config['marca_attribute_id'];
        $modelAttributeId = $config['model_attribute_id'];
        $caroserieAttributeId = $config['caroserie_attribute_id'];

        $marcaAttribute = $this->attributeRepository->get('catalog_product', $marcaAttributeId);
        $modelAttribute = $this->attributeRepository->get('catalog_product', $modelAttributeId);
        $caroserieAttribute = $this->attributeRepository->get('catalog_product', $caroserieAttributeId);

        $marcaCode = $marcaAttribute->getAttributeCode();
        $modelCode = $modelAttribute->getAttributeCode();
        $caroserieCode = $caroserieAttribute->getAttributeCode();

        $this->logger->info(sprintf(
            'Attribute codes - Marca: %s, Model: %s, Caroserie: %s',
            $marcaCode,
            $modelCode,
            $caroserieCode
        ));

        $pageCollection = $this->pageCollectionFactory->create();
        $pageCollection->addFieldToFilter('categories', ['finset' => $categoryId]);

        $updatedCount = 0;

        foreach ($pageCollection as $page) {
            try {
                $currentUrl = $page->getUrl();

                $conditionsData = $page->getData('conditions');

                if (is_string($conditionsData)) {
                    $conditions = $this->jsonSerializer->unserialize($conditionsData);
                } else {
                    $conditions = $conditionsData;
                }

                if (!is_array($conditions)) {
                    continue;
                }

                $conditionAttributeIds = $this->getConditionAttributeIds($conditions);

                $marcaAttribute = $this->attributeRepository->get('catalog_product', $marcaCode);
                $modelAttribute = $this->attributeRepository->get('catalog_product', $modelCode);
                $caroserieAttribute = $this->attributeRepository->get('catalog_product', $caroserieCode);

                $expectedAttributeIds = [
                    (string)$marcaAttribute->getAttributeId(),
                    (string)$modelAttribute->getAttributeId()
                ];

                sort($conditionAttributeIds);
                sort($expectedAttributeIds);

                if ($conditionAttributeIds !== $expectedAttributeIds) {
                    continue;
                }

                $marcaValue = $this->getAttributeValueFromConditions($conditions, $marcaCode);
                $modelValue = $this->getAttributeValueFromConditions($conditions, $modelCode);

                if (!$marcaValue || !$modelValue) {
                    continue;
                }

                $caroserieValue = $this->getSingleCaroserieValue(
                    $categoryId,
                    $marcaCode,
                    $marcaValue,
                    $modelCode,
                    $modelValue,
                    $caroserieCode
                );

                if ($caroserieValue) {
                    $newUrl = $this->buildNewUrl(
                        $categoryId,
                        $marcaCode,
                        $marcaValue,
                        $modelCode,
                        $modelValue,
                        $caroserieCode,
                        $caroserieValue
                    );

                    $seoUrl = $this->urlHelper->seofyUrl($newUrl, $categoryId, true);
                    $seoUrl .= '.html';

                    if ($seoUrl !== $currentUrl) {
                        if ($this->urlExistsForOtherPage($seoUrl, $page->getPageId())) {
                            $this->logger->warning(sprintf(
                                'Page ID %d - Cannot update: URL "%s" already exists for another page',
                                $page->getPageId(),
                                $seoUrl
                            ));
                            continue;
                        }

                        $page->setUrl($seoUrl);
                        $page->save();
                        $updatedCount++;

                        $this->logger->info(sprintf(
                            'Page ID %d - URL updated from "%s" to "%s"',
                            $page->getPageId(),
                            $currentUrl,
                            $seoUrl
                        ));
                    }
                } else {
                    if ($this->urlHasCaroserie($currentUrl, $caroserieCode)) {

                        $newUrl = $this->buildUrlWithoutCaroserie(
                            $categoryId,
                            $marcaCode,
                            $marcaValue,
                            $modelCode,
                            $modelValue
                        );

                        $seoUrl = $this->urlHelper->seofyUrl($newUrl, $categoryId, true);
                        $seoUrl .= '.html';

                        if ($seoUrl !== $currentUrl) {
                            if ($this->urlExistsForOtherPage($seoUrl, $page->getPageId())) {
                                $this->logger->warning(sprintf(
                                    'Page ID %d - Cannot update: URL "%s" already exists for another page',
                                    $page->getPageId(),
                                    $seoUrl
                                ));
                                continue;
                            }

                            $page->setUrl($seoUrl);
                            $page->save();
                            $updatedCount++;

                            $this->logger->info(sprintf(
                                'Page ID %d - URL updated from "%s" to "%s" (caroserie removed)',
                                $page->getPageId(),
                                $currentUrl,
                                $seoUrl
                            ));
                        }
                    }
                }
            } catch (\Exception $e) {
                $this->logger->error(sprintf(
                    'Error processing page ID %d: %s',
                    $page->getPageId(),
                    $e->getMessage()
                ));
            }
        }

        return $updatedCount;
    }

    /**
     * Check if URL already contains caroserie parameter
     *
     * @param string $url
     * @param string $caroserieCode
     * @return bool
     */
    protected function urlAlreadyHasCaroserie($url, $caroserieCode)
    {
        return strpos($url, $caroserieCode . '-') !== false;
    }

    /**
     * Get attribute value from conditions array
     *
     * @param array $conditions
     * @param string $attributeCode
     * @return string|null
     */
    protected function getAttributeValueFromConditions($conditions, $attributeCode)
    {
        if (isset($conditions[$attributeCode]) && is_array($conditions[$attributeCode])) {
            $values = $conditions[$attributeCode];
            return !empty($values) ? reset($values) : null;
        }

        if (is_array($conditions)) {
            try {
                $attribute = $this->attributeRepository->get('catalog_product', $attributeCode);
                $attributeId = $attribute->getAttributeId();

                foreach ($conditions as $condition) {
                    if (is_string($condition)) {
                        $conditionData = $this->jsonSerializer->unserialize($condition);
                    } else {
                        $conditionData = $condition;
                    }

                    if (isset($conditionData['filter']) && $conditionData['filter'] == $attributeId) {
                        return $conditionData['value'] ?? null;
                    }
                }
            } catch (\Exception $e) {
                $this->logger->error(sprintf(
                    'Error parsing conditions for attribute %s: %s',
                    $attributeCode,
                    $e->getMessage()
                ));
            }
        }

        return null;
    }

    /**
     * Check if there's only one caroserie value for the given filters
     *
     * @param int $categoryId
     * @param string $marcaCode
     * @param string $marcaValue
     * @param string $modelCode
     * @param string $modelValue
     * @param string $caroserieCode
     * @return string|null
     */
    protected function getSingleCaroserieValue(
        $categoryId,
        $marcaCode,
        $marcaValue,
        $modelCode,
        $modelValue,
        $caroserieCode
    ) {
        try {
            $productCollection = $this->productCollectionFactory->create();
            $productCollection->addAttributeToSelect($caroserieCode);
            $productCollection->addAttributeToFilter($marcaCode, $marcaValue);
            $productCollection->addAttributeToFilter($modelCode, $modelValue);
            $productCollection->addCategoriesFilter(['eq' => $categoryId]);

            $caroserieValues = [];
            foreach ($productCollection as $product) {
                $value = $product->getData($caroserieCode);
                if ($value) {
                    $caroserieValues[$value] = $value;
                }
            }

            if (count($caroserieValues) === 1) {
                return reset($caroserieValues);
            }
        } catch (\Exception $e) {
            $this->logger->error(sprintf(
                'Error getting caroserie value: %s',
                $e->getMessage()
            ));
        }

        return null;
    }

    /**
     * Get attribute IDs from conditions array
     *
     * @param array $conditions
     * @return array
     */
    protected function getConditionAttributeIds($conditions)
    {
        $attributeIds = [];

        foreach ($conditions as $condition) {
            if (is_string($condition)) {
                $conditionData = $this->jsonSerializer->unserialize($condition);
            } else {
                $conditionData = $condition;
            }

            if (isset($conditionData['filter'])) {
                $attributeIds[] = (string)$conditionData['filter'];
            }
        }

        return array_unique($attributeIds);
    }

    /**
     * Check if URL already exists for another page
     *
     * @param string $url
     * @param int $currentPageId
     * @return bool
     */
    protected function urlExistsForOtherPage($url, $currentPageId)
    {
        $pageCollection = $this->pageCollectionFactory->create();
        $pageCollection->addFieldToFilter('url', $url);
        $pageCollection->addFieldToFilter('page_id', ['neq' => $currentPageId]);

        return $pageCollection->getSize() > 0;
    }

    /**
     * Check if URL contains caroserie parameter
     *
     * @param string $url
     * @param string $caroserieCode
     * @return bool
     */
    protected function urlHasCaroserie($url, $caroserieCode)
    {
        $attributeUrlAliases = $this->seoHelper->getAttributeUrlAliases();
        $store = $this->storeManager->getStore()->getId();
        $alias = $attributeUrlAliases[$caroserieCode][$store] ?? $caroserieCode;

        return strpos($url, $alias) !== false;
    }

    /**
     * Build URL without caroserie parameter (only marca and model)
     *
     * @param int $categoryId
     * @param string $marcaCode
     * @param string $marcaValue
     * @param string $modelCode
     * @param string $modelValue
     * @return string
     */
    protected function buildUrlWithoutCaroserie(
        $categoryId,
        $marcaCode,
        $marcaValue,
        $modelCode,
        $modelValue
    ) {
        // Get category URL path
        try {
            $category = $this->categoryRepository->get($categoryId);
            $categoryUrlKey = $this->getCategoryUrlPath($category);

            if (!$categoryUrlKey) {
                $this->logger->error(sprintf('Category %d has no URL key', $categoryId));
                return '';
            }
        } catch (\Exception $e) {
            $this->logger->error(sprintf(
                'Error getting category URL for category ID %d: %s',
                $categoryId,
                $e->getMessage()
            ));
            return '';
        }

        $store = $this->storeManager->getStore();
        $baseUrl = $store->getBaseUrl(UrlInterface::URL_TYPE_WEB);
        $baseUrl = rtrim($baseUrl, '/');

        $url = $baseUrl . '/' . $categoryUrlKey . '/?'.$marcaCode.'='.$marcaValue.'&'.$modelCode.'='.$modelValue;

        return $url;
    }

    /**
     * Get category URL path from url_rewrite table
     *
     * @param object $category
     * @return string
     */
    protected function getCategoryUrlPath($category)
    {
        $storeId = $this->storeManager->getStore()->getId();

        $rewrite = $this->urlFinder->findOneByData([
            UrlRewrite::ENTITY_TYPE => 'category',
            UrlRewrite::ENTITY_ID => $category->getId(),
            UrlRewrite::STORE_ID => $storeId,
            UrlRewrite::REDIRECT_TYPE => 0,
        ]);

        if ($rewrite) {
            $path = $rewrite->getRequestPath();
            $path = str_replace('.html', '', $path);
            return $path;
        }

        $this->logger->warning(sprintf(
            'No URL rewrite found for category %d in store %d, falling back to url_key',
            $category->getId(),
            $storeId
        ));

        return $category->getUrlKey();
    }

    /**
     * Build new URL with all filter parameters in query format
     *
     * @param int $categoryId
     * @param string $marcaCode
     * @param string $marcaValue
     * @param string $modelCode
     * @param string $modelValue
     * @param string $caroserieCode
     * @param string $caroserieValue
     * @return string
     */
    protected function buildNewUrl(
        $categoryId,
        $marcaCode,
        $marcaValue,
        $modelCode,
        $modelValue,
        $caroserieCode,
        $caroserieValue
    ) {
        try {
            $category = $this->categoryRepository->get($categoryId);
            $categoryUrlKey = $this->getCategoryUrlPath($category);

            if (!$categoryUrlKey) {
                $this->logger->error(sprintf('Category %d has no URL key', $categoryId));
                return '';
            }
        } catch (\Exception $e) {
            $this->logger->error(sprintf(
                'Error getting category URL for category ID %d: %s',
                $categoryId,
                $e->getMessage()
            ));
            return '';
        }

        $store = $this->storeManager->getStore();
        $baseUrl = $store->getBaseUrl(UrlInterface::URL_TYPE_WEB);
        $baseUrl = rtrim($baseUrl, '/');

        $url = $baseUrl . '/' . $categoryUrlKey . '/?'.$marcaCode.'='.$marcaValue.'&'.$modelCode.'='.$modelValue.'&'.$caroserieCode.'='.$caroserieValue;

        return $url;
    }
}
