from save_to_db.adapters.utils.adapter_manager import get_adapter_cls
from save_to_db.adapters.utils.column_type import ColumnType
from save_to_db.adapters.utils.relation_type import RelationType

from save_to_db.utils.test_base import TestBase


class TestFieldsAndRelations(TestBase):
    """Tests that adapter correctly identifies model column types and
    relationship types of foreign keys.
    """

    def test_iter_fields(self):
        # --- test column types ---
        model_cls = self.ModelFieldTypes
        adapter_cls = get_adapter_cls(model_cls)

        required_column_types = [
            ct for ct in ColumnType.__members__.values() if ct != ColumnType.OTHER
        ]

        for col_name, col_type in adapter_cls.iter_fields(model_cls):
            if col_name == "id":
                continue  # ignoring "id" field
            if col_type in required_column_types:
                required_column_types.remove(col_type)

            col_type_from_name = col_name.split("_", 2)[0].upper()
            self.assertEqual(
                col_type_from_name,
                col_type.name,
                "Wrong column type for: {}".format(col_name),
            )

        self.assertTrue(
            len(required_column_types) == 0,
            "Not all columns types were defined, missing: {}".format(
                ", ".join(t.name for t in required_column_types)
            ),
        )

        # --- foreign keys must not be reported ---
        model_cls = self.ModelGeneralOne
        expected_fields = {
            ("id", ColumnType.INTEGER),
            ("f_binary", ColumnType.BINARY),
            ("f_boolean", ColumnType.BOOLEAN),
            ("f_string", ColumnType.STRING),
            ("f_text", ColumnType.TEXT),
            ("f_integer", ColumnType.INTEGER),
            ("f_float", ColumnType.FLOAT),
            ("f_decimal", ColumnType.DECIMAL),
            ("f_date", ColumnType.DATE),
            ("f_time", ColumnType.TIME),
            ("f_datetime", ColumnType.DATETIME),
        }

        actual_fields = set(
            (fname, ftype) for fname, ftype, in adapter_cls.iter_fields(model_cls)
        )

        self.assertFalse(
            expected_fields - actual_fields, "Not all columns were reported"
        )
        self.assertFalse(actual_fields - expected_fields, "Extra columns were reported")

    def test_iter_relations(self):
        model_one = self.ModelGeneralOne
        model_two = self.ModelGeneralTwo

        adapter_cls = get_adapter_cls(model_one)

        # --- model_one ---

        expected_relation_set = {
            # self relations
            ("parent_1_1", model_one, RelationType.ONE_TO_ONE, "child_1_1"),
            ("child_1_1", model_one, RelationType.ONE_TO_ONE, "parent_1_1"),
            ("parent_x_1", model_one, RelationType.MANY_TO_ONE, "child_1_x"),
            ("child_1_x", model_one, RelationType.ONE_TO_MANY, "parent_x_1"),
            ("parent_x_x", model_one, RelationType.MANY_TO_MANY, "child_x_x"),
            ("child_x_x", model_one, RelationType.MANY_TO_MANY, "parent_x_x"),
            # relations with other
            ("two_1_1", model_two, RelationType.ONE_TO_ONE, "one_1_1"),
            ("two_1_x", model_two, RelationType.ONE_TO_MANY, "one_x_1"),
            ("two_x_1", model_two, RelationType.MANY_TO_ONE, "one_1_x"),
            ("two_x_x", model_two, RelationType.MANY_TO_MANY, "one_x_x"),
        }
        actual_relation_set = set(adapter_cls.iter_relations(model_one))

        # not all relations reported?
        not_reported_relations = expected_relation_set - actual_relation_set
        self.assertFalse(
            bool(not_reported_relations),
            "Not all relations reported for {}, not reported: {}".format(
                model_one.__name__, str(not_reported_relations)
            ),
        )

        # reported extra relations?
        extra_relations = actual_relation_set - expected_relation_set
        self.assertFalse(
            extra_relations,
            "Extra relations reported for {}".format(model_one.__name__),
        )

        # relation reported twice?
        actual_relation_list = list(adapter_cls.iter_relations(model_one))
        actual_relation_list.sort()
        for actual_relation in actual_relation_set:
            actual_relation_list.remove(actual_relation)
        actual_relation_list = list(set(actual_relation_list))
        actual_relation_list.sort()
        self.assertFalse(
            actual_relation_list,
            "Relations reported more then once for {}".format(model_one.__name__),
        )

        # --- model_two ---

        expected_relation_set = {
            # self relations
            ("parent_1_1", model_two, RelationType.ONE_TO_ONE, "child_1_1"),
            ("child_1_1", model_two, RelationType.ONE_TO_ONE, "parent_1_1"),
            ("parent_x_1", model_two, RelationType.MANY_TO_ONE, "child_1_x"),
            ("child_1_x", model_two, RelationType.ONE_TO_MANY, "parent_x_1"),
            ("parent_x_x", model_two, RelationType.MANY_TO_MANY, "child_x_x"),
            ("child_x_x", model_two, RelationType.MANY_TO_MANY, "parent_x_x"),
            # relations with other
            ("one_1_1", model_one, RelationType.ONE_TO_ONE, "two_1_1"),
            ("one_1_x", model_one, RelationType.ONE_TO_MANY, "two_x_1"),
            ("one_x_1", model_one, RelationType.MANY_TO_ONE, "two_1_x"),
            ("one_x_x", model_one, RelationType.MANY_TO_MANY, "two_x_x"),
        }
        actual_relation_set = set(adapter_cls.iter_relations(model_two))

        # not all relations reported?
        not_reported_relations = expected_relation_set - actual_relation_set
        self.assertFalse(
            not_reported_relations,
            "Not all relations reported for {}".format(model_two.__name__),
        )

        # reported extra relations?
        extra_relations = actual_relation_set - expected_relation_set
        self.assertFalse(
            extra_relations,
            "Extra relations reported for {}".format(model_two.__name__),
        )

        # relation reported twice?
        actual_relation_list = list(adapter_cls.iter_relations(model_two))
        actual_relation_list.sort()
        for actual_relation in actual_relation_set:
            actual_relation_list.remove(actual_relation)
        actual_relation_list = list(set(actual_relation_list))
        actual_relation_list.sort()
        self.assertFalse(
            actual_relation_list,
            "Relations reported more then once for {}".format(model_two.__name__),
        )

    def test_table_fullname(self):
        adapter_cls = get_adapter_cls(self.ModelFieldTypes)

        table_name = adapter_cls.get_table_fullname(self.ModelFieldTypes)
        self.assertEqual(table_name, "model_field_types")

        table_name = adapter_cls.get_table_fullname(self.ModelGeneralOne)
        self.assertEqual(table_name, "model_general_one")

        table_name = adapter_cls.get_table_fullname(self.ModelGeneralTwo)
        self.assertEqual(table_name, "model_general_two")
