from decimal import Decimal as D
from unittest import mock

from django.test import TestCase
from django.urls import reverse

from oscar.core.loading import get_class, get_model

import ocyan.core.fender
from ocyan.core.utils import round_to_two_dec

Product = get_model("catalogue", "Product")
Basket = get_model("basket", "Basket")
Selector = get_class("partner.strategy", "Selector")

OCYAN_MOCK_CONFIG_INCL_TAX = {
    "oscar": {"use_price_incl_tax": True},
    "shipping": {
        "enable_free_shipping": False,
        "enable_weightbased_shipping": True,
        "enable_charged_shipping": True,
        "paid_shipping_first": True,
    },
}
OCYAN_MOCK_CONFIG_EXCL_TAX = {
    "oscar": {"use_price_incl_tax": False},
    "shipping": {
        "enable_free_shipping": False,
        "enable_weightbased_shipping": True,
        "enable_charged_shipping": True,
        "paid_shipping_first": True,
    },
}


@mock.patch.object(ocyan.core.fender.config, "settings", OCYAN_MOCK_CONFIG_EXCL_TAX)
class FourDecimalsExclTaxRoundingTest(TestCase):
    fixtures = ["taxrounding"]

    def test_price(self):
        product = Product.objects.get(pk=11544)
        strategy = Selector().strategy()
        purchase_info = strategy.fetch_for_product(product)
        self.assertEqual(purchase_info.price.excl_tax, D("0.16"))
        self.assertEqual(purchase_info.price.incl_tax, D("0.1936"))

    def test_add_to_basket(self):
        basket_add_url = reverse("basket:add", args=(11544,))
        body = {"quantity": 2}
        response = self.client.post(basket_add_url, body, follow=True)
        basket = response.context["basket"]
        self.assertEqual(basket.total_incl_tax, D("0.39"))  # 0.3872
        self.assertEqual(basket.total_excl_tax, D("0.32"))
        (line,) = basket.all_lines()

        self.assertEqual(line.unit_effective_price, D("0.1936"))
        self.assertEqual(line.unit_price_incl_tax, D("0.1936"))
        self.assertEqual(line.unit_price_excl_tax, D("0.16"))
        self.assertEqual(line.line_price_excl_tax * D("1.21"), line.line_price_incl_tax)

    def test_add_to_basket_ten(self):
        basket_add_url = reverse("basket:add", args=(11544,))
        body = {"quantity": 10}
        response = self.client.post(basket_add_url, body, follow=True)
        basket = response.context["basket"]
        self.assertEqual(basket.total_incl_tax, D("1.94"))  # 1.9360
        self.assertEqual(basket.total_excl_tax, D("1.60"))
        (line,) = basket.all_lines()

        self.assertEqual(line.unit_effective_price, D("0.1936"))
        self.assertEqual(line.unit_price_incl_tax, D("0.1936"))
        self.assertEqual(line.unit_price_excl_tax, D("0.16"))
        self.assertEqual(line.line_price_excl_tax * D("1.21"), line.line_price_incl_tax)

    def test_basket_total(self):
        self.test_add_to_basket()

        basket_add_url = reverse("basket:add", args=(1,))
        body = {"quantity": 2}
        response = self.client.post(basket_add_url, body, follow=True)
        basket = response.context["basket"]
        self.assertEqual(
            basket.total_incl_tax, D("0.94"), "The rounded sum should be 0.94"
        )
        self.assertEqual(basket.total_excl_tax, D("0.78"))
        _, line = basket.all_lines()
        self.assertEqual(line.unit_effective_price, D("0.2783"))
        self.assertEqual(line.unit_price_incl_tax, D("0.2783"))
        self.assertEqual(line.unit_price_excl_tax, D("0.23"))
        self.assertEqual(line.line_price_excl_tax * D("1.21"), line.line_price_incl_tax)


@mock.patch.object(ocyan.core.fender.config, "settings", OCYAN_MOCK_CONFIG_INCL_TAX)
class FourDecimalsInclTaxRoundingTest(TestCase):
    fixtures = ["taxrounding"]

    def test_price(self):
        product = Product.objects.get(pk=11544)
        strategy = Selector().strategy()
        purchase_info = strategy.fetch_for_product(product)
        self.assertEqual(
            purchase_info.price.excl_tax,
            D("0.1322"),
            "nothing should happen because it is",
        )
        self.assertEqual(purchase_info.price.incl_tax, D("0.16"))

    def test_add_to_basket(self):
        basket_add_url = reverse("basket:add", args=(11544,))
        body = {"quantity": 7}
        response = self.client.post(basket_add_url, body, follow=True)
        basket = response.context["basket"]
        self.assertEqual(basket.total_incl_tax, D("1.12"))
        self.assertEqual(basket.total_excl_tax, D("0.93"))
        self.assertEqual(basket.total_tax, D("0.19"))
        (line,) = basket.all_lines()

        self.assertEqual(line.unit_effective_price, D("0.16"))
        self.assertEqual(line.unit_price_incl_tax, D("0.16"))
        self.assertEqual(line.unit_price_excl_tax, D("0.1322"))
        self.assertEqual(
            round_to_two_dec(line.unit_price_excl_tax * D("1.21")),
            line.unit_price_incl_tax,
        )

    def test_add_to_basket_ten(self):
        basket_add_url = reverse("basket:add", args=(11544,))
        body = {"quantity": 10}
        response = self.client.post(basket_add_url, body, follow=True)
        basket = response.context["basket"]
        self.assertEqual(basket.total_incl_tax, D("1.60"))
        self.assertEqual(basket.total_excl_tax, D("1.32"))
        (line,) = basket.all_lines()

        self.assertEqual(line.unit_effective_price, D("0.16"))
        self.assertEqual(line.unit_price_incl_tax, D("0.16"))
        self.assertEqual(line.unit_price_excl_tax, D("0.1322"))
        self.assertEqual(
            round_to_two_dec(line.unit_price_excl_tax * D("1.21")),
            line.unit_price_incl_tax,
        )

    def test_basket_total(self):
        self.test_add_to_basket()

        basket_add_url = reverse("basket:add", args=(1,))
        body = {"quantity": 53}
        response = self.client.post(basket_add_url, body, follow=True)
        basket = response.context["basket"]

        self.assertEqual(basket.total_incl_tax, D("13.31"))
        self.assertEqual(basket.total_excl_tax, D("11.00"))
        _, line = basket.all_lines()
        self.assertEqual(line.unit_effective_price, D("0.23"))
        self.assertEqual(line.unit_price_incl_tax, D("0.23"))
        self.assertEqual(line.unit_price_excl_tax, D("0.1901"))
        self.assertEqual(
            round_to_two_dec(line.unit_price_excl_tax * D("1.21")),
            line.unit_price_incl_tax,
        )
