<?php

namespace FiloBlu\Flow\Model\From;

use Exception;
use FiloBlu\Flow\Block\Adminhtml\Channel\Edit\Tab\Config;
use FiloBlu\Flow\Helper\Data;
use FiloBlu\Flow\Helper\EmailNotification;
use FiloBlu\Flow\Helper\LoggerProvider;
use InvalidArgumentException;
use Magento\Catalog\Model\ProductFactory;
use Magento\CatalogInventory\Api\StockConfigurationInterface;
use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\CatalogInventory\Model\Stock as CatalogInventoryStock;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Event\ManagerInterface;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\MailException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Indexer\ActionFactory;
use Magento\Framework\Indexer\IndexerRegistry;
use Magento\Framework\Indexer\StateInterface;
use Magento\Framework\Model\Context;
use Magento\Framework\Mview\View\CollectionFactory;
use Magento\Framework\Registry;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Framework\Validation\ValidationException;
use Magento\Indexer\Cron\UpdateMview;
use Magento\Inventory\Model\ResourceModel\SourceItem\SaveMultiple;
use Magento\Inventory\Model\ResourceModel\StockSourceLink\Collection;
use Magento\Inventory\Model\StockSourceLink;
use Magento\InventoryApi\Api\Data\SourceItemInterface;
use Magento\InventoryApi\Api\SourceItemsSaveInterface;
use Magento\InventoryApi\Api\StockRepositoryInterface;
use Magento\InventoryIndexer\Model\Queue\ReservationDataFactory;
use Magento\InventoryIndexer\Model\Queue\UpdateIndexSalabilityStatus;
use Magento\InventoryReservationsApi\Model\ReservationInterface;
use Zend_Db;
use Zend_Db_Expr;
use Zend_Db_Statement_Exception;
use function in_array;
use function is_array;

/**
 * @see https://devdocs.magento.com/guides/v2.3/inventory/
 * Class Stock
 * @package FiloBlu\Flow\Model\From
 * @method getMetaId()
 * @method getMetaFile()
 */
class Stock extends AbstractFrom
{
    /**
     * @var string[]
     */
    const SOURCES = ['snatt', 'default', 'magento', 've'];

    /**
     * @var string
     */
    const STOCK_CONFIG_XML_PATH = 'cataloginventory/item_options/manage_stock';

    /**
     * @var Data
     */
    protected $helperFlow;

    /**
     * @var ProductFactory
     */
    protected $productFactory;

    /**
     * @var StockRegistryInterface
     */
    protected $stockRegistry;

    /**
     * @var \FiloBlu\Flow\Helper\Stock
     */
    protected $_stock_qty;

    /**
     * @var ProductMetadataInterface
     */
    protected $_productMetadata;

    /**
     * @var EmailNotification
     */
    protected $emailNotification;

    /**
     * @var ScopeConfigInterface
     */
    protected $scopeConfig;

    /** @var */
    protected $connection;

    /**
     * @var StockConfigurationInterface
     */
    protected $stockConfiguration;

    /**
     * @var SourceItemsSaveInterface
     */
    protected $sourceItemSave;

    /**
     * @var IndexerRegistry
     */
    protected $indexerRegistry;

    /**
     * @var ActionFactory
     */
    protected $actionFactory;

    /**
     * @var ObjectManager
     */
    protected $objectManager;

    /**
     * @var CollectionFactory
     */
    private $mviewCollectionFactory;

    /**
     * @var ManagerInterface
     */
    private $eventManager;

