# pylint: disable=no-member
from decimal import Decimal as D
from unittest import mock

from django.http import HttpRequest
from django.test import TestCase
from django.test.utils import override_settings

from oscar.apps.checkout.calculators import OrderTotalCalculator
from oscar.apps.checkout.exceptions import FailedPreCondition
from oscar.apps.checkout.mixins import CheckoutSessionMixin, OrderPlacementMixin
from oscar.apps.shipping.methods import FixedPrice, Free
from oscar.core.loading import get_class, get_model
from oscar.test import factories
from oscar.test.basket import add_product
from oscar.test.utils import RequestFactory

Order = get_model("order", "Order")
Surcharge = get_model("order", "Surcharge")

SurchargeApplicator = get_class("checkout.applicator", "SurchargeApplicator")


class TestOrderPlacementMixin(TestCase):
    def test_returns_none_when_no_shipping_address_passed_to_creation_method(self):
        address = OrderPlacementMixin().create_shipping_address(
            user=mock.Mock(), shipping_address=None
        )
        self.assertEqual(address, None)

    def test_update_address_book(self):
        basket = factories.create_basket(empty=True)
        user = factories.UserFactory()
        add_product(basket, D("12.00"))
        shipping_method = FixedPrice(D("5.00"), D("5.00"))

        billing_address = factories.BillingAddressFactory(
            line1="1 Boardwalk Place",
            line2="Trafalgar Way",
            line3="Tower Hamlets",
            line4="London",
        )
        shipping_address = factories.ShippingAddressFactory(
            line1="Knightsbridge", line2="159", line4="London"
        )
        shipping_charge = shipping_method.calculate(basket)
        applicator = SurchargeApplicator()
        surcharges = applicator.get_applicable_surcharges(basket)
        order_total = OrderTotalCalculator().calculate(
            basket, shipping_charge, surcharges
        )

        order_submission_data = {
            "user": user,
            "order_number": "12345",
            "basket": basket,
            "shipping_address": shipping_address,
            "shipping_method": shipping_method,
            "shipping_charge": shipping_charge,
            "billing_address": billing_address,
            "order_total": order_total,
        }
        OrderPlacementMixin().place_order(**order_submission_data)

        self.assertEqual(
            user.addresses.filter(hash=billing_address.generate_hash()).count(), 1
        )
        self.assertEqual(
            user.addresses.filter(hash=shipping_address.generate_hash()).count(), 1
        )

        user_billing_address = user.addresses.get(hash=billing_address.generate_hash())
        user_shipping_address = user.addresses.get(
            hash=shipping_address.generate_hash()
        )
        self.assertEqual(user_billing_address.num_orders_as_billing_address, 1)
        self.assertEqual(user_shipping_address.num_orders_as_shipping_address, 1)

        order_submission_data["order_number"] = "12346"

        OrderPlacementMixin().place_order(**order_submission_data)

        user_billing_address = user.addresses.get(hash=billing_address.generate_hash())
        user_shipping_address = user.addresses.get(
            hash=shipping_address.generate_hash()
        )
        self.assertEqual(user_billing_address.num_orders_as_billing_address, 2)
        self.assertEqual(user_shipping_address.num_orders_as_shipping_address, 2)

        order_submission_data.pop("billing_address", None)
        order_submission_data["order_number"] = "123457"
        OrderPlacementMixin().place_order(**order_submission_data)

        user_billing_address = user.addresses.get(hash=billing_address.generate_hash())
        user_shipping_address = user.addresses.get(
            hash=shipping_address.generate_hash()
        )
        self.assertEqual(user_billing_address.num_orders_as_billing_address, 2)
        self.assertEqual(user_shipping_address.num_orders_as_shipping_address, 3)

        shipping_address.line2 = "160"
        order_submission_data["billing_address"] = billing_address
        order_submission_data["order_number"] = "123458"
        OrderPlacementMixin().place_order(**order_submission_data)

        user_billing_address = user.addresses.get(hash=billing_address.generate_hash())
        user_shipping_address = user.addresses.get(
            hash=shipping_address.generate_hash()
        )
        self.assertEqual(user_billing_address.num_orders_as_billing_address, 3)
        self.assertEqual(user_shipping_address.num_orders_as_shipping_address, 1)

    @override_settings(SITE_ID="")
    def test_multi_site(self):
        basket = factories.create_basket(empty=True)
        site1 = factories.SiteFactory()
        site2 = factories.SiteFactory()
        request = HttpRequest()
        request.META["SERVER_PORT"] = 80
        request.META["SERVER_NAME"] = site1.domain
        user = factories.UserFactory()
        add_product(basket, D("12.00"))
        shipping_method = Free()
        shipping_charge = shipping_method.calculate(basket)
        applicator = SurchargeApplicator()
        surcharges = applicator.get_applicable_surcharges(basket)
        order_total = OrderTotalCalculator().calculate(
            basket, shipping_charge, surcharges
        )

        billing_address = factories.BillingAddressFactory()
        shipping_address = factories.ShippingAddressFactory()
        order_submission_data = {
            "user": user,
            "order_number": "12345",
            "basket": basket,
            "shipping_method": shipping_method,
            "shipping_charge": shipping_charge,
            "order_total": order_total,
            "billing_address": billing_address,
            "shipping_address": shipping_address,
            "request": request,
        }
        OrderPlacementMixin().place_order(**order_submission_data)
        order1 = Order.objects.get(number="12345")
        for charge in surcharges:
            Surcharge.objects.create(
                order=order1,
                name=charge.surcharge.name,
                code=charge.surcharge.code,
                excl_tax=charge.price.excl_tax,
                incl_tax=charge.price.incl_tax,
            )
        self.assertEqual(order1.site, site1)

        add_product(basket, D("12.00"))
        request.META["SERVER_NAME"] = site2.domain
        order_submission_data["order_number"] = "12346"
        order_submission_data["request"] = request
        OrderPlacementMixin().place_order(**order_submission_data)
        order2 = Order.objects.get(number="12346")
        self.assertEqual(order2.site, site2)

    def test_multiple_payment_events(self):
        basket = factories.create_basket(empty=True)
        user = factories.UserFactory()
        add_product(basket, D("100.00"))
        order_placement = OrderPlacementMixin()
        order_placement.add_payment_event("Gift Card Payment", D("10"))
        order_placement.add_payment_event("Credit Card Payment", D("90"))
        shipping_method = Free()
        shipping_charge = shipping_method.calculate(basket)
        applicator = SurchargeApplicator()
        surcharges = applicator.get_applicable_surcharges(basket)
        order_total = OrderTotalCalculator().calculate(
            basket, shipping_charge, surcharges
        )

        billing_address = factories.BillingAddressFactory()
        shipping_address = factories.ShippingAddressFactory()
        order_submission_data = {
            "user": user,
            "order_number": "12345",
            "basket": basket,
            "shipping_method": shipping_method,
            "shipping_charge": shipping_charge,
            "order_total": order_total,
            "billing_address": billing_address,
            "shipping_address": shipping_address,
        }
        order_placement.place_order(**order_submission_data)
        order1 = Order.objects.get(number="12345")
        for charge in surcharges:
            Surcharge.objects.create(
                order=order1,
                name=charge.surcharge.name,
                code=charge.surcharge.code,
                excl_tax=charge.price.excl_tax,
                incl_tax=charge.price.incl_tax,
            )
        self.assertEqual(order1.payment_events.count(), 2)
        event1 = order1.payment_events.all()[0]
        event2 = order1.payment_events.all()[1]
        self.assertEqual(event1.event_type.name, "Credit Card Payment")
        self.assertEqual(event1.amount, D("90"))
        self.assertEqual(event1.lines.count(), 1)
        self.assertEqual(event2.event_type.name, "Gift Card Payment")
        self.assertEqual(event2.amount, D("10"))
        self.assertEqual(event2.lines.count(), 1)


class TestCheckoutSessionMixin(TestCase):
    def setUp(self):
        self.request = RequestFactory().get("/")
        self.product = factories.create_product(num_in_stock=10)
        self.stock_record = self.product.stockrecords.first()

    def add_product_to_basket(self, product, quantity=1):
        self.request.basket.add_product(product, quantity=quantity)
        self.assertEqual(len(self.request.basket.all_lines()), 1)
        self.assertEqual(self.request.basket.all_lines()[0].product, product)

    def test_check_basket_is_valid_no_stock_available(self):
        self.add_product_to_basket(self.product)
        CheckoutSessionMixin().check_basket_is_valid(self.request)
        self.stock_record.allocate(10)
        self.stock_record.save()
        with self.assertRaises(FailedPreCondition):
            CheckoutSessionMixin().check_basket_is_valid(self.request)

    def test_check_basket_is_valid_stock_exceeded(self):
        self.add_product_to_basket(self.product)
        CheckoutSessionMixin().check_basket_is_valid(self.request)
        self.request.basket.add_product(self.product, quantity=11)
        with self.assertRaises(FailedPreCondition):
            CheckoutSessionMixin().check_basket_is_valid(self.request)
