<?php

declare(strict_types=1);

namespace FiloBlu\Refilo\Helper;

use Exception;
use FiloBlu\Core\Model\Configuration;
use FiloBlu\Refilo\Helper\Catalog\CategoryHelper;
use FiloBlu\Refilo\Helper\ContentManager\Data;
use FiloBlu\Refilo\Model\Config\Seo\HrefLangConfigurationSourceInterface;
use FiloBlu\Refilo\Remote\Entity\Provider\CategoryUrlProvider;
use FiloBlu\Refilo\Remote\Entity\UrlRewrite;
use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Catalog\Model\Category;
use Magento\Cms\Helper\Page as PageHelper;
use Magento\Eav\Model\ResourceModel\Entity\Attribute;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\Service\StoreConfigManager;
use Magento\Store\Model\Store;
use Magento\Store\Model\StoreManagerInterface;
use Zend_Db;
USE FiloBlu\Refilo\Model\CanonicalUrlModifierFactory;

use function in_array;
use function is_string;

/**
 * Class Url
 */
class Url
{
    /** @var string */
    public const XML_PATH_REDIRECT_CATALOG_PRODUCT = 'catalog/product/enable_product_redirect_observer';
    /** @var string */
    public const XML_PATH_EXCLUDED_CATEGORIES_IDS = 'catalog/product/excluded_categories';
    /** @var StoreConfigManager */
    protected $storeConfigManager;
    /** @var ResourceConnection */
    private $resourceConnection;
    /** @var array|null */
    private $storeUrls;
    /** @var array|null */
    private $storeBaseUrls;
    /** @var \FiloBlu\Refilo\Helper\Data */
    private $helper;
    /** @var StoreManagerInterface */
    private $storeManager;
    /** @var HrefLangConfigurationSourceInterface */
    private $hrefLangConfig;
    /** @var array | null */
    private $seoBaseLocales;
    /** @var array | null */
    private $hrefLangConfigLookup;
    /** @var array|null */
    private $storeLocales;
    /** @var  array|null */
    private $storeCodes;
    /** @var ScopeConfigInterface */
    private $scopeConfig;
    /** @var array|null */
    private $homeUrls;
    /** @var  array|null */
    private $hreflangEnabled;
    /** @var Configuration */
    private $coreConfiguration;
    /** @var @var array|null */
    private $storeFilters;
    /** @var Configuration */
    private $defaultStoreByCode;
    /** @var array|null */
    private $storeIds;
    /** @var CategoryHelper */
    private $categoryHelper;
    /** @var MetadataPool */
    private $metadataPool;
    /** @var Attribute */
    private $eavAttribute;
    /** @var ManagerInterface */
    private $eventManager;
    /** @var CanonicalUrlModifierFactory */
    private $canonicalUrlModifierFactory;
    /** @var ContentManager\Data */
    private $contentManagerHelper;

    /**
     * @param Configuration $coreConfiguration
     * @param \FiloBlu\Refilo\Helper\Data $helper
     * @param HrefLangConfigurationSourceInterface $hrefLangConfig
     * @param ResourceConnection $resourceConnection
     * @param ScopeConfigInterface $scopeConfig
     * @param StoreConfigManager $storeConfigManager
     * @param CategoryHelper $categoryHelper
     * @param StoreManagerInterface $storeManager
     * @param MetadataPool $metadataPool
     * @param CanonicalUrlModifierFactory $canonicalUrlModifierFactory
     * @param Attribute $eavAttribute
     * @param ManagerInterface $eventManager
     * @param Data $contentManagerHelper
     */
    public function __construct(
        Configuration                        $coreConfiguration,
        \FiloBlu\Refilo\Helper\Data          $helper,
        HrefLangConfigurationSourceInterface $hrefLangConfig,
        ResourceConnection                   $resourceConnection,
        ScopeConfigInterface                 $scopeConfig,
        StoreConfigManager                   $storeConfigManager,
        CategoryHelper                       $categoryHelper,
        StoreManagerInterface                $storeManager,
        MetadataPool                         $metadataPool,
        CanonicalUrlModifierFactory          $canonicalUrlModifierFactory,
        Attribute                            $eavAttribute,
        ManagerInterface                     $eventManager,
        Data                                 $contentManagerHelper
    )
    {
        $this->resourceConnection = $resourceConnection;
        $this->helper = $helper;
        $this->storeManager = $storeManager;
        $this->hrefLangConfig = $hrefLangConfig;
        $this->storeConfigManager = $storeConfigManager;
        $this->scopeConfig = $scopeConfig;
        $this->coreConfiguration = $coreConfiguration;
        $this->categoryHelper = $categoryHelper;
        $this->metadataPool = $metadataPool;
        $this->eavAttribute = $eavAttribute;
        $this->eventManager = $eventManager;
        $this->canonicalUrlModifierFactory = $canonicalUrlModifierFactory;
        $this->contentManagerHelper = $contentManagerHelper;
    }

