<?php

namespace FiloBlu\Flow\Model\Channel\In;

use Exception;
use FiloBlu\Flow\Helper\Data;
use FiloBlu\Flow\Helper\LoggerProvider;
use FiloBlu\Flow\Model\Channel\ConfigFactory;
use FiloBlu\Flow\Model\InboundflowFactory;
use Magento\Catalog\Model\Product\Url;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\ObjectManagerInterface;
use Monolog\Logger;

/**
 * Class Anagrafica
 * @package FiloBlu\Flow\Model\Channel\In
 */
class Anagrafica extends AbstractModel
{
    /**
     * @var Logger
     */
    protected $_logger;

    /**
     * @var array
     */
    protected $_storesByLocale = [];

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

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

    /**
     * @var array
     */
    protected $_specialActionProperties = [];

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

    /**
     * @var array
     */
    protected $_storeByLocale;

    /**
     * @var InboundflowFactory
     */
    protected $inboundFlowFactory;

    /**
     * Anagrafica constructor.
     * @param LoggerProvider $loggerProvider
     * @param ConfigFactory $channelConfigFactory
     * @param ResourceConnection $resourceConnection
     * @param Data $helper
     * @param InboundflowFactory $inboundFlowFactory
     * @param ObjectManagerInterface $objectManager
     */
    public function __construct(
        LoggerProvider $loggerProvider,
        ConfigFactory $channelConfigFactory,
        ResourceConnection $resourceConnection,
        Data $helper,
        InboundflowFactory $inboundFlowFactory,
        ObjectManagerInterface $objectManager
    ) {
        parent::__construct($loggerProvider, $channelConfigFactory, $objectManager);
        $this->resourceConnection = $resourceConnection;
        $this->_helper = $helper;
        $this->_storeByLocale = $helper->getAllStoresCodeByLocale();
        $this->inboundFlowFactory = $inboundFlowFactory;
    }

    /**
     * @param $channelName
     * @return AbstractModel
     * @throws Exception
     */
    public function load($channelName)
    {
        $this->_specialActionProperties = $this->_helper->getSpecialActionsPriorities();
        return parent::load($channelName);
    }

    /**
     * @param $row
     * @return void
     * @throws Exception
     */
    public function insertData($row)
    {
        $binds = [];

        /** @var AdapterInterface $connection */
        $connection = $this->resourceConnection->getConnection();
        $tableName = $connection->getTableName('flow_from_eavs');
        $meta_file = $this->getFile()->getId();

        if ($this->getChannelConfig()->usesMap()) {
            $binds_raw = $this->getSimpleFields($row, $meta_file);
            $actions_raw = $this->getActionFields($row, $binds_raw['actions'], $meta_file);
            $binds = array_merge($binds_raw['simple'], $actions_raw);
        } else {
            $metas = $this->getMetaRefIdAndExecutedOn($row);

            $bind = [
                'e'                => $row['e'] ?? $row['sku'],
                'a'                => $row['a'],
                'v'                => $row['v'],
                's'                => $row['s'],
                'executed_on'      => $metas['executed_on'],
                'meta_file'        => $meta_file,
                'meta_ref_id'      => $metas['meta_ref_id'],
                'meta_processed'   => 0,
                'meta_insert_time' => date('Y-m-d H:i:s')
            ];

            if (!empty($row['l'])) {
                // Missing storeview with this language!
                if (!isset($this->_storeByLocale[$row['l']])) {
                    $this->_logger->info("There is no storeview with language {$row['l']} (meta_file {$meta_file})");
                    return;
                }
                /* Expand languages per store view */
                $storeViews = $this->_storeByLocale[$row['l']];

                foreach ($storeViews as $store) {
                    $dataPerStore = $bind;
                    $dataPerStore['s'] = $store;
                    $binds[] = $dataPerStore;
                }
            } else {
                if (empty($bind['s'])){
                    $bind['s'] = 'admin';
                }
                $binds = $bind;
            }
        }

        $connection->insertOnDuplicate($tableName, $binds);
    }