    /**
     * Stock constructor.
     * @param Context $context
     * @param Registry $registry
     * @param Data $helper_flow
     * @param LoggerProvider $loggerProvider
     * @param ProductFactory $productFactory
     * @param StockRegistryInterface $stockRegistry
     * @param StockConfigurationInterface $stockConfiguration
     * @param \FiloBlu\Flow\Helper\Stock $stock_qty
     * @param ProductMetadataInterface $productMetadata
     * @param EmailNotification $emailNotification
     * @param ScopeConfigInterface $scopeConfig
     * @param IndexerRegistry $indexerRegistry
     * @param ActionFactory $actionFactory
     * @param CollectionFactory $mviewCollectionFactory
     * @param array $data
     */
    public function __construct(
        Context                     $context,
        Registry                    $registry,
        Data                        $helper_flow,
        LoggerProvider              $loggerProvider,
        ProductFactory              $productFactory,
        StockRegistryInterface      $stockRegistry,
        StockConfigurationInterface $stockConfiguration,
        \FiloBlu\Flow\Helper\Stock  $stock_qty,
        ProductMetadataInterface    $productMetadata,
        EmailNotification           $emailNotification,
        ScopeConfigInterface        $scopeConfig,
        IndexerRegistry             $indexerRegistry,
        ActionFactory               $actionFactory,
        CollectionFactory           $mviewCollectionFactory,
        array                       $data = []
    )
    {
        parent::__construct($context, $registry, null, null, $data);
        $this->mviewCollectionFactory = $mviewCollectionFactory;
        $this->helperFlow = $helper_flow;
        $this->_logger = $loggerProvider->getLogger();
        $this->productFactory = $productFactory;
        $this->stockRegistry = $stockRegistry;
        $this->_stock_qty = $stock_qty;
        $this->_productMetadata = $productMetadata;
        $this->emailNotification = $emailNotification;
        $this->scopeConfig = $scopeConfig;
        $this->stockConfiguration = $stockConfiguration;
        $this->indexerRegistry = $indexerRegistry;
        $this->actionFactory = $actionFactory;
        $this->objectManager = ObjectManager::getInstance();
        $this->eventManager = $context->getEventDispatcher();
    }

    /**
     * @noinspection MagicMethodsValidityInspection
     * @return void
     */
    public function _construct()
    {
        $this->_init(\FiloBlu\Flow\Model\ResourceModel\From\Stock::class);
    }

    /**
     * @return $this
     * @throws Exception
     */
    public function process()
    {
        if (version_compare($this->_productMetadata->getVersion(), '2.3.0', '>=')) {
            $this->process23();
            return $this;
        }

        $this->process22();
        return $this;
    }

    /**
     *  Set meta_processed = 2 for products != simple
     */
    public function removeUnwantedProductTypes($file)
    {
        $select = $this->getConnection()
            ->select()
            ->joinInner(
                [
                    'cpe' => 'catalog_product_entity'
                ],
                'ffs.sku = cpe.sku',
                [
                    'meta_processed' => new Zend_Db_Expr('2'),
                    'sku'            => 'cpe.sku'
                ])
            ->where("ffs.meta_file = {$file->getId()} AND cpe.type_id NOT IN ('simple', 'giftcard','virtual')");

        $query = $this->getConnection()->updateFromSelect($select, ['ffs' => 'flow_from_stock']);
        $this->getConnection()->query($query);
    }

    /**
     * @return false|AdapterInterface
     */
    protected function getConnection()
    {
        if ($this->connection) {
            return $this->connection;
        }

        return ($this->connection = $this->getResource()->getConnection());
    }

    /**
     * @return $this
     */
    protected function process23()
    {
        $channelConfig = $this->getChannel()->getChannelConfig();
        $processReservedStock = $channelConfig->getProcessReservedStock();
        $orderStatuses = $channelConfig->getProcessReservedStockStatuses();
        $sku = $this->getData('sku');
        $sourceCode = $this->getData('magazzino');
        $quantity = (int)$this->getData('fisico_disponibile');

        if($quantity < 0) {
            $quantity = 0;
        }

        try {
            $originalQuantity = $quantity;
            $sourceItem = $this->createSourceItemInterface();
            $sourceItem->setSourceCode($sourceCode);
            $sourceItem->setSku($sku);

            if ($processReservedStock) {
                $reservedQty = $this->getReservedQuantity23($sku, $sourceCode, $orderStatuses);
                $quantity += $reservedQty;

                if ($reservedQty !== $quantity) {
                    $this->_logger->info("Stock compensation created for {$sku} - Quantity {$quantity} ({$originalQuantity} + {$reservedQty})");
                }
            }

            $sourceItem->setQuantity($quantity);

            // Safely put in out ouf stock items with stock less than 0
            // do not touch items with stock > 0 because we need to consider reservations and other stuff
            // so lets Magento take care about this.
            // Putting in stock items without proper checks can break stock.
            if ($originalQuantity <= 0) {
                $sourceItem->setStatus(SourceItemInterface::STATUS_OUT_OF_STOCK);
            } else {
                $sourceItem->setStatus(SourceItemInterface::STATUS_IN_STOCK);
            }

            $this->getSourceItemsSave()->execute([$sourceItem]);
            $this->_logger->info("Process STOCK - qty {$quantity} SKU: {$sku}");

            $this->getConnection()->update('flow_from_stock', ['final_quantity' => $quantity], ['meta_id = ?' => $this->getMetaId()]);
        } catch (Exception $e) {
            $this->_logger->info('Errore nel salvataggio dello Stock. ' . $e->getMessage());
        }

        return $this;
    }

