<?php
/**
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade Smile ElasticSuite to newer
 * versions in the future.
 *
 * @category  Smile
 * @package   Smile\ElasticsuiteVirtualCategory
 * @author    Dmytro ANDROSHCHUK <dmand@smile.fr>
 * @copyright 2020 Smile
 * @license   Open Software License ("OSL") v. 3.0
 */

namespace Smile\ElasticsuiteVirtualCategory\Model;

use Magento\Catalog\Api\Data\CategoryInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\DataObject;
use Magento\Framework\Exception\LocalizedException;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;
use Magento\UrlRewrite\Model\UrlFinderInterface;
use Magento\UrlRewrite\Service\V1\Data\UrlRewrite;
use Smile\ElasticsuiteVirtualCategory\Model\ResourceModel\VirtualCategory\CollectionFactory as CategoryCollectionFactory;
use Smile\ElasticsuiteVirtualCategory\Model\VirtualCategory\Root as VirtualCategoryRoot;

/**
 * Url Model for Virtual Categories
 *
 * @category Smile
 * @package  Smile\ElasticsuiteVirtualCategory
 * @author   Dmytro ANDROSHCHUK <dmand@smile.fr>
 */
class Url
{
    /**
     * XML path for product url suffix
     */
    public const XML_PATH_PRODUCT_URL_SUFFIX = 'catalog/seo/product_url_suffix';

    /**
     * XML path for product url suffix
     */
    const XML_PATH_CATEGORY_URL_SUFFIX = 'catalog/seo/category_url_suffix';

    /**
     * Store config
     *
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    private $scopeConfig;

    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    private $storeManager;

    /**
     * @var \Magento\Catalog\Model\ResourceModel\Category\CollectionFactory
     */
    private $categoryCollectionFactory;

    /**
     * @var \Magento\UrlRewrite\Model\UrlFinderInterface
     */
    private $urlFinder;

    /**
     * @var VirtualCategoryRoot
     */
    private $virtualCategoryRoot;

    /**
     * ProductPlugin constructor.
     *
     * @param ScopeConfigInterface      $scopeConfig               Scope Configuration
     * @param StoreManagerInterface     $storeManager              Store Manager Interface
     * @param CategoryCollectionFactory $categoryCollectionFactory Category Collection Factory
     * @param UrlFinderInterface        $urlFinder                 URL Finder
     * @param VirtualCategoryRoot       $virtualCategoryRoot       Virtual Category Root model
     */
    public function __construct(
        ScopeConfigInterface $scopeConfig,
        StoreManagerInterface $storeManager,
        CategoryCollectionFactory $categoryCollectionFactory,
        UrlFinderInterface $urlFinder,
        VirtualCategoryRoot $virtualCategoryRoot
    ) {
        $this->scopeConfig = $scopeConfig;
        $this->storeManager = $storeManager;
        $this->categoryCollectionFactory = $categoryCollectionFactory;
        $this->urlFinder = $urlFinder;
        $this->virtualCategoryRoot       = $virtualCategoryRoot;
    }

    /**
     * Retrieve Url for a given product/category couple
     *
     * @param Product  $product  The Product
     * @param Category $category The Category
     * @param array    $params   Additional Url Params, if needed
     *
     * @return string
     */
    public function getProductUrl($product, $category, $params = null): string
    {
        return trim($this->urlBuilder->getUrl($this->getProductRequestPath($product, $category), $params), '/');
    }

    /**
     * Retrieve rewrite object for a given product/category request path couple.
     * Will only succeed if the category path is related to a virtual one.
     *
     * @SuppressWarnings(PHPMD.ElseExpression)
     *
     * @param string   $productRequestPath  A product Request Path
     * @param string   $categoryRequestPath A category Request Path
     * @param int|null $storeId             Store Id
     *
     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null
     */
    public function getProductRewrite($productRequestPath, $categoryRequestPath, $storeId = null): ?UrlRewrite
    {
        $productRewrite = null;
        if (null === $storeId) {
            $storeId = $this->storeManager->getStore()->getId();
        }

        if (!$this->virtualCategoryRoot->getAppliedRootCategory()) {
            $categoryId = $this->getVirtualCategoryIdByPath($categoryRequestPath);
        } else {
            $urlKeys = explode('/', $categoryRequestPath);
            $urlKey  = array_pop($urlKeys);
            $category = $this->loadCategoryByUrlKey($urlKey);
            $categoryId = $category->getId();
        }
        if ($categoryId) {
            $productRewrite = $this->urlFinder->findOneByData([
                UrlRewrite::REQUEST_PATH => trim($productRequestPath, '/') . ($this->getProductUrlSuffix() === '/' ? '/' : ''),
                UrlRewrite::STORE_ID => $storeId,
            ]);
            if (null !== $productRewrite) {
                $targetPath = $productRewrite->getTargetPath();
                $targetPath .= "/category/{$categoryId}";
                $productRewrite->setTargetPath($targetPath);
            }
        }

        return $productRewrite;
    }

