<?php

namespace FiloBlu\Flow\Helper;

use Exception;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Serialize\Serializer\Json;
use Magento\Framework\Unserialize\Unserialize;
use Magento\Indexer\Model\Indexer;
use Magento\Indexer\Model\Indexer\CollectionFactory;
use Magento\Indexer\Model\Indexer\State;
use Zend_Db;

/**
 * Class System
 * @package FiloBlu\Flow\Helper
 */
class System
{
    /**
     * @var string
     */
    const FLOW_DISABLE_TRIGGERS_PATH = 'filoblu_flow/flow_optimization/flow_optimization_disable_triggers';
    /**
     * @var string
     */
    const FLOW_INDEXER_EAVS_PATH = 'filoblu_flow/flow_optimization/flow_optimization_indexer_status_eavs';
    /**
     * @var string
     */
    const FLOW_INDEXER_ITE_PATH = 'filoblu_flow/flow_optimization/flow_optimization_indexer_status_product';
    /**
     * @var string
     */
    const FLOW_INDEXER_STOCK_PATH = 'filoblu_flow/flow_optimization/flow_optimization_indexer_status_stock';
    /**
     * @var string
     */
    const FLOW_INDEXER_MULTISTOCK_PATH = 'filoblu_flow/flow_optimization/flow_optimization_indexer_status_multistock';
    /**
     * @var string
     */
    const FLOW_INDEXER_PRICE_PATH = 'filoblu_flow/flow_optimization/flow_optimization_indexer_status_price';
    /**
     * @var string
     */
    const FLOW_INDEXER_BROTHERHOOD_PATH = 'filoblu_flow/flow_optimization/flow_optimization_indexer_status_brotherhood';
    /**
     * @var string
     */
    const FLOW_INDEXER_IMAGEFLOW_PATH = 'filoblu_flow/flow_optimization/flow_optimization_indexer_status_imagesflow';

    /**
     * @var array
     */
    protected $_indexerSettingsMap = [
        'From\Product' => System::FLOW_INDEXER_ITE_PATH,
        'From\Eavs' => System::FLOW_INDEXER_EAVS_PATH,
        'From\Stock' => System::FLOW_INDEXER_STOCK_PATH,
        'From\Multistock' => System::FLOW_INDEXER_MULTISTOCK_PATH,
        'From\Price' => System::FLOW_INDEXER_PRICE_PATH,
        'From\Brotherhood' => System::FLOW_INDEXER_BROTHERHOOD_PATH,
        'Imagesflow' => System::FLOW_INDEXER_IMAGEFLOW_PATH
    ];

    /**
     * @var
     */
    protected $_indexerCollection;

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

    /**
     * @var
     */
    protected $_resourceConnection;

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

    /**
     * System constructor.
     * @param ProductMetadataInterface $productMetadata
     * @param CollectionFactory $collectionFactory
     * @param ResourceConnection $resourceConnection
     * @param ScopeConfigInterface $scopeConfig
     */
    public function __construct(
        ProductMetadataInterface $productMetadata,
        CollectionFactory $collectionFactory,
        ResourceConnection $resourceConnection,
        ScopeConfigInterface $scopeConfig
    )
    {
        $this->_productMetadata = $productMetadata;
        $this->_resourceConnection = $resourceConnection;
        $this->_scopeConfig = $scopeConfig;

        if ($collectionFactory !== null) {
            $this->_indexerCollection = $collectionFactory->create();
        }
    }

    /**
     * @return bool
     */
    public function canDropTriggers()
    {
        return boolval($this->_scopeConfig->getValue(self::FLOW_DISABLE_TRIGGERS_PATH));
    }

    /**
     *
     */
    public function dropTriggers()
    {
        $connection = $this->_resourceConnection->getConnection();

        $triggers = $connection->fetchAll('SHOW TRIGGERS', [], Zend_Db::FETCH_OBJ);

        foreach ($triggers as $trigger) {
            $connection->query("DROP TRIGGER {$trigger->Trigger}");
        }
    }

