<?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\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 bool|void
     * @throws Exception
     */
    public function insertData($row)
    {
        $binds = [];

        /** @var AdapterInterface $connection */
        $connection = $this->resourceConnection->getConnection(ResourceConnection::DEFAULT_CONNECTION);
        $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 {
                $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
     * @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 strpos($executionString, '_') === 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'                => isset($v['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(ResourceConnection::DEFAULT_CONNECTION);

        $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;
    }
}
