<?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\Model\Tax;

use Magento\Framework\DataObject;
use Magento\Tax\Api\Data\AppliedTaxInterfaceFactory;
use Magento\Tax\Api\Data\AppliedTaxRateInterfaceFactory;
use Magento\Tax\Api\Data\QuoteDetailsItemInterface;
use Magento\Tax\Api\Data\TaxDetailsItemInterface;
use Magento\Tax\Api\Data\TaxDetailsItemInterfaceFactory;
use Magento\Tax\Api\TaxClassManagementInterface;
use Magento\Tax\Model\Calculation\AbstractAggregateCalculator;
use Magento\Tax\Model\Config;

/**
 * Class Calculation
 * Mageplaza\EditOrder\Model\Tax
 */
class Calculation extends AbstractAggregateCalculator
{
    /**
     * Constructor
     *
     * @param TaxClassManagementInterface $taxClassService
     * @param TaxDetailsItemInterfaceFactory $taxDetailsItemDataObjectFactory
     * @param AppliedTaxInterfaceFactory $appliedTaxDataObjectFactory
     * @param AppliedTaxRateInterfaceFactory $appliedTaxRateDataObjectFactory
     * @param Calculation $calculationTool
     * @param Config $config
     * @param int $storeId
     * @param DataObject $addressRateRequest
     */
    public function __construct(
        TaxClassManagementInterface $taxClassService,
        TaxDetailsItemInterfaceFactory $taxDetailsItemDataObjectFactory,
        AppliedTaxInterfaceFactory $appliedTaxDataObjectFactory,
        AppliedTaxRateInterfaceFactory $appliedTaxRateDataObjectFactory,
        \Magento\Tax\Model\Calculation $calculationTool,
        \Magento\Tax\Model\Config $config,
        $storeId = null,
        ?\Magento\Framework\DataObject $addressRateRequest = null
    ) {
        parent::__construct(
            $taxClassService,
            $taxDetailsItemDataObjectFactory,
            $appliedTaxDataObjectFactory,
            $appliedTaxRateDataObjectFactory,
            $calculationTool,
            $config,
            $storeId,
            $addressRateRequest
        );
    }

    /**
     * Round amount
     *
     * @param float $amount
     * @param null|float $rate
     * @param null|bool $direction
     * @param string $type
     * @param bool $round
     * @param QuoteDetailsItemInterface $item
     *
     * @return float
     */
    protected function roundAmount(
        $amount,
        $rate = null,
        $direction = null,
        $type = self::KEY_REGULAR_DELTA_ROUNDING,
        $round = true,
        $item = null
    ) {
        return $this->deltaRound($amount, $rate, $direction, $type, $round);
    }

    /**
     * Following how to Core calculate with Custom Rate and Remove $appliedRates
     *
     * @inheritdoc
     */
    protected function calculateWithTaxInPrice(QuoteDetailsItemInterface $item, $quantity, $round = true, $rate = null)
    {
        $discountTaxCompensationAmount = 0;
        $applyTaxAfterDiscount         = $this->config->applyTaxAfterDiscount($this->storeId);
        $discountAmount                = $item->getDiscountAmount();

        // Calculate $rowTotalInclTax
        $priceInclTax      = $this->calculationTool->round($item->getUnitPrice());
        $rowTotalInclTax   = $priceInclTax * $quantity;
        $rowTaxExact       = $this->calculationTool->calcTaxAmount($rowTotalInclTax, $rate, true, false);
        $deltaRoundingType = self::KEY_REGULAR_DELTA_ROUNDING;
        if ($applyTaxAfterDiscount) {
            $deltaRoundingType = self::KEY_TAX_BEFORE_DISCOUNT_DELTA_ROUNDING;
        }
        $rowTax   = $this->roundAmount($rowTaxExact, $rate, true, $deltaRoundingType, $round, $item);
        $rowTotal = $rowTotalInclTax - $rowTax;
        $price    = $rowTotal / $quantity;
        if ($round) {
            $price = $this->calculationTool->round($price);
        }

        //Handle discount
        if ($applyTaxAfterDiscount) {
            //TODO: handle originalDiscountAmount
            $taxableAmount       = max($rowTotalInclTax - $discountAmount, 0);
            $rowTaxAfterDiscount = $this->calculationTool->calcTaxAmount(
                $taxableAmount,
                $rate,
                true,
                false
            );
            $rowTaxAfterDiscount = $this->roundAmount(
                $rowTaxAfterDiscount,
                $rate,
                true,
                self::KEY_REGULAR_DELTA_ROUNDING,
                $round,
                $item
            );
            // Set discount tax compensation
            $discountTaxCompensationAmount = $rowTax - $rowTaxAfterDiscount;
            $rowTax                        = $rowTaxAfterDiscount;
        }

        return $this->taxDetailsItemDataObjectFactory->create()
            ->setCode($item->getCode())
            ->setType($item->getType())
            ->setRowTax($rowTax)
            ->setPrice($price)
            ->setPriceInclTax($priceInclTax)
            ->setRowTotal($rowTotal)
            ->setRowTotalInclTax($rowTotalInclTax)
            ->setDiscountTaxCompensationAmount($discountTaxCompensationAmount)
            ->setAssociatedItemCode($item->getAssociatedItemCode())
            ->setTaxPercent($rate)
            ->setAppliedTaxes(null);
    }

