"""
Tests for dbserialize module
"""

from collections import defaultdict, deque
from enum import IntFlag, auto

from django.test import TestCase
from parameterized import parameterized

from evennia.objects.objects import DefaultObject
from evennia.utils import dbserialize


class TestDbSerialize(TestCase):
    """
    Database serialization operations.
    """

    def setUp(self):
        self.obj = DefaultObject(db_key="Tester")
        self.obj.save()

    def test_intflag(self):
        class TestFlag(IntFlag):
            foo = auto()

        self.obj.db.test = TestFlag.foo
        self.assertEqual(self.obj.db.test, TestFlag.foo)
        self.obj.save()

    def test_constants(self):
        self.obj.db.test = 1
        self.obj.db.test += 1
        self.assertEqual(self.obj.db.test, 2)
        self.obj.db.test -= 3
        self.assertEqual(self.obj.db.test, -1)
        self.obj.db.test *= -2
        self.assertEqual(self.obj.db.test, 2)
        self.obj.db.test /= 2
        self.assertEqual(self.obj.db.test, 1)

    def test_saverlist(self):
        self.obj.db.test = [1, 2, 3]
        self.assertEqual(self.obj.db.test, [1, 2, 3])
        self.obj.db.test.append("4")
        self.assertEqual(self.obj.db.test, [1, 2, 3, "4"])
        self.obj.db.test.insert(1, 1.5)
        self.assertEqual(self.obj.db.test, [1, 1.5, 2, 3, "4"])
        self.obj.db.test.pop()
        self.assertEqual(self.obj.db.test, [1, 1.5, 2, 3])
        self.obj.db.test.pop(0)
        self.assertEqual(self.obj.db.test, [1.5, 2, 3])
        self.obj.db.test.reverse()
        self.assertEqual(self.obj.db.test, [3, 2, 1.5])

    def test_saverlist__sort(self):
        self.obj.db.test = [3, 2, 1.5]
        self.obj.db.test.sort()
        self.assertEqual(self.obj.db.test, [1.5, 2, 3])
        self.obj.db.test.extend([0, 4, 5])
        self.assertEqual(self.obj.db.test, [1.5, 2, 3, 0, 4, 5])
        self.obj.db.test.sort()
        self.assertEqual(self.obj.db.test, [0, 1.5, 2, 3, 4, 5])
        self.obj.db.test = [[4, 5, 6], [1, 2, 3]]
        self.assertEqual(self.obj.db.test, [[4, 5, 6], [1, 2, 3]])
        self.obj.db.test.sort()
        self.assertEqual(self.obj.db.test, [[1, 2, 3], [4, 5, 6]])
        self.obj.db.test = [{1: 0}, {0: 1}]
        self.assertEqual(self.obj.db.test, [{1: 0}, {0: 1}])
        self.obj.db.test.sort(key=lambda d: str(d))
        self.assertEqual(self.obj.db.test, [{0: 1}, {1: 0}])

    def test_saverdict(self):
        self.obj.db.test = {"a": True}
        self.obj.db.test.update({"b": False})
        self.assertEqual(self.obj.db.test, {"a": True, "b": False})
        self.obj.db.test |= {"c": 5}
        self.assertEqual(self.obj.db.test, {"a": True, "b": False, "c": 5})

    @parameterized.expand(
        [
            ("list", list, dbserialize._SaverList, [1, 2, 3]),
            ("dict", dict, dbserialize._SaverDict, {"key": "value"}),
            ("set", set, dbserialize._SaverSet, {1, 2, 3}),
            ("deque", deque, dbserialize._SaverDeque, deque(("a", "b", "c"))),
            (
                "OrderedDict",
                dbserialize.OrderedDict,
                dbserialize._SaverOrderedDict,
                dbserialize.OrderedDict([("a", 1), ("b", 2), ("c", 3)]),
            ),
        ]
    )
    def test_deserialize(self, _, base_type, saver_type, default_value):
        self.assertIsInstance(default_value, base_type)
        self.obj.db.test = default_value
        for value in (dbserialize.deserialize(self.obj.db.test), self.obj.db.test.deserialize()):
            self.assertIsInstance(value, base_type)
            self.assertNotIsInstance(value, saver_type)
            self.assertEqual(value, default_value)
        self.obj.db.test = {"a": True}
        self.obj.db.test.update({"b": False})
        self.assertEqual(self.obj.db.test, {"a": True, "b": False})

    def test_defaultdict(self):
        # baseline behavior for a defaultdict
        _dd = defaultdict(list)
        _dd["a"]
        self.assertEqual(_dd, {"a": []})

        # behavior after defaultdict is set as attribute

        dd = defaultdict(list)
        self.obj.db.test = dd
        self.obj.db.test["a"]
        self.assertEqual(self.obj.db.test, {"a": []})

        self.obj.db.test["a"].append(1)
        self.assertEqual(self.obj.db.test, {"a": [1]})
        self.obj.db.test["a"].append(2)
        self.assertEqual(self.obj.db.test, {"a": [1, 2]})
        self.obj.db.test["a"].append(3)
        self.assertEqual(self.obj.db.test, {"a": [1, 2, 3]})
        self.obj.db.test |= {"b": [5, 6]}
        self.assertEqual(self.obj.db.test, {"a": [1, 2, 3], "b": [5, 6]})

    def test_deque_with_maxlen(self):
        _dd = deque((), maxlen=1)
        _dd.append(1)
        _dd.append(2)
        self.assertEqual(list(_dd), [2])

        dd = deque((), maxlen=1)
        self.obj.db.test = dd
        self.obj.db.test.append(1)
        self.obj.db.test.append(2)
        self.assertEqual(list(self.obj.db.test), [2])