    /**
     * Create a SourceItem
     * @return SourceItemInterface
     */
    public function createSourceItemInterface()
    {
        return ObjectManager::getInstance()->create(SourceItemInterface::class);
    }

    /**
     * FOS-720
     *
     * @param string $sku
     * @param string $sourceCode
     * @param array $orderStatuses
     * @return int
     */
    public function getReservedQuantity23($sku, $sourceCode, $orderStatuses = [])
    {
        $reservationQuantity = 0;
        /** @var SerializerInterface $serializer */
        $serializer = $this->objectManager->get(SerializerInterface::class);
        /** @var Collection $stockLinkCollection */
        $stockLinkCollection = $this->objectManager->create(Collection::class);
        /** @var StockSourceLink $stockLink */
        $stockLink = $stockLinkCollection->addFieldToFilter('source_code', ['eq' => $sourceCode])->getFirstItem();

        $connection = $this->getConnection();
        $reservationTable = $connection->getTableName('inventory_reservation');

        $select = $connection->select()
            ->from($reservationTable)
            ->where(ReservationInterface::SKU . ' = ?', $sku)
            ->where(ReservationInterface::STOCK_ID . ' = ?', $stockLink->getStockId());

        $reservations = $connection->fetchAll($select, [], Zend_Db::FETCH_ASSOC);

        foreach ($reservations as $reservation) {

            /** @var array $metadata */
            $metadata = $serializer->unserialize($reservation[ReservationInterface::METADATA]);

            if (!is_array($metadata) || empty($metadata)) {
                continue;
            }

            $objectType = $metadata['object_type'];
            $event = $metadata['event_type'];
            $value = $metadata['object_id'];
            $field = 'entity_id';

            if (!$value) {
                $value = $metadata['object_increment_id'];
                $field = 'increment_id';
            }

            if ($objectType !== 'order') {
                continue;
            }

            if ($event !== 'order_placed') {
                continue;
            }

            $orderStatus = $connection->fetchOne(
                $connection->select()->from('sales_order', ['status'])->where($field . ' = ? ', $value)
            );

            if (!in_array($orderStatus, $orderStatuses, true)) {
                continue;
            }

            $reservationQuantity -= (int)$reservation[ReservationInterface::QUANTITY];
        }

        return $reservationQuantity;
    }

    /**
     * @return SaveMultiple
     */
    public function getSourceItemsSave()
    {
        if ($this->sourceItemSave === null) {
            $this->sourceItemSave = ObjectManager::getInstance()->create(SaveMultiple::class);
        }

        return $this->sourceItemSave;
    }

