<?php

declare(strict_types=1);

namespace FiloBlu\Flow\Helper;

use Exception;
use FiloBlu\Flow\Model\From\SharedcatalogPrices;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\Data\TierPriceInterface;
use Magento\Catalog\Api\Data\TierPriceInterfaceFactory;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Api\ProductRepositoryInterfaceFactory;
use Magento\Catalog\Api\TierPriceStorageInterface;
use Magento\Customer\Api\{GroupRepositoryInterface, GroupRepositoryInterfaceFactory};
use Magento\Customer\Api\Data\GroupInterface;
use Magento\Customer\Model\Group;
use Magento\Customer\Model\GroupFactory;
use Magento\Framework\Api\{SearchCriteriaBuilderFactory};
use Magento\Framework\App\Helper\AbstractHelper;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\App\ObjectManager;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\GroupedProduct\Model\Product\Type\Grouped as GroupedModel;
use Magento\GroupedProduct\Model\ResourceModel\Product\Link;
use Magento\SharedCatalog\Api\{ProductManagementInterface, ProductManagementInterfaceFactory};
use Magento\SharedCatalog\Api\Data\{SharedCatalogInterface, SharedCatalogInterfaceFactory};
use Magento\SharedCatalog\Api\SharedCatalogRepositoryInterface;
use Magento\SharedCatalog\Api\SharedCatalogRepositoryInterfaceFactory;
use Magento\Tax\Api\TaxClassRepositoryInterfaceFactory;
use RuntimeException;

/**
 * Class SharedCatalogHelper
 * @package FiloBlu\Flow\Helper
 */
class SharedCatalogHelper extends AbstractHelper
{

    /** @var int */
    const DEFAULT_TAX_CLASS = 3;
    /** @var string[] */
    const CUSTOMER_GROUP_TYPES = ['Not logged in', 'General', 'Wholesale', 'Retailer'];
    /** @var string */
    const SHARED_CATALOG_PRODUCT_ITEMS_TABLE = 'shared_catalog_product_item';

    /** @var array */
    private $sharedCatalogs = [];
    /**
     * @var ResourceConnection
     */
    private $resourceConnection;
    /**
     * @var GroupRepositoryInterfaceFactory
     */
    private $groupRepositoryFactory;
    /**
     * @var SearchCriteriaBuilderFactory
     */
    private $searchCriteriaBuilderFactory;
    /**
     * @var TaxClassRepositoryInterfaceFactory
     */
    private $taxClassRepositoryFactory;
    /**
     * @var GroupFactory
     */
    private $groupFactory;
    /**
     * @var TierPriceInterfaceFactory
     */
    private $tierPriceFactory;
    /**
     * @var ProductRepositoryInterfaceFactory
     */
    private $productRepositoryFactory;
    /**
     * @var TierPriceStorageInterface
     */
    private $tierPriceStorage;
    /**
     * @var ProductManagementInterface
     */
    private $productManagement;
    private $productLinks;
    /**
     * @var GroupedModel
     */
    private $groupedModel;

    /**
     * @param GroupRepositoryInterfaceFactory $groupRepositoryFactory
     * @param GroupedModel $groupedModel
     * @param ProductRepositoryInterfaceFactory $productRepositoryFactory
     * @param TierPriceInterfaceFactory $tierPriceFactory
     * @param TierPriceStorageInterface $tierPriceStorage
     * @param GroupFactory $groupFactory
     * @param TaxClassRepositoryInterfaceFactory $taxClassRepositoryFactory
     * @param Context $context
     * @param SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory
     * @param ResourceConnection $resourceConnection
     */
    public function __construct(
        GroupRepositoryInterfaceFactory $groupRepositoryFactory,
        GroupedModel $groupedModel,
        ProductRepositoryInterfaceFactory $productRepositoryFactory,
        TierPriceInterfaceFactory $tierPriceFactory,
        TierPriceStorageInterface $tierPriceStorage,
        GroupFactory $groupFactory,
        TaxClassRepositoryInterfaceFactory $taxClassRepositoryFactory,
        Context $context,
        SearchCriteriaBuilderFactory $searchCriteriaBuilderFactory,
        ResourceConnection $resourceConnection
    ) {
        parent::__construct($context);
        $this->resourceConnection = $resourceConnection;
        $this->groupRepositoryFactory = $groupRepositoryFactory;
        $this->groupedModel = $groupedModel;
        $this->searchCriteriaBuilderFactory = $searchCriteriaBuilderFactory;
        $this->taxClassRepositoryFactory = $taxClassRepositoryFactory;
        $this->groupFactory = $groupFactory;
        $this->tierPriceFactory = $tierPriceFactory;
        $this->productRepositoryFactory = $productRepositoryFactory;
        $this->tierPriceStorage = $tierPriceStorage;
    }

