<?php
/**
 * @author Aitoc Team
 * @copyright Copyright (c) 2020 Aitoc (https://www.aitoc.com)
 * @package Aitoc_PreOrders
 */

/**
 * Copyright © 2018 Aitoc. All rights reserved.
 * Developed by Alex Yakimovich
 */

namespace Aitoc\PreOrders\Observer;

use Magento\Framework\Event\Observer;
use Magento\Framework\Event\ObserverInterface;
use Aitoc\PreOrders\Helper\PreOrder as PreOrderHelper;
use Aitoc\PreOrders\Helper\Settings as SettingsHelper;
use Magento\Framework\Exception\LocalizedException;
use Magento\Catalog\Api\Data\ProductInterface;
use Magento\Framework\Message\ManagerInterface;
use Aitoc\PreOrders\Api\Data\Source\CartTypeInterface;
use Magento\Framework\Registry;
use Magento\Store\Model\StoreManagerInterface;
use Magento\Catalog\Api\ProductRepositoryInterface;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\App\RequestInterface;
use Aitoc\PreOrders\Api\Data\Source\RegistryNameInterface;
use Magento\Catalog\Model\ResourceModel\Eav\Attribute as AttributeResource;
use Aitoc\PreOrders\Api\Data\Source\PaymentTypeInterface;

/**
 * Class ValidateAddToCart
 */
class ValidateAddToCart implements ObserverInterface
{
    /**
     * @var SettingsHelper
     */
    private $settingsHelper;

    /**
     * @var PreOrderHelper
     */
    private $preOrderHelper;

    /**
     * @var ManagerInterface
     */
    private $messageManager;

    /**
     * @var Registry
     */
    private $registry;

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

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

    /**
     * @var RequestInterface
     */
    private $request;

    /**
     * @var AttributeResource
     */
    private $attributeResource;

    /**
     * @param SettingsHelper $settingsHelper
     * @param PreOrderHelper $preOrderHelper
     * @param ManagerInterface $messageManager
     * @param Registry $registry
     * @param StoreManagerInterface $storeManager
     * @param ProductRepositoryInterface $productRepository
     * @param RequestInterface $request
     * @param AttributeResource $attributeResource
     */
    public function __construct(
        SettingsHelper $settingsHelper,
        PreOrderHelper $preOrderHelper,
        ManagerInterface $messageManager,
        Registry $registry,
        StoreManagerInterface $storeManager,
        ProductRepositoryInterface $productRepository,
        RequestInterface $request,
        AttributeResource $attributeResource
    ) {
        $this->settingsHelper = $settingsHelper;
        $this->preOrderHelper = $preOrderHelper;
        $this->messageManager = $messageManager;
        $this->registry = $registry;
        $this->storeManager = $storeManager;
        $this->productRepository = $productRepository;
        $this->request = $request;
        $this->attributeResource = $attributeResource;
    }

    /**
     * Validate product after adding to cart
     *
     * @param Observer $observer
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function execute(Observer $observer)
    {
        $productId = $this->request->getParam('product');
        $product = $this->getProduct($productId);
        if (!$product) {
            return;
        }

        if ($this->settingsHelper->getPaymentTypeSetting() == PaymentTypeInterface::TYPE_NOTIFICATION
            && $this->isProductPreOrder($product)
        ) {
            $this->processAddingRestrictionError();
        }


        $isMixedProductRestricted = (bool)$this->settingsHelper->getMixedProductRestrictionSetting();
        if (!$isMixedProductRestricted) {
            return;
        }

        $this->checkMixedGroupedProduct($product);

        $cartType = $this->getCartType();
        if ($cartType !== false) {
            switch ($cartType) {
                case CartTypeInterface::TYPE_REGULAR:
                    $this->processRegularAdding($product);
                    break;
                case CartTypeInterface::TYPE_PRE_ORDER:
                    $this->processPreOrderAdding($product);
                    break;
                case CartTypeInterface::TYPE_MIXED:
                    $this->processMixedAdding();
                    break;
            }
        }
    }

    /**
     * @param ProductInterface $product
     * @throws LocalizedException
     */
    private function processPreOrderAdding(ProductInterface $product)
    {
        if (!$this->isProductPreOrder($product)) {
            $this->processError();
        }
    }

    /**
     * @param ProductInterface $product
     * @throws LocalizedException
     */
    private function processRegularAdding(ProductInterface $product)
    {
        if ($this->isProductPreOrder($product)) {
            $this->processError();
        }
    }

    /**
     * Add notice after adding product to mixed cart
     */
    private function processMixedAdding()
    {
        $this->messageManager->addNoticeMessage("Shopping cart contain both Regular and Pre-Order products!");
    }