    /**
     * @return bool
     * @throws Exception
     */
    protected function process22()
    {
        $sku = $this->getData('sku');
        $productId = $this->productFactory->create()->getIdBySku($sku);

        if (!$productId) {
            return $this->error(false, "Product not found! product_code: '{$sku}'");
        }

        $stock = $this->stockRegistry->getStockItem($productId);

        $this->eventManager->dispatch('flow_stock_process22_before', ['flow_model_from_stock' => $this, 'stock_item' => $stock]);

        try {
            $raw_qty = (int)$this->getData('fisico_disponibile');
            $qty = $this->_stock_qty->getRealQty($productId, $raw_qty);

            // Se non viene ritornata una quantità numerica ma una stringa vuol dire che c'è stato un errore
            if (!is_numeric($qty)) {
                // Salvo il messaggio relativo all'errore
                $this->addEmailNotifyError($qty);
                // Dopo aver salvato il messaggio aggiorno la quantità da impostare a 0
                $qty = 0;
            }

            $order_list = $this->_stock_qty->getReservedQtyOrderList($productId);

            $stock->setQty($qty);
            $this->setData('final_quantity', $qty);
            $this->save();
            /* Leave the stock inventory status untouched for products having backorder flag set */

            if ($stock->getBackorders() === CatalogInventoryStock::BACKORDERS_NO) {
                if ($qty > $stock->getMinQty()) {
                    $stock->setIsInStock(true);
                } else {
                    $stock->setIsInStock(false);
                }
            }

            $this->process22UpdateStock($stock);

            if ($raw_qty !== $qty) {
                $preserved_qty = $raw_qty - $qty;
                $this->error(false, "Preserved Quantity '{$preserved_qty}' For Product ID '{$productId}' SKU '{$sku}' (From orders: '{$order_list}')");

                $this->eventManager->dispatch(
                    'flow_stock_process22_preserved_quantity_after',
                    [
                        'product_id'            => $productId,
                        'order_list'            => $order_list,
                        'csv_data'              => $this->getData(),
                        'flow_model_from_stock' => $this,
                        'preserved_qty'         => $preserved_qty,
                    ]
                );
            }

            $this->_logger->info("Process STOCK - qty {$qty} ID: {$productId} SKU: {$sku}");
        } catch (Exception $e) {
            $this->_logger->info('Errore nel salvataggio dello Stock. ' . $e->getMessage());
        }

        $this->eventManager->dispatch('flow_stock_process22_after',
            [
                'flow_model_from_stock' => $this,
                'stock_item'            => $stock
            ]
        );

        return true;
    }

    public function process22UpdateStock($stockItem)
    {
        $connection = $this->getConnection();
        $stockData = $stockItem->getData();
        if (isset($stockData['type_id'])) {
            unset($stockData['type_id']);
        }
        if (isset($stockData['stock_status_changed_automatically_flag'])) {
            unset($stockData['stock_status_changed_automatically_flag']);
        }
        $connection->insertOnDuplicate('cataloginventory_stock_item', $stockData);
    }

    /**
     * @param $file
     * @return mixed|void
     * @throws MailException
     * @throws NoSuchEntityException
     */
    public function sendErrorNotifications($file)
    {

        // Se sono stati rilevati errori e salvati i relativi messaggi si procede con la composizione della mail
        if (!empty($this->getEmailNotifyError())) {

            // Recupero gli indirizzi email dei destinatari
            $emailAddressString = $this->scopeConfig->getValue(self::XML_PATH_EMAIL_ADDRESS_NOTIFY_ERROR);

            if ($emailAddressString && $emailAddressString !== '') {
                $emailAddress = explode(',', $emailAddressString);
                $emailAddress = array_map('trim', $emailAddress);

                $storeName = $this->scopeConfig->getValue(self::XML_PATH_STORE_NAME);
                // Compongo il testo del messaggio con i vari errori
                $errorString = '<p>' . implode('</p><p>', $this->getEmailNotifyError()) . '</p>';

                // Array con i dati che serviranno a comporre la mail
                $data = [
                    'mail_subject' => 'Reporting breakdowns in stock loading for ' . $storeName,
                    'file_name'    => $file->getName(),
                    'errors'       => $errorString
                ];

                // Invio email di notifica
                $this->emailNotification->notify($emailAddress, $data);
            }
        }
    }

    /**
     * @param $query
     * @return bool
     */
    protected function postprocessFileRows($query)
    {
        // TODO : check also against module MSI. We need to check if MSI in sot uninstalled or disabled
        if (version_compare($this->_productMetadata->getVersion(), '2.3.0', '>=')) {
            return $this->postprocessFileRows23();
        }

        return $this->postprocessFileRows22();
    }

