<?php

namespace Leadlion\StockUpdates\Model;

use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\Framework\App\Action\Context;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\InventoryApi\Api\Data\SourceItemInterfaceFactory;
use Magento\InventoryApi\Api\SourceItemsSaveInterface;
use Magento\Store\Model\ScopeInterface;
use SmartBill\Integration\Helper\Data;
use Magento\InventoryApi\Api\StockRepositoryInterface;
use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Catalog\Model\Product\Action;
use Magento\Inventory\Model\SourceItem\Command\Handler\SourceItemsSaveHandler;
use Magento\InventoryCatalog\Model\ResourceModel\SetDataToLegacyStockItem;
use Magento\Framework\App\ResourceConnection;
use Magento\CatalogInventory\Api\Data\StockItemInterface;
use Magento\InventoryCatalog\Model\ResourceModel\SetDataToLegacyStockStatus;
use Monolog\Logger as MonoLogger;
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Framework\Event\ManagerInterface as EventManager;

class UpdateStocks
{
    const ALWAYS_STOCK_BEHAVIOR = 0;
    const MANAGE_STOCK_BEHAVIOR = 1;
    const IGNORE_STOCK_BEHAVIOR = 2;

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

    /**
     * @var ScopeConfigInterface|Config
     */
    protected $config;
    /**
     * @var LoggerInterface
     */
    protected $logger;
    /**
     * @var CollectionFactory
     */
    protected $collectionFactory;
    /**
     * @var SmartBillCloudRestClient
     */
    protected $restClient;
    /**
     * @var Data
     */
    private $helper;
    /**
     * @var \Magento\InventoryApi\Api\SourceItemsSaveInterface
     */
    private $sourceItemsSaveInterface;
    /**
     * @var SourceItemInterfaceFactory
     */
    private $sourceItemFactory;
    /**
     * @var ProductRepositoryInterface
     */
    private $productRepository;
    /**
     * @var Context
     */
    private $context;

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

    /**
     * @var Json
     */
    protected $serialize;

    /**
     * @var Action
     */
    private $productAction;
    
    /**
     * @var SourceItemsSaveHandler
     */
    private $sourceItemsSaveHandler;

    /**
     * @var SetDataToLegacyStockItem
     */
    private $setDataToLegacyStockItem;
    
    /**
     * @var SetDataToLegacyStockStatus
     */
    private $setDataToLegacyStockStatus;

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

    /**
     * @var \Leadlion\StockUpdates\Logger\VarnishPurge
     */
    private $varnishPurgeLogger;

    /**
     * ProcessProducts constructor.
     * @param State $state
     * @param Config $config
     * @param Handler $handler
     * @param CollectionFactory $collectionFactory
     */
    public function __construct(
        ScopeConfigInterface       $config,
        CollectionFactory          $collectionFactory,
        Data                       $helper,
        SourceItemsSaveInterface   $sourceItemsSaveInterface,
        SourceItemInterfaceFactory $sourceItemFactory,
        ProductRepositoryInterface $productRepository,
        Context                    $context,
        StockRegistryInterface     $stockRegistry,
        Json $serialize,
        Action $productAction,
        SourceItemsSaveHandler $sourceItemsSaveHandler,
        SetDataToLegacyStockItem $setDataToLegacyStockItem,
        ResourceConnection $resourceConnection,
        SetDataToLegacyStockStatus $setDataToLegacyStockStatus,
        EventManager $eventManager,
        \Leadlion\StockUpdates\Logger\VarnishPurge $varnishPurgeLogger
    ) {
        $this->helper = $helper;
        $this->config = $config;
        $this->collectionFactory = $collectionFactory;
        $this->sourceItemsSaveInterface = $sourceItemsSaveInterface;
        $this->sourceItemFactory = $sourceItemFactory;
        $this->productRepository = $productRepository;
        $this->context = $context;
        $this->stockRegistry = $stockRegistry;
        $this->serialize = $serialize;
        $this->productAction = $productAction;
        $this->sourceItemsSaveHandler = $sourceItemsSaveHandler;
        $this->setDataToLegacyStockItem = $setDataToLegacyStockItem;
        $this->resourceConnection = $resourceConnection;
        $this->setDataToLegacyStockStatus = $setDataToLegacyStockStatus;
        $this->eventManager = $eventManager;
        $this->varnishPurgeLogger = $varnishPurgeLogger;
    }

