<?php

declare(strict_types=1);

namespace FiloBlu\Refilo\Remote\Entity\Provider;

use FiloBlu\Refilo\Remote\Entity\EntityProviderInterface;
use FiloBlu\Refilo\Remote\Entity\ProductRelated;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\ResourceModel\Product\Collection;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Framework\App\Area;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Api\Data\WebsiteInterface;
use Magento\Store\Model\App\Emulation;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Store\Model\Website;
use Psr\Log\LoggerInterface;
use ReturnTypeWillChange;
use Throwable;

use function array_slice;
use function count;

/**
 * Class RelatedProvider
 * @package FiloBlu\Refilo\Remote\Entity\Provider
 */
class RelatedProvider extends BaseProvider
{

    /**
     * @var Emulation
     */
    private $emulation;

    /**
     * @var LoggerInterface
     */
    private $logger;

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

    /**
     * @var null |array
     */
    private $websites;
    /**
     * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
     */
    private $productCollectionFactory;

    /**
     * RelatedProvider constructor.
     * @param CollectionFactory $productCollectionFactory
     * @param Emulation $emulation
     * @param StoreManagerInterface $storeManager
     * @param ScopeConfigInterface $scopeConfig
     * @param LoggerInterface $logger
     */
    public function __construct(
        CollectionFactory $productCollectionFactory,
        Emulation $emulation,
        StoreManagerInterface $storeManager,
        ScopeConfigInterface $scopeConfig,
        LoggerInterface $logger
    ) {
        parent::__construct($scopeConfig);
        $this->productCollectionFactory = $productCollectionFactory;
        $this->emulation = $emulation;
        $this->logger = $logger;
        $this->storeManager = $storeManager;
    }

    /**
     * @param StoreInterface $store
     * @return EntityProviderInterface
     */
    public function forStore(StoreInterface $store): EntityProviderInterface
    {
        return $this;
    }

    /**
     * @param WebsiteInterface $website
     * @return EntityProviderInterface
     */
    public function forWebsite(WebsiteInterface $website): EntityProviderInterface
    {
        return $this;
    }

    /**
     *
     */
    #[ReturnTypeWillChange]
    public function rewind()
    {
        $this->items = [];

        $products = $this->getProductCollection();

        try {
            /** @var ProductInterface|Product $product */
            foreach ($products as $product) {
                $websiteIds = $product->getWebsiteIds();

                if (empty($websiteIds)) {
                    continue;
                }

                $this->emulation->startEnvironmentEmulation(
                    $this->getDefaultStoreForWebsiteGroup($websiteIds),
                    Area::AREA_FRONTEND,
                    true
                );

                $entityId = (int)$product->getId();
                $this->items[$entityId] = $this->getRelatedProduct($product);
                $this->emulation->stopEnvironmentEmulation();
            }
        } catch (Throwable $throwable) {
            $this->logger->error($throwable->getMessage(), ['exception' => $throwable]);
            $this->emulation->stopEnvironmentEmulation();
        }

        if (!$this->hasReadHandler()) {
            return;
        }

        // TODO: Handle pagination
        // Temporary workaround for Morato
        $pageSize = $this->getBulkSize();
        $size = count($this->items);

        while ($size > $pageSize) {
            $data = array_slice($this->items, 0, $pageSize);
            $this->getReadHandler()->onRead($data, $pageSize, $this->getReadHandlerArguments());
            $size -= $pageSize;
        }

        if ($size > 0) {
            $this->getReadHandler()->onRead($this->items, $size, $this->getReadHandlerArguments());
        }
    }

    /**
     * @return \Magento\Framework\DataObject[]
     */
    public function getProductCollection()
    {
        /** @var Collection $productCollection */
        $productCollection = $this->productCollectionFactory->create();
        if ($this->ids) {
            $productCollection->addFieldToFilter('entity_id', ['in' => $this->ids]);
        }

        $products = $productCollection
            ->addAttributeToSelect('*')
            ->addAttributeToFilter(
                ProductInterface::VISIBILITY,
                [
                    'in' => [Visibility::VISIBILITY_IN_SEARCH, Visibility::VISIBILITY_BOTH]
                ]
            )
            ->addAttributeToFilter(ProductInterface::STATUS, Status::STATUS_ENABLED)
            ->addWebsiteNamesToResult()
            ->getItems();

        return $products;
    }

    /**
     * @param int[] $websiteIds
     * @return int
     */
    public function getDefaultStoreForWebsiteGroup($websiteIds)
    {
        if ($this->websites === null) {
            $this->websites = [];
            /** @var Website $website */
            foreach ($this->storeManager->getWebsites() as $website) {
                $this->websites[$website->getId()] = $website->getDefaultStore()->getId();
            }
        }

        return (int)$this->websites[min($websiteIds)];
    }

    /**
     * @param \Magento\Catalog\Api\Data\ProductInterface $product
     * @return \FiloBlu\Refilo\Remote\Entity\ProductRelated
     */
    public function getRelatedProduct(ProductInterface $product)
    {
        return new ProductRelated([
            'id'       => (int)$product->getId(),
            'upsell'   => $this->getUpSellProductIds($product),
            'related'  => $this->getRelatedProductsIds($product),
            'crossell' => $this->getCrossSellProductIds($product)
        ]);
    }

    /**
     * @param Product|ProductInterface $product
     * @return array
     */
    public function getUpSellProductIds($product)
    {
        return array_map('\intval', $product->getUpSellProductIds());
    }

    /**
     * @param Product|ProductInterface $product
     * @return array
     */
    public function getRelatedProductsIds($product)
    {
        return array_map('\intval', $product->getRelatedProductIds());
    }

    /**
     * @param Product|ProductInterface $product
     * @return array
     */
    public function getCrossSellProductIds($product)
    {
        return array_map('\intval', $product->getCrossSellProductIds());
    }

    /**
     * @return \Generator
     */
    public function toGenerator(): \Generator
    {
        $products = $this->getProductCollection();

        try {
            /** @var ProductInterface|Product $product */
            foreach ($products as $product) {
                $websiteIds = $product->getWebsiteIds();

                if (empty($websiteIds)) {
                    continue;
                }

                $this->emulation->startEnvironmentEmulation(
                    $this->getDefaultStoreForWebsiteGroup($websiteIds),
                    Area::AREA_FRONTEND,
                    true
                );

                $entityId = (int)$product->getId();
                $productRelated = $this->getRelatedProduct($product);
                $this->emulation->stopEnvironmentEmulation();

                yield $productRelated;
            }
        } catch (Throwable $throwable) {
            $this->logger->error($throwable->getMessage(), ['exception' => $throwable]);
            $this->emulation->stopEnvironmentEmulation();
        }
    }

    public function release(): EntityProviderInterface
    {
        $this->items = [];
        return $this;
    }
}
