<?php

namespace FiloBlu\Rma\Model\Carrier;

use FiloBlu\Rma\Helper\RmaHelper;
use FiloBlu\Rma\Model\ResourceModel\Carrier\RateFactory;
use Magento\Framework\App\Config\ScopeConfigInterface;
use Magento\Framework\Exception\LocalizedException;
use Magento\Framework\Pricing\Helper\Data;
use Magento\Quote\Model\Quote\Address\RateRequest;
use Magento\Quote\Model\Quote\Address\RateResult\Error;
use Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory;
use Magento\Quote\Model\Quote\Address\RateResult\Method;
use Magento\Quote\Model\Quote\Address\RateResult\MethodFactory;
use Magento\Sales\Api\Data\OrderItemInterface;
use Magento\Shipping\Model\Carrier\AbstractCarrier;
use Magento\Shipping\Model\Carrier\CarrierInterface;
use Magento\Shipping\Model\Rate\Result;
use Magento\Shipping\Model\Rate\ResultFactory;
use Psr\Log\LoggerInterface;

/**
 *
 */
class CarrierRate extends AbstractCarrier implements CarrierInterface
{
    /**
     * @var string
     */
    protected $code;
    /**
     * @var bool
     */
    protected $isFixed = false;
    /**
     * @var string
     */
    protected $defaultConditionName = 'package_weight';
    /**
     * @var array
     */
    protected $conditionNames = [];

    /**
     * @var \FiloBlu\Rma\Model\ResourceModel\Carrier\RateFactory
     */
    protected $rateFactory;
    /**
     * @var ResultFactory
     */
    protected $rateResultFactory;
    /**
     * @var MethodFactory
     */
    protected $resultMethodFactory;
    /**
     * @var Data
     */
    protected $priceHelper;
    /**
     * @var RmaHelper
     */
    protected $rmaHelper;
    /**
     * @var String
     */
    protected $carrierName;
    /**
     * @var null
     */
    private $matrixRateSource;


    /**
     * @param \Magento\Framework\App\Config\ScopeConfigInterface $scopeConfig
     * @param \Magento\Quote\Model\Quote\Address\RateResult\ErrorFactory $rateErrorFactory
     * @param \Psr\Log\LoggerInterface $logger
     * @param \FiloBlu\Rma\Model\ResourceModel\Carrier\RateFactory $rateFactory
     * @param \Magento\Shipping\Model\Rate\ResultFactory $rateResultFactory
     * @param \Magento\Quote\Model\Quote\Address\RateResult\MethodFactory $resultMethodFactory
     * @param \Magento\Framework\Pricing\Helper\Data $priceHelper
     * @param \FiloBlu\Rma\Helper\RmaHelper $rmaHelper
     * @param $code
     * @param $defaultConditionName
     * @param $carrierName
     * @param $matrixRateSource
     * @param array $data
     * @throws \Magento\Framework\Exception\LocalizedException
     */
    public function __construct(
        ScopeConfigInterface $scopeConfig,
        ErrorFactory         $rateErrorFactory,
        LoggerInterface      $logger,
        RateFactory $rateFactory,
        ResultFactory        $rateResultFactory,
        MethodFactory        $resultMethodFactory,
        Data                 $priceHelper,
        RmaHelper            $rmaHelper,
                             $code = null,
                             $defaultConditionName = null,
                             $carrierName = null,
                             $matrixRateSource = null,
        array                $data = []
    )
    {
        $this->code = $code;
        $this->defaultConditionName = $defaultConditionName;
        $this->rateFactory = $rateFactory;
        $this->rateResultFactory = $rateResultFactory;
        $this->resultMethodFactory = $resultMethodFactory;
        $this->priceHelper = $priceHelper;
        $this->rmaHelper = $rmaHelper;
        $this->carrierName = $carrierName;
        $this->matrixRateSource = $matrixRateSource;
        parent::__construct($scopeConfig, $rateErrorFactory, $logger, $data);

        foreach ($this->getCode('condition_name') as $k => $v) {
            $this->conditionNames[] = $k;
        }
    }

    /**
     * @param string $type
     * @param string $code
     * @return array
     * @throws LocalizedException
     */
    public function getCode($type, $code = '')
    {
        $codes = [
            'condition_name' => [
                'package_weight' => __('Weight vs. Destination'),
                'package_value' => __('Order Subtotal vs. Destination'),
                'package_qty' => __('# of Items vs. Destination'),
            ],
            'condition_name_short' => [
                'package_weight' => __('Weight'),
                'package_value' => __('Order Subtotal'),
                'package_qty' => __('# of Items'),
            ],
        ];

        if (!isset($codes[$type])) {
            throw new LocalizedException(__('Please correct Matrix CarrierRate code type: %1.', $type));
        }

        if ('' === $code) {
            return $codes[$type];
        }

        if (!isset($codes[$type][$code])) {
            throw new LocalizedException(__('Please correct Matrix CarrierRate code for type %1: %2.', $type, $code));
        }

        return $codes[$type][$code];
    }

