<?php

namespace FiloBlu\Flow\Model\From;

use Exception;
use FiloBlu\Flow\Helper\Attribute;
use FiloBlu\Flow\Model\Inboundflow;
use FiloBlu\Flow\Model\ResourceModel\From\Bundle\CollectionFactory;
use Magento\Bundle\Api\Data\LinkInterfaceFactory;
use Magento\Bundle\Api\Data\OptionInterfaceFactory;
use Magento\Bundle\Api\ProductOptionRepositoryInterface;
use Magento\Bundle\Model\Product\Price;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Catalog\Api\Data\ProductLinkInterfaceFactory;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Catalog\Model\Product\Attribute\Source\Status;
use Magento\Catalog\Model\Product\Type;
use Magento\Catalog\Model\Product\Visibility;
use Magento\Catalog\Model\ProductFactory;
use Magento\Framework\App\ResourceConnection;
use Magento\Framework\Data\Collection\AbstractDb;
use Magento\Framework\Exception\CouldNotSaveException;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\Exception\StateException;
use Magento\Framework\Model\Context;
use Magento\Framework\Model\ResourceModel\AbstractResource;
use Magento\Framework\Registry;
use Magento\Store\Model\StoreManagerInterface;
use Zend_Db;

/**
 * Class Bundle
 * @package FiloBlu\Flow\Model\From
 * @method getBundleSku()
 * @method getBundleName()
 * @method getSimpleSku()
 * @method getBundleOptionName()
 * @method getBundleTypeOption()
 * @method getBundleOptionRequired()
 * @method getBundleOptionDefault()
 * @method getBundleOptionDefaultQty()
 * @method getBundleOptionCanChangeQty()
 * @method getBundlePriceType()
 * @method getBundlePriceView()
 * @method getBundleOptionPosition()
 * @method getBundleGroupPosition()
 */
class Bundle extends AbstractFrom
{
    /**
     * @var int
     */
    const PRICE_VIEW_RANGE = 0;

    /**
     * @var int
     */
    const PRICE_VIEW_AS_LOW_AS = 1;

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

    /**
     * @var ProductRepositoryInterface
     */
    protected $productRepository;

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

    /**
     * @var ProductLinkInterfaceFactory
     */
    protected $productLinkInterfaceFactory;

    /**
     * @var OptionInterfaceFactory
     */
    protected $optionInterfaceFactory;

    /**
     * @var ProductOptionRepositoryInterface
     */
    protected $optionRepository;

    /**
     * @var LinkInterfaceFactory
     */
    protected $linkInterfaceFactory;

    /**
     * @var StoreManagerInterface
     */
    protected $storeManager;

    /**
     * @var ResourceConnection
     */
    protected $resourceConnection;
    /**
     * @var Attribute
     */
    protected $attributeHelper;

    /**
     * @param Context $context
     * @param Registry $registry
     * @param ProductFactory $productFactory
     * @param ProductLinkInterfaceFactory $productLinkInterfaceFactory
     * @param ProductRepositoryInterface $productRepository
     * @param OptionInterfaceFactory $optionInterfaceFactory
     * @param ProductOptionRepositoryInterface $optionRepository
     * @param LinkInterfaceFactory $linkInterfaceFactory
     * @param CollectionFactory $bundleCollectionFactory
     * @param StoreManagerInterface $storeManager
     * @param ResourceConnection $resourceConnection
     * @param Attribute $attributeHelper
     * @param AbstractResource|null $resource
     * @param AbstractDb|null $resourceCollection
     * @param array $data
     * @see \Magento\Bundle\Model\OptionRepositoryTest
     * Bundle constructor.
     */
    public function __construct(
        Context $context,
        Registry $registry,
        ProductFactory $productFactory,
        ProductLinkInterfaceFactory $productLinkInterfaceFactory,
        ProductRepositoryInterface $productRepository,
        OptionInterfaceFactory $optionInterfaceFactory,
        ProductOptionRepositoryInterface $optionRepository,
        LinkInterfaceFactory $linkInterfaceFactory,
        CollectionFactory $bundleCollectionFactory,
        StoreManagerInterface $storeManager,
        ResourceConnection $resourceConnection,
        Attribute $attributeHelper,
        AbstractResource $resource = null,
        AbstractDb $resourceCollection = null,
        array $data = []
    )
    {
        $this->productFactory = $productFactory;
        $this->productRepository = $productRepository;
        $this->bundleCollectionFactory = $bundleCollectionFactory;
        $this->productLinkInterfaceFactory = $productLinkInterfaceFactory;
        $this->optionInterfaceFactory = $optionInterfaceFactory;
        $this->storeManager = $storeManager;
        $this->resourceConnection = $resourceConnection;
        $this->linkInterfaceFactory = $linkInterfaceFactory;
        $this->optionRepository = $optionRepository;
        $this->attributeHelper = $attributeHelper;

        parent::__construct($context, $registry, $resource, $resourceCollection, $data);
    }

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

