<?php
/**
 * Mageplaza
 *
 * NOTICE OF LICENSE
 *
 * This source file is subject to the Mageplaza.com license that is
 * available through the world-wide-web at this URL:
 * https://www.mageplaza.com/LICENSE.txt
 *
 * DISCLAIMER
 *
 * Do not edit or add to this file if you wish to upgrade this extension to newer
 * version in the future.
 *
 * @category    Mageplaza
 * @package     Mageplaza_EditOrder
 * @copyright   Copyright (c) Mageplaza (https://www.mageplaza.com/)
 * @license     https://www.mageplaza.com/LICENSE.txt
 */

namespace Mageplaza\EditOrder\Helper;

use Exception;
use Magento\Backend\Model\Session\Quote;
use Magento\Catalog\Model\Product\Type as ProductType;
use Magento\CatalogInventory\Api\StockRegistryInterface;
use Magento\CatalogInventory\Model\Stock;
use Magento\ConfigurableProduct\Model\Product\Type\Configurable as Configurable;
use Magento\Directory\Model\CurrencyFactory;
use Magento\Framework\App\Helper\Context;
use Magento\Framework\App\ProductMetadataInterface;
use Magento\Framework\App\RequestInterface;
use Magento\Framework\Exception\InputException;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Exception\NoSuchEntityException;
use Magento\Framework\ObjectManagerInterface;
use Magento\InventoryConfigurationApi\Api\GetStockItemConfigurationInterface;
use Magento\InventoryConfigurationApi\Exception\SkuIsNotAssignedToStockException;
use Magento\InventoryReservationsApi\Model\GetReservationsQuantityInterface;
use Magento\InventorySalesAdminUi\Model\GetSalableQuantityDataBySku as GetSalableQuantityDataBySkuAlias;
use Magento\InventorySalesApi\Api\Data\ProductSalableResultInterfaceFactory;
use Magento\InventorySalesApi\Model\GetStockItemDataInterface;
use Magento\Quote\Model\Quote\Item\AbstractItem;
use Magento\Sales\Model\AdminOrder\Create;
use Magento\Sales\Model\Order;
use Magento\Store\Model\ScopeInterface;
use Magento\Store\Model\StoreManagerInterface;
use Mageplaza\Core\Helper\AbstractData;
use Mageplaza\EditOrder\Model\ResourceModel\Logs\Collection;

class Data extends AbstractData
{
    const CONFIG_MODULE_PATH = 'mpeditorder';

    /**
     * @var Order
     */
    protected $_order;

    /**
     * @var RequestInterface
     */
    protected $_request;

    /**
     * @var Create
     */
    protected $orderCreate;
    /**
     * @var GetStockItemDataInterface
     */
    protected $getStockItemData;
    /**
     * @var ProductSalableResultInterfaceFactory
     */
    protected $productSalableResultFactory;

    /**
     * @var StockRegistryInterface
     */
    protected $stockRegistry;

    /**
     * @var GetSalableQuantityDataBySkuAlias
     */
    protected $_getSalableQuantityDataBySku;

    /**
     * @var GetReservationsQuantityInterface
     */
    protected $getReservationsQuantity;

    /**
     * @var Quote
     */
    public $quoteSession;

    /**
     * @var GetStockItemConfigurationInterface
     */
    protected $getStockItemConfiguration;

    /**
     * @var CurrencyFactory
     */
    protected $currencyFactory;

    /**
     * @var Currency
     */
    protected $currency;

    /**
     * @var Collection
     */
    protected $logCollection;

    /**
     * @param Context $context
     * @param ObjectManagerInterface $objectManager
     * @param StoreManagerInterface $storeManager
     * @param RequestInterface $request
     * @param Order $order
     * @param Create $orderCreate
     * @param GetStockItemDataInterface $getStockItemData
     * @param ProductSalableResultInterfaceFactory $productSalableResultFactory
     * @param GetSalableQuantityDataBySkuAlias $getSalableQuantityDataBySku
     * @param GetReservationsQuantityInterface $getReservationsQuantity
     * @param GetStockItemConfigurationInterface $getStockItemConfiguration
     * @param Quote $quoteSession
     * @param StockRegistryInterface $stockRegistry
     * @param CurrencyFactory $currencyFactory
     * @param Collection $logCollection
     */
    public function __construct(
        Context $context,
        ObjectManagerInterface $objectManager,
        StoreManagerInterface $storeManager,
        RequestInterface $request,
        Order $order,
        Create $orderCreate,
        GetStockItemDataInterface $getStockItemData,
        ProductSalableResultInterfaceFactory $productSalableResultFactory,
        GetSalableQuantityDataBySkuAlias $getSalableQuantityDataBySku,
        GetReservationsQuantityInterface $getReservationsQuantity,
        GetStockItemConfigurationInterface $getStockItemConfiguration,
        Quote $quoteSession,
        StockRegistryInterface $stockRegistry,
        CurrencyFactory $currencyFactory,
        Collection $logCollection
    ) {
        $this->_order                       = $order;
        $this->_request                     = $request;
        $this->getStockItemData             = $getStockItemData;
        $this->orderCreate                  = $orderCreate;
        $this->productSalableResultFactory  = $productSalableResultFactory;
        $this->_getSalableQuantityDataBySku = $getSalableQuantityDataBySku;
        $this->getReservationsQuantity      = $getReservationsQuantity;
        $this->getStockItemConfiguration    = $getStockItemConfiguration;
        $this->quoteSession                 = $quoteSession;
        $this->stockRegistry                = $stockRegistry;
        $this->currencyFactory              = $currencyFactory;
        $this->logCollection                = $logCollection;

        parent::__construct($context, $objectManager, $storeManager);
    }

