<?php
/**
 * Mageplaza
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Mageplaza.com license that is
 * available through the world-wide-web at this URL:
 * https://www.mageplaza.com/LICENSE.txt
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade this extension to newer
 * version in the future.
 *
 * @category    Mageplaza
 * @package     Mageplaza_Faqs
 * @copyright   Copyright (c) Mageplaza (http://www.mageplaza.com/)
 * @license     https://www.mageplaza.com/LICENSE.txt
 */

namespace Mageplaza\Faqs\Model\Import;

use Exception;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Stdlib\StringUtils;
use Magento\ImportExport\Model\Import;
use Magento\ImportExport\Model\Import\AbstractEntity;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingError;
use Magento\ImportExport\Model\Import\ErrorProcessing\ProcessingErrorAggregatorInterface;
use Magento\ImportExport\Model\ImportFactory;
use Magento\ImportExport\Model\ResourceModel\Helper;
use Mageplaza\Faqs\Model\Article as ArticleModel;
use Mageplaza\Faqs\Model\ArticleFactory;
use Mageplaza\Faqs\Model\Category as CategoryModel;
use Mageplaza\Faqs\Model\CategoryFactory;
use Mageplaza\Faqs\Model\ResourceModel\Category as ResourceCategory;
use Mageplaza\Faqs\Helper\Data;

/**
 * Class Category
 * @package Mageplaza\Faqs\Model\Import
 */
class Category extends AbstractEntity
{
    /**
     * columns
     */
    const CATEGORY_ID        = 'category_id';
    const NAME               = 'name';
    const DESCRIPTION        = 'description';
    const VIEWS              = 'views';
    const STORE_ID           = 'store_ids';
    const STATUS             = 'enabled';
    const ARTICLES           = 'articles';
    const POSITION           = 'position';
    const ICON_TYPE          = 'icon_type';
    const ICON               = 'icon';
    const ICON_CUSTOM_PATH   = 'icon_custom_path';
    const ICON_CUSTOM_WIDTH  = 'icon_custom_width';
    const ICON_CUSTOM_HEIGHT = 'icon_custom_height';
    const URL_KEY            = 'url_key';
    const META_TITLE         = 'meta_title';
    const META_DESCRIPTION   = 'meta_description';
    const META_KEYWORDS      = 'meta_keywords';
    const META_ROBOTS        = 'meta_robots';

    /**#@+
     * Error codes
     */
    const ERROR_STORE_IS_EMPTY        = 'storeIsEmpty';
    const ERROR_NAME_IS_EMPTY         = 'nameIsEmpty';
    const ERROR_INVALID_STORE         = 'invalidStore';

    /** @inheritdoc */
    protected $masterAttributeCode = 'category_id';

    /**
     * Permanent entity columns.
     *
     * @var string[]
     */
    protected $_permanentAttributes = [
        self::CATEGORY_ID,
        self::NAME,
        self::DESCRIPTION,
        self::STORE_ID,
        self::ARTICLES,
        self::STATUS,
        self::POSITION,
        self::ICON_TYPE,
        self::ICON,
        self::ICON_CUSTOM_PATH,
        self::ICON_CUSTOM_WIDTH,
        self::ICON_CUSTOM_HEIGHT,
        self::URL_KEY,
        self::META_TITLE,
        self::META_DESCRIPTION,
        self::META_KEYWORDS,
        self::META_ROBOTS
    ];

    /** @inheritdoc */
    protected $_availableBehaviors = [
        Import::BEHAVIOR_APPEND,
        Import::BEHAVIOR_REPLACE,
        Import::BEHAVIOR_DELETE
    ];

    /**
     * Valid column names
     *
     * @array
     */
    protected $validColumnNames = [
        self::NAME,
        self::STORE_ID
    ];

    protected $removeReplaceColumns = [
        self::VIEWS,
        self::ARTICLES,
    ];

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

    /**
     * @var ArticleFactory
     */
    protected $articleFactory;

    /**
     * @var ResourceCategory
     */
    protected $resourceCategory;

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

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

    /**
     * @var null
     */
    protected $stores;

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

    /**
     * @var CategoryFactory
     */
    protected $categoryFactory;