    /**
     * Retrieve request path for the product and a virtual category.
     *
     * @param Product  $product  The product
     * @param Category $category The category
     *
     * @return string
     */
    public function getProductRequestPath($product, $category): ?string
    {
        $requestPath     = null;
        $categoryUrlPath = null;
        $productUrlKey   = $product->getUrlKey();

        if ($this->isVirtualCategory($category) && $this->useCategoryPath()) {
            $categoryUrlPath = $category->getUrlPath();
        } elseif ($this->virtualCategoryRoot->getAppliedRootCategory() && $this->useCategoryPath()) {
            $categoryUrlPath = $this->virtualCategoryRoot->getVirtualCategorySubtreePath(
                $this->virtualCategoryRoot->getAppliedRootCategory(),
                $category
            );
        }

        if ($categoryUrlPath && $productUrlKey) {
            $requestPath = $categoryUrlPath . '/' . $productUrlKey . $this->getProductUrlSuffix();
        }

        return $requestPath;
    }

    /**
     * Build an url for a category being viewed under the subtree of a virtual category
     *
     * @param CategoryInterface $appliedRootCategory The applied root category
     * @param CategoryInterface $childCategory       The child category to retrieve Url for.
     *
     * @return string
     */
    public function getVirtualCategorySubtreeUrl($appliedRootCategory, $childCategory)
    {
        $path = $this->virtualCategoryRoot->getVirtualCategorySubtreePath($appliedRootCategory, $childCategory);
        $url  = $path . $this->getCategoryUrlSuffix();

        $baseUrl = $childCategory->getUrlInstance()->getBaseUrl();

        return $baseUrl . $url;
    }

    /**
     * Retrieve Category Url Rewrite by path and Store.
     *
     * @param string $categoryPath A category Path
     * @param int    $storeId      The Store Id
     *
     * @return \Magento\UrlRewrite\Service\V1\Data\UrlRewrite|null
     */
    public function getCategoryRewrite($categoryPath, $storeId)
    {
        $categoryPath = str_replace($this->getCategoryUrlSuffix() ?? '', '', $categoryPath);
        $category = $this->loadCategoryByUrlKey($categoryPath);
        $rewrite  = null;

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

            if ($rewrite) {
                $rewrite->setRequestPath($rewrite->getTargetPath());
            }
        }

        return $rewrite;
    }

    /**
     * Retrieve a virtual Category Id by its path
     *
     * @param string $categoryPath The category Path
     *
     * @return int|bool
     */
    private function getVirtualCategoryIdByPath($categoryPath)
    {
        $category = $this->loadVirtualCategoryByUrlPath($categoryPath);
        if ($category->getId()) {
            return $category->getId();
        }

        return false;
    }

    /**
     * Load a category by Url key.
     *
     * @param string $requestPath The Request Path
     *
     * @return \Magento\Framework\DataObject
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    private function loadCategoryByUrlKey($requestPath)
    {
        $collection = $this->categoryCollectionFactory->create();

        $collection->setStoreId($this->storeManager->getStore()->getId())
            ->addAttributeToFilter('url_key', ['eq' => $requestPath]);

        return $collection->getFirstItem();
    }

    /**
     * Load a virtual category by request path.
     *
     * @param string $requestPath The Request Path
     *
     * @return DataObject
     * @throws LocalizedException
     */
    private function loadVirtualCategoryByUrlPath($requestPath): DataObject
    {
        $collection = $this->categoryCollectionFactory->create();
        $collection->setStoreId($this->storeManager->getStore()->getId())
            ->addAttributeToFilter('url_path', ['eq' => $requestPath])
            ->addAttributeToFilter('is_virtual_category', ['eq' => 1]);

        return $collection->getFirstItem();
    }

    /**
     * Check if a category is virtual.
     *
     * @param Category $category The category
     *
     * @return bool
     */
    private function isVirtualCategory($category): bool
    {
        return (bool) $category->getIsVirtualCategory();
    }

    /**
     * Check if urls should be rendered by including the category path.
     *
     * @return bool
     */
    private function useCategoryPath(): bool
    {
        return (bool) $this->scopeConfig->getValue(
            \Magento\Catalog\Helper\Product::XML_PATH_PRODUCT_URL_USE_CATEGORY,
            ScopeInterface::SCOPE_STORE
        );
    }

    /**
     * Retrieve Product Url suffix
     *
     * @return string
     */
    private function getProductUrlSuffix()
    {
        return $this->scopeConfig->getValue(self::XML_PATH_PRODUCT_URL_SUFFIX, ScopeInterface::SCOPE_STORE);
    }

    /**
     * Retrieve Category Url suffix
     *
     * @return string
     */
    private function getCategoryUrlSuffix()
    {
        return $this->scopeConfig->getValue(self::XML_PATH_CATEGORY_URL_SUFFIX, ScopeInterface::SCOPE_STORE);
    }
}
