<?php

namespace FiloBlu\Flow\Model\From;

use Exception;
use FiloBlu\Flow\Helper\Attribute;
use FiloBlu\Flow\Helper\Data;
use FiloBlu\Flow\Helper\LoggerProvider;
use FiloBlu\Flow\Helper\Website;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\Indexer\Product\Price\Processor;
use Magento\Catalog\Model\Product\Action;
use Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\ProductFactory;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
use Magento\ConfigurableProduct\Model\Product\Type\ConfigurableFactory;
use Magento\Eav\Model\ResourceModel\Entity\Attribute\Set\CollectionFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\ObjectManagerInterface;
use Magento\Framework\Registry;
use Magento\Indexer\Model\Indexer;
use Magento\Store\Model\StoreManagerInterface;
use RuntimeException;
use function is_object;
use function sprintf;

/**
 * Class Product
 * Used in process of Item (Products)
 * @package FiloBlu\Flow\Model\From
 * @method getCodiceArticolo()
 * @method getCodiceConfigurabile()
 * @method getFasciaIvaArticoli()
 * @method getMetaFile()
 * @method getConfigurableAttributes()
 * @method getConfigurableValues()
 * @method getAttributeSet()
 * @method getNomeArticolo()
 * @method getCodiceArticoloDelFornitore()
 */
class Product extends AbstractFrom
{
    /**
     * @var string
     */
    const CONFIG_FLOW_WEBSITES_MAPPING = 'filoblu_flow/flow_ite/masterfile_website_mapping';

    /**
     * @var Context
     */
    protected $_context;
    /**
     * @var ScopeConfigInterface
     */
    protected $_scopeConfig;
    /**
     * @var array
     */
    protected $_attributeSets = [];
    /**
     * @var array
     */
    protected $_taxClasses = [];
    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;
    /**
     * @var \FiloBlu\Flow\Helper\Product
     */
    protected $helper_product;
    /**
     * @var Data
     */
    protected $helper_flow;
    /**
     * @var Attribute
     */
    protected $helper_attribute;

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

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

    /**
     * @var ConfigurableFactory
     */
    protected $configurableFactory;

    /**
     * @var Indexer\CollectionFactory
     */
    protected $indexerCollectionFactory;

    /**
     * @var Website
     */
    protected $websiteHelper;

    /**
     * Product constructor.
     * @param ScopeConfigInterface $scopeConfig
     * @param Context $context
     * @param Registry $registry
     * @param \FiloBlu\Flow\Helper\Product $helper_product
     * @param Data $helper_flow
     * @param Attribute $helper_attribute
     * @param LoggerProvider $loggerProvider
     * @param StoreManagerInterface $storeManager
     * @param ProductFactory $productFactory
     * @param CollectionFactory $attributeSetCollectionFactory
     * @param \Magento\Tax\Model\TaxClass\Source\Product $taxClassCollection
     * @param Action $productAction
     * @param Indexer\CollectionFactory $collectionFactory
     * @param ObjectManagerInterface $objectManager
     * @param ConfigurableFactory $configurableFactory
     * @param Website $websiteHelper
     * @param AbstractResource|null $resource
     * @param AbstractDb|null $resourceCollection
     * @param array $data
     */
    public function __construct(
        ScopeConfigInterface $scopeConfig,
        Context $context,
        Registry $registry,
        \FiloBlu\Flow\Helper\Product $helper_product,
        Data $helper_flow,
        Attribute $helper_attribute,
        LoggerProvider $loggerProvider,
        StoreManagerInterface $storeManager,
        ProductFactory $productFactory,
        CollectionFactory $attributeSetCollectionFactory,
        \Magento\Tax\Model\TaxClass\Source\Product $taxClassCollection,
        Action $productAction,
        Indexer\CollectionFactory $collectionFactory,
        ObjectManagerInterface $objectManager,
        ConfigurableFactory $configurableFactory,
        Website $websiteHelper,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        array $data = []
    )
    {
        $this->_scopeConfig = $scopeConfig;
        $this->_context = $context;
        $this->helper_product = $helper_product;
        $this->helper_flow = $helper_flow;
        $this->websiteHelper = $websiteHelper;
        $this->helper_attribute = $helper_attribute;
        $this->storeManager = $storeManager;
        $this->productFactory = $productFactory;
        $this->_productAction = $productAction;
        $this->objectManager = $objectManager;
        $this->configurableFactory = $configurableFactory;
        $this->indexerCollectionFactory = $collectionFactory;
        $this->_logger = $loggerProvider->getLogger();
        $this->getProductAttributeSetCollection($attributeSetCollectionFactory);
        $this->getProductTaxClassCollection($taxClassCollection);
        parent::__construct($context, $registry, $resource, $resourceCollection, $data);
    }

    /**
     * Loads all product attribute set in an array
     *
     * @param $collection
     */
    public function getProductAttributeSetCollection($collection)
    {
        $attributeSetCollection = $collection->create()->addFieldToFilter('entity_type_id', 4)->toArray();
        foreach ($attributeSetCollection['items'] as $item) {
            $this->_attributeSets[$item['attribute_set_name']] = $item['attribute_set_id'];
        }
    }