    /**
     * Category constructor.
     *
     * @param StringUtils $string
     * @param ScopeConfigInterface $scopeConfig
     * @param ImportFactory $importFactory
     * @param Helper $resourceHelper
     * @param ResourceConnection $resource
     * @param ProcessingErrorAggregatorInterface $errorAggregator
     * @param Data $helperData
     * @param ArticleFactory $articleFactory
     * @param ResourceCategory $resourceCategory
     * @param CategoryFactory $categoryFactory
     * @param array $data
     */
    public function __construct(
        StringUtils $string,
        ScopeConfigInterface $scopeConfig,
        ImportFactory $importFactory,
        Helper $resourceHelper,
        ResourceConnection $resource,
        ProcessingErrorAggregatorInterface $errorAggregator,
        Data $helperData,
        ArticleFactory $articleFactory,
        ResourceCategory $resourceCategory,
        CategoryFactory $categoryFactory,
        array $data = []
    ) {
        $this->helperData       = $helperData;
        $this->articleFactory   = $articleFactory;
        $this->resourceCategory = $resourceCategory;
        $this->categoryFactory  = $categoryFactory;

        parent::__construct($string, $scopeConfig, $importFactory, $resourceHelper, $resource, $errorAggregator, $data);

        $this->addMessageTemplate(self::ERROR_STORE_IS_EMPTY, __('Please specify a store.'));
        $this->addMessageTemplate(
            self::ERROR_NAME_IS_EMPTY,
            __("An name wasn't specified. Enter the name and try again.")
        );
        $this->addMessageTemplate(self::ERROR_INVALID_STORE, __('We found an invalid value in a store column.'));
    }

    /**
     * Entity type code getter.
     *
     * @return string
     */
    public function getEntityTypeCode()
    {
        return 'mp_faqs_categories';
    }

    /**
     * @param array $rowData
     * @param $rowNumber
     *
     * @return bool
     */
    protected function _checkUniqueKey(array $rowData, $rowNumber)
    {
        if (empty($rowData[self::NAME])) {
            $this->addRowError(self::ERROR_NAME_IS_EMPTY, $rowNumber, self::NAME);
        } else {
            $storeIds = explode(',', $rowData[self::STORE_ID]);

            foreach ($storeIds as $storeId) {
                if (!isset($this->stores[$storeId])) {
                    $this->addRowError(self::ERROR_INVALID_STORE, $rowNumber, self::STORE_ID);
                }
            }
        }

        return !$this->getErrorAggregator()->isRowInvalid($rowNumber);
    }

    /**
     * @param array $rowData
     * @param int $rowNum
     *
     * @return bool
     */
    public function validateRow(array $rowData, $rowNum)
    {
        $this->_validatedRows[$rowNum] = true;

        if ($this->getBehavior() === Import::BEHAVIOR_APPEND || $this->getBehavior() === Import::BEHAVIOR_REPLACE) {
            if (!$this->stores) {
                $this->stores = $this->helperData->getStores();
            }

            $this->_checkUniqueKey($rowData, $rowNum);
        }

        if ($this->getBehavior() === Import::BEHAVIOR_REPLACE && $rowData[self::CATEGORY_ID]) {
            $category = $this->categoryFactory->create()->load($rowData[self::CATEGORY_ID]);
            if ($category && $category->getId()) {
                $relateData                 = [];
                $articles                   = explode(',', $rowData[self::ARTICLES]);
                $relateData['articles_ids'] = $this->_prepareArticlesRelation($articles);
                $rowData                    = array_diff_key($rowData, array_flip($this->removeReplaceColumns));
                $this->entityList[]         = $rowData;

                $this->relateInformation[$rowData[self::CATEGORY_ID]] = $relateData;
            }
        }

        return !$this->getErrorAggregator()->isRowInvalid($rowNum);
    }

    /**
     * @return bool
     * @throws LocalizedException
     */
    protected function _importData()
    {
        switch ($this->getBehavior()) {
            case Import::BEHAVIOR_DELETE:
                $this->deleteEntity();
                break;
            case Import::BEHAVIOR_REPLACE:
                $this->replaceEntity();
                break;
            case Import::BEHAVIOR_APPEND:
                $this->saveEntity();
                break;
        }

        return true;
    }

    /**
     * @return $this
     */
    public function replaceEntity()
    {
        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
            foreach ($bunch as $rowNum => $rowData) {
                if (!$this->validateRow($rowData, $rowNum)) {
                    continue;
                }

                if ($this->getErrorAggregator()->hasToBeTerminated()) {
                    $this->getErrorAggregator()->addRowToSkip($rowNum);
                    continue;
                }
            }
        }

