from decimal import Decimal as D
from unittest import mock

from django.test import RequestFactory, TestCase, override_settings
from django.urls import reverse

from oscar.apps.partner import strategy
from oscar.core.loading import get_class
from oscar.test import factories

from oscarapi.basket.operations import get_user_basket, prepare_basket

import ocyan.core.fender

tax_rate_as_fraction = get_class("taxes.taxrate", "tax_rate_as_fraction")
factory = RequestFactory()

OCYAN_MOCK_CONFIG_INCL_TAX = {"oscar": {"use_price_incl_tax": True}}
OCYAN_MOCK_CONFIG_EXCL_TAX = {"oscar": {"use_price_incl_tax": False}}


@mock.patch.object(ocyan.core.fender.config, "settings", OCYAN_MOCK_CONFIG_INCL_TAX)
@override_settings(OSCAR_STRATEGY_ACCEPTS_OPTIONS=False)
class TestTaxRoundingInclTaxPrices(TestCase):
    def setUp(self):
        self.product = factories.create_product(price=D("5.00"), num_in_stock=100)
        self.user = factories.UserFactory()
        self.client.force_login(self.user)
        self.basket = get_user_basket(self.user)

    def test_total_excl_tax(self):
        self.basket.strategy = strategy.UK()
        self.basket.strategy.rate = tax_rate_as_fraction("high", "NL")
        body = {"quantity": 3}

        basket_add_url = reverse("basket:add", args=(self.product.id,))
        self.client.post(basket_add_url, body)

        request = factory.post(basket_add_url, body)
        request.user = self.user
        request.session = self.client.session

        self.basket.refresh_from_db()
        self.basket = prepare_basket(self.basket, request)

        self.assertEqual(self.basket.total_excl_tax, D("12.40"))
        self.assertEqual(self.basket.total_incl_tax, D("15"))
        self.assertEqual(self.basket.total_tax, D("2.60"))


@mock.patch.object(ocyan.core.fender.config, "settings", OCYAN_MOCK_CONFIG_EXCL_TAX)
class TestTaxRoundingExclTaxPrices(TestCase):
    def setUp(self):
        self.product = factories.create_product(price=D("0.01"), num_in_stock=100)
        self.user = factories.UserFactory()
        self.client.force_login(self.user)
        self.basket = get_user_basket(self.user)

    def test_total_incl_tax(self):
        """
        100 items of 1 cent, with 21% tax should have a total tax value of 0.21
        """
        body = {"quantity": 100}

        basket_add_url = reverse("basket:add", args=(self.product.id,))
        self.client.post(basket_add_url, body)

        request = factory.post(basket_add_url, body)
        request.user = self.user
        request.session = self.client.session

        self.basket.refresh_from_db()
        self.basket = prepare_basket(self.basket, request)

        self.assertEqual(self.basket.total_excl_tax, D("1"))
        self.assertEqual(self.basket.total_incl_tax, D("1.21"))
        self.assertEqual(self.basket.total_tax, D("0.21"))

    def test_one_cent_price_zero_tax(self):
        """
        For one cent price, the tax is less than 1 cent, with rounding there is no tax.
        """
        body = {"quantity": 1}

        basket_add_url = reverse("basket:add", args=(self.product.id,))
        self.client.post(basket_add_url, body)

        request = factory.post(basket_add_url, body)
        request.user = self.user
        request.session = self.client.session

        self.basket.refresh_from_db()
        self.basket = prepare_basket(self.basket, request)

        self.assertEqual(self.basket.total_excl_tax, D("0.01"))
        self.assertEqual(self.basket.total_incl_tax, D("0.01"))
        self.assertEqual(self.basket.total_tax, D("0.00"))

    def test_two_cent_price_zero_tax(self):
        """
        For one cent price, the tax is less than 1 cent, with rounding there is no tax.
        """
        body = {"quantity": 2}

        basket_add_url = reverse("basket:add", args=(self.product.id,))
        self.client.post(basket_add_url, body)

        request = factory.post(basket_add_url, body)
        request.user = self.user
        request.session = self.client.session

        self.basket.refresh_from_db()
        self.basket = prepare_basket(self.basket, request)

        self.assertEqual(self.basket.total_excl_tax, D("0.02"))
        self.assertEqual(self.basket.total_incl_tax, D("0.02"))
        self.assertEqual(self.basket.total_tax, D("0.00"))

    def test_three_cent_price_one_cent_tax(self):
        """
        For three cent price, the tax is less than 1 cent, rounded to 1 cent.
        """
        body = {"quantity": 3}

        basket_add_url = reverse("basket:add", args=(self.product.id,))
        self.client.post(basket_add_url, body)

        request = factory.post(basket_add_url, body)
        request.user = self.user
        request.session = self.client.session

        self.basket.refresh_from_db()
        self.basket = prepare_basket(self.basket, request)

        self.assertEqual(self.basket.total_excl_tax, D("0.03"))
        self.assertEqual(self.basket.total_incl_tax, D("0.04"))
        self.assertEqual(self.basket.total_tax, D("0.01"))

    def test_total_tax_rounding(self):
        """
        2 lines with price of 0.03, each having 0.0063 tax (rounded to 0.01).
        But when combined they have a combined tax of 0.0126 (rounded to 0.01).
        Combined total tax: 21% of 0.06 = 0.0126
        total_incl_tax = 0.06 + 0.0126 = 0.0726 rounds to 0.07
        """
        second_product = factories.create_product(price=D("0.01"), num_in_stock=100)
        body = {"quantity": 3}

        basket_add_url = reverse("basket:add", args=(self.product.id,))
        self.client.post(basket_add_url, body)
        basket_add_url = reverse("basket:add", args=(second_product.id,))
        self.client.post(basket_add_url, body)

        request = factory.post(basket_add_url, body)
        request.user = self.user
        request.session = self.client.session

        self.basket.refresh_from_db()
        self.basket = prepare_basket(self.basket, request)

        self.assertEqual(self.basket.total_excl_tax, D("0.06"))
        self.assertEqual(self.basket.total_incl_tax, D("0.07"))
        self.assertEqual(self.basket.total_tax, D("0.01"))