class _InvalidContainer:
    """Container not saveable in Attribute (if obj is dbobj, it 'hides' it)"""

    def __init__(self, obj):
        self.hidden_obj = obj


class _ValidContainer(_InvalidContainer):
    """Container possible to save in Attribute (handles hidden dbobj explicitly)"""

    def __serialize_dbobjs__(self):
        self.hidden_obj = dbserialize.dbserialize(self.hidden_obj)

    def __deserialize_dbobjs__(self):
        self.hidden_obj = dbserialize.dbunserialize(self.hidden_obj)


class DbObjWrappers(TestCase):
    """
    Test the `__serialize_dbobjs__` and `__deserialize_dbobjs__` methods.

    """

    def setUp(self):
        super().setUp()
        self.dbobj1 = DefaultObject(db_key="Tester1")
        self.dbobj1.save()
        self.dbobj2 = DefaultObject(db_key="Tester2")
        self.dbobj2.save()

    def test_dbobj_hidden_obj__fail(self):
        with self.assertRaises(TypeError):
            self.dbobj1.db.testarg = _InvalidContainer(self.dbobj1)

    def test_consecutive_fetch(self):
        con = _ValidContainer(self.dbobj2)
        self.dbobj1.db.testarg = con
        attrobj = self.dbobj1.attributes.get("testarg", return_obj=True)

        self.assertEqual(attrobj.value, con)
        self.assertEqual(attrobj.value, con)
        self.assertEqual(attrobj.value.hidden_obj, self.dbobj2)

    def test_dbobj_hidden_obj__success(self):
        con = _ValidContainer(self.dbobj2)
        self.dbobj1.db.testarg = con

        # accessing the same data multiple times
        res1 = self.dbobj1.db.testarg
        res2 = self.dbobj1.db.testarg
        res3 = self.dbobj1.db.testarg

        self.assertEqual(res1, res2)
        self.assertEqual(res1, res3)
        self.assertEqual(res1, con)
        self.assertEqual(res2, con)
        self.assertEqual(res1.hidden_obj, self.dbobj2)
        self.assertEqual(res2.hidden_obj, self.dbobj2)
        self.assertEqual(res3.hidden_obj, self.dbobj2)

    def test_dbobj_hidden_dict(self):
        con1 = _ValidContainer(self.dbobj2)
        con2 = _ValidContainer(self.dbobj2)

        self.dbobj1.db.dict = {}

        self.dbobj1.db.dict["key1"] = con1
        self.dbobj1.db.dict["key2"] = con2

        self.assertEqual(self.dbobj1.db.dict["key1"].hidden_obj, self.dbobj2)
        self.assertEqual(self.dbobj1.db.dict["key1"].hidden_obj, self.dbobj2)
        self.assertEqual(self.dbobj1.db.dict["key2"].hidden_obj, self.dbobj2)
        self.assertEqual(self.dbobj1.db.dict["key2"].hidden_obj, self.dbobj2)

    def test_dbobj_hidden_defaultdict(self):
        con1 = _ValidContainer(self.dbobj2)
        con2 = _ValidContainer(self.dbobj2)

        self.dbobj1.db.dfdict = defaultdict(dict)

        self.dbobj1.db.dfdict["key"]["con1"] = con1
        self.dbobj1.db.dfdict["key"]["con2"] = con2

        self.assertEqual(self.dbobj1.db.dfdict["key"]["con1"].hidden_obj, self.dbobj2)

        self.assertEqual(self.dbobj1.db.dfdict["key"]["con1"].hidden_obj, self.dbobj2)
        self.assertEqual(self.dbobj1.db.dfdict["key"]["con2"].hidden_obj, self.dbobj2)
        self.assertEqual(self.dbobj1.db.dfdict["key"]["con2"].hidden_obj, self.dbobj2)
