<?php

declare(strict_types=1);


namespace FiloBlu\Refilo\Helper\Catalog;


use FiloBlu\Refilo\Helper\Indexer;
use FiloBlu\Refilo\Remote\Entity\Product;
use FiloBlu\Refilo\Remote\Entity\Provider\StockProvider;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;

/**
 *
 */
class ProductFilters
{

    /**
     * @var mixed
     */
    private $stocks;
    /**
     * @var \Magento\Store\Model\StoreManagerInterface
     */
    private $storeManager;
    /**
     * @var \FiloBlu\Refilo\Remote\Entity\Provider\StockProvider
     */
    private $stockProvider;
    /**
     * @var \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory
     */
    private $productCollectionFactory;
    /**
     * @var \FiloBlu\Refilo\Helper\Catalog\ProductAttributeHelper
     */
    private $attributeHelper;
    /**
     * @var \Magento\Framework\App\Config\ScopeConfigInterface
     */
    private $scopeConfig;

    /**
     * @param \FiloBlu\Refilo\Helper\Catalog\ProductAttributeHelper $attributeHelper
     * @param \Magento\Store\Model\StoreManagerInterface $storeManager
     * @param \FiloBlu\Refilo\Remote\Entity\Provider\StockProvider $stockProvider
     * @param \Magento\Catalog\Model\ResourceModel\Product\CollectionFactory $productCollectionFactory
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     */
    public function __construct(
        ProductAttributeHelper $attributeHelper,
        StoreManagerInterface $storeManager,
        StockProvider $stockProvider,
        CollectionFactory $productCollectionFactory,
        ScopeConfigInterface $scopeConfig
    ) {
        $this->storeManager = $storeManager;
        $this->stockProvider = $stockProvider;
        $this->productCollectionFactory = $productCollectionFactory;
        $this->attributeHelper = $attributeHelper;
        $this->scopeConfig = $scopeConfig;
    }

    /**
     * @param ProductInterface $product
     * @param array $excludeList
     * @return array
     * @throws NoSuchEntityException
     * @throws LocalizedException
     * @override \FiloBlu\Refilo\Helper\Catalog\ProductAttributeHelper::getFilterableAttributesWithChildren
     */
    public function getFilterableAttributesWithChildrenWithoutOutOfStock(
        ProductInterface $product,
        array $excludeList = []
    ): array {
        $attributeList = $this->attributeHelper->getFilterableAttributesList();

        $children = $product->getTypeInstance()
                            ->getChildrenIds($product->getId())
        ;
        $storeId = $product->getStore()
                           ->getId()
        ;
        $products = [$product->getId() => $product];

        if (!empty($children))
        {
            //TODO get simple product from grouped
            $attributeCodes = array_keys($attributeList[$storeId]);
            $productCollection = $this->productCollectionFactory->create();
            $productCollection->setStore($product->getStore())
                              ->addIdFilter(current($children))
                              ->addAttributeToSelect($attributeCodes, true)
                              ->addAttributeToSelect(ProductInterface::STATUS)
            ;
            $products = $productCollection->getItems() + [$product->getId() => $product];
        }

        $superAttributes = $excludeList;

        $excludeOutOfStock = $this->excludeOutOfStock();

        if ($product->getTypeId() === Configurable::TYPE_CODE)
        {
            foreach (
                $product->getTypeInstance()
                        ->getUsedProductAttributes($product) as $attribute
            )
            {
                $superAttributes[] = $attribute->getAttributeCode();
            }
        }
        $lookUpOptions = $this->attributeHelper->getLookupOptions();

        $reduced = array_reduce(
            $products,
            function ($carry, $item) use ($storeId, $attributeList, $excludeList, $superAttributes, $lookUpOptions, $excludeOutOfStock) {
                if ((int)$item->getStatus() !== Status::STATUS_ENABLED)
                {
                    return $carry;
                }

                if($excludeOutOfStock) {
                    $stockData = $this->getStockFor($item);

                    if (!isset($stockData['in_stock']) || !$stockData['in_stock']) {
                        return $carry;
                    }
                }

                if ($item->getTypeId() === Configurable::TYPE_CODE)
                {
                    $excludeList = $superAttributes;
                }

                foreach ($attributeList[$storeId] as $code => $attribute)
                {
                    if (\in_array($code, $excludeList, true))
                    {
                        continue;
                    }

                    $type = $attribute['type'];
                    $carry[$code]['title'] = $attribute['title'];
                    $carry[$code]['type'] = $type;
                    $productValue = $item->getData($code);

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

                    switch ($type)
                    {
                        case 'select':
                            if (isset($lookUpOptions[$storeId][$code]['value'][$productValue]))
                            {
                                $carry[$code]['values'][$productValue] = $lookUpOptions[$storeId][$code]['value'][$productValue] ?? null;
                            }

                            break;
                        case 'multiselect':
                            $attributeData = explode(',', $productValue ?? '');

                            if (is_scalar($attributeData))
                            {
                                $attributeData = [$attributeData];
                            }

                            $keys = array_map('\intval', array_filter($attributeData, '\strlen'));

                            foreach ($keys as $k)
                            {
                                if (isset($lookUpOptions[$storeId][$code]['value'][$k]))
                                {
                                    $carry[$code]['values'][$k] = $lookUpOptions[$storeId][$code]['value'][$k];
                                }
                            }

                            break;
                        default:
                            $carry[$code]['values'][$productValue] = $productValue;
                            break;
                    }
                }

                return $carry;
            },
            []
        );

        $attributes = [];
        foreach ($reduced as $code => $data)
        {
            if (!isset($data['values']))
            {
                continue;
            }
            $attributes[$code] = [
                'title' => $data['title'],
                'key'   => array_keys($data['values']),
                'value' => array_values($data['values'])
            ];
        }

        return $attributes;
    }

    /**
     * @param ProductInterface $product
     * @return mixed
     * @throws NoSuchEntityException
     */
    public function getStockFor(ProductInterface $product)
    {
        $storeId = (int)$product->getStoreId();
        $entityId = $product->getEntityId();

        if ($this->stocks === null || !isset($this->stocks[$storeId]))
        {
            $store = $this->storeManager->getStore($storeId);
            foreach (iterator_to_array($this->stockProvider->forStore($store)) as $stock)
            {
                $this->stocks[$storeId][$stock->getId()] = $stock->getData(Product::INVENTORY);
            }
        }

        return $this->stocks[$storeId][$entityId] ?? [];
    }

    /**
     * @throws \Magento\Framework\Exception\NoSuchEntityException
     */
    public function excludeOutOfStock() : bool
    {
        return $this->scopeConfig->isSetFlag(
            Indexer::XML_SYNC_DATA_FILTERS_WITHOUT_OUT_OF_STOCK ,
            ScopeInterface::SCOPE_STORE,
            $this->storeManager->getStore());
    }
}
