# (c) cavaliba.com - test / field templates / unknown dataformat fallback

"""
The UI resolves a field's template by name from datapoint["dataformat"]
(fields/_field.html), so a dataformat with no _<dataformat>.html used to raise
TemplateDoesNotExist -> HTTP 500 on instance_edit/instance_new.

That was reachable: Schema.update_from_dict() rewrites the legacy "user",
"group" and "role" dataformats to "schema" + dataformat_ext on import, but
Schema.from_obj() reads dataformat straight from the data_field table with no
normalization, and no migration ever rewrote existing rows. A DB upgraded in
place from a pre-4.0 release can therefore still hold them.

Field.get_ui_dataformat() now normalizes anything outside
UI_TEMPLATE_DATAFORMATS to "unknown". These tests write a legacy dataformat
straight into data_field (the only way to reproduce the state, since the import
path normalizes it) and assert the UI degrades instead of crashing.
"""

from django.test import TestCase, override_settings
from django.urls import reverse

import app_home.cache as cache
from app_data.data import Instance
from app_data.fieldtypes.field import UI_TEMPLATE_DATAFORMATS
from app_data.models import DataInstance, DataSchema
from tests import helper


@override_settings(CAVALIBA_AUTH_MODE="unittest")
class TestFieldDataformatFallback(TestCase):
    def setUp(self):
        """A schema with one normal string field, plus an admin user for the view
        tests."""
        cache.clear()
        helper.add_admin_user(login="unittest")
        helper.add_schema(
            classname="legacyfmt",
            field_definition={
                "f_legacy": {"dataformat": "string", "displayname": "F Legacy"},
            },
        )
        helper.add_instance(classname="legacyfmt", keyname="obj01", fields={"f_legacy": "kept"})

    def force_dataformat(self, dataformat):
        """Rewrite the stored dataformat of legacyfmt.f_legacy straight in data_field,
        bypassing Schema.update_from_dict()'s normalization, and purge the schema
        cache. Returns a freshly loaded Instance."""

        DataSchema.objects.filter(classname="legacyfmt", keyname="f_legacy").update(
            dataformat=dataformat
        )
        cache.clear()
        return Instance.from_keyname(classname="legacyfmt", keyname="obj01", expand=True)

    def test_known_dataformat_passes_through(self):
        instance = self.force_dataformat("string")
        self.assertEqual(instance.fields["f_legacy"].get_ui_dataformat(), "string")

    def test_the_legacy_dataformat_really_survives_schema_load(self):
        # guard against this whole file going vacuous: if Schema.from_obj() ever
        # starts normalizing too, the fallback stops being what's under test
        instance = self.force_dataformat("user")
        self.assertEqual(instance.fields["f_legacy"].dataformat, "user")
        self.assertNotIn("user", UI_TEMPLATE_DATAFORMATS)

    def test_no_template_exists_for_the_legacy_dataformat(self):
        # the reason get_ui_dataformat() is needed at all
        from django.template import TemplateDoesNotExist
        from django.template.loader import get_template

        with self.assertRaises(TemplateDoesNotExist):
            get_template("app_data/fields/_user.html")

    def test_legacy_user_dataformat_falls_back_to_unknown(self):
        instance = self.force_dataformat("user")
        self.assertEqual(instance.fields["f_legacy"].get_ui_dataformat(), "unknown")

    def test_legacy_group_dataformat_falls_back_to_unknown(self):
        instance = self.force_dataformat("group")
        self.assertEqual(instance.fields["f_legacy"].get_ui_dataformat(), "unknown")

    def test_legacy_role_dataformat_falls_back_to_unknown(self):
        instance = self.force_dataformat("role")
        self.assertEqual(instance.fields["f_legacy"].get_ui_dataformat(), "unknown")

    def test_legacy_json_dataformat_falls_back_to_unknown(self):
        # "json" was a FIELD_FORMAT_CHOICE value in migration 0001_initial
        instance = self.force_dataformat("json")
        self.assertEqual(instance.fields["f_legacy"].get_ui_dataformat(), "unknown")

    def test_edit_datapoint_reports_unknown_dataformat(self):
        instance = self.force_dataformat("user")
        datapoint = instance.fields["f_legacy"].get_datapoint_ui_edit()
        self.assertEqual(datapoint["dataformat"], "unknown")

    def test_detail_datapoint_reports_unknown_dataformat(self):
        # the detail builder had no fallback at all before phase 1
        instance = self.force_dataformat("user")
        datapoint = instance.fields["f_legacy"].get_datapoint_ui_detail()
        self.assertEqual(datapoint["dataformat"], "unknown")

    def test_rendering_a_legacy_field_does_not_raise(self):
        instance = self.force_dataformat("user")
        out = helper.render_field(instance, "f_legacy", "edit")
        self.assertIn('name="f_legacy"', out)
        self.assertIn("UNKNOWN FORMAT unknown", out)

    def test_instance_edit_view_does_not_500_on_a_legacy_dataformat(self):
        self.force_dataformat("user")
        obj = DataInstance.objects.get(classname="legacyfmt", keyname="obj01")
        response = self.client.get(reverse("app_data:instance_edit", kwargs={"id": obj.id}))
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "UNKNOWN FORMAT")

    def test_instance_new_view_does_not_500_on_a_legacy_dataformat(self):
        self.force_dataformat("user")
        response = self.client.get(
            reverse("app_data:instance_new", kwargs={"classname": "legacyfmt"})
        )
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, "UNKNOWN FORMAT")

    def test_instance_detail_view_does_not_500_on_a_legacy_dataformat(self):
        self.force_dataformat("user")
        obj = DataInstance.objects.get(classname="legacyfmt", keyname="obj01")
        response = self.client.get(reverse("app_data:instance_detail", kwargs={"id": obj.id}))
        self.assertEqual(response.status_code, 200)

    def test_every_template_is_a_listed_dataformat(self):
        # the other direction: a fields/_x.html nobody can reach is dead weight
        # (this is what caught _file_edit.html / _file_new.html)
        import pathlib

        from django.conf import settings

        folder = pathlib.Path(settings.BASE_DIR) / "app_data/templates/app_data/fields"
        # _field.html is the dispatcher, not a dataformat
        on_disk = {path.stem.lstrip("_") for path in folder.glob("_*.html")} - {"field"}
        self.assertEqual(on_disk, UI_TEMPLATE_DATAFORMATS)

    def test_every_listed_dataformat_has_a_template(self):
        # the set is the contract between Python and the fields/ folder: if a
        # dataformat is listed here, _<dataformat>.html must exist
        from django.template.loader import get_template

        for dataformat in UI_TEMPLATE_DATAFORMATS:
            with self.subTest(dataformat=dataformat):
                get_template(f"app_data/fields/_{dataformat}.html")
