<?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\Catalog\Model\ProductFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Stdlib\StringUtils;
use Magento\Framework\Validator\EmailAddress;
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;
use Mageplaza\Faqs\Model\CategoryFactory;
use Mageplaza\Faqs\Model\Config\Source\Status;
use Mageplaza\Faqs\Model\ResourceModel\Article as ResourceArticle;
use Mageplaza\Faqs\Helper\Data;
use Mageplaza\Faqs\Model\Tag;
use Mageplaza\Faqs\Model\TagFactory;
use Zend_Validate;
use Zend_Validate_Exception;

/**
 * Class Article
 * @package Mageplaza\Faqs\Model\Import
 */
class Article extends AbstractEntity
{
    /**
     * columns
     */
    const ARTICLE_ID         = 'article_id';
    const NAME               = 'name';
    const AUTHOR_NAME        = 'author_name';
    const AUTHOR_EMAIL       = 'author_email';
    const STATUS             = 'status';
    const VISIBILITY         = 'visibility';
    const ARTICLE_CONTENT    = 'article_content';
    const CATEGORIES         = 'categories';
    const TAGS               = 'tags';
    const PRODUCTS           = 'products';
    const RELATED_ARTICLES   = 'related_articles';
    const STORE_ID           = 'store_ids';
    const POSITION           = 'position';
    const POSITIVES          = 'positives';
    const NEGATIVES          = 'negatives';
    const VIEWS              = 'views';
    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 IN_RSS             = 'in_rss';
    const EMAIL_NOTIFY       = 'email_notify';
    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_AUTHOR_NAME_IS_EMPTY  = 'authorNameIsEmpty';
    const ERROR_AUTHOR_EMAIL_IS_EMPTY = 'authorNameIsEmpty';
    const ERROR_INVALID_STORE         = 'invalidStore';
    const ERROR_INVALID_EMAIL         = 'invalidEmail';

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

    /**
     * Permanent entity columns.
     *
     * @var string[]
     */
    protected $_permanentAttributes = [
        self::ARTICLE_ID,
        self::NAME,
        self::AUTHOR_NAME,
        self::AUTHOR_EMAIL,
        self::VISIBILITY,
        self::ARTICLE_CONTENT,
        self::CATEGORIES,
        self::TAGS,
        self::PRODUCTS,
        self::RELATED_ARTICLES,
        self::STORE_ID,
        self::POSITION,
        self::ICON_TYPE,
        self::ICON,
        self::ICON_CUSTOM_PATH,
        self::ICON_CUSTOM_WIDTH,
        self::ICON_CUSTOM_HEIGHT,
        self::URL_KEY,
        self::IN_RSS,
        self::EMAIL_NOTIFY,
        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,
        self::AUTHOR_NAME,
        self::AUTHOR_EMAIL
    ];

    protected $removeReplaceColumns = [
        self::POSITIVES,
        self::NEGATIVES,
        self::VIEWS,
        self::CATEGORIES,
        self::TAGS,
        self::RELATED_ARTICLES,
        self::PRODUCTS,
    ];

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

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

    /**
     * @var ResourceArticle
     */
    protected $resourceArticle;

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

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

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

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

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

    /**
     * @var TagFactory
     */
    protected $tagFactory;

    /**
     * @var ProductFactory
     */
    protected $productFactory;