    /**
     * Loads all product tax class in an array
     *
     * @param $collection
     */
    public function getProductTaxClassCollection($collection)
    {
        $taxClasses = $collection->getAllOptions();
        foreach ($taxClasses as $taxClass) {
            $label = 'None';
            if (!is_object($taxClass['label'])) {
                $label = $taxClass['label'];
            }
            $this->_taxClasses[$label] = $taxClass['value'];
        }
    }

    /**
     * @return void
     * @noinspection MagicMethodsValidityInspection
     */
    public function _construct()
    {
        $this->_init(\FiloBlu\Flow\Model\ResourceModel\From\Product::class);
    }

    /**
     * @return $this
     * @throws Exception
     */
    public function process()
    {
        $this->checkIndexer();

        $productSku = $this->getCodiceArticolo();

        if($this->getChannel()->getChannelConfig()->stripWhiteSpaces()) {
            $productSku = $this->helper_flow->removeTextSpaces($productSku);
        }

        if (empty($productSku)) {
            throw new RuntimeException('Codice_articolo is empty.');
        }

        // TODO deprecated filter FILTER_SANITIZE_STRING
        if (@filter_var($this->getCodiceConfigurabile(), FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH) !== $this->getCodiceConfigurabile()) {
            throw new RuntimeException('Codice Configurabile contains invalid ASCII characters');
        }

        // TODO deprecated filter FILTER_SANITIZE_STRING
        if (@filter_var($this->getCodiceArticolo(), FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH) !== $this->getCodiceArticolo()) {
            throw new RuntimeException('Codice Articolo contains invalid ASCII characters');
        }

        $attributeSetId = $this->getProductAttributeSetFromName($this->getAttributeSet());

        if (empty($attributeSetId)) {
            throw new RuntimeException('Product attribute_set not defined or wrong! Aborting Process');
        }

        $skuConfigurable = $this->getCodiceConfigurabile();
        $TaxClassCode = trim($this->getFasciaIvaArticoli() ??'');

        $mappedTaxClassId = $this->getProductTaxClassFromName($TaxClassCode);

        if (!$mappedTaxClassId) {
            throw new RuntimeException("Tax_class_code '$TaxClassCode' has no Magento tax class associated. Aborting Process");
        }

        $productAttributesData = [
            'product_sku'  => $this->getCodiceArticolo(),
            'tax_class_id' => $mappedTaxClassId,
            'parent_sku'   => $skuConfigurable
        ];

        $attributesToCopy = $this->getAttributeToCopy($productAttributesData);

        $attributesToCopy['stock_data'] = $this->getInitialStockData();

        $ProdHelper = $this->helper_product;

        $product = $this->productFactory->create();
        $productId = $product->getIdBySku($productSku);

        if ($productId) {
            $product->load($productId);

            if($product->getTypeId() === 'simple') {
                $attributesToCopy = $this->getAttributeToCopy($product->getData());
                foreach (explode(',', $this->getConfigurableAttributes() ?? '') as $value) {
                    if ($value === null || $value === '' || ctype_space($value)) {
                        continue;
                    }
                    $product->setData($value,$attributesToCopy[$value] );
                }
                $product->save();
            }

            $product = $this->processProductUpdated($product);
        } else {
            if (!$ProdHelper->createNewProducts()) {
                throw new \RuntimeException('The product has not been found and the module is configured to not create it');
            }

            $product
                ->setTypeId(Type::TYPE_SIMPLE)
                ->setAttributeSetId($attributeSetId)
                ->setStatus($ProdHelper->getInitialProductStatus())
                ->setVisibility(Visibility::VISIBILITY_BOTH)
                ->setStoreId(0)
                ->setBarcode($this->getCodiceArticoloDelFornitore());

            //Check if the name need to be copied on Description
            if ($this->helper_product->getCopyNameInDescription()) {
                $product->setDescription($this->getNomeArticolo());
            }

            //Check if the name need to be copied on Short Description
            if ($this->helper_product->getCopyNameInShortDescription()) {
                $product->setShortDescription($this->getNomeArticolo());
            }

            //Check if Disable Module Shop is enabled
            if ($this->helper_product->isDisableShopModuleEnabled()) {
                $disableShopStatus = 0;
                $hidePriceStatus = 0;
                if ($this->helper_product->getDisableShopStatus()) {
                    $disableShopStatus = 1;
                }
                if ($this->helper_product->getHidePriceStatus()) {
                    $hidePriceStatus = 1;
                }

                $product->setDisableShop($disableShopStatus);
                $product->setHidePrice($hidePriceStatus);
            }

            $product
                ->setSku($productSku)
                ->setPrice(0)
                ->setPriceView(0)
                ->setWeight(0)
                ->setName($this->getNomeArticolo());

            if ($ProdHelper->getAddToWebsites()) {
                $websites = $this->storeManager->getWebsites();
                $websitesIds = [];

                foreach ($websites as $website) {
                    $websitesIds[] = $website->getId();
                }

                $product->setWebsiteIds($websitesIds);
            } else {
                $inbound_file = $this->helper_flow->getChannelByInbound($this->getMetaFile());
                $product->setWebsiteIds($this->websiteHelper->getIds($inbound_file['channel']));
            }

            $product->addData($attributesToCopy);
            $product->save();

            if($this->getChannel()->getChannelConfig()->simplePriceFromConfigurable()) {
                $this->helper_product->assignPriceToChildFromConfigurable($product, $this->getCodiceConfigurabile());
            }

            $product = $this->processProductCreated($product);
        }

        if (!$this->needConfigurableCreation()) {
            return $this;
        }

        $className = sprintf('\FiloBlu\Flow\Model\Actions\From\%s', ucfirst(str_replace('_', '', '_add_to_configurable')));

        /** @var Configurable $configurableFactory */
        $configurableFactory = $this->configurableFactory->create();

        foreach ($configurableAttributes = explode(',', $this->getConfigurableAttributes() ?? '') as $attributeCode) {
            $attribute = $this->helper_attribute->getAttribute($attributeCode);
            if (!$configurableFactory->canUseAttribute($attribute)) {
                throw new Exception("$attributeCode code is not suitable to be used as configurable attribute");
            }
        }

        $separator = '!';

        if (class_exists($className) && $product->getId()) {
            $specificSpecialClass = $this->objectManager->create($className);
            // Handling multiple parents for a single simple
            $configurableSkus = $this->getCodiceConfigurabile();
            $configurableSkusArray = explode(',', $configurableSkus);
            foreach ($configurableSkusArray as $singleConfigurableSku) {
                $singleConfigurableSku = trim($singleConfigurableSku);
                $value = $singleConfigurableSku . $separator . implode(',', $configurableAttributes);
                $specificSpecialClass->setSeparator($separator)->process($product, $value, 0);
            }
        }

        return $this;
    }

