<?php

namespace FiloBlu\Flow\Helper;

use Exception;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Model\AbstractModel;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\ProductRepository;
use Magento\Catalog\Model\ResourceModel\Product\CollectionFactory;
use Magento\ConfigurableProduct\Model\ResourceModel\Product\Type\Configurable;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\DataObject;
use Magento\Framework\DB\Adapter\AdapterInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Module\Manager;
use Magento\Store\Model\ScopeInterface;
use Monolog\Logger;
use Magento\Catalog\Model\ProductFactory;
use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
use Magento\Framework\EntityManager\MetadataPool;
use Zend_Db_Expr;

/**
 * Class Product
 * @package FiloBlu\Flow\Helper
 */
class Product extends AbstractHelper
{
    /**
     * @var string
     */
    public const CONFIG_FLOW_ATTRIBUTE_PATH = 'filoblu_flow/flow_process/flow_attribute_select';
    /**
     * @var string
     */
    public const CONFIG_FLOW_ASSIGN_FULL_TREE_PATH = 'filoblu_flow/flow_process/flow_assign_full_tree_category';
    /**
     * @var string
     */
    public const CONFIG_FLOW_ADD_TO_ALL_WEBSITES = 'filoblu_flow/flow_ite/add_to_websites';
    /**
     * @var string
     */
    public const CONFIG_FLOW_ITE_COPY_NAME_TO_DESCRIPTION = 'filoblu_flow/flow_ite/copy_name_in_description';
    /**
     * @var string
     */
    public const CONFIG_FLOW_ITE_COPY_NAME_TO_SHORT_DESCRIPTION = 'filoblu_flow/flow_ite/copy_name_in_short_description';
    /**
     * @var string
     */
    public const CONFIG_FLOW_ITE_DISABLE_SHOP_STATUS = 'filoblu_flow/flow_ite/disable_shop_status';
    /**
     * @var string
     */
    public const CONFIG_FLOW_ITE_HIDE_PRICE_STATUS = 'filoblu_flow/flow_ite/hide_price_status';

    /**
     * @var string
     */
    public const CONFIG_FLOW_GROUPED_ATTRIBUTE_PATH = 'filoblu_flow/flow_process/flow_attribute_togrouped';

    /**
     * @var string
     */
    public const CONFIG_FLOW_REPLACEWEBSITE_ATTRIBUTE_PATH = 'filoblu_flow/flow_process/flow_attribute_tochangewebsite';

    /**
     * @var ProductMetadataInterface
     */
    protected $_productMetadata;

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

    /**
     * @var
     */
    protected $connection;

    /**
     * @var Logger
     */
    protected $_logger;

    /**
     * @var ProductRepository
     */
    protected $_productRepository;

    /**
     * @var Manager
     */
    protected $_moduleManager;

    /**
     * @var CollectionFactory
     */
    protected $productCollectionFactory;

    /**
     * @var Configurable
     */
    private $configurableProduct;

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

    /**
     * @var ProductAttributeRepositoryInterface
     */
    private $attributeRepository;

    /**
     * @var MetadataPool
     */
    private $metadataPool;

    /**
     * Product constructor.
     * @param Context $context
     * @param ProductMetadataInterface $productMetadata
     * @param ResourceConnection $resource
     * @param LoggerProvider $loggerProvider
     * @param CollectionFactory $productCollectionFactory
     * @param ProductRepository $productRepository
     * @param Configurable $configurableProduct
     * @param ProductFactory $productFactory
     * @param ProductAttributeRepositoryInterface $attributeRepository
     * @param MetadataPool $metadataPool
     */
    public function __construct(
        Context                             $context,
        ProductMetadataInterface            $productMetadata,
        ResourceConnection                  $resource,
        LoggerProvider                      $loggerProvider,
        CollectionFactory                   $productCollectionFactory,
        ProductRepository                   $productRepository,
        Configurable                        $configurableProduct,
        ProductFactory                      $productFactory,
        ProductAttributeRepositoryInterface $attributeRepository,
        MetadataPool                        $metadataPool
    ) {
        $this->scopeConfig = $context->getScopeConfig();
        $this->_resource = $resource;
        $this->_productMetadata = $productMetadata;
        $this->_logger = $loggerProvider->getLogger();
        $this->_productRepository = $productRepository;
        $this->_moduleManager = $context->getModuleManager();
        $this->productCollectionFactory = $productCollectionFactory;
        $this->configurableProduct = $configurableProduct;
        $this->productFactory = $productFactory;
        $this->attributeRepository = $attributeRepository;
        $this->metadataPool = $metadataPool;
        parent::__construct($context);

    }

    /**
     * Do I need to assign the product to the full category tree?
     * @return bool
     */
    public function assignFullTree()
    {
        return (bool)$this->scopeConfig->getValue(
            self::CONFIG_FLOW_ASSIGN_FULL_TREE_PATH,
            ScopeInterface::SCOPE_WEBSITE
        );
    }