    /**
     * @return array|bool
     */
    public function getEditableOrderStatus()
    {
        if ($this->getConfigGeneral('editable_order_status') && $this->isEnabled()) {
            return explode(',', $this->getConfigGeneral('editable_order_status'));
        }

        return false;
    }

    /**
     * @param null $storeId
     *
     * @return bool
     */
    public function isEnabledQuickEdit($storeId = null)
    {
        return $this->isEnabled($storeId) && $this->getConfigGeneral('enabled_quick_edit', $storeId);
    }

    /**
     * @return array|bool
     */
    public function getUpdateAfterEditing()
    {
        if ($this->getConfigGeneral('update_after_editing') && $this->isEnabled()) {
            return explode(',', $this->getConfigGeneral('update_after_editing'));
        }

        return [0];
    }

    /**
     * @param null $storeId
     *
     * @return mixed
     */
    public function isRecalculateShippingFee($storeId = null)
    {
        return $this->getConfigGeneral('auto_recalculate_shipping_fee', $storeId);
    }

    /**
     * @param null $storeId
     *
     * @return mixed
     */
    public function isReturnItemToStock($storeId = null)
    {
        return $this->getConfigGeneral('return_item_to_stock', $storeId);
    }

    /**
     * @param null $storeId
     *
     * @return mixed
     */
    public function isEnabledTrackingShipment($storeId = null)
    {
        return $this->getConfigGeneral('enable_tracking_shipment', $storeId);
    }

    /**
     * get array different between 2 array
     *
     * @param array $array1
     * @param array $array2
     *
     * @return array
     */
    public function arrayDifferent($array1, $array2)
    {
        $difference = [];
        foreach ($array1 as $key => $value) {
            if (is_array($value)) {
                if (!array_key_exists($key, $array2)) {
                    $difference[$key] = $value;
                } elseif (!is_array($array2[$key])) {
                    $difference[$key] = $value;
                } else {
                    $multidimensionalDiff = $this->arrayDifferent($value, $array2[$key]);
                    if (count($multidimensionalDiff) > 0) {
                        $difference[$key] = $multidimensionalDiff;
                    }
                }
            } else {
                /**
                 * Not the same type
                 */
                if (!array_key_exists($key, $array2) || $array2[$key] != $value) {
                    $difference[$key] = $value;
                }
            }
        }

        return $difference;
    }

    /**
     * @param $itemSku
     *
     * @return array
     */
    public function getItemDefaultTaxAndDiscount($itemSku)
    {
        $orderId = $this->getOrderId();
        $logs    = $this->logCollection->addFieldToFilter('order_id', $orderId)
            ->addFieldToFilter('edited_type', 'Items')->setOrder('log_id', 'asc');

        if ($logs->getSize()) {
            foreach ($logs as $log) {
                $oldDataJson = $log->getOldData();
                if ($oldDataJson) {
                    $oldData      = self::jsonDecode($oldDataJson);
                    $oldDataItems = $oldData['item'];
                    foreach ($oldDataItems as $oldDataItem) {
                        if ($oldDataItem['sku'] === $itemSku && $oldDataItem['is_custom_tax'] == 0 && $oldDataItem['discount_type'] == 'default') {
                            return [$oldDataItem['custom_tax'], $oldDataItem['discount_value']];
                        }
                    }
                }
            }
        }

        return [0, ""];
    }

