"""mergedeep test module"""
import inspect
import unittest
from collections import Counter
from copy import deepcopy

from mergedeep import merge, Strategy


class test_mergedeep(unittest.TestCase):
    """mergedeep function tests."""

    ##############################################################################################################################
    # REPLACE
    ##############################################################################################################################

    def test_should_merge_3_dicts_into_new_dict_using_replace_strategy_and_only_mutate_target(self,):
        expected = {
            "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
            "d": 3,
            "e": {1: 2, "a": {"f": 2}},
            "f": [4, 5, 6],
            "g": (100, 200),
            "h": Counter({"a": 5, "b": 1, "c": 1}),
            "i": 2,
            "j": Counter({"z": 2}),
            "z": Counter({"a": 2}),
        }

        a = {
            "a": {"b": {"c": 5}},
            "d": 1,
            "e": {2: 3},
            "f": [1, 2, 3],
            "g": (2, 4, 6),
            "h": Counter({"a": 1, "b": 1}),
            "j": 1,
        }
        a_copy = deepcopy(a)

        b = {
            "a": {"B": {"C": 10}},
            "d": 2,
            "e": 2,
            "f": [4, 5, 6],
            "g": (100, 200),
            "h": Counter({"a": 5, "c": 1}),
            "i": Counter({"a": 1}),
            "z": Counter({"a": 2}),
        }
        b_copy = deepcopy(b)

        c = {
            "a": {"b": {"_c": 15}},
            "d": 3,
            "e": {1: 2, "a": {"f": 2}},
            "i": 2,
            "j": Counter({"z": 2}),
            "z": Counter({"a": 2}),
        }
        c_copy = deepcopy(c)

        actual = merge({}, a, b, c, strategy=Strategy.REPLACE)

        self.assertEqual(actual, expected)
        self.assertEqual(a, a_copy)
        self.assertEqual(b, b_copy)
        self.assertEqual(c, c_copy)

    def test_should_merge_2_dicts_into_existing_dict_using_replace_strategy_and_only_mutate_target(self,):
        expected = {
            "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
            "d": 3,
            "e": {1: 2, "a": {"f": 2}},
            "f": [4, 5, 6],
            "g": (100, 200),
            "h": Counter({"a": 1, "b": 1, "c": 1}),
            "i": 2,
            "j": Counter({"z": 2}),
        }

        a = {
            "a": {"b": {"c": 5}},
            "d": 1,
            "e": {2: 3},
            "f": [1, 2, 3],
            "g": (2, 4, 6),
            "h": Counter({"a": 1, "b": 1}),
            "j": 1,
        }
        a_copy = deepcopy(a)

        b = {
            "a": {"B": {"C": 10}},
            "d": 2,
            "e": 2,
            "f": [4, 5, 6],
            "g": (100, 200),
            "h": Counter({"a": 1, "c": 1}),
            "i": Counter({"a": 1}),
        }
        b_copy = deepcopy(b)

        c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}, "i": 2, "j": Counter({"z": 2})}
        c_copy = deepcopy(c)

        actual = merge(a, b, c, strategy=Strategy.REPLACE)

        self.assertEqual(actual, expected)
        self.assertEqual(actual, a)
        self.assertNotEqual(a, a_copy)
        self.assertEqual(b, b_copy)
        self.assertEqual(c, c_copy)

    def test_should_have_default_strategy_of_replace(self):
        func_spec = inspect.getfullargspec(merge)
        default_strategy = Strategy.REPLACE

        self.assertEqual(func_spec.kwonlydefaults.get("strategy"), default_strategy)

        # mock_merge.method.assert_called_with(target, source, strategy=Strategy.REPLACE)

    ##############################################################################################################################
    # ADDITIVE
    ##############################################################################################################################

    def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_lists_and_only_mutate_target(self,):
        expected = {
            "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
            "d": 3,
            "e": {1: 2, "a": {"f": 2}},
            "f": [1, 2, 3, 4, 5, 6],
        }

        a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
        a_copy = deepcopy(a)

        b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
        b_copy = deepcopy(b)

        c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
        c_copy = deepcopy(c)

        actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)

        self.assertEqual(actual, expected)
        self.assertEqual(a, a_copy)
        self.assertEqual(b, b_copy)
        self.assertEqual(c, c_copy)

    def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_sets_and_only_mutate_target(self,):
        expected = {
            "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
            "d": 3,
            "e": {1: 2, "a": {"f": 2}},
            "f": {1, 2, 3, 4, 5, 6},
        }

        a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": {1, 2, 3}}
        a_copy = deepcopy(a)

        b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": {4, 5, 6}}
        b_copy = deepcopy(b)

        c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
        c_copy = deepcopy(c)

        actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)

        self.assertEqual(actual, expected)
        self.assertEqual(a, a_copy)
        self.assertEqual(b, b_copy)
        self.assertEqual(c, c_copy)

    def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_tuples_and_only_mutate_target(self,):
        expected = {
            "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
            "d": 3,
            "e": {1: 2, "a": {"f": 2}},
            "f": (1, 2, 3, 4, 5, 6),
        }

        a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": (1, 2, 3)}
        a_copy = deepcopy(a)

        b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": (4, 5, 6)}
        b_copy = deepcopy(b)

        c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}
        c_copy = deepcopy(c)

        actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)

        self.assertEqual(actual, expected)
        self.assertEqual(a, a_copy)
        self.assertEqual(b, b_copy)
        self.assertEqual(c, c_copy)

    def test_should_merge_3_dicts_into_new_dict_using_additive_strategy_on_counters_and_only_mutate_target(self,):
        expected = {
            "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
            "d": 3,
            "e": {1: 2, "a": {"f": 2}},
            "f": Counter({"a": 2, "c": 1, "b": 1}),
            "i": 2,
            "j": Counter({"z": 2}),
            "z": Counter({"a": 4}),
        }

        a = {
            "a": {"b": {"c": 5}},
            "d": 1,
            "e": {2: 3},
            "f": Counter({"a": 1, "c": 1}),
            "i": Counter({"f": 9}),
            "j": Counter({"a": 1, "z": 4}),
        }
        a_copy = deepcopy(a)

        b = {
            "a": {"B": {"C": 10}},
            "d": 2,
            "e": 2,
            "f": Counter({"a": 1, "b": 1}),
            "j": [1, 2, 3],
            "z": Counter({"a": 2}),
        }
        b_copy = deepcopy(b)

        c = {
            "a": {"b": {"_c": 15}},
            "d": 3,
            "e": {1: 2, "a": {"f": 2}},
            "i": 2,
            "j": Counter({"z": 2}),
            "z": Counter({"a": 2}),
        }
        c_copy = deepcopy(c)

        actual = merge({}, a, b, c, strategy=Strategy.ADDITIVE)

        self.assertEqual(actual, expected)
        self.assertEqual(a, a_copy)
        self.assertEqual(b, b_copy)
        self.assertEqual(c, c_copy)

    def test_should_not_copy_references(self):
        before = 1
        after = 99

        o1 = {"key1": before}
        o2 = {"key2": before}

        expected = {"list": deepcopy([o1, o2]), "tuple": deepcopy((o1, o2))}

        a = {"list": [o1], "tuple": (o1,)}
        b = {"list": [o2], "tuple": (o2,)}

        actual = merge({}, a, b, strategy=Strategy.ADDITIVE)

        o1["key1"] = after
        o2["key2"] = after

        self.assertEqual(actual, expected)

        # Copied dicts should `not` mutate
        self.assertEqual(actual["list"][0]["key1"], before)
        self.assertEqual(actual["list"][1]["key2"], before)
        self.assertEqual(actual["tuple"][0]["key1"], before)
        self.assertEqual(actual["tuple"][1]["key2"], before)

        # Non-copied dicts should mutate
        self.assertEqual(a["list"][0]["key1"], after)
        self.assertEqual(b["list"][0]["key2"], after)
        self.assertEqual(a["tuple"][0]["key1"], after)
        self.assertEqual(b["tuple"][0]["key2"], after)

    ##############################################################################################################################
    # TYPESAFE
    # TYPESAFE_REPLACE
    ##############################################################################################################################

    def test_should_raise_TypeError_using_typesafe_strategy_if_types_differ(self):
        a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
        b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
        c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}

        with self.assertRaises(TypeError):
            merge({}, a, b, c, strategy=Strategy.TYPESAFE)

    def test_should_raise_TypeError_using_typesafe_replace_strategy_if_types_differ(self,):
        a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
        b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
        c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}

        with self.assertRaises(TypeError):
            merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE)

    def test_should_merge_3_dicts_into_new_dict_using_typesafe_strategy_and_only_mutate_target_if_types_are_compatible(
        self,
    ):
        expected = {
            "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
            "d": 3,
            "f": [4, 5, 6],
            "g": {2, 3, 4},
            "h": (1, 3),
            "z": Counter({"a": 1, "b": 1, "c": 1}),
        }

        a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
        a_copy = deepcopy(a)

        b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
        b_copy = deepcopy(b)

        c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
        c_copy = deepcopy(c)

        actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE)

        self.assertEqual(actual, expected)
        self.assertEqual(a, a_copy)
        self.assertEqual(b, b_copy)
        self.assertEqual(c, c_copy)

    def test_should_merge_3_dicts_into_new_dict_using_typesafe_replace_strategy_and_only_mutate_target_if_types_are_compatible(
        self,
    ):
        expected = {
            "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
            "d": 3,
            "f": [4, 5, 6],
            "g": {2, 3, 4},
            "h": (1, 3),
            "z": Counter({"a": 1, "b": 1, "c": 1}),
        }

        a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
        a_copy = deepcopy(a)

        b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
        b_copy = deepcopy(b)

        c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
        c_copy = deepcopy(c)

        actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_REPLACE)

        self.assertEqual(actual, expected)
        self.assertEqual(a, a_copy)
        self.assertEqual(b, b_copy)
        self.assertEqual(c, c_copy)

    ##############################################################################################################################
    # TYPESAFE_ADDITIVE
    ##############################################################################################################################

    def test_should_raise_TypeError_using_typesafe_additive_strategy_if_types_differ(self,):
        a = {"a": {"b": {"c": 5}}, "d": 1, "e": {2: 3}, "f": [1, 2, 3]}
        b = {"a": {"B": {"C": 10}}, "d": 2, "e": 2, "f": [4, 5, 6]}
        c = {"a": {"b": {"_c": 15}}, "d": 3, "e": {1: 2, "a": {"f": 2}}}

        with self.assertRaises(TypeError):
            merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE)

    def test_should_merge_3_dicts_into_new_dict_using_typesafe_additive_strategy_and_only_mutate_target_if_types_are_compatible(
        self,
    ):
        expected = {
            "a": {"b": {"c": 5, "_c": 15}, "B": {"C": 10}},
            "d": 3,
            "f": [1, 2, 3, 4, 5, 6],
            "g": {1, 2, 3, 4},
            "h": (1, 1, 3),
            "z": Counter({"a": 2, "b": 1, "c": 1}),
        }

        a = {"a": {"b": {"c": 5}}, "d": 1, "f": [1, 2, 3], "g": {1, 2, 3}, "z": Counter({"a": 1, "b": 1})}
        a_copy = deepcopy(a)

        b = {"a": {"B": {"C": 10}}, "d": 2, "f": [4, 5, 6], "g": {2, 3, 4}, "h": (1,)}
        b_copy = deepcopy(b)

        c = {"a": {"b": {"_c": 15}}, "d": 3, "h": (1, 3), "z": Counter({"a": 1, "c": 1})}
        c_copy = deepcopy(c)

        actual = merge({}, a, b, c, strategy=Strategy.TYPESAFE_ADDITIVE)

        self.assertEqual(actual, expected)
        self.assertEqual(a, a_copy)
        self.assertEqual(b, b_copy)
        self.assertEqual(c, c_copy)


if __name__ == "__main__":
    unittest.main()