    /**
     * Run cron
     */
    public function execute()
    {
        $stocks = $this->getStocks();
        $request = $this->context->getRequest();

        $monolog = new MonoLogger('smartbill_stock');
        $monolog->pushHandler(new StreamHandler(BP . '/var/log/smartbill_stock.log', Level::Debug));
        $logger = $monolog;

        $sourceItems = [];
        $productsToInvalidate = [];
        $productsToInvalidateSkus = [];
        $skuFzs[] = array_keys($stocks);
        //return all products by sku_fz
        $collection = $this->collectionFactory->create()
            ->addAttributeToSelect('sku_fz')
            ->addAttributeToSelect('sku')
            ->addAttributeToSelect('manufacturer')
            ->addAttributeToFilter('sku_fz', $skuFzs)
            ->addAttributeToFilter('status', Status::STATUS_ENABLED);


        foreach ($collection as $product) {
            if (isset($stocks[$product->getSkuFz()])) {
                $sku = $product->getSku();
                if (in_array($sku, ['RAS21', 'RAS21-4'])) {
                    continue;
                }
                $stockInfo = $stocks[$product->getSkuFz()];
                $logger->info('smarbill $item: '.$sku);
                $qty = $stockInfo['qty'];
                $measuringUnit = $stockInfo['measuringUnit'];

                $logger->info('             $product sku: ' .$product->getData('sku').' sku_fz: '. $product->getData('sku_fz'). ' qty: '. $qty);
                try{
                    $this->saveManufacturerDeliveryTerm($product, $qty);
                } catch (\Exception $e) {
                    $logger->info('            cant save ManufacturerDeliveryTerm $product sku: ' .$product->getData('sku').$e->getMessage());
                }
                switch ($this->getStockBehavior($product)) {
                    case self::MANAGE_STOCK_BEHAVIOR :
                        if ($request->getParam('um')) {
                            try {
                                $this->productAction->updateAttributes(
                                    [$product->getId()],
                                    ['measuringUnitName' => $measuringUnit],
                                    0
                                );
                            } catch (\Exception $e) {
                                $logger->info('cant save the value for measuring unit'.$product->getData('sku').$e->getMessage());
                            }
                            break;
                        }
                        $isInStock = 1;

                        $oldStock = $this->getCurrentStockData($product->getData('sku'));

                        $sourceItem = $this->sourceItemFactory->create();
                        $sourceItem->setData('source_code', 'default');
                        $sourceItem->setData('sku', $product->getData('sku'));
                        $sourceItem->setData('quantity', $qty);
                        $sourceItem->setData('status', $isInStock);
                        $sourceItems[] = $sourceItem;

                        try {
                            $this->setDataToLegacyStockItem->execute(
                                (string)$product->getData('sku'),
                                (float)$qty,
                                $isInStock
                            );
                            $this->setDataToLegacyStockStatus->execute(
                                (string)$product->getData('sku'),
                                (float)$qty,
                                $isInStock
                            );
                        } catch (\Exception $e) {
                            $logger->info('             (stockRegistry) cant save stock $product sku: ' .$product->getData('sku').$e->getMessage());
                            break;
                        }

                        if ($oldStock && $this->stockStatusChanged($oldStock, $isInStock, (float)$qty)) {
                            $productsToInvalidate[] = $product->getId();
                            $productsToInvalidateSkus[] = $product->getData('sku');
                            $logger->info('             cache invalidation queued for sku: ' . $product->getData('sku'));
                        }

                        break;
                    case self::ALWAYS_STOCK_BEHAVIOR :
                        if ($request->getParam('um')) {
                            break;
                        }
                        $sourceItem = $this->sourceItemFactory->create();
                        $sourceItem->setData('source_code', 'default');
                        $sourceItem->setData('sku', $product->getData('sku'));
                        $sourceItem->setData('quantity', 9999);
                        $sourceItem->setData('status', 1);
                        $sourceItems[] = $sourceItem;

                        $this->updateStockItemUseConfManageStock($product->getId(), 9999, true);

                        break;
                    case self::IGNORE_STOCK_BEHAVIOR :
                        //do nothing
                        break;
                }
            }
        }
        if (!empty($sourceItems)) {
            $this->sourceItemsSaveHandler->execute($sourceItems);
        }

        if (!empty($productsToInvalidate)) {
            $logger->info('Varnish PURGE SmartBill: ' . count($productsToInvalidate) . ' SKUs: ' . implode(', ', $productsToInvalidateSkus));
            $this->invalidateVarnishForProducts($productsToInvalidate, $logger);
        }
    }

