<?php
/**
 * Copyright © Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
declare(strict_types=1);

namespace Magento\InventoryLowQuantityNotification\Model\ResourceModel;

use Magento\Catalog\Api\Data\ProductInterface;
use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\Eav\Api\AttributeRepositoryInterface;
use Magento\Framework\Data\Collection\Db\FetchStrategyInterface;
use Magento\Framework\Data\Collection\EntityFactoryInterface;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\EntityManager\MetadataPool;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Model\ResourceModel\Db\AbstractDb;
use Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection;
use Magento\Inventory\Model\ResourceModel\Source;
use Magento\Inventory\Model\ResourceModel\SourceItem as SourceItemResourceModel;
use Magento\Inventory\Model\SourceItem as SourceItemModel;
use Magento\InventoryApi\Api\Data\SourceInterface;
use Magento\InventoryApi\Api\Data\SourceItemInterface;
use Magento\InventoryConfigurationApi\Model\GetAllowedProductTypesForSourceItemManagementInterface;
use Magento\InventoryLowQuantityNotificationApi\Api\Data\SourceItemConfigurationInterface;
use Magento\Store\Model\Store;
use Psr\Log\LoggerInterface;

/**
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
 */
class LowQuantityCollection extends AbstractCollection
{
    /**
     * @var StockConfigurationInterface
     */
    private $stockConfiguration;

    /**
     * @var GetAllowedProductTypesForSourceItemManagementInterface
     */
    private $getAllowedProductTypesForSourceItemManagement;

    /**
     * @var AttributeRepositoryInterface
     */
    private $attributeRepository;

    /**
     * @var MetadataPool
     */
    private $metadataPool;

    /**
     * @var int
     */
    private $filterStoreId;

    /**
     * @param EntityFactoryInterface $entityFactory
     * @param LoggerInterface $logger
     * @param FetchStrategyInterface $fetchStrategy
     * @param ManagerInterface $eventManager
     * @param AttributeRepositoryInterface $attributeRepository
     * @param StockConfigurationInterface $stockConfiguration
     * @param GetAllowedProductTypesForSourceItemManagementInterface $getAllowedProductTypesForSourceItemManagement
     * @param MetadataPool $metadataPool
     * @param AdapterInterface|null $connection
     * @param AbstractDb|null $resource
     *
     * @SuppressWarnings(PHPMD.ExcessiveParameterList)
     */
    public function __construct(
        EntityFactoryInterface $entityFactory,
        LoggerInterface $logger,
        FetchStrategyInterface $fetchStrategy,
        ManagerInterface $eventManager,
        AttributeRepositoryInterface $attributeRepository,
        StockConfigurationInterface $stockConfiguration,
        GetAllowedProductTypesForSourceItemManagementInterface $getAllowedProductTypesForSourceItemManagement,
        MetadataPool $metadataPool,
        AdapterInterface $connection = null,
        AbstractDb $resource = null
    ) {
        parent::__construct(
            $entityFactory,
            $logger,
            $fetchStrategy,
            $eventManager,
            $connection,
            $resource
        );

        $this->attributeRepository = $attributeRepository;
        $this->stockConfiguration = $stockConfiguration;
        $this->getAllowedProductTypesForSourceItemManagement = $getAllowedProductTypesForSourceItemManagement;
        $this->metadataPool = $metadataPool;
    }

    /**
     * @inheritdoc
     */
    protected function _construct()
    {
        $this->_init(SourceItemModel::class, SourceItemResourceModel::class);

        $this->addFilterToMap('source_code', 'main_table.source_code');
        $this->addFilterToMap('sku', 'main_table.sku');
        $this->addFilterToMap('product_name', 'product_entity_varchar.value');
    }

    /**
     * @param int $storeId
     * @return void
     */
    public function addStoreFilter(int $storeId)
    {
        $this->filterStoreId = $storeId;
    }

    /**
     * @inheritdoc
     */
    protected function _renderFilters()
    {
        if (false === $this->_isFiltersRendered) {
            $this->joinInventoryConfiguration();
            $this->joinCatalogProduct();

            $this->addProductTypeFilter();
            $this->addNotifyStockQtyFilter();
            $this->addEnabledSourceFilter();
            $this->addSourceItemInStockFilter();
        }
        return parent::_renderFilters();
    }