    /**
     * Get Edited type for manage logs
     *
     * @param array $diffData
     *
     * @return string
     */
    public function getEditedType($diffData)
    {
        if (isset($diffData['order'])) {
            if (isset($diffData['items']) ||
                count($diffData['order']) > 1 ||
                (count($diffData['order']) === 1 && isset($diffData['payment'])) ||
                (!isset($diffData['order']['shipping_method']) &&
                    count($diffData['order']) === 1 && isset($diffData['method_detail']))
            ) {
                return __('Quick Edit (all)');
            }
        }

        if (isset($diffData['method_detail']) && !isset($diffData['order']) && count($diffData) > 1) {
            return __('Quick Edit (all)');
        }

        $keys = $this->arrayKeysMulti($diffData);

        if (in_array('info', $keys, true)) {
            return __('Order Information');
        }

        if (in_array('customer', $keys, true)) {
            return __('Customer Information');
        }

        if (in_array('billing_address', $keys, true)) {
            return __('Billing Address');
        }

        if (in_array('shipping_address', $keys, true)) {
            return __('Shipping Address');
        }

        if (in_array('method_detail', $keys, true) || in_array('shipping_method', $keys, true)) {
            return __('Shipping Method');
        }

        if (in_array('payment', $keys, true)) {
            return __('Payment Method');
        }

        if (in_array('item', $keys, true)) {
            return __('Items');
        }

        return '';
    }

    /**
     * Get all key array
     *
     * @param array $data
     *
     * @return array
     */
    public function arrayKeysMulti($data)
    {
        $keys     = [];
        $childKey = [];

        foreach ($data as $key => $value) {
            $keys[] = $key;

            if (is_array($value)) {
                $childKey = $this->arrayKeysMulti($value);
            }
        }

        return array_merge($keys, $childKey);
    }

    /**
     * @param $array
     *
     * @return bool
     */
    public function isEdited($array)
    {
        $isEdited = false;
        foreach ($array as $key => $value) {
            if ($key === 'payment' && isset($array[$key])) {
                foreach ($array[$key] as $p_key => $p_value) {
                    if ($array[$key][$p_key]) {
                        $isEdited = true;
                        break;
                    }
                }
            } elseif ($array[$key]) {
                $isEdited = true;
                break;
            }
        }

        return $isEdited;
    }

    /**
     * @param $orderId
     *
     * @return Order|string
     */
    public function getOrderById($orderId)
    {
        if ($orderId) {
            return $this->_order->load($orderId);
        }

        return '';
    }

    /**
     * @return mixed|string
     */
    public function getOrderId()
    {
        $orderId = $this->_request->getParam('order_id') ?: $this->quoteSession->getOrderId();
        if ($orderId) {
            return $orderId;
        }

        return '';
    }

    /**
     * @param $order
     *
     * @return bool
     */
    public function canCredit($order)
    {
        $isCheck = false;
        if (empty($order->getInvoiceCollection())) {
            return false;
        }

        foreach ($order->getAllItems() as $item) {
            if ($item->getQtyInvoiced() > 0 && ($item->getQtyInvoiced() > $item->getQtyRefunded())) {
                $isCheck = true;
                break;
            }
        }

        return $isCheck;
    }

    /**
     * @return bool
     */
    public function isEnableInventorySales()
    {
        return $this->isModuleOutputEnabled('Magento_InventorySales');
    }

    /**
     * @param string $url
     *
     * @return string
     */
    public function getLoadTrackingHtml($url)
    {
        return '<div class="mp-load-tracking"><div class="mp-tracking-loader">
                    <div class="loader">
                        <img src="' . $url . '"
                           alt="' . __('Loading...') . '">
                    </div>
                </div></div>';
    }

    /**
     * @param $ver
     * @param $operator
     *
     * @return bool|int
     */
    public function versionCompare($ver, $operator = ">=")
    {
        $productMetadata = $this->objectManager->get(ProductMetadataInterface::class);
        $version         = explode('-', $productMetadata->getVersion());

        return version_compare($version[0], $ver, $operator);
    }

    /**
     * @return bool|int
     */
    public function checkVersion()
    {
        return $this->versionCompare('2.4.6');
    }

    /**
     * @param Order $order
     *
     * @return $this
     * @throws Exception
     */
    public function setConvertQuoteItem($order)
    {
        $ignoreKeys = [
            'item_id',
            'created_at',
            'updated_at',
            'parent_item_id'
        ];

        $quote      = $this->orderCreate->getQuote();
        $quoteItems = $quote->getAllItems();
        $orderItems = $order->getAllItems();
        foreach ($orderItems as $orderItem) {
            foreach ($quoteItems as $item) {
                if ($item->getProductId() === $orderItem->getProductId() && $item->getSku() === $orderItem->getSku()) {
                    foreach ($orderItem->getData() as $key => $data) {
                        if (!in_array($key, $ignoreKeys, true)) {
                            $item->setData($key, $orderItem->getData($key));
                        }
                    }
                    $item->save();
                }
            }
        }

        return $this;
    }