    /**
     * Workaround until stock reindex will be fixed
     */
    protected function postprocessFileRows23()
    {
        if (class_exists(UpdateIndexSalabilityStatus::class)) {
            $this->updateIndexSalabilityStatus($this->getMetaFile());
        }

        $channelConfig = $this->getChannel()->getChannelConfig();

        // Remove products != simple form inventory_source_item

        if ($channelConfig->getFixInventorySourceItems()) {
            $connection = $this->getConnection();
            $query = $connection->deleteFromSelect(
                $connection->select()
                    ->from(['isi' => $connection->getTableName('inventory_source_item')], ['sku'])
                    ->joinLeft(['cpe' => $connection->getTableName('catalog_product_entity')], 'isi.sku = cpe.sku')
                    ->where('cpe.type_id NOT IN (\'simple\', \'giftcard\',\'virtual\')'),
                'isi'
            );

            $connection->query($query);
        }

        // Switch indexer strategy using channel config

        $indexerStrategy = $channelConfig->getIndexerStrategy();

        switch ($indexerStrategy) {
            case Config::INDEXER_STRATEGY_NATIVE:
                return true;
            case Config::INDEXER_STRATEGY_FORCE:
                $this->sendToChangelog();
                return true;
            case Config::INDEXER_STRATEGY_FULL:
                /** @var UpdateMview $command */
                $indexer = $this->objectManager->get(UpdateMview::class);
                $indexer->execute();
                $this->resetReindex(['inventory', 'cataloginventory_stock', 'beehive_stock']);
        }

        return true;
    }

    /**
     * @param $metaFileId
     */
    protected function updateIndexSalabilityStatus($metaFileId)
    {
        $connection = $this->getConnection();
        $select = $connection->select()
            ->from('flow_from_stock', ['sku', 'sku'])
            ->where("meta_file='$metaFileId' AND fisico_disponibile <> final_quantity");

        $result_skus = array_keys($connection->fetchPairs($select));

        ### fix needed for numeric skus because Magento\InventoryIndexer\Model\IsProductSalable
        ### wants to be passed a string value ###
        $skus = array_map('strval', $result_skus);

        $stockRepository = $this->createStockRepositoryInterface();
        $updateSaleability = $this->objectManager->get(UpdateIndexSalabilityStatus::class);
        $stockList = $stockRepository->getList();

        foreach ($stockList->getItems() as $stock) {
            $data = $this->objectManager->get(ReservationDataFactory::class)->create(
                [
                    'skus'  => $skus,
                    'stock' => $stock->getStockId()
                ]);
            $updateSaleability->execute($data);
        }
    }

    public function createStockRepositoryInterface()
    {
        return ObjectManager::getInstance()->create(StockRepositoryInterface::class);
    }

    /**
     * @return void
     */
    protected function sendToChangelog()
    {
        $connection = $this->getConnection();

        $catalogInventoryStockClQuery = $this->getClTablesQuery('cataloginventory_stock_cl', 'catalog_product_entity', 'entity_id');

        $connection->query($catalogInventoryStockClQuery);

        $flowStockClQuery = $this->getClTablesQuery('filoblu_flow_stock_cl', 'catalog_product_entity', 'entity_id');

        $connection->query($flowStockClQuery);

        if ($connection->isTableExists($connection->getTableName('inventory_cl'))) {

            $inventoryClQuery = $this->getClTablesQuery('inventory_cl', 'inventory_source_item', 'source_item_id');

            $connection->query($inventoryClQuery);
        }
    }

    /**
     * @param $indexers
     * @return void
     * @throws Exception
     */
    protected function resetReindex($indexers)
    {
        foreach ($indexers as $name) {
            try {
                $indexer = $this->indexerRegistry->get($name);
                $state = $indexer->getState();
                $state->setStatus(StateInterface::STATUS_INVALID);
                $state->save();
                $indexer->setState($state);
            } catch (InvalidArgumentException $e) {
                continue;
            }
        }
    }

    /**
     * @return bool
     */
    protected function postprocessFileRows22()
    {
        try {
            $this->updateConfigurablesStockStatus();
            $this->resetReindex(['cataloginventory_stock']);
        } catch (Exception $e) {
            $this->_logger->info('Errore nel salvataggio del Post Stock Item Fix Tuning. ' . $e->getMessage());
            return false;
        }

        return true;
    }

