from django.contrib.auth import get_user_model
from django.test import TestCase
from django.urls import reverse

from oscar.core.loading import get_model

from ocyan.plugin.oscar.pipeline import (
    PIPELINE_STATUS_CANCELLED,
    PIPELINE_STATUS_PAID,
    PIPELINE_STATUS_PENDING_PAYMENT,
    PIPELINE_STATUS_REFUND,
    PIPELINE_STATUS_RESTOCKED,
)

User = get_user_model()
Order = get_model("order", "Order")


def change_line_status(status):
    return {
        "selected_line": "1",
        "selected_line_qty_1": "1",
        "selected_line_qty_2": "1",
        "line_action": "change_line_statuses",
        "new_status": status,
        "shipping_event_type": "",
        "reference": "",
        "payment_event_type": "",
        "amount": "",
    }


def change_order_status(status):
    return {"new_status": status, "order_action": "change_order_status"}


def refund(amount):
    return {
        "selected_line": "1",
        "selected_line_qty_1": "1",
        "selected_line_qty_2": "1",
        "new_status": "",
        "shipping_event_type": "",
        "reference": "",
        "line_action": "create_payment_event",
        "payment_event_type": PIPELINE_STATUS_REFUND,
        "amount": amount,
    }


class TestOrderStatus(TestCase):
    fixtures = [
        "communications/auth",
        "communications/address",
        "communications/catalogue",
        "communications/order",
        "communications/basket",
        "communications/customer",
        "communications/invoices",
    ]

    def setUp(self):
        super().setUp()
        user = User.objects.get(is_superuser=True)
        self.client.force_login(user)

    def order_under_test(self):
        return Order.objects.get(pk=1)

    def assert_initial_status(self, order):
        order.refresh_from_db()
        self.assertEqual(
            order.lines.count(), 2, "there should be 2 lines on the order under test"
        )

        stockrecord1, stockrecord2 = order.lines.values_list("stockrecord", flat=True)
        self.assertNotEqual(
            stockrecord1, stockrecord2, "The lines should have different stockrecords"
        )

        status1, status2 = order.lines.values_list("status", flat=True)
        self.assertEqual(status1, status2, "The lines should have the same status")

        line1, line2 = order.lines.all()
        stockrecord1, stockrecord2 = line1.stockrecord, line2.stockrecord

        self.assertEqual(stockrecord1.num_in_stock, 34)
        self.assertEqual(stockrecord1.num_allocated, 3)
        self.assertEqual(stockrecord1.net_stock_level, 31)

        self.assertEqual(stockrecord2.num_in_stock, 345)
        self.assertEqual(stockrecord2.num_allocated, 2)
        self.assertEqual(stockrecord2.net_stock_level, 343)

        source = order.sources.first()
        self.assertEqual(source.balance, 0)

    def assert_deallocation(self, order):
        order.refresh_from_db()
        line1, line2 = order.lines.all()
        stockrecord1, stockrecord2 = line1.stockrecord, line2.stockrecord

        self.assertEqual(stockrecord1.num_in_stock, 34)
        self.assertEqual(
            stockrecord1.num_allocated,
            2,
            "After cancelling the order, there should be one less allocated item",
        )
        self.assertEqual(
            stockrecord1.net_stock_level,
            32,
            "After cancelling the order, there should be one more product available",
        )

        self.assertEqual(stockrecord2.num_in_stock, 345)
        self.assertEqual(
            stockrecord2.num_allocated,
            1,
            "After cancelling the order, there should be one less allocated item",
        )
        self.assertEqual(
            stockrecord2.net_stock_level,
            344,
            "After cancelling the order, there should be one more product available",
        )

    def test_parameters_are_correct(self):
        order = self.order_under_test()
        status1, _ = order.lines.values_list("status", flat=True)
        self.assertEqual(
            status1, PIPELINE_STATUS_PAID, "The lines should have status paid"
        )

        self.assert_initial_status(order)

    def test_refund_status_only(self):
        order = self.order_under_test()

        url = reverse("dashboard:order-detail", kwargs={"number": order.number})
        response = self.client.post(
            url, change_order_status(PIPELINE_STATUS_REFUND), follow=True
        )
        self.assertEqual(response.status_code, 200)

        self.assert_initial_status(order)

    def test_cancel_whole_order(self):
        "When an orderis being cancelled, all stock should be unallocated."
        order = self.order_under_test()

        url = reverse("dashboard:order-detail", kwargs={"number": order.number})
        response = self.client.post(
            url, change_order_status(PIPELINE_STATUS_CANCELLED), follow=True
        )
        self.assertEqual(response.status_code, 200)

        # you can't cancel order in one go, they should be refunded before cancelling
        for message in response.context["messages"]:
            self.assertEqual(message.message, "Ongeldige formulier invoer")
            self.assertEqual(message.level, 40)

        # nothing should have happened
        self.assert_initial_status(order)

        # now refund first before cancelling
        self.test_refund_status_only()
        response = self.client.post(
            url, change_order_status(PIPELINE_STATUS_CANCELLED), follow=True
        )
        self.assertEqual(response.status_code, 200)

        self.assert_deallocation(order)

    def test_cancel_pending_cancel_whole_order(self):
        "When an orderis being cancelled, all stock should be unallocated."
        order = self.order_under_test()

        url = reverse("dashboard:order-detail", kwargs={"number": order.number})
        response = self.client.post(
            url, change_order_status(PIPELINE_STATUS_CANCELLED), follow=True
        )
        self.assertEqual(response.status_code, 200)

        # you can't cancel order in one go, they should be refunded before cancelling
        for message in response.context["messages"]:
            self.assertEqual(message.message, "Ongeldige formulier invoer")
            self.assertEqual(message.level, 40)

        # nothing should have happened
        self.assert_initial_status(order)

        line1, line2 = order.lines.all()
        stockrecord1, stockrecord2 = line1.stockrecord, line2.stockrecord

        self.assertEqual(stockrecord1.num_allocated, 3)
        self.assertEqual(stockrecord2.num_allocated, 2)

        # now refund first before cancelling
        self.test_refund_status_only()
        response = self.client.post(
            url, change_order_status(PIPELINE_STATUS_CANCELLED), follow=True
        )
        self.assertEqual(response.status_code, 200)

        stockrecord1.refresh_from_db()
        stockrecord2.refresh_from_db()

        self.assertEqual(stockrecord1.num_allocated, 2)
        self.assertEqual(stockrecord2.num_allocated, 1)

        response = self.client.post(
            url, change_order_status(PIPELINE_STATUS_PENDING_PAYMENT), follow=True
        )
        self.assertEqual(response.status_code, 200)

        stockrecord1.refresh_from_db()
        stockrecord2.refresh_from_db()
        self.assertEqual(stockrecord1.num_allocated, 2)
        self.assertEqual(stockrecord2.num_allocated, 1)

        response = self.client.post(
            url, change_order_status(PIPELINE_STATUS_CANCELLED), follow=True
        )
        self.assertEqual(response.status_code, 200)

        stockrecord1.refresh_from_db()
        stockrecord2.refresh_from_db()
        self.assertEqual(stockrecord1.num_allocated, 2)
        self.assertEqual(stockrecord2.num_allocated, 1)

        self.assert_deallocation(order)

    def test_refund_order_line(self):
        order = self.order_under_test()

        self.assert_initial_status(order)

        url = reverse("dashboard:order-detail", kwargs={"number": order.number})
        response = self.client.post(url, refund(12), follow=True)
        self.assertEqual(response.status_code, 200)

        source = order.sources.first()
        self.assertEqual(
            source.balance,
            12,
            "There should now be open balance on the order due to the refund",
        )

        line1, line2 = order.lines.all()
        self.assertEqual(line1.status, PIPELINE_STATUS_REFUND)
        self.assertEqual(line2.status, PIPELINE_STATUS_PAID)

    def test_cancel_order_line(self):
        order = self.order_under_test()

        url = reverse("dashboard:order-detail", kwargs={"number": order.number})

        self.test_refund_order_line()
        response = self.client.post(
            url, change_line_status(PIPELINE_STATUS_CANCELLED), follow=True
        )
        self.assertEqual(response.status_code, 200)

        line1, line2 = order.lines.all()
        self.assertEqual(line1.status, PIPELINE_STATUS_RESTOCKED)
        self.assertEqual(line2.status, PIPELINE_STATUS_PAID)

        stockrecord1, stockrecord2 = line1.stockrecord, line2.stockrecord

        self.assertEqual(stockrecord2.num_in_stock, 345)
        self.assertEqual(stockrecord2.num_allocated, 2)
        self.assertEqual(stockrecord2.net_stock_level, 343)

        self.assertEqual(stockrecord1.num_in_stock, 34)
        self.assertEqual(
            stockrecord1.num_allocated,
            2,
            "After cancelling the order, there should be one less allocated item",
        )
        self.assertEqual(
            stockrecord1.net_stock_level,
            32,
            "After cancelling the order, there should be one more product available",
        )