    /**
     * @param $requestedQty
     * @param $quoteItem
     *
     * @return bool
     * @throws InputException
     * @throws LocalizedException
     * @throws NoSuchEntityException
     * @throws SkuIsNotAssignedToStockException
     */
    public function isBackorderAllowed($requestedQty, $quoteItem)
    {
        $product = $quoteItem->getProduct();
        $sku     = $product->getSku();

        $stockRegistry = $this->stockRegistry;
        $stockId       = Stock::DEFAULT_STOCK_ID;
        switch ($product->getTypeId()) {
            case ProductType::TYPE_SIMPLE:
            case ProductType::TYPE_VIRTUAL:
                $stockItem = $this->stockRegistry->getStockItemBySku($sku);
                $stockId   = $stockItem ? $stockItem->getStockId() : $stockId;
                break;
            case Configurable::TYPE_CODE:
                $childProducts = $product->getTypeInstance()->getUsedProducts($product);
                if (!empty($childProducts)) {
                    foreach ($childProducts as $childProduct) {
                        $stockItem = $stockRegistry->getStockItem($childProduct->getId());
                        $stockId   = $stockItem->getStockId();
                        break;
                    }
                }
                break;
            case ProductType::TYPE_BUNDLE:
                $optionIds = $product->getTypeInstance()->getOptionsIds($product);
                if (!empty($optionIds)) {
                    foreach ($optionIds as $optionId) {
                        $selections = $product->getTypeInstance()->getSelections($optionId, $product);
                        foreach ($selections as $selection) {
                            $stockItem = $stockRegistry->getStockItem($selection->getId());
                            $stockId   = $stockItem->getStockId();
                            break;
                        }
                    }
                }
                break;

        }
        $stockItem          = $this->stockRegistry->getStockItemBySku($sku);
        $stockItemData      = $this->getStockItemData->execute($sku, $stockId);
        $qtyWithReservation = $stockItemData[GetStockItemDataInterface::QUANTITY] +
            $this->getReservationsQuantity->execute(
                $sku,
                $stockId
            );
        $oldOrderQty        = $quoteItem->getData('mp_custom_can_exceed_stock_quantity');
        $qtyWithReservation = $qtyWithReservation + $oldOrderQty;

        $isInStock = (bool) $stockItem->getIsInStock();

        return (bool) $stockItemData[GetStockItemDataInterface::IS_SALABLE] && $isInStock ||
            $qtyWithReservation >= $requestedQty;
    }

    /**
     * @param string $quoteItemWeeTax
     *
     * @return int|mixed
     */
    public static function getWeeTaxsItemAmountApplied($quoteItemWeeTax)
    {
        if ($quoteItemWeeTax) {
            return 0;
        }
        $amount           = 0;
        $quoteItemWeeTaxs = self::jsonDecode($quoteItemWeeTax);
        foreach ($quoteItemWeeTaxs as $quoteItemWeeTax) {
            $amount += max(0, $quoteItemWeeTax['amount_incl_tax'] - $quoteItemWeeTax['amount']);
        }

        return $amount;
    }

    /**
     * @param string $quoteItemWeeTax
     *
     * @return int|mixed
     */
    public static function getWeeTaxsItemAmount($quoteItemWeeTax)
    {
        if ($quoteItemWeeTax) {
            return 0;
        }
        $amount           = 0;
        $quoteItemWeeTaxs = self::jsonDecode($quoteItemWeeTax);
        foreach ($quoteItemWeeTaxs as $quoteItemWeeTax) {
            $amount += max(0, $quoteItemWeeTax['amount']);
        }

        return $amount;
    }

    /**
     * @return bool
     */
    public function isIncludeFPTInSubtotal()
    {
        return (bool) $this->scopeConfig->getValue(
            'tax/weee/include_in_subtotal',
            ScopeInterface::SCOPE_STORE
        );
    }

    /**
     * ConvertCurrency Order to Base Currency
     *
     * @param string $amount
     * @param Order $order
     *
     * @return string
     */
    public function convertCurrency($amount, $order)
    {
        try {
            if (!$this->currency) {
                $currencyCodeFrom = $order->getOrderCurrencyCode();
                $this->currency   = $this->currencyFactory->create()->load($currencyCodeFrom);
            }
            $currencyCodeTo = $order->getBaseCurrencyCode();

            return $this->currency->convert($amount, $currencyCodeTo);
        } catch (LocalizedException $e) {
            return $amount;
        }
    }

    /**
     * Check mpeditorder Controller
     *
     * @return bool
     */
    public function isMpEdit()
    {
        $actionName = $this->_request->getFullActionName();
        if (str_contains($actionName, 'mpeditorder')) {
            return true;
        }

        return false;
    }

    /**
     * GetQtyItem with check Bundle product
     *
     * @param AbstractItem $quoteItem
     *
     * @return int
     */
    public function getQtyItem($quoteItem)
    {
        if ($quoteItem->getParentItemId()) {
            return $quoteItem->getParentItem()->getProductType() === 'bundle'
                ? $quoteItem->getParentItem()->getQty() * $quoteItem->getQty()
                : $quoteItem->getQty();
        }

        return $quoteItem->getQty();
    }
}