        if ($this->entityList) {
            $connection = $this->_connection;
            $connection->beginTransaction();
            try {
                $connection->insertOnDuplicate(
                    $this->resourceCategory->getMainTable(),
                    $this->entityList,
                    [
                        self::CATEGORY_ID,
                        self::NAME,
                        self::DESCRIPTION,
                        self::STORE_ID,
                        self::URL_KEY,
                        self::ICON_TYPE,
                        self::ICON,
                        self::ICON_CUSTOM_PATH,
                        self::ICON_CUSTOM_WIDTH,
                        self::ICON_CUSTOM_HEIGHT,
                        self::STATUS,
                        self::META_TITLE,
                        self::META_DESCRIPTION,
                        self::META_KEYWORDS,
                        self::META_ROBOTS,
                        self::POSITION
                    ]
                );

                $connection->commit();

                if ($this->relateInformation) {
                    $categoryIds = array_keys($this->relateInformation);
                    $condition = $this->_connection->quoteInto('category_id IN (?)', $categoryIds);
                    $this->_connection->delete(
                        $this->resourceCategory->getTable('mageplaza_faqs_article_category'),
                        $condition
                    );

                    foreach ($this->relateInformation as $key => $data) {
                        if ($data['articles_ids']) {
                            foreach ($data['articles_ids'] as $articleId => $position) {
                                $article = [
                                    'article_id'  => $articleId,
                                    'category_id' => $key
                                ];
                                $this->_connection->insert(
                                    $this->resourceCategory->getTable('mageplaza_faqs_article_category'),
                                    $article
                                );
                            }
                        }
                    }
                }
            } catch (Exception $e) {
                $this->addException($e);
                $connection->rollBack();
            }

            $this->updateItemsCounterStats([], $this->entityList);
        }

        return $this;
    }

    /**
     * @return $this
     */
    public function saveEntity()
    {
        $entityList = [];
        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
            foreach ($bunch as $rowNum => $rowData) {
                if (!$this->validateRow($rowData, $rowNum)) {
                    continue;
                }

                if ($this->getErrorAggregator()->hasToBeTerminated()) {
                    $this->getErrorAggregator()->addRowToSkip($rowNum);
                    continue;
                }

                foreach ($this->_permanentAttributes as $field) {
                    $entityList[$rowNum][$field] = $rowData[$field];
                }
            }
        }

        $this->saveEntityFinish($entityList);

        return $this;
    }

    /**
     * @param array $entityData
     *
     * @return $this
     */
    public function saveEntityFinish(array $entityData)
    {
        $update     = [];
        $create     = [];
        $connection = $this->_connection;
        $connection->beginTransaction();

        try {
            foreach ($entityData as $key => $data) {
                $categoryId = (int) $data['category_id'];
                /** @var CategoryModel $category */
                $category   = $this->categoryFactory->create()->load($categoryId);
                if ($category && $category->getId()) {
                    $this->_prepareData($category, $data, true);
                    $update[] = $data;
                } else {
                    $category = $this->categoryFactory->create();
                    $this->_prepareData($category, $data);
                    $create[] = $data;
                }

                $category->save();
            }

            $connection->commit();
        } catch (Exception $e) {
            $this->addException($e);

            $connection->rollBack();
        }

        $this->updateItemsCounterStats($create, $update);

        return $this;
    }

    /**
     * @param CategoryModel $category
     * @param array $data
     * @param bool $isReplace
     *
     * @return $this
     */
    public function _prepareData($category, $data, $isReplace = false)
    {
        $articles             = explode(',', $data[self::ARTICLES]);
        $data['articles_ids'] = $this->_prepareArticlesRelation($articles);

        if (!empty($data['articles_ids'])) {
            $category->setIsArticleGrid(true);
        }

        if ($isReplace) {
            $category->setData($data);
        } else {
            unset($data[self::CATEGORY_ID]);
            $category->addData($data);
        }

        return $this;
    }

    /**
     * @param array $data
     *
     * @return array
     */
    public function _prepareArticlesRelation($data)
    {
        $ids = [];
        if (!empty($data)) {
            foreach ($data as $id) {
                /** @var ArticleModel $collection */
                $collection = $this->articleFactory->create()->load($id);
                if ($collection && $collection->getId()) {
                    $ids[$id] = [
                        'position' => ''
                    ];
                }
            }
        }

        return $ids;
    }

    /**
     * @return $this
     * @throws LocalizedException
     */
    public function deleteEntity()
    {
        while ($bunch = $this->_dataSourceModel->getNextBunch()) {
            foreach ($bunch as $rowNum => $rowData) {
                if ($rowData[self::CATEGORY_ID]) {
                    $this->ids[] = $rowData[self::CATEGORY_ID];
                }
            }
        }

        if ($this->ids) {
            $this->updateItemsCounterStats([], [], $this->ids);
        }

        $condition = $this->_connection->quoteInto('category_id IN (?)', $this->ids);
        $this->_connection->delete($this->resourceCategory->getMainTable(), $condition);

        return $this;
    }

    /**
     * @param $e
     *
     * @return $this
     */
    public function addException($e)
    {
        $errorAggregator = $this->getErrorAggregator();
        $errorAggregator->addError(
            AbstractEntity::ERROR_CODE_SYSTEM_EXCEPTION,
            ProcessingError::ERROR_LEVEL_CRITICAL,
            null,
            null,
            $e->getMessage()
        );

        return $this;
    }
}