    private function invalidateVarnishForProducts(array $productIds, $logger): void
    {
        try {
            /** @var \Magento\PageCache\Model\Config $pageCacheConfig */
            $pageCacheConfig = \Magento\Framework\App\ObjectManager::getInstance()
                ->get(\Magento\PageCache\Model\Config::class);

            if (!$pageCacheConfig->isEnabled() ||
                (int)$pageCacheConfig->getType() !== \Magento\PageCache\Model\Config::VARNISH
            ) {
                return;
            }

            $pattern = '((^|,)%s(,|$))';
            $tags = [];
            foreach ($productIds as $productId) {
                $tags[] = sprintf($pattern, 'cat_p_' . (int)$productId);
            }

            /** @var \Magento\CacheInvalidate\Model\PurgeCache $purgeCache */
            $purgeCache = \Magento\Framework\App\ObjectManager::getInstance()
                ->get(\Magento\CacheInvalidate\Model\PurgeCache::class);

            $purgeCache->sendPurgeRequest(array_unique($tags));
            $logger->info('cache invalidation sent for ' . count($productIds) . ' products');
            $this->varnishPurgeLogger->info('[SmartBill] PURGE sent for ' . count($productIds) . ' products');
        } catch (\Exception $e) {
            $logger->info('cant invalidate cache: ' . $e->getMessage());
        }
    }

    protected function getCurrentStockData(string $sku): array
    {
        $connection = $this->resourceConnection->getConnection();
        $select = $connection->select()
            ->from(
                ['si' => $this->resourceConnection->getTableName('cataloginventory_stock_item')],
                ['qty', 'is_in_stock']
            )
            ->joinInner(
                ['cpe' => $this->resourceConnection->getTableName('catalog_product_entity')],
                'cpe.entity_id = si.product_id',
                []
            )
            ->where('cpe.sku = ?', $sku)
            ->where('si.website_id = 0')
            ->limit(1);

        return $connection->fetchRow($select) ?: [];
    }

    protected function stockStatusChanged(array $oldStock, int $newIsInStock, float $newQty): bool
    {
        $oldIsInStock = (int)$oldStock['is_in_stock'];
        $oldQty       = (float)$oldStock['qty'];

        if ($oldIsInStock !== $newIsInStock) {
            return true;
        }

        $oldPositive = $oldQty > 0;
        $newPositive = $newQty > 0;

        return $oldPositive !== $newPositive;
    }

    protected function updateStockItemUseConfManageStock($productId, $quantity, $status)
    {
        $connection = $this->resourceConnection->getConnection();
        $connection->update(
            $this->resourceConnection->getTableName('cataloginventory_stock_item'),
            [
                StockItemInterface::MANAGE_STOCK => 1,
                StockItemInterface::USE_CONFIG_MANAGE_STOCK => 1,
                StockItemInterface::QTY => $quantity,
                StockItemInterface::IS_IN_STOCK => $status,
            ],
            [
                StockItemInterface::PRODUCT_ID . ' = ?' => $productId,
                'website_id = ?' => 0,
            ]
        );
    }
    protected function saveManufacturerDeliveryTerm($product, $qty)
    {
        $delTermConfig = $this->getConfigDelTermPerManufacturer();
        if ($delTermConfig) {
            foreach ($delTermConfig as $key => $info) {
                if ($info['manufacturer'] == $product->getData('manufacturer')) {
                    if ($info['always_in_stock'] == 1) {
                        $this->productAction->updateAttributes(
                            [$product->getId()],
                            ['termen_livrare' => $info['delivery_text']],
                            0
                        );
                    } else {
                        $this->saveDelTermByQty($product, $info, $qty);
                    }
                }
            }
        }
    }