    /**
     * @return mixed
     */
    public function getCopyNameInDescription()
    {
        return $this->scopeConfig->getValue(
            self::CONFIG_FLOW_ITE_COPY_NAME_TO_DESCRIPTION,
            ScopeInterface::SCOPE_WEBSITE
        );
    }

    /**
     * @return mixed
     */
    public function getCopyNameInShortDescription()
    {
        return $this->scopeConfig->getValue(
            self::CONFIG_FLOW_ITE_COPY_NAME_TO_SHORT_DESCRIPTION,
            ScopeInterface::SCOPE_WEBSITE
        );
    }

    /**
     * @return bool
     */
    public function isDisableShopModuleEnabled()
    {
        return $this->_moduleManager->isEnabled('FiloBlu_DisableShop');
    }

    /**
     * @return mixed
     */
    public function getDisableShopStatus()
    {
        return $this->scopeConfig->getValue(self::CONFIG_FLOW_ITE_DISABLE_SHOP_STATUS,
            ScopeInterface::SCOPE_WEBSITE);
    }

    /**
     * @return mixed
     */
    public function getHidePriceStatus()
    {
        return $this->scopeConfig->getValue(self::CONFIG_FLOW_ITE_HIDE_PRICE_STATUS,
            ScopeInterface::SCOPE_WEBSITE);
    }

    /**
     * @return bool
     */
    public function createNewProducts()
    {
        return true;
    }

    /**
     * @return int
     */
    public function getInitialProductStatus()
    {
        return Status::STATUS_DISABLED;
    }

    /**
     * @return mixed
     */
    public function getAddToWebsites()
    {
        return $this->scopeConfig->getValue(self::CONFIG_FLOW_ADD_TO_ALL_WEBSITES);
    }

    /**
     * @param $childProducts
     * @return mixed
     * @throws Exception
     */
    public function getParentProductsIds($childProducts)
    {
        $relationTableName = $this->getConnection()->getTableName('catalog_product_super_link');

        $products = $this->productCollectionFactory->create();

        $products->getSelect()->join(['super_link' => $relationTableName], 'e.entity_id = super_link.parent_id');
        $products->getSelect()->where('super_link.product_id IN (?)', $childProducts);
        //$products->addFieldToFilter('type_id', $productType);
        $products->getSelect()->group('e.entity_id');

        $result = $products->getAllIds();

        // Need to convert row_id into entity_id
        if ($this->isEnterpriseEdition() && count($result)) {
            foreach ($result as $key => $value) {
                if (!$value) {
                    continue;
                }

                try {
                    $entity_id = $this->getConnection()->fetchRow(
                        'SELECT entity_id FROM catalog_product_entity WHERE row_id = ?',
                        [$value]
                    );
                } catch (Exception $e) {
                    $message = "Can't find entity_id from row_id '{$value}')";
                    $this->_logger->info($message);
                    throw new Exception($e->getMessage());
                }

                if (isset($entity_id['entity_id']) && $entity_id['entity_id'] > 0) {
                    $result[$key] = $entity_id['entity_id'];
                }
            }
        }

        return $result;
    }

    /**
     * @return AdapterInterface
     */
    public function getConnection()
    {
        if (!$this->connection) {
            $this->connection = $this->_resource->getConnection();
        }
        return $this->connection;
    }

    /**
     *
     * @return bool
     */
    protected function isEnterpriseEdition()
    {
        return ($this->_productMetadata->getEdition() === 'Enterprise') ? true : false;
    }

    /**
     * @return array
     */
    public function getSimpleToConfigurableSyncAttributes()
    {
        $attributesToCopy = $this->scopeConfig->getValue(self::CONFIG_FLOW_ATTRIBUTE_PATH,
            ScopeInterface::SCOPE_WEBSITE);

        if (!empty($attributesToCopy)) {
            return explode(',', $attributesToCopy);
        }

        return [];
    }

    /**
     * @return bool
     */
    public function getUpdateConfigurablePrice()
    {
        return false;
    }

    /**
     * Get Configurable Childs by Sku
     *
     * @param $sku
     *
     * @return array
     */
    public function getConfigurableChildsBySku($sku)
    {
        $simples = [];

        try {
            // Get Product
            $product = $this->_productRepository->get($sku);

            //If product is configurable
            if ($product->getTypeId() === 'configurable') {
                $attributes = $product->getTypeInstance()->getConfigurableOptions($product);
                //Get childs for super attribute
                foreach ($attributes as $attribute) {
                    foreach ($attribute as $child) {
                        $simples[] = $child['sku'];
                    }
                }
            } //if is simple
            else {
                $simples[] = $sku;
            }
        } catch (Exception $e) {
            $this->_logger->info($e->getMessage() . ' for sku ' . $sku);
            return [];
        }

        return $simples;
    }