    /**
     * @param string $code
     * @return $this
     */
    public function setCode(string $code)
    {
        $this->code = $code;

        return $this;
    }

    /**
     * @param RateRequest $request
     * @return Result
     */
    public function collectRates(RateRequest $request)
    {
        // exclude Virtual products price from Package value if pre-configured
        if (!$this->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());
                }
            }
        }

        // Free shipping by qty
        $freeQty = 0;
        if ($request->getAllItems()) {
            $freePackageValue = 0;
            /** @var OrderItemInterface $item */
            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 = is_numeric($child->getFreeShipping()) ? $child->getFreeShipping() : 0;
                            $freeQty += $item->getQty() * ($child->getQty() - $freeShipping);
                        }
                    }
                } elseif ($item->getFreeShipping()) {
                    $freeShipping = is_numeric($item->getFreeShipping()) ? $item->getFreeShipping() : 0;
                    $freeQty += $item->getQty() - $freeShipping;
                    $freePackageValue += $item->getBaseRowTotal();
                }
            }
            $oldValue = $request->getPackageValue();
            $request->setPackageValue($oldValue - $freePackageValue);
        }

        if (!$conditionName = $request->getConditionMRName()) {
            $conditionName = $this->setCode($this->carrierName)->getConfigData('condition_name');

            if ($this->rmaHelper->getEasyReturnMatrixrateSource() == $this->matrixRateSource) {
                $conditionName = $this->rmaHelper->getEasyReturnMatrixrateConditionName();
            }

            $request->setConditionMRName($conditionName ?: $this->defaultConditionName);
        }

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

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

        /** @var Result $result */
        $result = $this->rateResultFactory->create();
        $zipRange = $this->setCode($this->carrierName)->getConfigData('zip_range');
        $rateArray = $this->getRate($request, $zipRange);

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

        $foundRates = false;

        foreach ($rateArray as $rate) {
            if (!empty($rate) && $rate['price'] >= 0) {
                /** @var Method $method */
                $method = $this->resultMethodFactory->create();

                $method->setCarrier($this->carrierName);
                $method->setCarrierTitle($this->getConfigData('title'));

                $method->setMethod($this->carrierName . '_' . $rate['pk']);
                $method->setMethodTitle(__($rate['shipping_method']));

                $shippingPrice = $this->calculateShippingPrice($request, $rate, $freeQty, $conditionName);

                $method->setPrice($shippingPrice);

                $taxRate = (1 + ($request->getTaxPercentage() / 100));
                $shippingCost = (float)number_format(($shippingPrice / $taxRate), 4, '.', '');

                $method->setCost($shippingCost);

                $result->append($method);
                $foundRates = true; // have found some valid rates
            }
        }

        if (!$foundRates) {
            /** @var Error $error */
            $error = $this->_rateErrorFactory->create(
                [
                    'data' => [
                        'carrier' => $this->code,
                        'carrier_title' => $this->getConfigData('title'),
                        'error_message' => $this->getConfigData('specificerrmsg'),
                    ],
                ]
            );
            $result->append($error);
        }

        return $result;
    }

    /**
     * @param RateRequest $request
     * @param $zipRange
     * @return array
     */
    public function getRate(RateRequest $request, $zipRange)
    {
        return $this->rateFactory->create()->getRate($request, $zipRange);
    }

    /**
     * TODO: Transform using strategy design pattern
     *
     * @param $request
     * @param $rate
     * @param $freeQty
     * @param $conditionName
     * @return float|int
     */
    protected function calculateShippingPrice($request, $rate, $freeQty, $conditionName)
    {
        $shippingPrice = 0;

        switch ($conditionName) {
            case 'package_value':
                if ($request->getOrderTotal() >= $rate['condition_from_value'] && $request->getOrderTotal() <= $rate['condition_to_value']) {
                    $shippingPrice = $this->getFinalPriceWithHandlingFee($rate['price']);
                }
                break;
            case 'package_qty':
            default:
                if (empty($request->getFreeShipping()) || $request->getPackageQty() != $freeQty) {
                    $shippingPrice = $this->getFinalPriceWithHandlingFee($rate['price']);
                }
                break;
        }

        return $shippingPrice;
    }

    /**
     * Get allowed shipping methods
     *
     * @return array
     */
    public function getAllowedMethods()
    {
        return [$this->carrierName => $this->getConfigData('name')];
    }

}