    protected function saveDelTermByQty($product, $info, $qty)
    {
        switch ($info['comparison']) {
            case 'eq':
                if ($qty == $info['qty']) {
                    $this->productAction->updateAttributes(
                        [$product->getId()],
                        ['termen_livrare' => $info['delivery_text']],
                        0
                    );
                }
                break;
            case 'gte':
                if ($qty >= $info['qty']) {
                    $this->productAction->updateAttributes(
                        [$product->getId()],
                        ['termen_livrare' => $info['delivery_text']],
                        0
                    );
                }
                break;
            case 'gt':
                if ($qty > $info['qty']) {
                    $this->productAction->updateAttributes(
                        [$product->getId()],
                        ['termen_livrare' => $info['delivery_text']],
                        0
                    );
                }
                break;
            case 'lte':
                if ($qty <= $info['qty']) {
                    $this->productAction->updateAttributes(
                        [$product->getId()],
                        ['termen_livrare' => $info['delivery_text']],
                        0
                    );
                }
                break;
            case 'lt':
                if ($qty < $info['qty']) {
                    $this->productAction->updateAttributes(
                        [$product->getId()],
                        ['termen_livrare' => $info['delivery_text']],
                        0
                    );
                }
                break;
        }
    }

    protected function getConfigDelTermPerManufacturer()
    {
        $config = $this->config->getValue('stock_updates/manufacturers/del_term_smartbill', ScopeInterface::SCOPE_WEBSITE);
        if ($config) {
            return $this->serialize->unserialize($config);
        }
        return null;
    }

    protected function getStockBehavior(Product $product)
    {
        if ($product->getId() == '26621') {
            return self::MANAGE_STOCK_BEHAVIOR;
        }
        $updateStockManufacturers = $this->getConfigUpdateStockManufacturers();
        $ignoreStockManufacturers = $this->getConfigIgnoreStockManufacturers();
        $alwaysStockManufacturers = $this->getConfigAlwaysStockManufacturers();

        if (in_array($product->getData('manufacturer'), $updateStockManufacturers)) {

            return self::MANAGE_STOCK_BEHAVIOR;
        }

        if (in_array($product->getData('manufacturer'), $ignoreStockManufacturers)) {
            return self::IGNORE_STOCK_BEHAVIOR;
        }

        if (in_array($product->getData('manufacturer'), $alwaysStockManufacturers)) {
            return self::ALWAYS_STOCK_BEHAVIOR;
        }
    }

    protected function getConfigUpdateStockManufacturers()
    {
        $config = $this->config->getValue('stock_updates/manufacturers/get_from_smartbill', ScopeInterface::SCOPE_WEBSITE);

        return explode(',', $config);
    }

    protected function getConfigIgnoreStockManufacturers()
    {
        $config = $this->config->getValue('stock_updates/manufacturers/ignore_from_smartbill', ScopeInterface::SCOPE_WEBSITE);

        return explode(',', $config);
    }

    protected function getConfigAlwaysStockManufacturers()
    {

        $config = $this->config->getValue('stock_updates/manufacturers/allways_in_stock', ScopeInterface::SCOPE_WEBSITE);

        return explode(',', $config);
    }

    protected function getStocks()
    {
        try {
            $connector = $this->helper->getSmartBillConnector();
            $data = [
                "cif" => $this->helper->getCompanyVAT(),
                "date" => date('Y-m-d'),
                "warehouseName" => "",
                "productName" => "",
                "productCode" => ""
            ];
            $stocks = $connector->productsStock($data);
            
            $items = [];
            if (isset($stocks[0]['products'])) {
                foreach ($stocks[0]['products'] as $stock) {
                    if (isset($stock['productCode']) && $stock['productCode']) {
                        $items[$stock['productCode']] = [
                            'qty' => $stock['quantity'],
                            'measuringUnit' => $stock['measuringUnit']
                        ];
                    }
                }
            }

            // Bare transversale stock smartbill
            foreach ($stocks as $key => $info) {
                if (isset($info['warehouse']) &&
                    isset($info['warehouse']['warehouseName']) &&
                    $info['warehouse']['warehouseName'] == 'BARE TRANSVERSALE'
                ) {
                    if (isset($info['products'])) {
                        foreach ($info['products'] as $stock) {
                            if (isset($stock['productCode']) && $stock['productCode'] && $stock['productCode'] != 'HV2400') {
                                $items[$stock['productCode']] = [
                                    'qty' => $stock['quantity'],
                                    'measuringUnit' => $stock['measuringUnit']
                                ];
                            }
                        }
                    }
                }
            }
            return $items;
        } catch (\Exception $e) {
            $monolog = new MonoLogger('smartbill_stock');
            $monolog->pushHandler(new StreamHandler(BP . '/var/log/smartbill_stock.log', Level::Debug));
            $logger = $monolog;
            $logger->info($e->getMessage());
            return [];
        }
    }

}
