<?php

namespace FiloBlu\Refilo\Remote\Entity\Provider;

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\DB\Select;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Store\Api\Data\StoreInterface;
use Magento\Store\Api\Data\WebsiteInterface;
use Zend_Db;
use Zend_Db_Expr;
use Zend_Db_Select_Exception;

use function array_slice;

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

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

    /**
     * @var \Magento\Framework\EntityManager\MetadataPool
     */
    private $metadataPool;

    /**
     * @param \Magento\Framework\App\ResourceConnection $resourceConnection
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Magento\Framework\EntityManager\MetadataPool $metadataPool
     */
    public function __construct(
        ResourceConnection $resourceConnection,
        ScopeConfigInterface $scopeConfig,
        MetadataPool  $metadataPool
    ) {
        parent::__construct($scopeConfig);
        $this->resourceConnection = $resourceConnection;
        $this->metadataPool = $metadataPool;
    }

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

        $prices = $this->resourceConnection
            ->getConnection()
            ->fetchAll($this->getPricesSelect(), [], Zend_Db::FETCH_ASSOC);

        foreach ($prices as $price) {
            $productId = (int)$price['id'];

            if (!isset($this->items[$productId])) {
                $this->items[$productId] = new Product();
                $this->items[$productId]->setId($productId);
            }

            $finalPrice = (float)$price['final_price'];

            if (((float)$price['final_price'] > (float)$price['min_price'] && (float)$price['min_price'] != 0.0)) {
                $finalPrice = (float)$price['min_price'];
            }

            $this->items[$productId]->setData("pricing_{$price['customer_group']}", [
                'final_price' => $finalPrice,
                'max_price'   => (float)$price['max_price'],
                'min_price'   => (float)$price['min_price'],
                'base_price'  => (float)$price['base_price']
            ]);
        }

        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 Select
     * @throws Zend_Db_Select_Exception
     */
    public function getPricesSelect()
    {
        $connection = $this->resourceConnection->getConnection();

        //
        // 1] Query for all product except configurables
        //
        $selectNotConfigurable = $connection->select();
        $selectNotConfigurable
            ->from(
                ['cpp' => 'catalog_product_index_price'],
                [
                    'id'             => 'entity_id',
                    'website_id'     => 'website_id',
                    'customer_group' => 'customer_group_id',
                    'final_price'    => new Zend_Db_Expr('IF (final_price,final_price,min_price)'),
                    'min_price'      => 'min_price',
                    'max_price'      => 'max_price',
                    'base_price'     => new Zend_Db_Expr('IF (price,price,min_price)')
                ]
            )
            ->joinInner(['cpe' => 'catalog_product_entity'], 'cpp.entity_id = cpe.entity_id', [])
            ->where('cpe.type_id <> \'configurable\'');

        //
        // 1] Query for all configurable proucts
        //

        $selectConfigurable = $connection->select();
        $linkField = $this->metadataPool->getMetadata(ProductInterface::class)->getLinkField();

        $selectConfigurable->from(
            ['cpsl' => 'catalog_product_super_link'],
            [
                'id'             => 'cpe.entity_id',
                'website_id'     => 'cpip.website_id',
                'customer_group' => 'cpip.customer_group_id',
                'final_price'    => new Zend_Db_Expr('MIN(cpip.final_price)'),
                'min_price'      => new Zend_Db_Expr('MIN(cpip.min_price)'),
                'max_price'      => new Zend_Db_Expr('MIN(cpip.max_price)'),
                'base_price'     => new Zend_Db_Expr('MIN(cpip.price)')
            ]
        )
            ->joinInner(['cpip' => 'catalog_product_index_price'], 'cpsl.product_id = cpip.entity_id', [])
            ->joinInner(['cpe' => 'catalog_product_entity'], "cpsl.parent_id = cpe.$linkField", [])
            ->group(['cpip.website_id', 'cpip.customer_group_id', 'cpip.tax_class_id', 'cpe.entity_id']);

        //
        // 3] Set boundaries
        //

        if (!empty($this->ids)) {
            $selectNotConfigurable->where('cpp.entity_id IN (' . implode(',', $this->ids) . ')');
            $selectConfigurable->where('cpip.entity_id IN (' . implode(',', $this->ids) . ')');
        }

        if ($this->website !== null) {
            $selectNotConfigurable->where("cpp.website_id = {$this->website}");
            $selectConfigurable->where("cpip.website_id = {$this->website}");
        } else {
            $selectNotConfigurable->where('cpp.website_id = 0');
            $selectConfigurable->where('cpip.website_id = 0');
        }

        return $connection->select()
            ->union([$selectNotConfigurable, $selectConfigurable]);
    }

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

    /**
     * @param WebsiteInterface $website
     * @return $this|EntityProviderInterface
     */
    public function forWebsite(WebsiteInterface $website): EntityProviderInterface
    {
        $this->website = $website->getId();
        return $this;
    }

    /**
     * @return \Generator
     * @throws Zend_Db_Select_Exception
     * @throws \Zend_Db_Statement_Exception
     */
    public function toGenerator(): \Generator
    {
        $statement = $this->resourceConnection->getConnection()->query($this->getPricesSelect());

        while (($price = $statement->fetch(Zend_Db::FETCH_ASSOC))) {

            $product = new Product();
            $product->setId((int)$price['id']);

            $finalPrice = (float)$price['final_price'];

            if (((float)$price['final_price'] > (float)$price['min_price'] && (float)$price['min_price'] != 0.0)) {
                $finalPrice = (float)$price['min_price'];
            }

            $product->setData("pricing_{$price['customer_group']}", [
                'final_price' => $finalPrice,
                'max_price'   => (float)$price['max_price'],
                'min_price'   => (float)$price['min_price'],
                'base_price'  => (float)$price['base_price']
            ]);

            yield $product;
        }
    }

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