from django.conf import settings

from oscar.core.loading import get_class, get_classes

from ocyan.core.utils import round_to_four_dec
from ocyan.plugin.oscar.fender import InvalidTaxRateException

ExcludingTaxPrice, IncludingTaxPrice = get_classes(
    "partner.prices", ["ExcludingTaxPrice", "IncludingTaxPrice"]
)
(
    tax_from_price_incl_tax,
    get_vat_country_from_request,
    is_local_vat_applicable,
    get_vat_info_for_country,
) = get_classes(
    "partner.utils",
    [
        "tax_from_price_incl_tax",
        "get_vat_country_from_request",
        "is_local_vat_applicable",
        "get_vat_info_for_country",
    ],
)

COUNTRY_TAX_RATES = get_class("taxes.constants", "COUNTRY_TAX_RATES")
exclusive_tax_component = get_class("taxes.utils", "exclusive_tax_component")
tax_rate_as_fraction = get_class("taxes.taxrate", "tax_rate_as_fraction")


class IncludingTaxPriceSelectorFunctor:
    def __call__(
        self, user, request, vat_rate=None, is_shipping_charge=False, **kwargs
    ):
        if vat_rate is None:
            try:
                return IncludingTaxPrice(**kwargs)
            except TypeError as e:
                raise InvalidTaxRateException(str(e)) from e

        return self.get_price(
            incl_tax=kwargs["incl_tax"],
            currency=kwargs["currency"],
            vat_rate=vat_rate,
            vat_country=get_vat_country_from_request(request),
            is_shipping_charge=is_shipping_charge,
            basket=getattr(request, "basket", None),
            user=user,
        )

    def get_price(
        self,
        incl_tax,
        currency,
        vat_rate,
        vat_country,
        is_shipping_charge=False,
        basket=None,
        user=None,
    ):
        tax_percentage = get_vat_info_for_country(
            vat_rate,
            vat_country,
            is_shipping_charge,
            basket=basket,
            user=user,
        ).percentage

        # When the local vat is applicable and the relative price should be used we want to calculate the excl_tax price for the country of origin.
        # With the relative excl_tax we can calculate the correct tax amount for the designated shipping country.
        if (
            settings.OCYAN_PARTNER_RELATIVE_PRICE
            and is_local_vat_applicable(vat_country)
            and vat_country != settings.OSCAR_VAT_COUNTRY_OF_ORIGIN_CODE
        ):
            return self.get_relative_price_for_vat_country(
                incl_tax=incl_tax,
                currency=currency,
                vat_rate=vat_rate,
                tax_percentage=tax_percentage,
            )

        return IncludingTaxPrice(
            currency=currency,
            incl_tax=incl_tax,
            tax=tax_from_price_incl_tax(incl_tax, tax_percentage),
            tax_code=vat_rate,
        )

    def get_relative_price_for_vat_country(
        self, incl_tax, currency, vat_rate, tax_percentage
    ):
        COUNTRY_OF_ORIGIN_VAT_COMPONENT = exclusive_tax_component(
            tax_rate_as_fraction(vat_rate)
        )
        excl_tax = incl_tax / COUNTRY_OF_ORIGIN_VAT_COMPONENT
        return ExcludingTaxPrice(
            currency=currency,
            excl_tax=round_to_four_dec(excl_tax),
            tax=round_to_four_dec(excl_tax * tax_percentage),
            tax_code=vat_rate,
        )


IncludingTaxPriceSelector = IncludingTaxPriceSelectorFunctor()


class ExcludingTaxPriceSelectorFunctor:
    # pylint: disable=unused-argument
    def __call__(
        self, user, request, vat_rate=None, is_shipping_charge=False, **kwargs
    ):
        if vat_rate is None:
            try:
                return ExcludingTaxPrice(**kwargs)
            except TypeError as e:
                raise InvalidTaxRateException(str(e)) from e

        return self.get_price(
            excl_tax=kwargs["excl_tax"],
            currency=kwargs["currency"],
            vat_rate=vat_rate,
            vat_country=get_vat_country_from_request(request),
            is_shipping_charge=is_shipping_charge,
            basket=getattr(request, "basket", None),
            user=user,
        )

    def get_price(
        self,
        excl_tax,
        currency,
        vat_rate,
        vat_country,
        is_shipping_charge,
        basket=None,
        user=None,
    ):
        tax_percentage = get_vat_info_for_country(
            vat_rate,
            vat_country,
            is_shipping_charge,
            basket=basket,
            user=user,
        ).percentage
        return ExcludingTaxPrice(
            currency=currency,
            excl_tax=excl_tax,
            tax=round_to_four_dec(excl_tax * tax_percentage),
            tax_code=vat_rate,
        )


ExcludingTaxPriceSelector = ExcludingTaxPriceSelectorFunctor()
