# (c) cavaliba.com - tests / data / exporter view - async task path

import shutil
import tempfile

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

import app_home.cache as cache
from app_data.models import DataFile, DataTask
from app_home.configuration import set_configuration
from tests import helper


@override_settings(CAVALIBA_AUTH_MODE="unittest", CELERY_TASK_ALWAYS_EAGER=True)
class ViewExporterAsyncTest(TestCase):
    def _isolate_filestore(self):
        """Point the filestore at a throwaway directory for this test.

        An async export writes a real blob, and the test DB is dropped at the end
        of the run while the blob is not - so without this every run would leave
        orphans behind in the shared CAVALIBA_FILESTORE.
        """

        rootdir = tempfile.mkdtemp(prefix="cavaliba_test_filestore_")
        override = override_settings(CAVALIBA_FILESTORE=rootdir)
        override.enable()
        self.addCleanup(override.disable)
        self.addCleanup(shutil.rmtree, rootdir, ignore_errors=True)
        return rootdir

    def setUp(self):
        self._isolate_filestore()
        cache.clear()
        helper.add_admin_user(login="unittest")
        helper.add_schema(
            classname="vasync",
            field_definition={
                "mystring": {"dataformat": "string", "displayname": "MyString"},
            },
        )
        for i in range(1, 6):
            helper.add_instance(
                classname="vasync",
                keyname=f"vasync-{i:02d}",
                fields={"mystring": f"row {i}"},
            )

    def _post(self, **extra):
        data = {"schema": "vasync", "format": "csv", "columns": "*"}
        data.update(extra)
        return self.client.post(reverse("app_data:data_exporter"), data)

    def _lower_threshold(self, value=2):
        # EXPORT_INTERACTIVE_MAX_SIZE is a runtime configuration entry, not a
        # setting, so it is lowered in the DB and the cache dropped - patching
        # cache_configuration directly would be undone by the next cache reload
        set_configuration(
            appname="data", keyname="EXPORT_INTERACTIVE_MAX_SIZE",
            value=value, description="", page="", order=100,
        )  # fmt: skip
        cache.clear()

    def test_large_export_creates_a_task(self):
        self._lower_threshold()

        response = self._post()

        self.assertEqual(response.status_code, 200)
        self.assertEqual(DataTask.objects.count(), 1)
        task = DataTask.objects.first()
        self.assertEqual(response.context["task_handle"], str(task.handle))

    def test_task_is_owned_by_the_requester(self):
        self._lower_threshold()
        self._post()

        task = DataTask.objects.first()
        self.assertEqual(task.owner_type, "user")
        self.assertEqual(task.owner_id, "unittest")
        self.assertIn("vasync", task.name)

    def test_task_completes_and_attaches_a_file(self):
        self._lower_threshold()
        self._post()

        task = DataTask.objects.first()
        self.assertEqual(task.state, "DONE")
        self.assertEqual(task.attachment["type"], "file")
        self.assertTrue(task.attachment["fileid"])
        self.assertTrue(task.attachment["filename"].endswith(".csv"))
        self.assertGreater(task.attachment["size"], 0)

    def test_attached_file_has_an_expiry(self):
        self._lower_threshold()
        self._post()

        task = DataTask.objects.first()
        datafile = DataFile.objects.get(fileid=task.attachment["fileid"])
        self.assertIsNotNone(datafile.not_after)
        self.assertTrue(datafile.hash)

    def test_task_output_carries_the_stats(self):
        self._lower_threshold()
        self._post()

        output = DataTask.objects.first().output
        self.assertEqual(output["total"], 5)
        self.assertEqual(output["count_ok"], 5)
        self.assertEqual(output["count_ko"], 0)
        self.assertFalse(output["aborted"])

    def test_result_file_holds_the_whole_export(self):
        self._lower_threshold()
        self._post()

        task = DataTask.objects.first()
        datafile = DataFile.objects.get(fileid=task.attachment["fileid"])
        with open(datafile.filepath, encoding="utf-8") as f:
            lines = f.read().splitlines()

        self.assertEqual(len(lines), 6)
        self.assertTrue(lines[0].startswith("_schema,keyname"))

    def test_small_export_still_runs_inline(self):
        response = self._post()

        self.assertEqual(DataTask.objects.count(), 0)
        self.assertIn("attachment;", response["Content-Disposition"])

    def test_async_export_honours_the_options(self):
        self._lower_threshold()
        self._post(columns="keyname,mystring", action="noop", format="json")

        task = DataTask.objects.first()
        self.assertEqual(task.params["spec"]["columns"], "keyname,mystring")
        self.assertEqual(task.params["spec"]["action"], "noop")
        self.assertEqual(task.params["spec"]["format"], "json")
        self.assertTrue(task.attachment["filename"].endswith(".json"))