    /**
     * @param UrlRewrite $urlRewrite
     * @return array
     * @throws NoSuchEntityException
     * @throws LocalizedException
     * @throws Exception
     */
    public function getHreflang(UrlRewrite $urlRewrite): array
    {
        $storeFilters = $this->getStoreFilter($urlRewrite->getStore());
        $otherUrlRewrite = clone $urlRewrite;

        // Home has special treatment
        if ($this->isHome($urlRewrite)) {
            $result = [];

            if ($this->hrefLangConfig->isEnabledForHome($urlRewrite->getStore())) {
                $xDefaultStoreCode = $this->hrefLangConfig->getXDefaultStoreCode();
                if ($xDefaultStoreCode !== null) {
                    $xDefaultUrl = $this->getStoreUrl(
                        (int) $this->storeManager->getStore($xDefaultStoreCode)->getId()
                    );
                } else {
                    $xDefaultUrl = $this->helper->getStoreFrontBaseUrl($urlRewrite->getStore());
                }
                $result['x-default'] = [
                    'code' => 'x-default',
                    'url' => $xDefaultUrl,
                ];
            }

            if (!$this->isHrefLangEnabled($urlRewrite)) {
                return $result;
            }

            foreach ($this->storeManager->getStores() as $store) {
                $storeId = $store->getId();

                if (!in_array($storeId, $storeFilters, false)) {
                    continue;
                }

                $otherUrlRewrite->setStore($store->getCode());

                if (!$this->isHrefLangEnabled($otherUrlRewrite)) {
                    continue;
                }

                $storeCode = $this->getStoreCodeById($store->getId());
                $result[$storeCode] = [
                    'code' => $this->hrefLangConfig->getLanguageCode($storeCode),
                    'url' => $this->getStoreUrl($storeId)
                ];
            }

            return $result;
        }

        if (!$this->isHrefLangEnabled($urlRewrite)) {
            return [];
        }

        $connection = $this->resourceConnection->getConnection();

        $select = $connection->select()->from(
            ['u' => 'url_rewrite'],
            ['store_id' => 'u.store_id', 'request_path' => 'u.request_path']
        );
        $entityType = $urlRewrite->getType();
        $trail = '';
        switch ($entityType) {
            case 'cms-page':
                $trail = '/';
                $select->joinRight(
                    ['cp' => 'cms_page'],
                    'cp.page_id = u.entity_id AND entity_type = \'cms-page\'',
                    ['refilo_page_identifier' => 'cp.refilo_page_identifier']
                )->where(
                    "COALESCE (
                    IF(cp.refilo_page_identifier = '' OR cp.refilo_page_identifier IS NULL, NULL, cp.refilo_page_identifier),
                    IF(cp.identifier = '' OR cp.identifier IS NULL, NULL, cp.identifier)
                    ) = ?",
                    $urlRewrite->getPageIdentifier()
                );
                break;
            case 'category':
                $linkField = $this->metadataPool->getMetadata(CategoryInterface::class)->getLinkField();
                $attributeId = $this->eavAttribute->getIdByCode(Category::ENTITY, CategoryUrlProvider::CATEGORY_IDENTIFIER_ATTRIBUTE_CODE);
                if (!$urlRewrite->getCategoryIdentifier()) {
                    $select->where('u.entity_type = ?', $entityType)
                        ->where('target_path = ?', $urlRewrite->getTargetUrl());
                    break;
                }
                $select->joinRight(
                    [
                        'c' => 'catalog_category_entity'
                    ],
                    'c.entity_id = u.entity_id AND entity_type = \'category\'',
                    [
                        $linkField => "c.$linkField"
                    ]
                )->joinLeft(
                    [
                        'ccev' => 'catalog_category_entity_varchar'
                    ],
                    "ccev.$linkField = c.$linkField AND ccev.attribute_id = $attributeId",
                    [
                        UrlRewrite::CATEGORY_IDENTIFIER => 'ccev.value'
                    ]
                )->where('ccev.value = ?', $urlRewrite->getCategoryIdentifier());
                break;

            case 'product':
                $select->where('u.entity_type = ?', $entityType)
                    ->where('target_path = ?', $urlRewrite->getTargetUrl());
                break;
            case 'master_data':
                $select->where("u.entity_type = 'contenttype_content'")
                    ->where('target_path = ?', $urlRewrite->getTargetUrl());
                break;
            default:
                return [];
        }