    /**
     * @return mixed
     */
    public function getSharedCatalogFactory()
    {
        if (!class_exists(SharedCatalogInterfaceFactory::class)) {
            throw new RuntimeException('Installed Magento environment is not able to manage Shared Catalogs');
        }

        return ObjectManager::getInstance()->create(SharedCatalogInterfaceFactory::class);
    }

    /**
     * @param string $sharedCatalogName
     * @return bool
     * @throws LocalizedException
     */
    public function checkIfSharedCatalogExists(string $sharedCatalogName): bool
    {
        $result = $this->getSharedCatalogByName("$sharedCatalogName");

        return !empty($result);
    }

    /**
     * @param string $sharedCatalogName
     * @return false|SharedCatalogInterface
     * @throws LocalizedException
     */
    public function getSharedCatalogByName(string $sharedCatalogName)
    {
        /** @var SharedCatalogRepositoryInterface $sharedCatalogRepository */
        $sharedCatalogRepository = $this->getSharedCatalogRepository();
        $searchCriteria = $this->searchCriteriaBuilderFactory
            ->create()
            ->addFilter('name', $sharedCatalogName)
            ->create();

        return current($sharedCatalogRepository->getList($searchCriteria)->getItems());
    }

    /**
     * @return mixed
     */
    public function getSharedCatalogRepository()
    {
        if (!class_exists(SharedCatalogRepositoryInterfaceFactory::class)) {
            throw new RuntimeException('Current Magento environment is not able to manage Shared Catalogs');
        }

        $sharedCatalogRepositoryFactory = ObjectManager::getInstance()->create(
            SharedCatalogRepositoryInterfaceFactory::class
        );

        return $sharedCatalogRepositoryFactory->create();
    }

    /**
     * @param int|string $rowId
     * @return mixed
     */
    public function getInboundSharedCatalogRows($rowId)
    {
        $connection = $this->resourceConnection->getConnection();
        $mainTable = $connection->getTableName('flow_from_sharedcatalog');
        $select = $connection->select()->where('meta_file = :meta_file');
        $selectSharedCatalogInboundRow = $select->from($mainTable);

        return $connection->fetchAll($selectSharedCatalogInboundRow, ['meta_file' => $rowId]);
    }

    /**
     * @param int $metaFile
     * @return array
     */
    public function getSharedCatalogPricesRows(int $metaFile): array
    {
        $connection = $this->resourceConnection->getConnection();
        $mainTable = $connection->getTableName(SharedCatalogPrices::SHARED_CATALOG_PRICES_TABLE);
        $query = $connection->select()->from($mainTable)->where('meta_file = :meta_file');

        return $connection->fetchAssoc($query, ['meta_file' => $metaFile]);
    }

    /**
     * @param string $customerGroupName
     * @return mixed|false
     * @throws LocalizedException
     */
    public function getCustomerGroupByName(string $customerGroupName)
    {
        if (!in_array($customerGroupName, self::CUSTOMER_GROUP_TYPES)) {
            throw new \Magento\Framework\Exception\RuntimeException(
                sprintf('Given customer Group (%s) is not allowed', $customerGroupName)
            ); // manage
        }

        $groupRepository = $this->groupRepositoryFactory->create();
        $searchCriteria = $this->searchCriteriaBuilderFactory
            ->create()
            ->addFilter('customer_group_code', $customerGroupName)
            ->create();

        $itemsList = $groupRepository->getList($searchCriteria)->getItems();

        return current($itemsList);
    }

