<?php
/**
 * TaxService.php
 *
 * @copyright  2022 opencart.cn - All Rights Reserved
 * @link       http://www.guangdawangluo.com
 * @author     Edward Yang <yangjin@opencart.cn>
 * @created    2022-07-21 16:29:54
 * @modified   2022-07-21 16:29:54
 */

namespace Beike\Libraries;

use Beike\Models\Address;
use Beike\Models\TaxRate;
use Beike\Models\TaxRule;

class Tax
{
    private array $taxRates = [];
    private static array $taxRules = [];

    const AVAILABLE_TYPES = ['shipping', 'payment', 'store'];

    public function __construct($data = array())
    {
        $countryId = (int)system_setting('base.country_id');
        $zoneId = (int)system_setting('base.zone_id');

        $shippingAddress = $data['shipping_address'] ?? null;
        $paymentAddress = $data['payment_address'] ?? null;

        if ($shippingAddress) {
            if ($shippingAddress instanceof Address) {
                $this->setShippingAddress($shippingAddress->country_id, $shippingAddress->zone_id);
            } elseif ($shippingAddress instanceof \ArrayObject) {
                $this->setShippingAddress($countryId, $zoneId);
            } else {
                $this->setShippingAddress($shippingAddress['country_id'], $shippingAddress['zone_id']);
            }
        } elseif (system_setting('base.config_tax_default') == 'shipping') {
            $this->setShippingAddress($countryId, $zoneId);
        }

        if ($paymentAddress) {
            if ($paymentAddress instanceof Address) {
                $this->setPaymentAddress($paymentAddress->country_id, $paymentAddress->zone_id);
            } elseif ($paymentAddress instanceof \ArrayObject) {
                $this->setShippingAddress($countryId, $zoneId);
            } else {
                $this->setPaymentAddress($paymentAddress['country_id'], $paymentAddress['zone_id']);
            }
        } elseif (system_setting('base.config_tax_default') == 'payment') {
            $this->setPaymentAddress($countryId, $zoneId);
        }

        $this->setStoreAddress($countryId, $zoneId);
    }

    public static function getInstance($data = array())
    {
        return new self($data);
    }

    private function getTaxRules($type, $countryId, $zoneId)
    {
        if (self::$taxRules !== null && isset(self::$taxRules["$type-$countryId-$zoneId"])) {
            return self::$taxRules["$type-$countryId-$zoneId"];
        }

        $sqlBuilder = TaxRule::query()
            ->from('tax_rules as rule')
            ->select('rule.*', 'rate.*')
            ->leftJoin('tax_rates as rate', 'rule.tax_rate_id', '=', 'rate.id')
            ->leftJoin('region_zones as rz', 'rate.region_id', '=', 'rz.region_id')
            ->leftJoin('regions as region', 'rate.region_id', '=', 'region.id')
            ->where('rule.based', $type)
            ->where('rz.country_id', $countryId)
            ->where(function ($query) use ($zoneId) {
                $query->where('rz.zone_id', '=', 0)
                    ->orWhere('rz.zone_id', '=', (int)$zoneId);
            })
            ->orderBy('rule.priority');
        $data = $sqlBuilder->get();
        self::$taxRules["$type-$countryId-$zoneId"] = $data;
        return $data;
    }

    private function setAddress($type, $countryId, $zoneId)
    {
        if (!in_array($type, self::AVAILABLE_TYPES)) {
            throw new \Exception('invalid tax types');
        }

        $data = $this->getTaxRules($type, $countryId, $zoneId);

        foreach ($data as $result) {
            $this->taxRates[$result->tax_class_id][$result->tax_rate_id] = array(
                'tax_rate_id' => $result->tax_rate_id,
                'name' => $result->name,
                'rate' => $result->rate,
                'type' => $result->type,
                'priority' => $result->priority
            );
        }
    }

    public function unsetRates()
    {
        $this->taxRates = array();
    }

    public function setShippingAddress($countryId, $zoneId)
    {
        $this->setAddress('shipping', $countryId, $zoneId);
    }

    public function setPaymentAddress($countryId, $zoneId)
    {
        $this->setAddress('payment', $countryId, $zoneId);
    }

    public function setStoreAddress($countryId, $zoneId)
    {
        $this->setAddress('store', $countryId, $zoneId);
    }

    /**
     * $tax = \App\Models\Library\Tax::getInstance();
     * $tax->setShippingAddress(1, 0);
     * $tax->calculate(123.45, 9, $tax->config->getValue('config_tax'))
     *
     * @param $value
     * @param $taxClassId
     * @param bool|true $calculate
     * @return mixed
     */
    public function calculate($value, $taxClassId, bool $calculate = true)
    {
        if ($taxClassId && $calculate) {
            $amount = 0;
            $taxRates = $this->getRates($value, $taxClassId);
            foreach ($taxRates as $taxRate) {
                if ($calculate != 'P' && $calculate != 'F') {
                    $amount += $taxRate['amount'];
                } elseif ($taxRate['type'] == $calculate) {
                    $amount += $taxRate['amount'];
                }
            }
            return $value + $amount;
        } else {
            return $value;
        }
    }

    public function getTax($value, $taxClassId)
    {
        $amount = 0;
        $taxRates = $this->getRates($value, $taxClassId);
        foreach ($taxRates as $taxRate) {
            $amount += $taxRate['amount'];
        }
        return $amount;
    }

    public function getRateName($taxRateId)
    {
        $taxRate = TaxRate::query()->find($taxRateId);
        if ($taxRate) {
            return $taxRate->name;
        } else {
            return false;
        }
    }

    public function getRates($value, $taxClassId)
    {
        $taxRateData = array();

        if (isset($this->taxRates[$taxClassId])) {
            foreach ($this->taxRates[$taxClassId] as $taxRate) {
                if (isset($taxRateData[$taxRate['tax_rate_id']])) {
                    $amount = $taxRateData[$taxRate['tax_rate_id']]['amount'];
                } else {
                    $amount = 0;
                }

                if ($taxRate['type'] == 'flat') {
                    $amount += $taxRate['rate'];
                } elseif ($taxRate['type'] == 'percent') {
                    $amount += ($value / 100 * $taxRate['rate']);
                }

                $taxRateData[$taxRate['tax_rate_id']] = array(
                    'tax_rate_id' => $taxRate['tax_rate_id'],
                    'name' => $taxRate['name'],
                    'rate' => $taxRate['rate'],
                    'type' => $taxRate['type'],
                    'amount' => $amount
                );
            }
        }

        return $taxRateData;
    }
}