        $result = [];
        $select->where('u.store_id IN (?)', $storeFilters);

        foreach ($connection->fetchAll($select, [], Zend_Db::FETCH_ASSOC) as $url) {
            $storeCode = $this->getStoreCodeById($url['store_id']);

            $otherUrlRewrite->setStore($storeCode);

            if (!$this->isHrefLangEnabled($otherUrlRewrite)) {
                continue;
            }

            $storeId = $url['store_id'];

            $hrefLangPath = $this->getStoreUrl($storeId, $url['request_path']);

            $hrefLangPath = rtrim($hrefLangPath, '/');

            $result[$storeCode] = [
                'code' => $this->hrefLangConfig->getLanguageCode($storeCode),
                'url' => $hrefLangPath !== '/' ? $hrefLangPath . $trail : $hrefLangPath
            ];
        }
        return $result;
    }

    /**
     * @param $storeCode
     * @return array|mixed
     * @throws LocalizedException
     */
    public function getStoreFilter($storeCode)
    {
        if ($this->storeFilters === null) {
            $this->storeFilters = [];
            foreach ($this->coreConfiguration->getWebsiteMapping() as $map) {
                $this->storeFilters = array_merge(
                    $this->storeFilters,
                    array_fill_keys($map->getStoreCodes(), $map->getStoreIds())
                );
            }
        }

        return $this->storeFilters[$storeCode] ?? null;
    }

    /**
     * @param UrlRewrite $urlRewrite
     * @return bool
     */
    public function isHome(UrlRewrite $urlRewrite): bool
    {
        if ($urlRewrite->getType() !== 'cms-page') {
            return false;
        }

        return in_array($urlRewrite->getPageIdentifier(), $this->getHomeUrls(), false);
    }

    /**
     * @return array
     */
    public function getHomeUrls(): ?array
    {
        if ($this->homeUrls === null) {
            $this->homeUrls = [];
            foreach ($this->storeManager->getStores() as $store) {
                $this->homeUrls[$store->getCode()] = $this->scopeConfig->getValue(
                    PageHelper::XML_PATH_HOME_PAGE,
                    ScopeInterface::SCOPE_STORE,
                    $store
                );
            }
        }
        return $this->homeUrls;
    }

    /**
     * @param UrlRewrite $urlRewrite
     * @return bool
     */
    public function isHrefLangEnabled(UrlRewrite $urlRewrite): bool
    {
        if ($this->hreflangEnabled === null) {
            $this->hreflangEnabled = [];
            foreach ($this->storeManager->getStores() as $store) {

                $this->hreflangEnabled[$store->getCode()] = [
                    'product' => $this->hrefLangConfig->isEnabledForProduct($store),
                    'category' => $this->hrefLangConfig->isEnabledForCategory($store),
                    'cms-page' => $this->hrefLangConfig->isEnabledForCms($store),
                    'master_data' => $this->hrefLangConfig->isEnabledForCms($store)
                ];
            }
        }

        $store = $urlRewrite->getStore();
        $type = $urlRewrite->getType();

        if (is_string($store) && isset($this->hreflangEnabled[$store][$type])) {
            return $this->hreflangEnabled[$store][$type];
        }

        return false;
    }

    /**
     * @param $storeId
     * @return mixed|string
     */
    public function getStoreCodeById($storeId)
    {
        if ($this->storeCodes === null) {
            $this->storeCodes = [];
            foreach ($this->storeManager->getStores() as $store) {
                $this->storeCodes[$store->getId()] = $store->getCode();
            }
        }

        return $this->storeCodes[$storeId] ?? null;
    }

    /**
     * @param $storeId
     * @param string|null $path
     * @return string
     * @throws NoSuchEntityException
     */
    public function getStoreUrl($storeId, string $path = null): string
    {
        if ($this->storeUrls === null) {
            $this->storeUrls = [];
            foreach ($this->storeManager->getStores() as $store) {
                $this->storeUrls[$store->getId()] = $this->helper->getFrontendUrl($store);
            }
        }

        return ($this->storeUrls[$storeId] ?? '') . $path;
    }

    /**
     * @param $storeCode
     * @return array|mixed
     */
    public function getHrefLangConfig($storeCode)
    {
        if ($this->hrefLangConfigLookup === null) {
            $this->hrefLangConfigLookup = [];
            foreach ($this->storeManager->getStores() as $store) {
                $this->hrefLangConfigLookup[$store->getCode()] = $this->hrefLangConfig->getConfig($store);
            }
        }

        return $this->hrefLangConfigLookup[$storeCode];
    }

    /**
     * @param $storeCode
     * @return mixed|string
     */
    public function getStoreLocale($storeCode)
    {
        if ($this->storeLocales === null) {
            $this->storeLocales = [];
            foreach ($this->storeConfigManager->getStoreConfigs() as $storeConfig) {
                $this->storeLocales[$storeConfig->getCode()] = $storeConfig->getLocale();
            }
        }

        return $this->storeLocales[$storeCode];
    }

    /**
     * @param $storeCode
     * @return mixed|string
     */
    public function getSeoBaseLocale($storeCode)
    {
        if ($this->seoBaseLocales === null) {
            $this->seoBaseLocales = [];
            foreach ($this->storeManager->getStores() as $store) {
                $this->seoBaseLocales[$store->getCode()] = $this->hrefLangConfig->getSeoLocaleCodeForStore($store);
            }
        }

        return $this->seoBaseLocales[$storeCode];
    }

    /**
     * @param $url
     * @return void
     */
    public function isAbsolute($url): bool
    {
        return strpos($url, '://') !== false;
    }

    /**
     * @param $storeId
     * @return mixed
     */
    public function getStoreBaseUrl($storeId)
    {
        if ($this->storeBaseUrls === null) {
            $this->storeBaseUrls = [];
            foreach ($this->storeManager->getStores() as $store) {
                $this->storeBaseUrls[$store->getId()] = $this->helper->getStoreFrontBaseUrl($store);
            }
        }

        return $this->storeBaseUrls[$storeId];
    }

    /**
     * @param $storeCode
     * @return mixed
     */
    public function getHomeUrl($storeCode)
    {
        return $this->getHomeUrls()[$storeCode];
    }

    /**
     * @param UrlRewrite $urlRewrite
     * @return string|null
     * @throws NoSuchEntityException
     * @throws LocalizedException
     */
    public function getCanonicalUrl(UrlRewrite $urlRewrite): ?string
    {
        $storeCode = $urlRewrite->getStore();

        if (!$this->hrefLangConfig->usesCanonical()) {
            return null;
        }

        $storeId = $this->hrefLangConfig->getCanonicalStore($storeCode);

        if ($storeId === 0) {
            $storeId = $this->getStoreIdByCode($storeCode);
        }

        if ($this->isHome($urlRewrite)) {
            return $this->getStoreUrl($storeId);
        }

        $connection = $this->resourceConnection->getConnection();

        $identifier = $urlRewrite->getPageIdentifier();
        $targetUrl = $urlRewrite->getTargetUrl();
        $url = $urlRewrite->getUrl();

        $select = $connection->select()->from(['u' => 'url_rewrite'], ['request_path' => 'u.request_path']);

        $this->eventManager->dispatch('filoblu_refilo_canonical_url_select_build_before', [
            'select' => $select,
            'store_code' => $storeCode,
            'content_type' => $urlRewrite->getType(),
            'url' => $url,
        ]);

        $trail = '';
        switch ($urlRewrite->getType()) {
            case 'cms-page':
                $trail = '/';
                $select->joinRight(
                    ['cp' => 'cms_page'],
                    'cp.page_id = u.entity_id AND entity_type = \'cms-page\'',
                    ['refilo_page_identifier' => 'cp.refilo_page_identifier']
                )->where(
                    "COALESCE (
                    IF(cp.refilo_page_identifier = '' OR cp.refilo_page_identifier IS NULL, NULL, cp.refilo_page_identifier),
                    IF(cp.identifier = '' OR cp.identifier IS NULL, NULL, cp.identifier)
                    ) = ?",
                    $identifier
                );
                break;
            case 'category':
                $select->where("u.entity_type = 'category'")
                    ->where('target_path = ?', $targetUrl);
                break;
            case 'product':
                $select->where("u.entity_type = 'product'")
                    ->where('target_path = ?', $targetUrl);
                break;
            case 'master_data':
                $trail = '/';
                $select->where("u.entity_type = 'contenttype_content'")
                    ->where('target_path = ?', $targetUrl);
                break;
            default:
                return null;
        }

        $select->where('u.store_id = ? AND u.is_autogenerated = 1', $storeId);
        $path = $connection->fetchOne($select);

        $canonicalUrlModifier = $this->canonicalUrlModifierFactory->create();

        $canonicalUrlModifier->setData([
            'store_code' => $storeCode,
            'path' => $path,
            'url' => $url
        ]);

        $this->eventManager->dispatch('filoblu_refilo_canonical_url_after_results_fetch', [
            'canonical_url_modifier' => $canonicalUrlModifier,
            'content_type' => $urlRewrite->getType()
        ]);

        if (!$canonicalUrlModifier->getPath()) {
            return $this->getStoreUrl($storeId, $canonicalUrlModifier->getUrl()) . $trail;
        }

        return $this->getStoreUrl($storeId, $canonicalUrlModifier->getPath()) . $trail;
    }

    /**
     * @param $storeCode
     * @return mixed|string
     */
    public function getStoreIdByCode($storeCode)
    {
        if ($this->storeIds === null) {
            $this->storeIds = [];
            foreach ($this->storeManager->getStores() as $store) {
                $this->storeIds[$store->getCode()] = $store->getId();
            }
        }

        return $this->storeIds[$storeCode] ?? null;
    }

    /**
     * @param $storeCode
     * @return StoreInterface|Store
     * @throws LocalizedException
     */
    public function getDefaultStore($storeCode)
    {
        if (!$this->defaultStoreByCode) {
            $this->defaultStoreByCode = [];

            foreach ($this->coreConfiguration->getWebsiteMapping() as $map) {
                $this->defaultStoreByCode = array_merge(
                    $this->defaultStoreByCode,
                    array_fill_keys($map->getStoreCodes(), $map->getDefaultStore())
                );
            }
        }

        return $this->defaultStoreByCode[$storeCode] ?? null;
    }

    /**
     * @param UrlRewrite $urlRewrite
     * @return array
     * @throws LocalizedException
     */
    public function getBreadcrumbs(UrlRewrite $urlRewrite): array
    {
        switch ($urlRewrite->getType()) {
            case 'category':
                return $this->categoryHelper->getBreadcrumbs($urlRewrite->getEntityId(), $urlRewrite->getStore());
            case 'master_data':
                return $this->contentManagerHelper->getContentTypeBreadcrumbs($urlRewrite);
            default:
                return [];
        }
    }

    /**
     * @param $productId
     * @return string
     */
    public function getCategoryByProductId($productId)
    {

        $connection = $this->resourceConnection->getConnection();
        $categoryProductTable = $connection->getTableName('catalog_category_product');
        $categoryTable = $connection->getTableName('catalog_category_entity');

        // TODO: fare in modo che punti sempre ad una categoria attiva o di default
        $select = $connection->select()
            ->from(['cce' => $categoryTable], ['cce.entity_id'])
            ->joinInner(['ccp' => $categoryProductTable], 'ccp.category_id = cce.entity_id', [])
            ->where('ccp.product_id = ?', $productId)
            ->where('level > 1')
            ->order('level ASC')
            ->limitPage(1, 1);

        return $connection->fetchOne($select);
    }


    /**
     * @return bool
     */
    public function isRedirectCatalogProductEnabled()
    {
        return $this->scopeConfig->isSetFlag(self::XML_PATH_REDIRECT_CATALOG_PRODUCT, ScopeInterface::SCOPE_STORE);
    }

    /**
     * @return mixed
     */
    public function getExcludedCategoriesIds()
    {
        return $this->scopeConfig->getValue(self::XML_PATH_EXCLUDED_CATEGORIES_IDS, ScopeInterface::SCOPE_STORE);
    }

    /**
     * @param $categoryId
     * @param $storeId
     * @return string
     */
    public function getCategoryUrl($categoryId, $storeId)
    {
        if (!$categoryId) {
            return '/';
        }

        $connection = $this->resourceConnection->getConnection();
        $urlRewriteTable = $connection->getTableName('url_rewrite');

        $select = $connection->select()
            ->from(['ur' => $urlRewriteTable], ['ur.request_path'])
            ->where('ur.entity_type = \'category\'')
            ->where('ur.entity_id = ?', $categoryId)
            ->where('ur.is_autogenerated = 1')
            ->where('ur.store_id = ? ', $storeId)
            ->limitPage(1, 1);

        $path = $connection->fetchOne($select);
        if (!$path) {
            return "catalog/category/view/id/$categoryId";
        }
        return $path;
    }
}