    /**
     * 1] $this->stockConfiguration->getBackorders($this->getStoreId()) = 0
     *      backorders = 0 && use_config_backorders = 1
     *
     * 2]  backorders = 0 && use_config_backorders = 0
     */
    protected function updateConfigurablesStockStatus()
    {
        $connection = $this->getConnection();
        $entityField = $this->getEntityField();

        $sql = $connection->select()
            ->from(['csi' => 'cataloginventory_stock_item'], [])
            ->joinLeft(['cpe' => 'catalog_product_entity'], 'cpe.entity_id = csi.product_id', [])
            ->joinLeft(['cpsl' => 'catalog_product_super_link'], 'cpe.entity_id = cpsl.product_id', ['cpsl.parent_id'])
            ->where('cpsl.parent_id is not null')
            ->group('cpsl.parent_id');

        $outOfStockSql = clone $sql;
        $inStockSql = clone $sql;

        $outOfStockSql->having('SUM(csi.qty) <= 0');
        $inStockSql->having('SUM(csi.qty) > 0');

        /* 1] Update Out Of Stock configurables */
        $products = $connection->select()
            ->from('catalog_product_entity', ['entity_id'])
            ->where('type_id = ?', ['configurable'])
            ->where("{$entityField} IN (?)", [$outOfStockSql])
            ->query()->fetchAll(Zend_Db::FETCH_NUM);

        $configurableIds = [];

        foreach ($products as $row) {
            $configurableIds[] = $row[0];
        }

        $ids = implode(',', $configurableIds);
        $connection->query("UPDATE cataloginventory_stock_item SET is_in_stock = 0 WHERE `product_id` IN ({$ids})");

        /* 2] Update In Stock configurables */
        $products = $connection->select()
            ->from('catalog_product_entity', ['entity_id'])
            ->where('type_id = ?', ['configurable'])
            ->where("{$entityField} IN (?)", [$inStockSql])
            ->query()->fetchAll(Zend_Db::FETCH_NUM);

        $configurableIds = [];

        foreach ($products as $row) {
            $configurableIds[] = $row[0];
        }

        $ids = implode(',', $configurableIds);
        $connection->query("UPDATE cataloginventory_stock_item SET is_in_stock = 1 WHERE `product_id` IN ({$ids})");
    }

    /**
     * @return string
     */
    protected function getEntityField()
    {
        $edition = $this->_productMetadata->getEdition();
        return ($edition === 'Enterprise' || $edition === 'B2B') ? 'row_id' : 'entity_id';
    }

    /**
     * @param $query
     * @param $file
     * @return mixed
     * @throws CouldNotSaveException
     * @throws InputException
     * @throws LocalizedException
     * @throws ValidationException
     * @throws Zend_Db_Statement_Exception
     */
    protected function preprocessFileRows($query, $file)
    {
        $this->removeUnwantedProductTypes($file);
        $this->discardNonExistingStock($file);
        $this->discardPreorder($file);
        $this->discardNoManageStock($file);
        $this->setNotSpecifiedSkuQtyToZero($file);

        if (version_compare($this->_productMetadata->getVersion(), '2.3.0', '>=')) {
            // Disabled for work in progress
            $this->preprocessNotReservedSku($query, $file);
            return $this->preprocessFileRows23($query, $file);
        }

        return $this->preprocessFileRows22($query, $file);
    }

    /**
     * @param $file
     */
    public function discardNonExistingStock($file)
    {
        $connection = $this->getConnection();
        $ffs = $connection->getTableName('flow_from_stock');
        $cpe = $connection->getTableName('catalog_product_entity');
        $query = "UPDATE {$ffs} SET meta_processed = 2 WHERE {$ffs}.sku NOT IN (SELECT sku FROM {$cpe}) AND {$ffs}.meta_file = '{$file->getId()}'";
        $connection->query($query);
    }