    /**
     * @param $data
     * @param $meta_file
     * @return array
     * @throws Exception
     */
    public function getSimpleFields($data, $meta_file)
    {
        $binds = ['simple' => [], 'actions' => []];

        foreach ($data as $field => $value) {
            $fieldMap = $this->map->get($field);

            if ((!isset($fieldMap['skip']) || !$fieldMap['skip'])
                && (trim($value ?? '') !== '' || isset($fieldMap['allowNull']))
            ) {
                $to_field = '';
                if (isset($fieldMap['to']) && $fieldMap['to']) {
                    $to_field = $fieldMap['to'];
                    $metas = $this->getMetaRefIdAndExecutedOn(['a' => $to_field]);

                    if (!isset($fieldMap['actions']) || !in_array(
                        '_translate_attribute',
                        $fieldMap['actions'],
                        false
                    )) {
                        //devo inserire una riga per ogni attributo
                        $template_bind = [
                            'e'                => $data['sku'],
                            'a'                => $to_field,
                            'v'                => trim($value),
                            's'                => '',
                            'executed_on'      => $metas['executed_on'],
                            'meta_file'        => $meta_file,
                            'meta_ref_id'      => 0,
                            'meta_processed'   => 0,
                            'meta_insert_time' => date('Y-m-d H:i:s')
                        ];

                        $storeViews = ['s' => ''];

                        if ($fieldMap['stores'] && isset($fieldMap['stores']) && count($fieldMap['stores']) > 0) {
                            $storeViews = $fieldMap['stores'];
                        } else {
                            if ($fieldMap['locale'] && isset($fieldMap['locale']) && !empty($fieldMap['locale'])) {
                                //if there are no hashes it process the value normally
                                if (strpos($fieldMap['locale'], '#') === false) {
                                    $storeViews = $this->_storeByLocale[$fieldMap['locale']];
                                } //Otherwise it gets the value in the column of array
                                else {
                                    $localeField = strtolower($data[str_replace('#', '', $fieldMap['locale'])]);
                                    $storeViews = $this->_storeByLocale[$localeField];
                                }
                            } else {
                                // TODO : Unreachable statement ??
                            }
                        }

                        foreach ($storeViews as $store) {
                            $template_bind['s'] = $store;
                            $binds['simple'][] = $template_bind;
                        }
                    }
                }
            }
            // Le azioni le conservo per inserirle alla fine
            if (isset($fieldMap['actions'])) {
                foreach ($fieldMap['actions'] as $action) {
                    $a = ['value' => $value, 'stores' => []];

                    if (isset($to_field) && ($action === '_translate_attribute' || $action === '_set_default_locale')) {
                        $a['e'] = $to_field;
                    }
                    if ($fieldMap['stores'] && isset($fieldMap['stores']) && count($fieldMap['stores']) > 0) {
                        $a['stores'] = $fieldMap['stores'];
                    } else {
                        if ($fieldMap['locale'] && isset($fieldMap['locale']) && !empty($fieldMap['locale'])) {
                            //$storeViews = $this->_storeByLocale[$fieldMap['locale']];
                            //if there are no hashes it process the value normally
                            if (strpos($fieldMap['locale'], '#') === false) {
                                $storeViews = $this->_storeByLocale[$fieldMap['locale']];
                            } //Otherwise it gets the value in the column of array
                            else {
                                $localeField = strtolower($data[str_replace('#', '', $fieldMap['locale'])]);
                                $storeViews = $this->_storeByLocale[$localeField];
                            }
                            foreach ($storeViews as $store) {
                                $a['stores'][] = $store;
                            }
                        } else {
                            // TODO : unreachable statement ??
                        }
                    }

                    // If value is empty i do not add in action
                    if (!empty($a['value'])) {
                        $binds['actions'][$action][] = $a;
                    }
                }
            }
        }

        return $binds;
    }

