from save_to_db.core.item import Item
from save_to_db.exceptions import MultipleModelsMatch
from save_to_db.utils.test_base import TestBase


class TestItemHooks(TestBase):
    """This class contains tests that make sure that item hooks are called at
    proper times and arguments.
    """

    ModelGeneralOne = None
    ModelGeneralTwo = None

    def setup_models(
        self,
        one_dict=None,
        two_dict=None,
        one_conf=None,
        two_conf=None,
        clear_registry=False,
    ):
        if clear_registry:
            self.item_cls_manager.clear()

        one_dict = one_dict if one_dict is not None else {}
        one_dict["model_cls"] = self.ModelGeneralOne
        if one_conf:
            one_dict.update(one_conf)
        self.ItemGeneralOne = type("ItemGeneralOne", (Item,), one_dict)

        two_dict = one_dict if two_dict is not None else {}
        two_dict["model_cls"] = self.ModelGeneralTwo
        if two_conf:
            two_dict.update(two_conf)
        self.ItemGeneralTwo = type("ItemGeneralTwo", (Item,), two_dict)

    def test_before_and_after_process(self):
        process_log = []

        def before_process(item):
            self.assertIsInstance(item["f_integer"], str)  # still a string
            process_log.append(
                "BEFORE {}: {} ({})".format(
                    item.__class__.__name__,
                    item["f_integer"],
                    type(item["f_integer"]).__name__,
                )
            )

        def after_process(item):
            self.assertIsInstance(item["f_integer"], int)  # an integer
            process_log.append(
                "AFTER {}: {} ({})".format(
                    item.__class__.__name__,
                    item["f_integer"],
                    type(item["f_integer"]).__name__,
                )
            )

        self.setup_models(
            one_dict={"before_process": before_process, "after_process": after_process},
            two_dict={"before_process": before_process, "after_process": after_process},
        )

        item_one = self.ItemGeneralOne(f_integer="1000")
        for i in range(2):
            item_one["two_x_x"].gen(f_integer=str(2000 + i))

        item_one.process()

        expected_log = [
            "BEFORE ItemGeneralOne: 1000 (str)",
            "BEFORE ItemGeneralTwo: 2000 (str)",
            "AFTER ItemGeneralTwo: 2000 (int)",
            "BEFORE ItemGeneralTwo: 2001 (str)",
            "AFTER ItemGeneralTwo: 2001 (int)",
            "AFTER ItemGeneralOne: 1000 (int)",
        ]

        self.assertEqual(process_log, expected_log)

    def test_before_and_after_persist(self):
        update_log = []

        def before_model_update(item, model):
            update_log.append(
                [
                    "before_model_update",
                    [item["f_integer"], type(item["f_integer"]).__name__],
                    model.f_integer,
                ]
            )

        def after_model_save(item, model):
            update_log.append(
                [
                    "after_model_save",
                    [item["f_integer"], type(item["f_integer"]).__name__],
                    model.f_integer,
                ]
            )

        persister = self.persister

        self.setup_models(
            one_dict={
                "before_model_update": before_model_update,
                "after_model_save": after_model_save,
            },
            one_conf={"creators": ["f_float"], "getters": ["f_float"]},
            two_dict={
                "before_model_update": before_model_update,
                "after_model_save": after_model_save,
            },
            two_conf={"creators": ["f_float"], "getters": ["f_float"]},
        )

        # initial item persisted, some items in a bulk omitted -----------------
        item_one = self.ItemGeneralOne(f_integer="1000", f_float="10.1")
        for i in range(3):
            item_two = item_one["two_x_x"].gen(f_integer=str(2000 + i))
            if i % 2 == 0:  # some items will be omitted
                item_two["f_float"] = str(2100 + i)

        persister.persist(item_one)

        expected = [
            ["before_model_update", [2000, "int"], None],
            ["after_model_save", [2000, "int"], 2000],
            # item with `f_integer='2001'` was not created
            ["before_model_update", [2002, "int"], None],
            ["after_model_save", [2002, "int"], 2002],
            ["before_model_update", [1000, "int"], None],
            ["after_model_save", [1000, "int"], 1000],
        ]

        sort_func = lambda x: (
            x[0],
            x[1][0],
            x[2],
        )
        expected.sort(key=sort_func)
        update_log.sort(key=sort_func)
        self.assertEqual(update_log, expected)

        # initial item omitted -------------------------------------------------
        update_log.clear()

        item_one = self.ItemGeneralOne(f_integer="10000")
        # 1 item updated, 1 item created
        for i in range(2):
            item_one["two_x_x"].gen(f_integer=str(2000 + i), f_float=str(2100 + i))

        persister.persist(item_one)

        expected = [
            ["before_model_update", [2000, "int"], 2000],  # updated
            ["after_model_save", [2000, "int"], 2000],
            ["before_model_update", [2001, "int"], None],  # created
            ["after_model_save", [2001, "int"], 2001],
        ]

        expected.sort(key=sort_func)
        update_log.sort(key=sort_func)

        self.assertTrue(update_log == expected)

    def test_item_hooks_order(self):
        log = []

        def before_process(*_args, **_kwargs):
            log.append("before_process")

        def after_process(*_args, **_kwargs):
            log.append("after_process")

        def before_model_update(*_args, **_kwargs):
            log.append("before_model_update")

        def after_model_save(*_args, **_kwargs):
            log.append("after_model_save")

        self.setup_models(
            one_dict={
                "before_process": before_process,
                "after_process": after_process,
                "before_model_update": before_model_update,
                "after_model_save": after_model_save,
            }
        )

        item_one = self.ItemGeneralOne(f_integer="1000")
        self.persister.persist(item_one)

        expected_log = [
            "before_process",
            "after_process",
            "before_model_update",
            "after_model_save",
        ]
        self.assertTrue(log, expected_log)