    /**
     * @param array $sharedCatalogRow
     * @return Group
     * @throws Exception
     */
    public function createCustomerGroup(array $sharedCatalogRow): Group
    {
        $group = $this->groupFactory->create();
        $taxClassId = $this->getTaxClass($sharedCatalogRow['tax_class']);
        $group->setCode($sharedCatalogRow['customer_group_code'])->setTaxClassId($taxClassId)->save();

        return $group->getEntityId();
    }

    /**
     * @param $taxClass
     * @return int
     * @throws InputException
     */
    public function getTaxClass($taxClass): int
    {
        $field = is_numeric($taxClass) ? 'class_id' : 'class_name';
        $taxClassRepository = $this->taxClassRepositoryFactory->create();
        $searchCriteria = $this->searchCriteriaBuilderFactory
            ->create()
            ->addFilter($field, $taxClass)
            ->setPageSize(1)
            ->create();

        $itemsList = $taxClassRepository->getList($searchCriteria)->getItems();
        $taxClass = current($itemsList);
        if (empty($taxClass)) {
            return self::DEFAULT_TAX_CLASS;
        }

        return (int)$taxClass->getId();
    }

    /**
     * @param $sharedCatalogName
     * @param array $productRows
     * @return int[]
     * @throws LocalizedException
     */
    public function processSharedCatalog($sharedCatalogName, array $productRows): array
    {
        if (!isset($this->productManagement)) {
            $this->productManagement = $this->getProductManagement();
        }

        /** @var SharedCatalogInterface $sharedCatalog */
        $sharedCatalog = $this->getSharedCatalogByName("$sharedCatalogName");
        if (empty($sharedCatalog)) {
            // customer group not found
            return [];
        }

        $customerGroupIds = [(int)$sharedCatalog->getCustomerGroupId()];
        if ($sharedCatalog->getType() == SharedCatalogInterface::TYPE_PUBLIC) {
            $customerGroupIds[] = GroupInterface::NOT_LOGGED_IN_ID;
        }

        $searchCriteria = $this->searchCriteriaBuilderFactory
            ->create()
            ->addFilter('sku', array_keys($productRows), 'in')
            ->create();

        /** @var ProductRepositoryInterface $productRepository */
        $productRepository = $this->productRepositoryFactory->create();
        $items = $productRepository->getList($searchCriteria)->getItems();

        $this->productManagement->assignProducts($sharedCatalog->getId(), $items);

        foreach ($customerGroupIds as $customerGroupId) {
            $prices = [];
            /** @var GroupRepositoryInterface $groupRepository */
            $groupRepository = $this->groupRepositoryFactory->create();
            $groupCode = $groupRepository->getById($customerGroupId)->getCode();
            foreach ($items as $product) {
                $productRowData = $productRows[$product->getSku()];
                $quantity = (int)$productRowData['quantity'] ? $productRowData['quantity'] : 1; // default value
                $prices[] = $this->tierPriceFactory->create(
                    [
                        'data' => [
                            TierPriceInterface::CUSTOMER_GROUP => $groupCode,
                            TierPriceInterface::PRICE          => $productRowData['price'],
                            TierPriceInterface::SKU            => $product->getSku(),
                            TierPriceInterface::WEBSITE_ID     => $sharedCatalog->getStoreId(),
                            TierPriceInterface::QUANTITY       => $quantity,
                            TierPriceInterface::PRICE_TYPE     => $productRowData['price_type']
                        ]
                    ]
                );
            }

            $this->tierPriceStorage->update($prices);
        }

        return $customerGroupIds;
    }

    /**
     * @return mixed
     */
    public function getProductManagement()
    {
        if (!class_exists(ProductManagementInterfaceFactory::class)) {
            throw new RuntimeException('Current Magento environment is not able to manage Shared Catalogs');
        }

        $sharedCatalogRepositoryInterfaceFactory = ObjectManager::getInstance()->create(
            ProductManagementInterfaceFactory::class
        );

        return $sharedCatalogRepositoryInterfaceFactory->create();
    }