    /**
     *  Following how to Core calculate with Custom Rate and Remove $appliedRates
     *
     * @inheritdoc
     */
    protected function calculateWithTaxNotInPrice(
        QuoteDetailsItemInterface $item,
        $quantity,
        $round = true,
        $rate = null
    ) {
        $applyTaxAfterDiscount         = $this->config->applyTaxAfterDiscount($this->storeId);
        $discountAmount                = $item->getDiscountAmount();
        $discountTaxCompensationAmount = 0;

        // Calculate $rowTotal
        $price                     = $this->calculationTool->round($item->getUnitPrice());
        $rowTotal                  = $price * $quantity;
        $rowTotalForTaxCalculation = $this->getPriceForTaxCalculation($item, $price) * $quantity;
        $rowTaxRate                = $this->calculationTool
            ->calcTaxAmount($rowTotalForTaxCalculation, $rate, false, false);

        $rowTotalForTaxCalculation = $this->getPriceForTaxCalculation($item, $price) * $quantity;
        $deltaRoundingType         = self::KEY_REGULAR_DELTA_ROUNDING;
        if ($applyTaxAfterDiscount) {
            $deltaRoundingType = self::KEY_TAX_BEFORE_DISCOUNT_DELTA_ROUNDING;
        }
        $rowTaxRate          = $this->roundAmount($rowTaxRate, $rate, false, $deltaRoundingType, $round, $item);
        $rowTaxAfterDiscount = $rowTaxRate;
        //Handle discount
        if ($applyTaxAfterDiscount) {
            //TODO: handle originalDiscountAmount
            $taxableAmount = max($rowTotalForTaxCalculation - $discountAmount, 0);
            if ($taxableAmount && !$applyTaxAfterDiscount) {
                $taxableAmount = $rowTotalForTaxCalculation;
            }
            $rowTaxAfterDiscount = $this->calculationTool->calcTaxAmount(
                $taxableAmount,
                $rate,
                false,
                false
            );
            $rowTaxAfterDiscount = $this->roundAmount(
                $rowTaxAfterDiscount,
                $rate,
                false,
                self::KEY_REGULAR_DELTA_ROUNDING,
                $round,
                $item
            );
        }

        $rowTotalInclTax = $rowTotal + $rowTaxAfterDiscount;
        $priceInclTax    = $rowTotalInclTax / $quantity;
        $rowTax          = $rowTaxAfterDiscount;
        if ($round) {
            $priceInclTax = $this->calculationTool->round($priceInclTax);
        }

        return $this->taxDetailsItemDataObjectFactory->create()
            ->setCode($item->getCode())
            ->setType($item->getType())
            ->setRowTax($rowTax)
            ->setPrice($price)
            ->setPriceInclTax($priceInclTax)
            ->setRowTotal($rowTotal)
            ->setRowTotalInclTax($rowTotalInclTax)
            ->setDiscountTaxCompensationAmount($discountTaxCompensationAmount)
            ->setAssociatedItemCode($item->getAssociatedItemCode())
            ->setTaxPercent($rate)
            ->setAppliedTaxes(null);
    }

    /**
     * Calculate tax details for quote item with given quantity
     *
     * @param QuoteDetailsItemInterface $item
     * @param int $quantity
     * @param bool $round
     * @param float $rate
     *
     * @return TaxDetailsItemInterface
     */
    public function calculate(QuoteDetailsItemInterface $item, $quantity, $round = true, $rate = null)
    {
        if ($item->getIsTaxIncluded()) {
            return $this->calculateWithTaxInPrice($item, $quantity, $round, $rate);
        } else {
            return $this->calculateWithTaxNotInPrice($item, $quantity, $round, $rate);
        }
    }

    /**
     * Get price for tax calculation.
     *
     * @param QuoteDetailsItemInterface $item
     * @param float $price
     *
     * @return float
     */
    private function getPriceForTaxCalculation(QuoteDetailsItemInterface $item, float $price)
    {
        if ($item->getExtensionAttributes() && $item->getExtensionAttributes()->getPriceForTaxCalculation()) {
            $priceForTaxCalculation = $this->calculationTool->round(
                $item->getExtensionAttributes()->getPriceForTaxCalculation()
            );
        } else {
            $priceForTaxCalculation = $price;
        }

        return $priceForTaxCalculation;
    }
}