    /**
     * @param $file
     * @throws LocalizedException
     * @throws Exception
     */
    private function discardPreorder($file)
    {
        /** @var \FiloBlu\Flow\Model\Channel\Config $channelConfig */
        $channelConfig = $this->getChannel()->getChannelConfig();

        if (!$channelConfig->getPreorderDiscard()) {
            return;
        }

        if (!$channelConfig->getPreorderAttributeCode()) {
            return;
        }

        $value = $channelConfig->getPreorderAttributeValue();

        $attribute = $this->helperFlow->getProductAttributeByCode($channelConfig->getPreorderAttributeCode());

        if (!$attribute) {
            return;
        }

        $linkField = $this->helperFlow->getProductLinkField();
        $attributeId = $attribute->getAttributeId();

        $select = $this->getConnection()
            ->select()
            ->joinInner(['cpe' => 'catalog_product_entity'], 'ffs.sku = cpe.sku', [
                'meta_processed' => new Zend_Db_Expr('2'), 'sku' => 'cpe.sku'
            ])
            ->joinInner(['cpa' => "catalog_product_entity_{$attribute->getBackendType()}"],
                "cpa.{$linkField} = cpe.{$linkField} AND cpa.attribute_id = {$attributeId} AND cpa.value = {$value}",
                []
            );

        $query = $this->getConnection()->updateFromSelect($select, ['ffs' => 'flow_from_stock']);

        $this->getConnection()->query($query);
    }

    /**
     * @param $file
     */
    public function discardNoManageStock($file)
    {
        $channelConfig = $this->getChannel()->getChannelConfig();
        if (!$channelConfig->getDiscardNoManagedStock()) {
            return;
        }

        $connection = $this->getConnection();
        $configManaged = $this->scopeConfig->getValue(self::STOCK_CONFIG_XML_PATH);
        $ffs = $connection->getTableName('flow_from_stock');
        $cpe = $connection->getTableName('catalog_product_entity');
        $csi = $connection->getTableName('cataloginventory_stock_item');
        $query = "SELECT
                    ffs.meta_id,
                    cpe.sku,
                    csi.manage_stock,
                    csi.use_config_manage_stock
                    FROM
                    {$cpe} cpe
                    INNER JOIN {$csi} csi ON
                    cpe.entity_id = csi.product_id
                    INNER JOIN {$ffs} ffs ON
                    ffs.sku = cpe.sku
                    WHERE ffs.meta_processed = 0 AND ffs.meta_file = '{$file->getId()}'";

        // default config manage stock a no
        if ($configManaged == '0') {
            $query .= ' AND ((csi.use_config_manage_stock = 0 AND csi.manage_stock = 0) OR csi.use_config_manage_stock = 1)';
        } else {
            $query .= ' AND (csi.use_config_manage_stock = 0 AND csi.manage_stock = 0)';
        }

        $rows = $connection->fetchAll($query);

        $ids = [];
        foreach ($rows as $row) {
            $ids[] = $row['meta_id'];
        }
        if (count($ids)) {
            $idToUpdate = implode(',', $ids);
            $updateQuery = "UPDATE {$ffs} SET meta_processed = 3 WHERE {$ffs}.meta_id IN (" . $idToUpdate . ')';
            $connection->query($updateQuery);
        }
    }