    /**
     * @throws NoSuchEntityException
     * @throws LocalizedException
     */
    public function assignGroupedToSharedCatalog(array $ids = [])
    {
        $searchCriteria = $this->searchCriteriaBuilderFactory
            ->create()
            ->addFilter('type_id', GroupedModel::TYPE_CODE);

        if (count($ids) > 0) {
            $searchCriteria->addFilter('entity_id', $ids, 'in');
        }

        /** @var ProductRepositoryInterface $productRepository */
        $productRepository = $this->productRepositoryFactory->create();
        $groupedList = $productRepository->getList($searchCriteria->create())->getItems();
        $connection = $this->resourceConnection->getConnection();
        $mainTable = $connection->getTableName(self::SHARED_CATALOG_PRODUCT_ITEMS_TABLE);
        $query = $connection->select()
            ->from(['sc' => 'shared_catalog'], ['name'])
            ->joinLeft(['scpi' => $connection->getTableName(self::SHARED_CATALOG_PRODUCT_ITEMS_TABLE)],
                'sc.customer_group_id = scpi.customer_group_id')
            ->where('scpi.sku = :sku');

        foreach ($groupedList as $grouped) {
            if ($grouped->getId() == null) {
                continue;
            }

            $childrenArray = $this->groupedModel->getChildrenIds($grouped->getId());
            if (empty($childrenArray)) {
                continue;
            }

            $sharedCatalogNames = $this->getAllSharedCatalogsOfProductArray(
                $childrenArray[Link::LINK_TYPE_GROUPED],
                $connection,
                $query
            );

            $this->addGroupedToSharedCatalog($grouped, $sharedCatalogNames);
        }
    }

    /**
     * @param array $childrenArray
     * @param $connection
     * @param $query
     * @return array
     * @throws NoSuchEntityException
     */
    public function getAllSharedCatalogsOfProductArray(array $childrenArray, $connection, $query): array
    {
        $sharedCatalogs = [];
        foreach ($childrenArray as $childId) {
            $child = $this->productRepositoryFactory->create()->getById($childId);
            if ($child->getId() == null) {
                continue;
            }

            $productSharedCatalogs = $connection->fetchAssoc($query, ['sku' => $child->getSku()]);
            if (empty($productSharedCatalogs)) {
                continue;
            }

            foreach (array_keys($productSharedCatalogs) as $sharedCatalogName) {
                if (!in_array($sharedCatalogName, $sharedCatalogs)) {
                    $sharedCatalogs[] = (string)$sharedCatalogName;
                }
            }
        }

        return $sharedCatalogs;
    }

    /**
     * @param ProductInterface $grouped
     * @param array $sharedCatalogNames
     * @return true
     * @throws LocalizedException
     */
    public function addGroupedToSharedCatalog(ProductInterface $grouped, array $sharedCatalogNames): bool
    {
        if (!isset($this->productManagement)) {
            $this->productManagement = $this->getProductManagement();
        }

        foreach ($sharedCatalogNames as $sharedCatalogName) {
            if (!isset($this->sharedCatalogs[$sharedCatalogName])) {
                $this->sharedCatalogs[$sharedCatalogName] = $this->getSharedCatalogByName($sharedCatalogName);
            }

            $this->productManagement->assignProducts($this->sharedCatalogs[$sharedCatalogName]->getId(), [$grouped]);
            /*$tierPrice = $this->tierPriceFactory->create(
                [
                    'data' => [
                        TierPriceInterface::CUSTOMER_GROUP => $this->sharedCatalogs[$sharedCatalogName],
                        TierPriceInterface::PRICE => 0,//$productRowData['price'],
                        TierPriceInterface::SKU => $grouped->getSku(),
                        TierPriceInterface::WEBSITE_ID => $this->sharedCatalogs[$sharedCatalogName]->getStoreId(),
                        TierPriceInterface::QUANTITY => 0,
                        TierPriceInterface::PRICE_TYPE => 'fixed'//$productRowData['price_type']
                    ]
                ]);*/
        }

        return true;
    }
}

