<?php

declare(strict_types=1);

namespace FiloBlu\Refilo\Remote\Entity\Provider;

use FiloBlu\Refilo\Remote\Entity\Inventory;
use function array_slice;
use FiloBlu\Refilo\Model\StockIdResolverInterface;
use FiloBlu\Refilo\Remote\Entity\AbstractEntity;
use FiloBlu\Refilo\Remote\Entity\EntityProviderInterface;
use FiloBlu\Refilo\Remote\Entity\Product;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Api\Data\WebsiteInterface;
use ReturnTypeWillChange;

use Zend_Db;

/**
 * Class StockProvider
 * @package FiloBlu\Refilo\Remote\Entity\Provider
 */
class StockProvider extends BaseProvider
{
    /** @var int */
    protected $website;

    /** @var ResourceConnection */
    private $resourceConnection;

    /**
     * @var \FiloBlu\Refilo\Model\StockIdResolverInterface
     */
    private $stockIdResolver;
    /**
     * @var \Magento\Framework\EntityManager\MetadataPool
     */
    private $metadataPool;

    /**
     * StockProvider constructor.
     * @param ResourceConnection $resourceConnection
     * @param ScopeConfigInterface $scopeConfig
     * @param \FiloBlu\Refilo\Model\StockIdResolverInterface $stockIdResolver
     * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
     */
    public function __construct(
        ResourceConnection $resourceConnection,
        ScopeConfigInterface $scopeConfig,
        StockIdResolverInterface $stockIdResolver,
        MetadataPool $metadataPool
    ) {
        parent::__construct($scopeConfig);
        $this->resourceConnection = $resourceConnection;
        $this->stockIdResolver = $stockIdResolver;
        $this->metadataPool = $metadataPool;
    }

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

        $stockId = $this->stockIdResolver->getStockIdByWebsite($this->website);

        $select = $this->getSelectForStock($stockId);

        foreach ($this->resourceConnection->getConnection()->fetchAll($select, [], Zend_Db::FETCH_ASSOC) as $item) {
            $productId = (int)$item['id'];
            $this->items[$productId] = new Product([
                AbstractEntity::ID => $productId,
                Product::INVENTORY => [
                    Inventory::IN_STOCK   => $item['is_salable'] > 0,
                    Inventory::BACKORDERS => (bool)$item['backorders'],
                    Inventory::SALEABLE   => $item['is_salable'] > 0,
                    Inventory::QUANTITY   => (float)$item['quantity']
                ],
                Product::VISIBLE => (int)$item['visibility'] > 1 ? '1' : '0'
            ]);
        }

        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());
        }
    }

    /**
     * @param int $stockId
     * @return \Magento\Framework\DB\Select
     * @throws \Exception
     */
    public function getSelectForStock($stockId)
    {
        $usePreorderAttribute = $this->scopeConfig->isSetFlag('sync/data/use_preorder_attribute');
        $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();
        $select = $this->resourceConnection->getConnection()
            ->select()
            ->from(
                [
                    's' => "inventory_stock_$stockId"
                ],
                [
                    'id'         => 'cpe.entity_id',
                    'quantity'   => 'quantity',
                    'is_salable' => 'is_salable',
                    'backorders' => ($usePreorderAttribute) ? 'IFNULL(csi.value,0)' : 'IFNULL(csi.backorders,0)',
                    'visibility' => 'cpei.value'
                ]
            )->join(
                [
                    'cpe' => 'catalog_product_entity'
                ],
                's.sku = cpe.sku',
                []
            )->joinLeft(
                ['csi' => ($usePreorderAttribute) ? 'catalog_product_entity_int' : 'cataloginventory_stock_item'],
                ($usePreorderAttribute) ?
                    "cpe.$linkField = csi.$linkField AND csi.attribute_id = (select attribute_id from eav_attribute WHERE attribute_code = 'is_preorder') AND csi.store_id = 0" : 'cpe.entity_id = csi.product_id',
                []
            )->joinLeft(
                ['cpei' => 'catalog_product_entity_int'],
                "cpe.$linkField = cpei.$linkField AND cpei.attribute_id = (select attribute_id from eav_attribute WHERE attribute_code = 'visibility') ",
                []
            );

        if (!empty($this->ids)) {
            $select->where('cpe.entity_id IN (' . implode(',', $this->ids) . ')');
        }

        return $select;
    }

    /**
     * @param array $ids
     * @return \FiloBlu\Refilo\Remote\Entity\EntityProviderInterface
     */
    public function withIds(array $ids): EntityProviderInterface
    {
        $this->ids = $ids;
        return $this;
    }

    /**
     * @param StoreInterface $store
     * @return \FiloBlu\Refilo\Remote\Entity\EntityProviderInterface
     */
    public function forStore(StoreInterface $store): EntityProviderInterface
    {
        $this->website = $store->getWebsiteId();
        return $this;
    }

    /**
     * @param WebsiteInterface $website
     * @return \FiloBlu\Refilo\Remote\Entity\EntityProviderInterface
     */
    public function forWebsite(WebsiteInterface $website): EntityProviderInterface
    {
        $this->website = $website->getId();
        return $this;
    }

    /**
     * @return \Generator
     * @throws \Zend_Db_Statement_Exception
     */
    public function toGenerator(): \Generator
    {
        $stockId = $this->stockIdResolver->getStockIdByWebsite($this->website);

        $select = $this->getSelectForStock($stockId);

        $statement = $this->resourceConnection->getConnection()->query($select);
        while (($item = $statement->fetch(Zend_Db::FETCH_ASSOC))) {
            $productId = (int)$item['id'];
            $product = new Product([
                AbstractEntity::ID => $productId,
                Product::INVENTORY => [
                    Inventory::IN_STOCK   => $item['is_salable'] > 0,
                    Inventory::BACKORDERS => (bool)$item['backorders'],
                    Inventory::SALEABLE   => $item['is_salable'] > 0,
                    Inventory::QUANTITY   => (float)$item['quantity']
                ],
                Product::VISIBLE => (int)$item['visibility'] > 1 ? '1' : '0'
            ]);

            yield $product;
        }
    }

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