<?php

namespace FiloBlu\Rma\Plugin\OfflineShipping\Model\Carrier;

use FiloBlu\Rma\Helper\BundlesHelper;
use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory;
use Magento\Quote\Model\Quote\Address\RateResult\MethodFactory;
use Magento\Shipping\Model\Rate\ResultFactory;

class TableRate
{
    protected $code = 'tablerate';


    /**
     * @var BundlesHelper
     */
    private $bundlesHelper;
    /**
     * @var ResultFactory
     */
    private $rateResultFactory;
    /**
     * @var MethodFactory
     */
    private $resultMethodFactory;
    /**
     * @var ErrorFactory
     */
    private $rateErrorFactory;

    /**
     * @param BundlesHelper $bundlesHelper
     * @param ResultFactory $rateResultFactory
     * @param MethodFactory $resultMethodFactory
     * @param ErrorFactory $rateErrorFactory
     */
    public function __construct(
        BundlesHelper $bundlesHelper,
        ResultFactory $rateResultFactory,
        MethodFactory $resultMethodFactory,
        ErrorFactory $rateErrorFactory
    )
    {
        $this->bundlesHelper = $bundlesHelper;
        $this->rateResultFactory = $rateResultFactory;
        $this->resultMethodFactory = $resultMethodFactory;
        $this->rateErrorFactory = $rateErrorFactory;
    }

    public function afterCollectRates($subject, $result, $request)
    {
        if (!$this->bundlesHelper->isAllowCartRuleApplicationOnShippedSeparatelyItems()) {
            return $result;
        }

        return $this->collectRate($subject, $request);
    }

    public function collectRate($parent, $request)
    {
        if (!$parent->getConfigFlag('active')) {
            return false;
        }

        // exclude Virtual products price from Package value if pre-configured
        if (!$parent->getConfigFlag('include_virtual_price') && $request->getAllItems()) {
            foreach ($request->getAllItems() as $item) {
                if ($item->getParentItem()) {
                    continue;
                }
                if ($item->getHasChildren() && $item->isShipSeparately()) {
                    foreach ($item->getChildren() as $child) {
                        if ($child->getProduct()->isVirtual()) {
                            $request->setPackageValue($request->getPackageValue() - $child->getBaseRowTotal());
                        }
                    }
                } elseif ($item->getProduct()->isVirtual()) {
                    $request->setPackageValue($request->getPackageValue() - $item->getBaseRowTotal());
                    $request->setPackageValueWithDiscount(
                        $request->getPackageValueWithDiscount() - $item->getBaseRowTotal()
                    );
                }
            }
        }

        // Free shipping by qty
        $freeQty = 0;
        $freePackageValue = 0;
        $freeWeight = 0;
        $freeShipping = false;

        if ($request->getAllItems()) {
            foreach ($request->getAllItems() as $item) {
                if ($item->getProduct()->isVirtual() || $item->getParentItem()) {
                    continue;
                }

                if ($item->getHasChildren() && $item->isShipSeparately()) {
                    foreach ($item->getChildren() as $child) {
                        if ($child->getFreeShipping() && !$child->getProduct()->isVirtual()) {
                            $freeShipping = (int)$child->getFreeShipping();
                            $freeQty += $item->getQty() * ($child->getQty() - $freeShipping);
                        }
                    }
                    if ($freeShipping) {
                        $freePackageValue += $item->getBaseRowTotalInclTax();
                    }
                } elseif (($item->getFreeShipping() || $item->getAddress()->getFreeShipping()) &&
                    ($item->getFreeShippingMethod() == null || $item->getFreeShippingMethod() &&
                        $item->getFreeShippingMethod() == 'tablerate_bestway')
                ) {
                    $freeShipping = $item->getFreeShipping() ?
                        $item->getFreeShipping() : $item->getAddress()->getFreeShipping();
                    $freeShipping = is_numeric($freeShipping) ? $freeShipping : 0;
                    $freeQty += $item->getQty() - $freeShipping;
                    $freePackageValue += $item->getBaseRowTotal();
                }

                if ($item->getFreeShippingMethod() && $item->getFreeShippingMethod() !== 'tablerate_bestway') {
                    $freeWeight += (int) $item->getWeight();
                }
            }

            $request->setPackageValue($request->getPackageValue() - $freePackageValue);
            $request->setPackageValueWithDiscount($request->getPackageValueWithDiscount() - $freePackageValue);
        }

        if ($freeWeight > 0) {
            $request->setFreeMethodWeight($freeWeight);
        }

        if (!$request->getConditionName()) {
            $conditionName = $parent->getConfigData('condition_name');
            $request->setConditionName($conditionName ? $conditionName : $parent->_defaultConditionName);
        }

        // Package weight and qty free shipping
        $oldWeight = $request->getPackageWeight();
        $oldQty = $request->getPackageQty();

        $request->setPackageWeight($request->getFreeMethodWeight());
        $request->setPackageQty($oldQty - $freeQty);

        /** @var \Magento\Shipping\Model\Rate\Result $result */
        $result = $this->rateResultFactory->create();
        $rate = $parent->getRate($request);

        $request->setPackageWeight($oldWeight);
        $request->setPackageQty($oldQty);

        if (!empty($rate) && $rate['price'] >= 0) {
            if ($request->getPackageQty() == $freeQty) {
                $shippingPrice = 0;
            } else {
                $shippingPrice = $parent->getFinalPriceWithHandlingFee($rate['price']);
            }
            $method = $this->createShippingMethod($parent,$shippingPrice, $rate['cost']);
            $result->append($method);
        } elseif ($request->getPackageQty() == $freeQty) {

            /**
             * Promotion rule was applied for the whole cart.
             *  In this case all other shipping methods could be omitted
             * Table rate shipping method with 0$ price must be shown if grand total is more than minimal value.
             * Free package weight has been already taken into account.
             */
            $request->setPackageValue($freePackageValue);
            $request->setPackageValueWithDiscount($freePackageValue);
            $request->setPackageQty($freeQty);
            $rate = $parent->getRate($request);
            if (!empty($rate) && $rate['price'] >= 0) {
                $method = $this->createShippingMethod($parent,0, 0);
                $result->append($method);
            }
        } else {
            /** @var \Magento\Quote\Model\Quote\Address\RateResult\Error $error */
            $error = $this->rateErrorFactory->create(
                [
                    'data' => [
                        'carrier' => $this->code,
                        'carrier_title' => $parent->getConfigData('title'),
                        'error_message' => $parent->getConfigData('specificerrmsg'),
                    ],
                ]
            );
            $result->append($error);
        }

        return $result;
    }

    private function createShippingMethod($parent, $shippingPrice, $cost)
    {
        /** @var  \Magento\Quote\Model\Quote\Address\RateResult\Method $method */
        $method = $this->resultMethodFactory->create();

        $method->setCarrier($parent->getCarrierCode());
        $method->setCarrierTitle($parent->getConfigData('title'));

        $method->setMethod('bestway');
        $method->setMethodTitle($parent->getConfigData('name'));

        $method->setPrice($shippingPrice);
        $method->setCost($cost);
        return $method;
    }
}
