# (c) cavaliba.com - test / app_data / keyname rename

from django.core.cache import cache as cache_django
from django.test import TestCase

import app_home.cache as cache
from app_data.data import Instance
from app_data.keyname import rename
from app_data.models import DataEAV, DataInstance, DataRevision
from tests import helper


class TestKeynameRename(TestCase):
    def setUp(self):
        cache.clear()
        cache_django.clear()

    def _setup_related_schemas(self, ref_cardinal_max=1):
        helper.add_schema("tschema_b", displayname="Schema B")
        helper.add_schema(
            "tschema_a",
            displayname="Schema A",
            field_definition={
                "ref_b": {
                    "dataformat": "schema",
                    "dataformat_ext": "tschema_b",
                    "cardinal_max": ref_cardinal_max,
                }
            },
        )

    def test_rename_no_referrers(self):
        helper.add_schema("tschema_b", displayname="Schema B")
        helper.add_instance("tschema_b", "b_old")

        result = rename(classname="tschema_b", oldkeyname="b_old", newkeyname="b_new", dryrun=False)

        self.assertEqual(result["errors"], [])
        self.assertTrue(result["target_renamed"])
        self.assertFalse(
            DataInstance.objects.filter(classname="tschema_b", keyname="b_old").exists()
        )
        self.assertTrue(
            DataInstance.objects.filter(classname="tschema_b", keyname="b_new").exists()
        )
        self.assertFalse(DataEAV.objects.filter(classname="tschema_b", keyname="b_old").exists())
        self.assertTrue(
            DataEAV.objects.filter(
                classname="tschema_b", keyname="b_new", fieldname="keyname", value="b_new"
            ).exists()
        )

    def test_rename_records_revision_for_target(self):
        helper.add_schema("tschema_b", displayname="Schema B")
        helper.add_instance("tschema_b", "b_old")

        result = rename(classname="tschema_b", oldkeyname="b_old", newkeyname="b_new", dryrun=False)

        self.assertEqual(result["errors"], [])
        revisions = DataRevision.objects.filter(classname="tschema_b", keyname="b_new")
        self.assertEqual(revisions.count(), 1)
        revision = revisions.first()
        self.assertEqual(revision.action, "keyname")
        self.assertEqual(
            revision.diff_parsed,
            {"keyname": {"before": "b_old", "after": "b_new"}},
        )

    def test_rename_single_referrer_single_valued_field(self):
        self._setup_related_schemas()
        helper.add_instance("tschema_b", "b_old")
        helper.add_instance("tschema_a", "a1", fields={"ref_b": "b_old"})

        result = rename(classname="tschema_b", oldkeyname="b_old", newkeyname="b_new", dryrun=False)

        self.assertEqual(result["errors"], [])
        self.assertEqual(result["referrers_changed"], 1)

        referrer = Instance.from_keyname(classname="tschema_a", keyname="a1")
        self.assertEqual(referrer.fields["ref_b"].value, ["b_new"])
        self.assertTrue(
            DataEAV.objects.filter(
                classname="tschema_a", keyname="a1", fieldname="ref_b", value="b_new"
            ).exists()
        )
        self.assertFalse(
            DataEAV.objects.filter(
                classname="tschema_a", keyname="a1", fieldname="ref_b", value="b_old"
            ).exists()
        )

    def test_rename_same_value_different_case(self):
        helper.add_schema("tschema_b", displayname="Schema B")
        helper.add_instance("tschema_b", "object01")

        result = rename(
            classname="tschema_b", oldkeyname="object01", newkeyname="OBJECT01", dryrun=False
        )

        self.assertEqual(result["errors"], [])
        self.assertTrue(result["target_renamed"])

        instance = DataInstance.objects.get(classname="tschema_b")
        self.assertEqual(instance.keyname, "OBJECT01")
        self.assertTrue(
            DataEAV.objects.filter(
                classname="tschema_b", fieldname="keyname", value="OBJECT01"
            ).exists()
        )

    def test_rename_case_insensitive_referrers_updated_to_exact_new_value(self):
        self._setup_related_schemas()
        helper.add_instance("tschema_b", "object01")
        helper.add_instance("tschema_a", "a1", fields={"ref_b": "object01"})
        helper.add_instance("tschema_a", "a2", fields={"ref_b": "ObJeCt01"})
        helper.add_instance("tschema_a", "a3", fields={"ref_b": "OBJECT01"})
        helper.add_instance("tschema_a", "a4", fields={"ref_b": "Object01"})

        result = rename(
            classname="tschema_b", oldkeyname="object01", newkeyname="OBJECT01", dryrun=False
        )

        self.assertEqual(result["errors"], [])
        self.assertTrue(result["target_renamed"])

        for referrer_keyname in ("a1", "a2", "a3", "a4"):
            referrer = Instance.from_keyname(classname="tschema_a", keyname=referrer_keyname)
            self.assertEqual(referrer.fields["ref_b"].value, ["OBJECT01"])
            self.assertTrue(
                DataEAV.objects.filter(
                    classname="tschema_a",
                    keyname=referrer_keyname,
                    fieldname="ref_b",
                    value="OBJECT01",
                ).exists()
            )

    def test_rename_multi_valued_field_partial_match(self):
        self._setup_related_schemas(ref_cardinal_max=0)
        helper.add_instance("tschema_b", "b_old")
        helper.add_instance("tschema_b", "b_other")
        helper.add_instance("tschema_a", "a1", fields={"ref_b": ["b_old", "b_other"]})

        rename(classname="tschema_b", oldkeyname="b_old", newkeyname="b_new", dryrun=False)

        referrer = Instance.from_keyname(classname="tschema_a", keyname="a1")
        self.assertEqual(referrer.fields["ref_b"].value, ["b_new", "b_other"])

    def test_rename_self_reference(self):
        helper.add_schema(
            "tschema_self",
            field_definition={
                "ref_self": {
                    "dataformat": "schema",
                    "dataformat_ext": "tschema_self",
                }
            },
        )
        helper.add_instance("tschema_self", "s_old", fields={"ref_self": "s_old"})

        result = rename(
            classname="tschema_self", oldkeyname="s_old", newkeyname="s_new", dryrun=False
        )

        self.assertEqual(result["errors"], [])
        renamed = Instance.from_keyname(classname="tschema_self", keyname="s_new")
        self.assertIsNotNone(renamed)
        self.assertTrue(renamed.is_bound)
        self.assertEqual(renamed.fields["ref_self"].value, ["s_new"])

    def test_rename_collision_aborts(self):
        helper.add_schema("tschema_b", displayname="Schema B")
        helper.add_instance("tschema_b", "b_old")
        helper.add_instance("tschema_b", "b_new")

        result = rename(classname="tschema_b", oldkeyname="b_old", newkeyname="b_new", dryrun=False)

        self.assertNotEqual(result["errors"], [])
        self.assertTrue(
            DataInstance.objects.filter(classname="tschema_b", keyname="b_old").exists()
        )

    def test_rename_dryrun_no_db_changes(self):
        self._setup_related_schemas()
        helper.add_instance("tschema_b", "b_old")
        helper.add_instance("tschema_a", "a1", fields={"ref_b": "b_old"})

        before_instance = list(DataInstance.objects.filter(classname="tschema_b").values())
        before_eav = list(DataEAV.objects.filter(classname="tschema_a").values())

        result = rename(classname="tschema_b", oldkeyname="b_old", newkeyname="b_new", dryrun=True)

        self.assertEqual(result["errors"], [])
        self.assertEqual(result["referrers_changed"], 1)
        self.assertFalse(result["target_renamed"])

        after_instance = list(DataInstance.objects.filter(classname="tschema_b").values())
        after_eav = list(DataEAV.objects.filter(classname="tschema_a").values())
        self.assertEqual(before_instance, after_instance)
        self.assertEqual(before_eav, after_eav)

    def test_rename_cache_invalidated(self):
        self._setup_related_schemas()
        helper.add_instance("tschema_b", "b_old")
        helper.add_instance("tschema_a", "a1", fields={"ref_b": "b_old"})

        Instance.from_keyname(classname="tschema_b", keyname="b_old")
        referrer = Instance.from_keyname(classname="tschema_a", keyname="a1")
        referrer.fields["ref_b"].get_datapoint_ui_detail()

        self.assertIsNotNone(cache.cache2_instance.get("tschema_b::b_old"))
        self.assertIsNotNone(cache.cache2_datapoint.get("tschema_b::b_old"))

        rename(classname="tschema_b", oldkeyname="b_old", newkeyname="b_new", dryrun=False)

        self.assertIsNone(cache.cache2_instance.get("tschema_b::b_old"))
        self.assertIsNone(cache.cache2_datapoint.get("tschema_b::b_old"))

        reloaded = Instance.from_keyname(classname="tschema_b", keyname="b_new")
        self.assertIsNotNone(reloaded)
        self.assertEqual(reloaded.keyname, "b_new")

    def test_rename_nonexistent_instance(self):
        helper.add_schema("tschema_b", displayname="Schema B")

        result = rename(
            classname="tschema_b", oldkeyname="missing", newkeyname="b_new", dryrun=False
        )

        self.assertNotEqual(result["errors"], [])

    def test_rename_nonexistent_schema(self):
        result = rename(classname="tschema_missing", oldkeyname="a", newkeyname="b", dryrun=False)

        self.assertNotEqual(result["errors"], [])

    def test_rename_rejects_user_group_role(self):
        result = rename(classname="user", oldkeyname="a", newkeyname="b", dryrun=False)

        self.assertNotEqual(result["errors"], [])

    def test_rename_force_allows_user_group_role(self):
        helper.add_role(name="role_custom01")

        result = rename(
            classname="role",
            oldkeyname="role_custom01",
            newkeyname="role_custom02",
            dryrun=False,
            force=True,
        )

        self.assertEqual(result["errors"], [])
        self.assertTrue(result["target_renamed"])
        self.assertTrue(
            DataInstance.objects.filter(classname="role", keyname="role_custom02").exists()
        )

    def test_rename_force_still_rejects_builtin_object(self):
        helper.add_role(name="role_custom01")
        helper.add_instance("role", "role_custom01", action="update", fields={"is_builtin": True})

        result = rename(
            classname="role",
            oldkeyname="role_custom01",
            newkeyname="role_custom02",
            dryrun=False,
            force=True,
        )

        self.assertNotEqual(result["errors"], [])
        self.assertTrue(
            DataInstance.objects.filter(classname="role", keyname="role_custom01").exists()
        )

    def test_rename_force_still_rejects_protected_keynames(self):
        for classname, keyname in (
            ("user", "admin"),
            ("role", "role_admin"),
            ("role", "role_default"),
        ):
            result = rename(
                classname=classname,
                oldkeyname=keyname,
                newkeyname="somethingelse",
                dryrun=False,
                force=True,
            )
            self.assertNotEqual(result["errors"], [])

    def test_rename_enumerate_purges_cache2_enumerate(self):
        from app_data.fieldtypes.field_enumerate import get_enumerate

        content = '- value: "A"\n  widget: "green_circle"\n'
        helper.add_instance(classname="_enumerate", keyname="enum_old", fields={"content": content})

        get_enumerate("enum_old")
        self.assertIsNotNone(cache.cache2_enumerate.get("enum_old"))

        result = rename(
            classname="_enumerate", oldkeyname="enum_old", newkeyname="enum_new", dryrun=False
        )

        self.assertEqual(result["errors"], [])
        self.assertIsNone(cache.cache2_enumerate.get("enum_old"))
