# (c) cavaliba.com - data - task_manager.py

# Helpers for long-running async tasks tracked via DataTask model.
# Each long-running function gets a thin submit_* wrapper + a @shared_task below.

from django.db import transaction
from django.utils import timezone

from celery import shared_task

from app_home.log import log, INFO, WARNING, ERROR

from app_data.models import DataTask


# ------------------------------------------------------------------
# Helpers
# ------------------------------------------------------------------

def create_datatask(name, params=None, singleton=None, owner_type="user", owner_id=""):
    """Create a DataTask entry. Returns DataTask or None if singleton already active."""
    with transaction.atomic():
        if singleton:
            active = (
                DataTask.objects.select_for_update()
                .filter(singleton=singleton, state__in=["QUEUED", "RUNNING"])
                .exists()
            )
            if active:
                return None
        dt = DataTask.objects.create(
            name=name,
            params=params,
            singleton=singleton,
            owner_type=owner_type,
            owner_id=owner_id,
            state="QUEUED",
        )
    return dt


def update_progress(handle, percent=0, count=0, total=0, errors=0, message=""):
    """Update progress JSON and elapsed duration on a running DataTask."""
    now = timezone.now()
    fields = {
        "progress": {
            "percent": percent,
            "count": count,
            "total": total,
            "errors": errors,
            "message": message,
            "last_update": now.isoformat(),
        }
    }
    try:
        dt = DataTask.objects.values("state", "created_at").get(handle=handle)
        if dt["state"] == "RUNNING":
            fields["duration"] = (now - dt["created_at"]).total_seconds()
    except DataTask.DoesNotExist:
        pass
    DataTask.objects.filter(handle=handle).update(**fields)


def is_aborted(handle):
    """Return True if the task has been set to ABORTED in the DB."""
    return DataTask.objects.filter(handle=handle, state="ABORTED").exists()


def finish_task(handle, state="DONE", output=None, attachment=None):
    """Set final state + finished_at + duration on a DataTask."""
    now = timezone.now()
    try:
        dt = DataTask.objects.values("created_at", "progress").get(handle=handle)
        duration = (now - dt["created_at"]).total_seconds()
        progress = dt["progress"] or {}
    except DataTask.DoesNotExist:
        duration = None
        progress = {}

    if state == "DONE":
        progress["percent"] = 100

    DataTask.objects.filter(handle=handle).update(
        state=state,
        output=output,
        attachment=attachment,
        finished_at=now,
        duration=duration,
        progress=progress,
    )


def abort_task(handle, aaa=None):
    """
    Request abort of a QUEUED or RUNNING task.
    Returns True if the task was found and updated, False otherwise.
    Ownership check is left to the caller (view/API layer).
    """
    now = timezone.now()
    try:
        created_at = DataTask.objects.values_list("created_at", flat=True).get(handle=handle)
        duration = (now - created_at).total_seconds()
    except DataTask.DoesNotExist:
        duration = None
    updated = DataTask.objects.filter(
        handle=handle, state__in=["QUEUED", "RUNNING"]
    ).update(state="ABORTED", finished_at=now, duration=duration)
    return updated > 0


def datatask_cleanup(days=30):
    """Delete terminal DataTask entries older than `days` days. Called from housekeeping."""
    cutoff = timezone.now() - timezone.timedelta(days=days)
    deleted, _ = DataTask.objects.filter(
        state__in=["DONE", "FAILED", "ABORTED"],
        finished_at__lt=cutoff,
    ).delete()
    log(INFO, aaa=None, app="data", view="datatask_cleanup", action="cleanup",
        status="OK", data=f"{deleted} old tasks removed (older than {days}d)")
    return deleted