    /**
     * @param ProductInterface $product
     * @return ProductInterface
     */
    public function processProductCreated(ProductInterface $product)
    {
        return $product;
    }

    /**
     * @param ProductInterface $product
     * @return ProductInterface
     */
    public function processProductUpdated(ProductInterface $product)
    {
        return $product;
    }

    /**
     * @throws Exception
     */
    public function checkIndexer()
    {
        $indexers = $this->indexerCollectionFactory
            ->create()
            ->getItems();

        /** @var Indexer $indexer */
        foreach ($indexers as $indexer) {
            if (!$indexer->isScheduled() && $indexer->getViewId() === Processor::INDEXER_ID) {
                throw new Exception(sprintf('Indexer "%s" must be set to "Update on schedule"', $indexer->getViewId()));
            }
        }
    }

    /**
     * Get Attribute Set From Name
     *
     * @param $name
     *
     * @return int|mixed|null
     */
    public function getProductAttributeSetFromName($name)
    {
        $setId = 4;
        if (!empty($name)) {
            $setId = $this->_attributeSets[$name] ?? null;
        }
        return $setId;
    }

    /**
     * Get Tax Class Id From Name
     *
     * @param $name
     *
     * @return int|mixed|null
     */
    public function getProductTaxClassFromName($name)
    {
        $taxClassId = 2;
        //If empty name it returns the default value
        if (!empty($name) && $name !== 'STD') {
            $taxClassId = $this->_taxClasses[$name] ?? null;
        }
        return $taxClassId;
    }

    /**
     * @return bool
     * @throws Exception
     */
    protected function needConfigurableCreation()
    {
        return strcasecmp($this->getCodiceArticolo(), $this->getCodiceConfigurabile()) !== 0;
    }

    /**
     * Get configurable attributes from simple (if there are any)
     *
     * @param $productAttributesData
     * @return array|null
     * @throws Exception
     */
    protected function getAttributeToCopy($productAttributesData)
    {
        $configurable_attributes = [];
        $configurableValues = [];

        foreach (explode(',', $this->getConfigurableValues() ?? '') as $value) {
            if ($value === null || $value === '' || ctype_space($value)) {
                continue;
            }

            $configurableValues[] = $value;
        }

        foreach (explode(',', $this->getConfigurableAttributes() ?? '') as $value) {
            if ($value === null || $value === '' || ctype_space($value)) {
                continue;
            }

            $configurable_attributes[] = $value;
        }

        if (count($configurableValues) !== count($configurable_attributes)) {
            throw  new Exception('Configurable values does not matches configurable attributes');
        }

        if (count($configurable_attributes) > 0) {
            $configurableAttributesData = array_combine($configurable_attributes, $configurableValues);
            foreach ($configurableAttributesData as $code => $label) {
                $optionId = $this->helper_attribute->createOrGetId($code, $label);
                $productAttributesData = array_merge($productAttributesData, [$code => $optionId]);
            }
            return $productAttributesData;
        }

        return null;
    }

    /**
     * @return array
     */
    public function getInitialStockData()
    {
        return [
            'qty'                => 0,
            'is_in_stock'        => 0,
            'use_config_min_qty' => true
        ];
    }

    /**
     * @param $file
     * @return bool
     */
    public function sendErrorNotifications($file)
    {
        return true;
    }
}