    /**
     * Article 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 ResourceArticle $resourceArticle
     * @param CategoryFactory $categoryFactory
     * @param TagFactory $tagFactory
     * @param ProductFactory $productFactory
     * @param array $data
     */
    public function __construct(
        StringUtils $string,
        ScopeConfigInterface $scopeConfig,
        ImportFactory $importFactory,
        Helper $resourceHelper,
        ResourceConnection $resource,
        ProcessingErrorAggregatorInterface $errorAggregator,
        Data $helperData,
        ArticleFactory $articleFactory,
        ResourceArticle $resourceArticle,
        CategoryFactory $categoryFactory,
        TagFactory $tagFactory,
        ProductFactory $productFactory,
        array $data = []
    ) {
        $this->helperData       = $helperData;
        $this->articleFactory   = $articleFactory;
        $this->resourceArticle  = $resourceArticle;
        $this->categoryFactory  = $categoryFactory;
        $this->tagFactory       = $tagFactory;
        $this->productFactory   = $productFactory;

        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_AUTHOR_NAME_IS_EMPTY,
            __("An author name wasn't specified. Enter the author name and try again.")
        );
        $this->addMessageTemplate(
            self::ERROR_AUTHOR_EMAIL_IS_EMPTY,
            __("An author email wasn't specified. Enter the author email and try again.")
        );
        $this->addMessageTemplate(self::ERROR_INVALID_STORE, __('We found an invalid value in a store column.'));
        $this->addMessageTemplate(self::ERROR_INVALID_EMAIL, __('Please enter a valid email.'));
    }

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

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

            if (!Zend_Validate::is($authorEmail, EmailAddress::class)) {
                $this->addRowError(self::ERROR_INVALID_EMAIL, $rowNumber, self::AUTHOR_EMAIL);
            } else {
                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
     * @throws Zend_Validate_Exception
     */
    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::ARTICLE_ID]) {
            $article = $this->articleFactory->create()->load($rowData[self::ARTICLE_ID]);
            if ($article && $article->getId()) {
                $relateData                         = [];
                $categories                         = explode(',', $rowData[self::CATEGORIES]);
                $tags                               = explode(',', $rowData[self::TAGS]);
                $relatedArticles                    = explode(',', $rowData[self::RELATED_ARTICLES]);
                $productsIds                        = explode(',', $rowData[self::PRODUCTS]);
                $relateData['categories_ids']       = $this->_prepareRelation($categories, Data::TYPE_CATEGORY);
                $relateData['tags_ids']             = $this->_prepareRelation($tags, Data::TYPE_TAG);
                $relateData['related_articles_ids'] = $this->_prepareRelation($relatedArticles, Data::TYPE_ARTICLE);
                $relateData['products_ids']         = $this->_prepareRelation($productsIds);
                $rowData[self::STATUS]              = $rowData[self::ARTICLE_CONTENT] ?
                                                        Status::ANSWERED : Status::PENDING;
                $rowData                            = array_diff_key($rowData, array_flip($this->removeReplaceColumns));
                $this->entityList[]                 = $rowData;

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

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

    /**
     * @return bool
     * @throws LocalizedException
     * @throws Zend_Validate_Exception
     */
    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
     * @throws Zend_Validate_Exception
     */
    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->resourceArticle->getMainTable(),
                    $this->entityList,
                    [
                        self::ARTICLE_ID,
                        self::NAME,
                        self::AUTHOR_NAME,
                        self::AUTHOR_EMAIL,
                        self::STATUS,
                        self::VISIBILITY,
                        self::ARTICLE_CONTENT,
                        self::STORE_ID,
                        self::POSITION,
                        self::ICON_TYPE,
                        self::ICON,
                        self::ICON_CUSTOM_PATH,
                        self::ICON_CUSTOM_WIDTH,
                        self::ICON_CUSTOM_HEIGHT,
                        self::URL_KEY,
                        self::IN_RSS,
                        self::EMAIL_NOTIFY,
                        self::META_TITLE,
                        self::META_DESCRIPTION,
                        self::META_KEYWORDS,
                        self::META_ROBOTS
                    ]
                );

                $connection->commit();

                if ($this->relateInformation) {
                    $articleIds = array_keys($this->relateInformation);
                    $condition  = $this->_connection->quoteInto('article_id IN (?)', $articleIds);
                    $this->_connection->delete(
                        $this->resourceArticle->getTable('mageplaza_faqs_article_article'),
                        $condition
                    );
                    $this->_connection->delete(
                        $this->resourceArticle->getTable('mageplaza_faqs_article_category'),
                        $condition
                    );
                    $this->_connection->delete(
                        $this->resourceArticle->getTable('mageplaza_faqs_article_product'),
                        $condition
                    );
                    $this->_connection->delete(
                        $this->resourceArticle->getTable('mageplaza_faqs_article_tag'),
                        $condition
                    );

                    foreach ($this->relateInformation as $key => $data) {
                        if ($data['categories_ids']) {
                            foreach ($data['categories_ids'] as $categoryId) {
                                $cat = [
                                    'article_id'  => $key,
                                    'category_id' => $categoryId
                                ];
                                $this->_connection->insert(
                                    $this->resourceArticle->getTable('mageplaza_faqs_article_category'),
                                    $cat
                                );
                            }
                        }
                        if ($data['tags_ids']) {
                            foreach ($data['tags_ids'] as $tagId) {
                                $tag = [
                                    'article_id' => $key,
                                    'tag_id'     => $tagId
                                ];
                                $this->_connection->insert(
                                    $this->resourceArticle->getTable('mageplaza_faqs_article_tag'),
                                    $tag
                                );
                            }
                        }
                        if ($data['related_articles_ids']) {
                            foreach ($data['related_articles_ids'] as $articleId => $position) {
                                $article = [
                                    'article_id'         => $key,
                                    'related_article_id' => $articleId
                                ];
                                $this->_connection->insert(
                                    $this->resourceArticle->getTable('mageplaza_faqs_article_article'),
                                    $article
                                );
                            }
                        }
                        if ($data['products_ids']) {
                            foreach ($data['products_ids'] as $productId => $position) {
                                $product = [
                                    'article_id' => $key,
                                    'entity_id'  => $productId
                                ];
                                $this->_connection->insert(
                                    $this->resourceArticle->getTable('mageplaza_faqs_article_product'),
                                    $product
                                );
                            }
                        }
                    }
                }
            } catch (Exception $e) {
                $this->addException($e);
                $connection->rollBack();
            }

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

        return $this;
    }

    /**
     * @return $this
     * @throws Zend_Validate_Exception
     */
    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) {
                $articleId = (int) $data['article_id'];
                /** @var ArticleModel $article */
                $article   = $this->articleFactory->create()->load($articleId);
                if ($article && $article->getId()) {
                    $this->_prepareData($article, $data, true);
                    $update[] = $data;
                } else {
                    $article = $this->articleFactory->create();
                    $this->_prepareData($article, $data);
                    $create[] = $data;
                }

                $article->save();
            }

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

            $connection->rollBack();
        }

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

        return $this;
    }

    /**
     * @param ArticleModel $article
     * @param array $data
     * @param bool $isReplace
     *
     * @return $this
     */
    public function _prepareData($article, $data, $isReplace = false)
    {
        $data[self::STATUS]           = $data[self::ARTICLE_CONTENT] ? Status::ANSWERED : Status::PENDING;
        $categories                   = explode(',', $data[self::CATEGORIES]);
        $tags                         = explode(',', $data[self::TAGS]);
        $relatedArticles              = explode(',', $data[self::RELATED_ARTICLES]);
        $productsIds                  = explode(',', $data[self::PRODUCTS]);
        $data['categories_ids']       = $this->_prepareRelation($categories, Data::TYPE_CATEGORY);
        $data['tags_ids']             = $this->_prepareRelation($tags, Data::TYPE_TAG);
        $data['related_articles_ids'] = $this->_prepareRelation($relatedArticles, Data::TYPE_ARTICLE);
        $data['products_ids']         = $this->_prepareRelation($productsIds);

        if (!empty($data['related_articles_ids'])) {
            $article->setIsRelatedArticleGrid(true);
        }

        if (!empty($data['products_ids'])) {
            $article->setIsProductGrid(true);
        }

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

        return $this;
    }

    /**
     * @param array $data
     * @param null $type
     *
     * @return array
     */
    public function _prepareRelation($data, $type = null)
    {
        $ids = [];
        if (!empty($data)) {
            foreach ($data as $id) {
                /** @var Category|Tag|ArticleModel $collection */
                if ($type === Data::TYPE_CATEGORY) {
                    $collection = $this->categoryFactory->create()->load($id);
                } elseif ($type === Data::TYPE_TAG) {
                    $collection = $this->tagFactory->create()->load($id);
                } elseif ($type === Data::TYPE_ARTICLE) {
                    $collection = $this->articleFactory->create()->load($id);
                } else {
                    $collection = $this->productFactory->create()->load($id);
                }
                if ($collection && $collection->getId()) {
                    if ($type === Data::TYPE_TAG || $type === Data::TYPE_CATEGORY) {
                        $ids[] = $id;
                    } else {
                        $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::ARTICLE_ID]) {
                    $this->ids[] = $rowData[self::ARTICLE_ID];
                }
            }
        }

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

        $condition = $this->_connection->quoteInto('article_id IN (?)', $this->ids);
        $this->_connection->delete($this->resourceArticle->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;
    }
}