    /**
     * @param Inboundflow $file
     * @return Inboundflow|mixed|void
     * @throws Exception
     */
    public function processFileRows($file)
    {
        $fileId = $file->getId();
        $connection = $this->resourceConnection->getConnection();
        $table = $connection->getTableName('flow_from_bundle');

        $sql = "SELECT DISTINCT(`bundle_sku`) AS `sku` FROM `{$table}` WHERE `meta_file` = ? AND (`meta_processed` = 0 OR meta_processed IS NULL)";

        foreach ($connection->fetchAll($sql, [$fileId], Zend_Db::FETCH_NUM) as $row) {
            $bundleSku = $row[0];
            $items = $this->bundleCollectionFactory
                ->create()
                ->addFilter('meta_file', $fileId)
                ->addFilter('bundle_sku', $bundleSku)
                ->setOrder('bundle_option_name')
                ->getItems();

            /** @var Bundle $item */
            foreach ($items as $item) {
                try {
                    $this->processItem($item);
                } catch (Exception $exception) {
                    $item->setMetaProcessTime(date('Y-m-d H:i:s'));
                    $item->setMetaProcessed(2);
                    $item->save();
                    $this->_logger->error("Error while processing bundle with sku '{$bundleSku}' on option '{$item->getSimpleSku()}' and bundle flow id {$item->getId()}", ['exception' => $exception]);
                }
            }
        }
    }

    /**
     * @param Bundle $item
     * @throws CouldNotSaveException
     * @throws InputException|NoSuchEntityException
     * @throws Exception
     */
    public function processItem(Bundle $item)
    {
        $this->storeManager->getStore()->setId(0);

        $currentBundle = $this->getOrCreateBundle($item);

        if (!$currentBundle->getId()) {
            $currentBundle->save();
        }

        $bundleSku = $item->getBundleSku();
        $optionName = $item->getBundleOptionName();
        $options = $currentBundle->getExtensionAttributes()->getBundleProductOptions();
        $found = false;

        $channelConfig = $this->getChannel('bundle')->getChannelConfig()->getConfigData();
        $automaticBundleDefaultOptionSet = (bool)$channelConfig['config']['automatic_bundle_default_option_set'];

        if ($options) {
            if ($this->checkIfOptionHasLink($options, $item)) {

                foreach ($options as $k => $option) {
                    $hasDataChanged = 0;
                    if ($option->getTitle() === $optionName) {
                        if (empty($option->getPosition()) || $option->getPosition() != $item->getBundleOptionPosition()) {
                            $options[$k]->setPosition($item->getBundleOptionPosition());
                            $hasDataChanged++;
                        }

                        foreach ($option->getProductLinks() as $link) {
                            if ($link->getSku() == $item->getSimpleSku()) {
                                if ($link->getPosition() != $item->getBundleGroupPosition()) {
                                    $link->setPosition($item->getBundleGroupPosition());
                                    $hasDataChanged++;
                                }

                                if (!$automaticBundleDefaultOptionSet) {
                                    if ($link->getIsDefault() != $item->getBundleOptionDefault()) {
                                        $link->setIsDefault($item->getBundleOptionDefault());
                                        $hasDataChanged++;
                                    }
                                }
                            }
                        }

                        if ($hasDataChanged) {
                            $this->optionRepository->save($currentBundle, $options[$k]);
                        }
                    }
                }

                $item->setMetaProcessTime(date('Y-m-d H:i:s'));
                $item->setMetaProcessed(1);
                $item->save();
                return;
            }

            foreach ($options as $k => $option) {
                if ($option->getTitle() === $optionName) {
                    $found = true;
                    $productLink = $this->linkInterfaceFactory->create();


                    $productLink->setLinkedProductSku($currentBundle->getSku());
                    $productLink->setSku($item->getSimpleSku());
                    $productLink->setIsDefault($item->getBundleOptionDefault());
                    $productLink->setQty($item->getBundleOptionDefaultQty());
                    $productLink->setPosition($item->getBundleGroupPosition());

                    if (empty($links = $options[$k]->getProductLinks())) {
                        $options[$k]->setProductLinks([$productLink]);
                    } else {
                        $links[] = $productLink;
                        $options[$k]->setProductLinks($links);
                    }

                    $options[$k]->setPosition($item->getBundleOptionPosition());

                    $this->optionRepository->save($currentBundle, $options[$k]);
                    break;
                }
            }
        }

        if (!$found) {

            /* Create new option */
            $option = $this->optionInterfaceFactory->create();
            $option->setTitle($optionName);
            $option->setSku($bundleSku);
            $option->setRequired($item->getBundleOptionRequired());
            $option->setType($item->getBundleTypeOption());
            $option->setPosition($item->getBundleOptionPosition());

            $productLink = $this->linkInterfaceFactory->create();
            $productLink->setLinkedProductSku($currentBundle->getSku());
            $productLink->setSku($item->getSimpleSku());
            $productLink->setIsDefault($item->getBundleOptionDefault());
            $productLink->setQty($item->getBundleOptionDefaultQty());
            $productLink->setPosition($item->getBundleGroupPosition());
            $option->setProductLinks([$productLink]);

            $this->optionRepository->save($currentBundle, $option);
        }

        $item->setMetaProcessTime(date('Y-m-d H:i:s'));
        $item->setMetaProcessed(1);
        $item->save();
    }