    /**
     * @param $query
     * @param $file
     * @return array|void
     * @throws CouldNotSaveException
     * @throws InputException
     * @throws ValidationException
     * @throws Zend_Db_Statement_Exception
     * @description This function will process (faster) all the SKUs that are not inside any reserved order
     */
    public function preprocessNotReservedSku($query, $file)
    {
        $channelConfig = $this->getChannel()->getChannelConfig();
        $processReservedStock = $channelConfig->getProcessReservedStock();

        if (!$processReservedStock) {
            return;
        }

        $orderStatuses = $channelConfig->getProcessReservedStockStatuses();
        $orderStatusesString = $this->implodeQuoting(',', $orderStatuses);

        $connection = $this->getConnection();

        $sql = "SELECT SOI.sku
                FROM sales_order SO
                INNER JOIN sales_order_item SOI ON SOI.order_id = SO.entity_id
                WHERE SO.status IN ({$orderStatusesString})
                GROUP BY SOI.sku";
        $skus_with_reserved_quantities = $connection->query($sql)->fetchAll(); //multidimensional array of one element
        $skus_with_reserved_quantities = array_map('current', $skus_with_reserved_quantities); //one dimension array

        // If I have some to skip
        $sku_filter = '';
        if (count($skus_with_reserved_quantities) > 0) {
            $skus_with_reserved_quantities_string = $this->implodeQuoting(',', $skus_with_reserved_quantities);
            $sku_filter = " AND sku NOT IN ({$skus_with_reserved_quantities_string})";
        }

        $selectRows = $connection->select()->from('flow_from_stock')->where(new Zend_Db_Expr("meta_processed = 0 AND meta_file = {$file->getId()} {$sku_filter}"));
        $flowFromStockRows = $connection->fetchAll($selectRows, [], Zend_Db::FETCH_ASSOC);

        $sourceItemsToSave = [];
        $counter = 0;
        $sourceItemObject = $this->createSourceItemInterface();
        $processedIds = [];
        foreach ($flowFromStockRows as $row) {
            $sourceItem = clone $sourceItemObject;
            $sourceItem->setSourceCode($row['magazzino']);
            $sourceItem->setSku($row['sku']);
            $sourceItem->setQuantity($row['fisico_disponibile']);
            $status = SourceItemInterface::STATUS_OUT_OF_STOCK;
            if ($row['fisico_disponibile'] > 0) {
                $status = SourceItemInterface::STATUS_IN_STOCK;
            }
            $sourceItem->setStatus($status);
            $sourceItemsToSave[] = $sourceItem;
            $processedIds[] = $row['meta_id'];

            if (++$counter % 1000 == 0) {
                $this->saveSourceItems($sourceItemsToSave, $processedIds);
                $processedIds = [];
                $sourceItemsToSave = [];
            }
        }

        if (empty($sourceItemsToSave)) {
            return;
        }

        $this->saveSourceItems($sourceItemsToSave, $processedIds);
    }

    /**
     * @param $file
     * @return void
     */
    public function setNotSpecifiedSkuQtyToZero($file)
    {
        $channelConfig = $this->getChannel()->getChannelConfig();
        if (!$channelConfig->getNotSpecifiedSkuQtyToZero()) {
            return;
        }

        $connection = $this->getConnection();
        $select = $connection
            ->select()
            ->joinLeft(
                [
                    'ffs' => 'flow_from_stock'
                ],
                "isi.sku = ffs.sku
                    AND  ffs.meta_file = {$file->getId()}
                    AND ffs.meta_processed != 0
                    AND isi.source_code = ffs.magazzino",
                [
                    'quantity' => new \Zend_Db_Expr('0')
                ])
            ->where("ffs.sku IS NULL");

        $query = $connection->updateFromSelect($select, ['isi' => 'inventory_source_item']);
        $connection->query($query);

    }

    /**
     * @param $glue
     * @param $array
     * @return string
     */
    public function implodeQuoting($glue, $array)
    {
        return implode(
            $glue,
            array_map(
                static function ($i) {
                    return "'$i'";
                },
                $array
            )
        );
    }

    /**
     * @param array $sourceItemsToSave
     * @param array $flowRowIds
     * @throws CouldNotSaveException
     * @throws InputException
     * @throws ValidationException
     */
    public function saveSourceItems(array $sourceItemsToSave, array $flowRowIds)
    {
        $this->getSourceItemsSave()->execute($sourceItemsToSave);
        $ids = implode(',', $flowRowIds);
        $sql = "UPDATE flow_from_stock SET meta_processed = 1, final_quantity = fisico_disponibile, meta_process_time = NOW() WHERE meta_id IN ({$ids});";
        $this->getConnection()->query($sql);
    }

    /**
     * @param $query
     * @param $file
     * @return mixed
     */
    protected function preprocessFileRows23($query, $file)
    {
        return $query;
    }

    /**
     * @param $query
     * @param $file
     * @return mixed
     */
    protected function preprocessFileRows22($query, $file)
    {
        return $query;
    }

    /**
     * @param $intoTable
     * @param $fromTable
     * @param $fromField
     * @return string
     */
    protected function getClTablesQuery($intoTable, $fromTable, $fromField)
    {
        return "INSERT INTO {$intoTable} (entity_id)
                SELECT {$fromField} FROM {$fromTable}
                WHERE sku IN (SELECT sku FROM flow_from_stock WHERE meta_file = {$this->getMetaFile()})";
    }
}