    /**
     * @inheritdoc
     */
    protected function _renderOrders()
    {
        if (false === $this->_isOrdersRendered) {
            $this->setOrder(SourceItemInterface::QUANTITY, self::SORT_ORDER_ASC);
        }
        return parent::_renderOrders();
    }

    /**
     * joinCatalogProduct depends on dynamic condition 'filterStoreId'
     *
     * @return void
     */
    private function joinCatalogProduct()
    {
        $productEntityTable = $this->getTable('catalog_product_entity');
        $productEavVarcharTable = $this->getTable('catalog_product_entity_varchar');
        $nameAttribute = $this->attributeRepository->get('catalog_product', 'name');

        $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
        $linkField = $metadata->getLinkField();

        $this->getSelect()->joinInner(
            ['product_entity' => $productEntityTable],
            'main_table.' . SourceItemInterface::SKU . ' = product_entity.' . ProductInterface::SKU,
            []
        );

        $this->getSelect()->joinInner(
            ['product_entity_varchar' => $productEavVarcharTable],
            'product_entity_varchar.' . $linkField . ' = product_entity.' . $linkField . ' ' .
            'AND product_entity_varchar.store_id = ' . Store::DEFAULT_STORE_ID. ' ' .
            'AND product_entity_varchar.attribute_id = ' . (int)$nameAttribute->getAttributeId(),
            []
        );

        if (null !== $this->filterStoreId) {
            $this->getSelect()->joinLeft(
                ['product_entity_varchar_store' => $productEavVarcharTable],
                'product_entity_varchar_store.' . $linkField . ' = product_entity.' . $linkField . ' ' .
                'AND product_entity_varchar_store.store_id = ' . (int)$this->filterStoreId . ' ' .
                'AND product_entity_varchar_store.attribute_id = ' . (int)$nameAttribute->getAttributeId(),
                [
                    'product_name' => $this->getConnection()->getIfNullSql(
                        'product_entity_varchar_store.value',
                        'product_entity_varchar.value'
                    )
                ]
            );
        } else {
            $this->getSelect()->columns(['product_name' => 'product_entity_varchar.value']);
        }
    }

    /**
     * @return void
     */
    private function joinInventoryConfiguration()
    {
        $sourceItemConfigurationTable = $this->getTable('inventory_low_stock_notification_configuration');

        $this->getSelect()->joinInner(
            ['notification_configuration' => $sourceItemConfigurationTable],
            sprintf(
                'main_table.%s = notification_configuration.%s AND main_table.%s = notification_configuration.%s',
                SourceItemInterface::SKU,
                SourceItemConfigurationInterface::SKU,
                SourceItemInterface::SOURCE_CODE,
                SourceItemConfigurationInterface::SOURCE_CODE
            ),
            []
        );
    }

    /**
     * @return void
     */
    private function addProductTypeFilter()
    {
        $this->addFieldToFilter(
            'product_entity.type_id',
            $this->getAllowedProductTypesForSourceItemManagement->execute()
        );
    }

    /**
     * @return void
     */
    private function addNotifyStockQtyFilter()
    {
        $notifyStockExpression = $this->getConnection()->getIfNullSql(
            'notification_configuration.' . SourceItemConfigurationInterface::INVENTORY_NOTIFY_QTY,
            (float)$this->stockConfiguration->getNotifyStockQty()
        );

        $this->getSelect()->where(
            SourceItemInterface::QUANTITY . ' < ?',
            $notifyStockExpression
        );
    }

    /**
     * @return void
     */
    private function addEnabledSourceFilter()
    {
        $this->getSelect()->joinInner(
            ['inventory_source' => $this->getTable(Source::TABLE_NAME_SOURCE)],
            sprintf(
                'inventory_source.%s = 1 AND inventory_source.%s = main_table.%s',
                SourceInterface::ENABLED,
                SourceInterface::SOURCE_CODE,
                SourceItemInterface::SOURCE_CODE
            ),
            []
        );
    }

    /**
     * @return void
     */
    private function addSourceItemInStockFilter()
    {
        $this->addFieldToFilter('main_table.status', SourceItemInterface::STATUS_IN_STOCK);
    }
}