    /**
     * @param Bundle $item
     * @return ProductInterface
     * @throws Exception
     */
    public function getOrCreateBundle(Bundle $item)
    {
        $sku = $item->getBundleSku();

        try {
            return $this->productRepository->get($sku, false, 0, true);
        } catch (NoSuchEntityException $e) {
            $product = $this->productFactory->create();
            $product->setSku($sku);
            if ($item->getBundleName()) {
                $product->setName($item->getBundleName());
            } else {
                $product->setName($item->getBundleSku());
            }
            $websites = $this->storeManager->getWebsites();
            $websitesIds = [];

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

            $product->setAttributeSetId(4);
            $product->setTypeId(Type::TYPE_BUNDLE);
            $product->setStatus(Status::STATUS_ENABLED);
            $product->setVisibility(Visibility::VISIBILITY_BOTH);
            $product->setPrice(0);
            $product->setTaxClassId(0);
            $product->setShipmentType(0);
            $product->setPriceType($item->getMagentoBundlePriceType());
            $product->setPriceView($item->getMagentoBundlePriceView());
            $product->setWebsiteIds($websitesIds);
            $product->setStoreId(0);
            $product->setStatus(Status::STATUS_DISABLED);
            // TODO : use repository when it will works better
            $product->setStoreId(0);
            return $product;
            // return $this->productRepository->save($product, true);
        }
    }

    /**
     * @return int
     * @throws Exception
     */
    public function getMagentoBundlePriceType()
    {
        switch ($type = $this->getBundlePriceType()) {
            case 'dynamic':
                return Price::PRICE_TYPE_DYNAMIC;
            case 'fixed':
                return Price::PRICE_TYPE_FIXED;
            default:
                throw new Exception("Unknown price type '{$type}'");
        }
    }

    /**
     * @return int
     * @throws Exception
     */
    public function getMagentoBundlePriceView()
    {
        switch ($type = $this->getBundlePriceView()) {
            case 'price_range':
                return self::PRICE_VIEW_RANGE;
            case 'as_low_as':
                return self::PRICE_VIEW_AS_LOW_AS;
            default:
                throw new Exception("Unknown price view type '{$type}'");
        }
    }

    /**
     * @param $options
     * @param Bundle $item
     * @return bool
     */
    protected function checkIfOptionHasLink($options, Bundle $item): bool
    {
        foreach ($options as $option) {
            $optionsProductLinks = $option->getProductLinks();

            if (empty($optionsProductLinks)) {
                return false;
            }

            foreach ($optionsProductLinks as $link) {
                if ($link->getSku() === $item->getSimpleSku()) {
                    return true;
                }
            }
        }

        return false;
    }

    /**
     * @param $file
     * @return mixed
     */
    public function sendErrorNotifications($file)
    {
    }

    /**
     * @return mixed
     */
    public function process()
    {
    }
}