    /**
     * TODO: Find better and stronger solution
     *
     * @param $data
     * @param null $executed_on
     * @return array
     */
    protected function getMetaRefIdAndExecutedOn($data, $executed_on = null)
    {
        $meta_ref_id = 0;
        if (!$executed_on) {
            // fallback
            $executed_on = 'product';
        }

        if (isset($data['a'])) {
            $a = $data['a'];

            if (self::isFlowAction($a)) {
                $meta_ref_id = 1;
                $executed_on = null;

                if (isset($this->_specialActionProperties[$a]['priority'])) {
                    $meta_ref_id = $this->_specialActionProperties[$a]['priority'];
                }

                if (isset($this->_specialActionProperties[$a]['executed_on'])) {
                    $executed_on = $this->_specialActionProperties[$a]['executed_on'];
                }
            }
        }

        return [
            'meta_ref_id' => $meta_ref_id,
            'executed_on' => $executed_on
        ];
    }

    /**
     * If $execution_string start with "_" then it is a Flow Action type
     *
     * @param $executionString
     * @return bool
     */
    public static function isFlowAction($executionString)
    {
        return strncmp($executionString, '_', 1) === 0;
    }

    /**
     * @param $data
     * @param $actions
     * @param $metaFile
     * @return array
     */
    public function getActionFields($data, $actions, $metaFile)
    {
        $actionsParsed = [];

        foreach ($actions as $a => $values) {
            $metas = $this->getMetaRefIdAndExecutedOn(['a' => $a]);

            foreach ($values as $v) {
                if (in_array($a, ['_add_related', '_add_upsell', '_add_crosssell'])) {
                    //$padre = explode('#',$data['cod_configurabile']);
                    $bind = [
                        'e'                => $data['sku'],
                        'a'                => $a,
                        'v'                => $v['value'],
                        's'                => '',
                        'executed_on'      => $metas['executed_on'],
                        'meta_file'        => $metaFile,
                        'meta_ref_id'      => $metas['meta_ref_id'],
                        'meta_processed'   => 0,
                        'meta_insert_time' => date('Y-m-d H:i:s')
                    ];
                } else {
                    $bind = [
                        'e'                => $v['e'] ?? $data['sku'],
                        'a'                => $a,
                        'v'                => $v['value'],
                        's'                => '',
                        'executed_on'      => $metas['executed_on'],
                        'meta_file'        => $metaFile,
                        'meta_ref_id'      => $metas['meta_ref_id'],
                        'meta_processed'   => 0,
                        'meta_insert_time' => date('Y-m-d H:i:s')
                    ];
                }

                if (count($v['stores']) > 0) {
                    $storeViews = $v['stores'];
                    foreach ($storeViews as $store) {
                        $bind['s'] = $store;
                        $actionsParsed[] = $bind;
                    }
                } else {
                    $actionsParsed[] = $bind;
                }
            }
        }

        return $actionsParsed;
    }

    /**
     * Receive file from remote source.
     *
     * Affected by following options
     * - Copy to Ite
     *
     *
     * @param $file
     * @return mixed
     * @throws Exception
     */
    public function receiveFile($file)
    {
        if (!$this->_connector) {
            throw new Exception('No connector joined to channel');
        }

        $received = $this->_connector->receiveResource($file->getName());

        if (!$received) {
            throw new Exception('No remote files found');
        }

        return $received;
    }

    /**
     * @param $tableName
     * @param $bind
     * @return bool
     */
    protected function checkDuplicates($tableName, $bind)
    {
        /** @var AdapterInterface $connection */
        $connection = $this->resourceConnection->getConnection();

        $table = $connection->getTableName($tableName);
        $query = "SELECT count(*) FROM {$table} WHERE e = ? AND a = ? AND v = ? AND meta_file = ? AND s = ?";
        $result = $connection->fetchOne(
            $query,
            [
                $bind['e'],
                $bind['a'],
                $bind['v'],
                $bind['meta_file'],
                $bind['s'],
            ]
        );

        return $result > 0;
    }