    /**
     * @param ProductInterface $product
     * @throws LocalizedException
     */
    private function checkMixedGroupedProduct(ProductInterface $product)
    {
        if ($product->getTypeId() == 'grouped' && $this->isMixedGroupedProduct()) {
            $this->processError();
        }
    }

    /**
     * @param ProductInterface $product
     * @return bool
     * @throws LocalizedException
     */
    private function isProductPreOrder(ProductInterface $product)
    {
        switch ($product->getTypeId()) {
            case 'bundle':
                $isPreOrder = $this->checkBundleProduct($product);
                break;

            case 'configurable':
                $isPreOrder = $this->checkConfigurableProduct($product);
                break;

            case 'grouped':
                $isPreOrder = $this->checkGroupedProduct();
                break;

            default:
                $isPreOrder = $this->preOrderHelper->isProductPreOrder($product->getId());
                break;
        }
        return $isPreOrder;
    }

    private function checkConfigurableProduct(ProductInterface $product)
    {
        $isPreOrder = false;
        $realProductId = false;
        $attributeValueMap = [];
        $superAttributesArray = $this->request->getParam('super_attribute');
        foreach ($superAttributesArray as $attributeId => $value) {
            $this->attributeResource->load($attributeId);
            $code = $this->attributeResource->getAttributeCode();
            $attributeValueMap[$code] = $value;
        }
        $childProducts = $product->getTypeInstance(true)->getUsedProducts($product);
        foreach ($childProducts as $childProduct) {
            $successFind = true;
            foreach ($attributeValueMap as $code => $value) {
                $attributeValue = $childProduct->getData($code);
                if ($attributeValue != $value) {
                    $successFind = false;
                }
            }
            if ($successFind) {
                $realProductId = $childProduct->getId();
                break;
            }
        }

        if ($realProductId) {
            $isPreOrder = $this->preOrderHelper->isProductPreOrder($realProductId);
        }
        return $isPreOrder;
    }

    private function checkBundleProduct(ProductInterface $product)
    {
        $isPreOrder = false;
        $bundleOptionsCollection = $product
            ->getTypeInstance(true)
            ->getSelectionsCollection(
                $product->getTypeInstance(true)->getOptionsIds($product),
                $product);
        $selectedOptions = $this->request->getParam('bundle_option');

        foreach ($selectedOptions as $optionId) {
            $childProduct = $bundleOptionsCollection->getItemById($optionId);
            $isPreOrder = $this->preOrderHelper->isProductPreOrder($childProduct->getId());
            if ($isPreOrder) {
                break;
            }
        }
        return $isPreOrder;
    }

    private function checkGroupedProduct()
    {
        $isPreOrder = false;
        $groupedOptions = $this->request->getParam('super_group');

        foreach ($groupedOptions as $childProductId => $qty) {
            if ($qty == 0) {
                continue;
            }

            $isPreOrder = $this->preOrderHelper->isProductPreOrder($childProductId);
            if ($isPreOrder) {
                break;
            }
        }

        return $isPreOrder;
    }

    private function isMixedGroupedProduct()
    {
        $containsRegular = false;
        $containsPreOrder = false;
        $groupedOptions = $this->request->getParam('super_group');

        foreach ($groupedOptions as $childProductId => $qty) {
            if ($qty == 0) {
                continue;
            }

            $isPreOrder = $this->preOrderHelper->isProductPreOrder($childProductId);
            if ($isPreOrder) {
                $containsPreOrder = true;
            } else {
                $containsRegular = true;
            }
        }

        return $containsPreOrder && $containsRegular;
    }

    /**
     * @throws LocalizedException
     */
    private function processError()
    {
        throw new LocalizedException(__("Shopping cart can not contain both Regular and Pre-Order products."));
    }

    /**
     * @throws LocalizedException
     */
    private function processAddingRestrictionError()
    {
        throw new LocalizedException(__("This product is in Pre-Order state and can't be added to cart."));
    }


    /**
     * @return int|bool
     */
    private function getCartType()
    {
        return $this->registry->registry(RegistryNameInterface::CURRENT_CART_TYPE) ?: false;
    }

    /**
     * @param int $productId
     * @return bool|ProductInterface
     */
    private function getProduct($productId)
    {
        if ($productId) {
            $storeId = $this->storeManager->getStore()->getId();
            try {
                return $this->productRepository->getById($productId, false, $storeId);
            } catch (NoSuchEntityException $e) {
                return false;
            }
        }
        return false;
    }
}