    /**
     * @param $initialArray
     * @param $arrayToSubtract
     * @return array
     */
    public function arrayDiff($initialArray, $arrayToSubtract): array
    {
        return array_diff($initialArray, $arrayToSubtract);
    }

    /**
     * get configurable attributes used in the project
     *
     * @return array
     */
    public function getSuperAttributesArray(): array
    {
        $superAttributesTable = $this->getConnection()->getTableName('catalog_product_super_attribute');
        $eavAttributeTable = $this->getConnection()->getTableName('eav_attribute');

        $result = $this->getConnection()->fetchAll(
            "SELECT DISTINCT
                attribute_code
            FROM
                {$superAttributesTable} AS sa
            LEFT JOIN
                {$eavAttributeTable} AS ea
            ON
                sa.attribute_id = ea.attribute_id"
        );

        $list = [];
        foreach ($result as $attribute) {
            $list[] = $attribute['attribute_code'];
        }

        return $list;
    }

    /**
     * @param ProductInterface $child
     * @return array|DataObject[]
     */
    public function getParentsByChild(ProductInterface $child)
    {
        $parents = [];
        $parentIds = $this->configurableProduct->getParentIdsByChild($child->getId());

        if (!empty($parentIds)) {
            return $this->productCollectionFactory->create()
                ->setStore($child->getStore())
                ->addAttributeToSelect('*')
                ->addIdFilter($parentIds)
                ->getitems();
        }

        return $parents;
    }

    /**
     * @return array
     */
    public function getSimpleAttributesSyncToGrouped()
    {
        $attributesToCopy = $this->scopeConfig->getValue(
            self::CONFIG_FLOW_GROUPED_ATTRIBUTE_PATH,
            ScopeInterface::SCOPE_WEBSITE
        );

        if (!empty($attributesToCopy)) {
            return explode(',', $attributesToCopy);
        }

        return [];
    }

    /**
     * @param $childId
     * @return bool|AbstractModel
     */
    public function getGroupedIdByChildId($childId) {

        $product = $this->productFactory->create()->setStoreId(0)->load($childId);
        if($product->getId()) {
            $grouped = $this->productFactory->create()->setStoreId(0)->loadByAttribute('sku', $product->getData('article_code'));
            if($grouped) {
                return $grouped;
            }
        }
        return false;
    }

    /**
     * @return array
     */
    public function getAttributesExcludedToChangeWebsite()
    {
        $attributesToExclude = $this->scopeConfig->getValue(
            self::CONFIG_FLOW_REPLACEWEBSITE_ATTRIBUTE_PATH,
            ScopeInterface::SCOPE_WEBSITE
        );

        if (!empty($attributesToExclude)) {
            return explode(',', $attributesToExclude);
        }

        return [];
    }

    /**
     * @return ScopeConfigInterface
     */
    public function getScopeConfig(): ScopeConfigInterface
    {
        return $this->scopeConfig;
    }


    /**
     * @param ProductInterface $simple
     * @param string $configurableSku
     * @return void
     * @throws NoSuchEntityException
     * @throws Exception
     */
    public function assignPriceToChildFromConfigurable(ProductInterface $simple, string $configurableSku)
    {
        $connection = $this->_resource->getConnection();
        $decimalTable = $this->_resource->getTableName('catalog_product_entity_decimal');
        $linkTable    = $this->_resource->getTableName('catalog_product_super_link');
        $entityTable  = $this->_resource->getTableName('catalog_product_entity');

        $metadata = $this->metadataPool->getMetadata(ProductInterface::class);
        $linkField = $metadata->getLinkField();
        $priceAttrId = $this->attributeRepository->get('price')->getAttributeId();
        $targetLinkId = $simple->getData($linkField);

        $select = $connection->select()
            ->from(['parent_e' => $entityTable], [])
            ->join(['links' => $linkTable], 'links.parent_id = parent_e.entity_id', [])
            ->join(['child_e' => $entityTable], 'links.product_id = child_e.entity_id', [])
            ->join(['s_prices' => $decimalTable], "child_e.$linkField = s_prices.$linkField", [
                $linkField     => new Zend_Db_Expr($targetLinkId), // Il row_id del figlio target
                'attribute_id' => new Zend_Db_Expr($priceAttrId),
                'store_id'     => 's_prices.store_id',
                'value'        => 'MAX(s_prices.value)'
            ])
            ->where('parent_e.sku = ?', $configurableSku)
            ->where('s_prices.attribute_id = ?', $priceAttrId)
            ->where('links.product_id <> ?', $simple->getId()) // Escludiamo il prodotto stesso
            ->group('s_prices.store_id');

        $query = $connection->insertFromSelect(
            $select,
            $decimalTable,
            [$linkField, 'attribute_id', 'store_id', 'value'],
            AdapterInterface::INSERT_ON_DUPLICATE
        );

        $connection->query($query);
    }

}