    /**
     * Generate URL keys for products and insert them into the `flow_from_eavs` table.
     *
     * Behaviour:
     * - Returns early if channel configuration disables generation on parse.
     * - Builds a set of attributes to use for url_key generation (maps 'sku' to 'e').
     * - Collects distinct store codes and rows for the current meta file.
     * - Assembles a lookup of attribute values per sku and store.
     * - Creates a normalized, hyphen-separated url_key for each sku/store using
     *   \Magento\Catalog\Model\Product\Url::formatUrlKey and inserts results into
     *   the `flow_from_eavs` table via insertMultiple.
     *
     * No parameters.
     *
     * @return void
     */
    public function generateUrlKey()
    {
        if (!$this->getChannelConfig()->generateUrlKeyOnParse()) {
            return;
        }

        $fileId = $this->getFile()->getId();

        $connection = $this->resourceConnection->getConnection();
        $tableName = $connection->getTableName('flow_from_eavs');
        $attributes = $this->objectManager->get(\FiloBlu\ProductUrlTools\Helper\Data::class)->getAttributeConfig();
        $productUrlManager = $this->objectManager->get(Url::class);

        foreach ($attributes as $key => $attribute) {
            if ($attribute === 'sku') {
                $attributes[$key] = 'e';
            }
        }

        $stores = $connection->fetchCol($connection->select()
            ->from($tableName, ['s'])
            ->where('a IN (?)', $attributes)
            ->where('meta_file = ?', $fileId)
            ->distinct());

        foreach ($stores as $key => $store) {
            if (empty($store)) {
                $stores[$key] = 'admin';
            }
        }

        $select = $connection->select()
            ->from($tableName)
            ->where('meta_file =?', $fileId)
            ->where('a IN (?)', $attributes);

        $looKupStores = $this->_helper->getAllStoreIds();

        $lookup = [];
        foreach ($connection->fetchAll($select) as $row) {
            if (!isset($lookup[$row['e']])) {
                $lookup[$row['e']] = [];
                foreach ($stores as $store) {
                    if (empty($store)) {
                        continue;
                    }

                    $lookup[$row['e']][$store] = [
                        'e' => $row['e'],
                        'store_id' => $looKupStores[$store],
                        'entity_id' => $this->_helper->getProductIdBySku($row['e']),
                        'store_code' => $store
                    ];
                }
            }

            if (!empty($row['s'])) {
                $lookup[$row['e']][$row['s']][$row['a']] = $row['v'];
                continue;
            }

            foreach ($stores as $store) {
                if (empty($store)) {
                    continue;
                }

                $lookup[$row['e']][$store][$row['a']] = $row['v'];
            }
        }

        // Generate url key
        foreach ($lookup as $e => $data) {
            $bindings = [];
            foreach ($data as $store => $a) {
                $v = [];
                $hasAllRequiredAttributes = true;
                foreach ($attributes as $attribute) {
                    if (empty($a[$attribute])) {
                        $hasAllRequiredAttributes = false;
                        break;
                    }
                    $v[] = str_replace(' ', '-', strtolower($a[$attribute]));
                }

                if (empty($v) || !$hasAllRequiredAttributes) {
                    continue;
                }

                $bindings[] = [
                    'e' => $e,
                    'a' => 'url_key',
                    'v' => $productUrlManager->formatUrlKey(preg_replace('/-+/', '-', implode('-', $v))),
                    's' => $store,
                    'l' => null,
                    'executed_on' => 'product',
                    'meta_file' => $fileId,
                    'meta_ref_id' => 0,
                    'meta_processed' => 0,
                    'meta_insert_time' => date('Y-m-d H:i:s')
                ];
            }

            if (!empty($bindings)) {
                $connection->insertMultiple($tableName, $bindings);
            }
        }
    }

    /**
     * @return bool
     */
    public function commitData()
    {
        $this->generateUrlKey();
        return true;
    }
}