    /**
     *
     */
    public function disableIndexers()
    {
        $indexers = $this->_indexerCollection;

        if ($indexers === null) {
            return;
        }

        foreach ($indexers->getItems() as $indexer) {
            try {
                if (!$indexer->isScheduled()) {
                    continue;
                }

                $indexer->setScheduled(false);

            } catch (Exception $e) {

            }
        }

    }

    /**
     * @param string|null $type One of 'From\Product', 'From\Eavs', 'From\Stock', 'From\Multistock', 'From\Price','From\Brotherhood', 'Imagesflow'
     * @throws Exception
     */
    public function enableIndexers($type = null)
    {
        $indexerToResume = [];
        $indexerToProcess = [];

        if (empty($type)) {
            $indexerToResume = $this->_indexerSettingsMap;
        } else {
            if (!array_key_exists($type, $this->_indexerSettingsMap)) {
                return;
            }

            $indexerToResume[$type] = $this->_indexerSettingsMap[$type];
        }

        $indexersCollection = $this->_indexerCollection;

        if ($indexersCollection === null) {
            return;
        }

        foreach ($indexerToResume as $t => $toResume) {
            $options = $this->getIndexerResumeOptions($t);

            if ($options === false) {
                continue;
            }

            foreach ($options as $k => $v) {
                $indexerToProcess[$k] = $v;
            }
        }

        $indexers = $indexersCollection->getItems();

        foreach ($indexers as $indexer) {
            $id = $indexer->getId();
            if (!array_key_exists($id, $indexerToProcess)) {
                continue;
            }

            $options = $indexerToProcess[$id];

            // TODO: use better logging logic
            echo "Resuming {$id} -> {$options['mode']}:{$options['status']} ... ";
            if ($this->processIndexer($indexer, $options)) {
                echo "ok\n";
            } else {
                echo "fail\n";
            }
        }
    }

    /**
     * @param $type
     * @return mixed|bool
     * @throws Exception
     */
    protected function getIndexerResumeOptions($type)
    {
        $options = [];

        if (!array_key_exists($type, $this->_indexerSettingsMap)) {
            throw new Exception("Unable to find indexer configuration for type '{$type}'");
        }

        $value = $this->_scopeConfig->getValue($this->_indexerSettingsMap[$type]);

        if (empty($value)) {
            return false;
        }

        $infos = $this->getSerializedValue($value);

        foreach ($infos as $info) {
            if (empty($info['indexer'])) {
                continue;
            }

            $options[$info['indexer']] = $info;
        }

        if (empty($options)) {
            return false;
        }

        return $options;
    }

    /**
     * @param string $value
     * @return mixed
     */
    public function getSerializedValue($value)
    {
        if ($this->isSerialized($value)) {
            $unserializer = ObjectManager::getInstance()->get(Unserialize::class);
        } else {
            $unserializer = ObjectManager::getInstance()->get(Json::class);
        }

        return $unserializer->unserialize($value);
    }

    /**
     * Check if value is a serialized string
     *
     * @param string $value
     * @return boolean
     */
    public function isSerialized($value)
    {
        return (boolean)preg_match('/^((s|i|d|b|a|O|C):|N;)/', $value);
    }

    /**
     * @param Indexer $indexer
     * @param $options
     * @return bool
     */
    protected function processIndexer($indexer, $options)
    {
        try {
            switch ($options['mode']) {

                case 'schedule':
                    $indexer->setScheduled(true);
                    break;
                case 'realtime':
                    $indexer->setScheduled(false);
                    break;
                default:
                    break;
            }

            switch ($options['status']) {
                case State::STATUS_VALID:
                case State::STATUS_INVALID:
                case State::STATUS_WORKING:
                    $state = $indexer->getState();
                    $state->setStatus($options['status'])->setUpdated(time())->save();
                    $indexer->setState($state);
                    break;
                case 'ignore':
                default:
                    break;
            }

            return true;
        } catch (Exception $e) {
            // TODO : handle exceptions
            return false;
        }
    }

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

}
