# (c) cavaliba.com - test_datatask_helpers

import uuid

from django.test import TestCase
from django.utils import timezone

from app_data.models import DataTask
from app_data.task_manager import (
    create_datatask,
    update_progress,
    is_aborted,
    finish_task,
    abort_task,
    datatask_cleanup,
)


class TestCreateDatatask(TestCase):

    def test_creates_entry(self):
        dt = create_datatask("my task", params={"key": "val"}, owner_id="u1")
        self.assertIsNotNone(dt)
        self.assertIsNotNone(dt.pk)
        self.assertEqual(dt.state, "QUEUED")
        self.assertEqual(dt.name, "my task")
        self.assertEqual(dt.params["key"], "val")
        self.assertEqual(dt.owner_id, "u1")


    def test_returns_datatask_instance(self):
        dt = create_datatask("t", owner_id="u1")
        self.assertIsInstance(dt, DataTask)


    def test_singleton_first_allowed(self):
        dt = create_datatask("t", singleton="my_singleton", owner_id="u1")
        self.assertIsNotNone(dt)


    def test_singleton_blocks_duplicate_queued(self):
        create_datatask("t", singleton="s1", owner_id="u1")
        dt2 = create_datatask("t", singleton="s1", owner_id="u1")
        self.assertIsNone(dt2)


    def test_singleton_blocks_duplicate_running(self):
        dt = create_datatask("t", singleton="s2", owner_id="u1")
        dt.state = "RUNNING"
        dt.save()
        dt2 = create_datatask("t", singleton="s2", owner_id="u1")
        self.assertIsNone(dt2)


    def test_singleton_allows_after_done(self):
        dt = create_datatask("t", singleton="s3", owner_id="u1")
        dt.state = "DONE"
        dt.finished_at = timezone.now()
        dt.save()
        dt2 = create_datatask("t", singleton="s3", owner_id="u1")
        self.assertIsNotNone(dt2)


    def test_no_singleton_allows_multiple(self):
        dt1 = create_datatask("t", owner_id="u1")
        dt2 = create_datatask("t", owner_id="u1")
        self.assertIsNotNone(dt1)
        self.assertIsNotNone(dt2)
        self.assertNotEqual(dt1.pk, dt2.pk)


class TestUpdateProgress(TestCase):

    def test_stores_progress(self):
        dt = create_datatask("t", owner_id="u1")
        update_progress(dt.handle, percent=50, count=5, total=10, errors=1, message="halfway")
        dt.refresh_from_db()
        self.assertEqual(dt.progress["percent"], 50)
        self.assertEqual(dt.progress["count"], 5)
        self.assertEqual(dt.progress["total"], 10)
        self.assertEqual(dt.progress["errors"], 1)
        self.assertEqual(dt.progress["message"], "halfway")
        self.assertIn("last_update", dt.progress)


    def test_progress_overwritten(self):
        dt = create_datatask("t", owner_id="u1")
        update_progress(dt.handle, percent=10)
        update_progress(dt.handle, percent=80)
        dt.refresh_from_db()
        self.assertEqual(dt.progress["percent"], 80)


class TestIsAborted(TestCase):

    def test_not_aborted_by_default(self):
        dt = create_datatask("t", owner_id="u1")
        self.assertFalse(is_aborted(dt.handle))


    def test_aborted_after_abort_task(self):
        dt = create_datatask("t", owner_id="u1")
        abort_task(dt.handle)
        self.assertTrue(is_aborted(dt.handle))


class TestFinishTask(TestCase):

    def test_sets_done(self):
        dt = create_datatask("t", owner_id="u1")
        finish_task(dt.handle, state="DONE", output={"result": 42})
        dt.refresh_from_db()
        self.assertEqual(dt.state, "DONE")
        self.assertEqual(dt.output["result"], 42)
        self.assertIsNotNone(dt.finished_at)


    def test_sets_failed(self):
        dt = create_datatask("t", owner_id="u1")
        finish_task(dt.handle, state="FAILED", output={"error": "oops"})
        dt.refresh_from_db()
        self.assertEqual(dt.state, "FAILED")


    def test_sets_attachment(self):
        dt = create_datatask("t", owner_id="u1")
        finish_task(dt.handle, state="DONE", attachment={"type": "file", "value": "/tmp/out.csv"})
        dt.refresh_from_db()
        self.assertEqual(dt.attachment["type"], "file")


class TestAbortTask(TestCase):

    def test_aborts_queued(self):
        dt = create_datatask("t", owner_id="u1")
        result = abort_task(dt.handle)
        self.assertTrue(result)
        dt.refresh_from_db()
        self.assertEqual(dt.state, "ABORTED")
        self.assertIsNotNone(dt.finished_at)


    def test_aborts_running(self):
        dt = create_datatask("t", owner_id="u1")
        dt.state = "RUNNING"
        dt.save()
        result = abort_task(dt.handle)
        self.assertTrue(result)
        dt.refresh_from_db()
        self.assertEqual(dt.state, "ABORTED")


    def test_cannot_abort_done(self):
        dt = create_datatask("t", owner_id="u1")
        finish_task(dt.handle, state="DONE")
        result = abort_task(dt.handle)
        self.assertFalse(result)
        dt.refresh_from_db()
        self.assertEqual(dt.state, "DONE")


    def test_returns_false_unknown_handle(self):
        result = abort_task(uuid.uuid4())
        self.assertFalse(result)


class TestDatataskCleanup(TestCase):

    def test_removes_old_terminal_tasks(self):
        dt = create_datatask("old done", owner_id="u1")
        finish_task(dt.handle, state="DONE")
        # backdate finished_at beyond retention
        DataTask.objects.filter(pk=dt.pk).update(
            finished_at=timezone.now() - timezone.timedelta(days=40)
        )
        deleted = datatask_cleanup(days=30)
        self.assertEqual(deleted, 1)
        self.assertFalse(DataTask.objects.filter(pk=dt.pk).exists())


    def test_keeps_recent_tasks(self):
        dt = create_datatask("recent done", owner_id="u1")
        finish_task(dt.handle, state="DONE")
        deleted = datatask_cleanup(days=30)
        self.assertEqual(deleted, 0)
        self.assertTrue(DataTask.objects.filter(pk=dt.pk).exists())


    def test_keeps_running_tasks(self):
        dt = create_datatask("running", owner_id="u1")
        dt.state = "RUNNING"
        dt.save()
        DataTask.objects.filter(pk=dt.pk).update(
            finished_at=timezone.now() - timezone.timedelta(days=40)
        )
        deleted = datatask_cleanup(days=30)
        self.assertEqual(deleted, 0)
        self.assertTrue(DataTask.objects.filter(pk=dt.pk).exists())
