import random

from pyramid import httpexceptions

from kinto.core.errors import ERRORS

from . import BaseTest


class SortingTest(BaseTest):
    def setUp(self):
        super().setUp()
        self.patch_known_field.start()

        indices = list(range(20))
        random.shuffle(indices)

        for i in indices:
            obj = {"title": "MoFo #{0:02}".format(i), "status": i % 4, "unread": (i % 2 == 0)}
            self.model.create_object(obj)

    def test_sort_works_with_empty_list(self):
        self.resource.model.parent_id = "alice"
        self.resource.request.validated["querystring"] = {"_sort": ["unread"]}
        result = self.resource.plural_get()
        self.assertEqual(len(result["data"]), 0)

    def test_sort_on_unknown_attribute_raises_error(self):
        self.patch_known_field.stop()
        self.resource.request.validated["querystring"] = {"_sort": ["foo"]}
        self.assertRaises(httpexceptions.HTTPBadRequest, self.resource.plural_get)

    def test_filter_errors_are_json_formatted(self):
        self.patch_known_field.stop()
        self.resource.request.validated["querystring"] = {"_sort": ["foo"]}
        try:
            self.resource.plural_get()
        except httpexceptions.HTTPBadRequest as e:
            error = e
        self.assertEqual(
            error.json,
            {
                "errno": ERRORS.INVALID_PARAMETERS.value,
                "message": "querystring: Unknown sort field 'foo'",
                "code": 400,
                "error": "Invalid parameters",
                "details": [
                    {
                        "description": "Unknown sort field 'foo'",
                        "location": "querystring",
                        "name": None,
                    }
                ],
            },
        )

    def test_sort_on_last_modified_is_supported(self):
        self.patch_known_field.stop()
        self.resource.request.validated["querystring"] = {"_sort": ["-last_modified"]}
        result = self.resource.plural_get()
        tstamp = self.model.timestamp()
        self.assertEqual(result["data"][0]["last_modified"], tstamp)

    def test_single_basic_sort_by_attribute(self):
        self.resource.request.validated["querystring"] = {"_sort": ["title"]}
        result = self.resource.plural_get()
        self.assertEqual(len(result["data"]), 20)
        self.assertEqual(result["data"][0]["title"], "MoFo #00")
        self.assertEqual(result["data"][-1]["title"], "MoFo #19")

    def test_single_basic_sort_by_attribute_reversed(self):
        self.resource.request.validated["querystring"] = {"_sort": ["-title"]}
        result = self.resource.plural_get()
        self.assertEqual(len(result["data"]), 20)
        self.assertEqual(result["data"][0]["title"], "MoFo #19")
        self.assertEqual(result["data"][-1]["title"], "MoFo #00")

    def test_multiple_sort(self):
        self.resource.request.validated["querystring"] = {"_sort": ["status", "title"]}
        result = self.resource.plural_get()
        self.assertEqual(result["data"][0]["status"], 0)
        self.assertEqual(result["data"][0]["title"], "MoFo #00")
        self.assertEqual(result["data"][1]["status"], 0)
        self.assertEqual(result["data"][1]["title"], "MoFo #04")
        self.assertEqual(result["data"][-2]["status"], 3)
        self.assertEqual(result["data"][-2]["title"], "MoFo #15")
        self.assertEqual(result["data"][-1]["status"], 3)
        self.assertEqual(result["data"][-1]["title"], "MoFo #19")

    def test_multiple_sort_with_order(self):
        self.resource.request.validated["querystring"] = {"_sort": ["status", "-title"]}
        result = self.resource.plural_get()
        self.assertEqual(result["data"][0]["status"], 0)
        self.assertEqual(result["data"][0]["title"], "MoFo #16")
        self.assertEqual(result["data"][1]["status"], 0)
        self.assertEqual(result["data"][1]["title"], "MoFo #12")
        self.assertEqual(result["data"][-2]["status"], 3)
        self.assertEqual(result["data"][-2]["title"], "MoFo #07")
        self.assertEqual(result["data"][-1]["status"], 3)
        self.assertEqual(result["data"][-1]["title"], "MoFo #03")

    def test_boolean_sort_brings_true_last(self):
        self.resource.request.validated["querystring"] = {"_sort": ["unread"]}
        result = self.resource.plural_get()
        self.assertEqual(result["data"][0]["unread"], False)
        self.assertEqual(result["data"][-1]["unread"], True)

    def test_default_sort_is_last_modified_desc_by_default(self):
        self.resource.request.validated["querystring"] = {"_sort": [""]}
        result = self.resource.plural_get()
        tstamp = self.model.timestamp()
        self.assertEqual(result["data"][0]["last_modified"], tstamp)

    def test_default_sort_is_last_modified_objects_have_same_status(self):
        self.resource.request.validated["querystring"] = {"_sort": ["status"]}
        result = self.resource.plural_get()
        self.assertEqual(result["data"][0]["status"], 0)
        self.assertEqual(result["data"][1]["status"], 0)
        self.assertGreater(result["data"][0]["last_modified"], result["data"][1]["last_modified"])


class SubobjectSortingTest(BaseTest):
    def setUp(self):
        super().setUp()
        self.patch_known_field.start()

        indices = list(range(20))
        random.shuffle(indices)

        for i in indices:
            obj = {"party": {"candidate": "Marie", "voters": i}, "location": "Creuse"}
            self.model.create_object(obj)

    def test_objects_can_be_sorted_by_subobjects(self):
        self.resource.request.validated["querystring"] = {"_sort": ["party.voters"]}
        result = self.resource.plural_get()
        values = [item["party"]["voters"] for item in result["data"]]
        self.assertEqual(sorted(values), values)

    def test_subobjects_sorting_is_ignored_if_not_object(self):
        self.resource.request.validated["querystring"] = {"_sort": ["location.city"]}
        result = self.resource.plural_get()
        values = [item["party"]["voters"] for item in result["data"]]
        self.assertNotEqual(sorted(values), values)
