# (c) cavaliba.com - tests / api / conf

import json

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

import app_home.cache as cache
from app_home.models import CavalibaConfiguration
from tests import helper


class ApiConfAuthTest(TestCase):
    def setUp(self):
        cache.clear()

    def test_get_no_auth_header(self):
        response = self.client.get(reverse("api:api_conf"))
        self.assertEqual(response.status_code, 401)

    def test_get_missing_p_api_conf_denied(self):
        headers = helper.add_apikey(keyname="conf_nogate", acl="p_conf_view\np_conf_admin")
        response = self.client.get(reverse("api:api_conf"), headers=headers)
        self.assertEqual(response.status_code, 401)

    def test_get_missing_p_conf_view_denied(self):
        headers = helper.add_apikey(keyname="conf_noview", acl="p_api_conf")
        response = self.client.get(reverse("api:api_conf"), headers=headers)
        self.assertEqual(response.status_code, 401)

    def test_get_allowed_with_p_conf_admin_only(self):
        headers = helper.add_apikey(keyname="conf_admin_only", acl="p_api_conf\np_conf_admin")
        response = self.client.get(reverse("api:api_conf"), headers=headers)
        self.assertEqual(response.status_code, 200)

    def test_post_missing_p_conf_admin_denied(self):
        headers = helper.add_apikey(keyname="conf_noadmin", acl="p_api_conf\np_conf_view")
        response = self.client.post(
            reverse("api:api_conf"),
            data=json.dumps({"appname": "home", "keyname": "GLOBAL_APPNAME", "value": "X"}),
            content_type="application/json",
            headers=headers,
        )
        self.assertEqual(response.status_code, 401)

    def test_post_readonly_key_denied(self):
        headers = helper.add_apikey(keyname="conf_ro", acl="p_api_conf\np_conf_admin")
        from app_data.data import Instance

        instance = Instance.from_keyname(classname="_apikey", keyname="conf_ro")
        instance.merge_import({"is_readonly": True})
        instance.save(action="update")
        response = self.client.post(
            reverse("api:api_conf"),
            data=json.dumps({"appname": "home", "keyname": "GLOBAL_APPNAME", "value": "X"}),
            content_type="application/json",
            headers=headers,
        )
        self.assertEqual(response.status_code, 401)

    def test_delete_method_not_allowed(self):
        headers = helper.add_apikey_admin()
        response = self.client.delete(reverse("api:api_conf"), headers=headers)
        self.assertEqual(response.status_code, 405)


class ApiConfInputTest(TestCase):
    def setUp(self):
        cache.clear()
        self.headers = helper.add_apikey_admin()

    def _post(self, body):
        return self.client.post(
            reverse("api:api_conf"),
            data=body if isinstance(body, str) else json.dumps(body),
            content_type="application/json",
            headers=self.headers,
        )

    def test_invalid_json_syntax(self):
        response = self._post("{not json")
        self.assertEqual(response.status_code, 400)

    def test_body_is_a_number(self):
        response = self._post("42")
        self.assertEqual(response.status_code, 400)

    def test_missing_appname_reported_as_ko(self):
        response = self._post({"keyname": "GLOBAL_APPNAME", "value": "X"})
        data = response.json()
        self.assertEqual(data["count_ko"], 1)
        self.assertEqual(data["results"][0]["status"], "ko")
        self.assertIn("error", data["results"][0])

    def test_missing_keyname_reported_as_ko(self):
        response = self._post({"appname": "home", "value": "X"})
        data = response.json()
        self.assertEqual(data["count_ko"], 1)

    def test_non_dict_entry_reported_as_ko(self):
        response = self._post(["not-an-object"])
        data = response.json()
        self.assertEqual(data["count_ko"], 1)


class ApiConfLogicTest(TestCase):
    def setUp(self):
        cache.clear()
        self.headers = helper.add_apikey_admin()

    def _get(self):
        return self.client.get(reverse("api:api_conf"), headers=self.headers)

    def _post(self, body):
        return self.client.post(
            reverse("api:api_conf"),
            data=body if isinstance(body, str) else json.dumps(body),
            content_type="application/json",
            headers=self.headers,
        )

    def test_post_creates_new_entry(self):
        response = self._post({"appname": "conftest", "keyname": "MYKEY", "value": "Acme"})
        data = response.json()
        self.assertEqual(response.status_code, 200)
        self.assertEqual(data["count_ok"], 1)
        entry = CavalibaConfiguration.objects.get(appname="conftest", keyname="MYKEY")
        self.assertEqual(entry.value, "Acme")

    def test_post_updates_existing_entry(self):
        CavalibaConfiguration.objects.update_or_create(
            appname="conftest", keyname="MYKEY", defaults={"value": "Old"}
        )
        response = self._post({"appname": "conftest", "keyname": "MYKEY", "value": "New"})
        data = response.json()
        self.assertEqual(data["count_ok"], 1)
        entry = CavalibaConfiguration.objects.get(appname="conftest", keyname="MYKEY")
        self.assertEqual(entry.value, "New")

    def test_post_batch_of_entries(self):
        body = [
            {"appname": "home", "keyname": "GLOBAL_APPNAME", "value": "A"},
            {"appname": "home", "keyname": "CSV_DELIMITER", "value": ";"},
        ]
        response = self._post(body)
        data = response.json()
        self.assertEqual(data["count"], 2)
        self.assertEqual(data["count_ok"], 2)
        self.assertEqual(data["count_ko"], 0)

    def test_post_env_appname_rejected(self):
        response = self._post({"appname": "env", "keyname": "CAVALIBA_FORCE_LOGIN", "value": "x"})
        data = response.json()
        self.assertEqual(data["count_ko"], 1)
        self.assertEqual(data["results"][0]["status"], "ko")
        self.assertFalse(
            CavalibaConfiguration.objects.filter(
                appname="env", keyname="CAVALIBA_FORCE_LOGIN"
            ).exists()
        )

    def test_get_returns_flat_list_of_persisted_entries(self):
        CavalibaConfiguration.objects.create(
            appname="conftest", keyname="MYKEY", value="Acme", description="d", order=5
        )
        response = self._get()
        self.assertEqual(response.status_code, 200)
        data = response.json()
        self.assertIsInstance(data, list)
        row = next(r for r in data if r["appname"] == "conftest" and r["keyname"] == "MYKEY")
        self.assertEqual(row["value"], "Acme")
        self.assertEqual(row["description"], "d")
        self.assertEqual(row["order"], 5)

    def test_get_excludes_env(self):
        # "env" is settings-backed and never persisted to DB in the first
        # place, so it can't appear via conf_list_export() - assert no
        # regression by checking a mixed set doesn't leak "env" rows.
        CavalibaConfiguration.objects.create(appname="conftest", keyname="MYKEY", value="Acme")
        response = self._get()
        data = response.json()
        appnames = {row["appname"] for row in data}
        self.assertNotIn("env", appnames)

    def test_post_refreshes_configuration_cache(self):
        self._post({"appname": "home", "keyname": "GLOBAL_APPNAME", "value": "Fresh"})
        self.assertEqual(cache.cache_configuration["home"]["GLOBAL_APPNAME"], "Fresh")
