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

"""
Shared pieces of the "tool submits a DataTask, then watches it in a modal" UI,
used by both the Exporter (views_exporter.py) and the Import Tool
(views_importer.py). Extracted when the Import Tool went always-async (5.4.0) -
the exporter owned both of these privately, and a second copy would have drifted.

Method inventory: own_task, progress_context.
"""

from app_data.models import DataTask


def own_task(handle, aaa):
    """Resolve a DataTask the caller is allowed to watch, or None.

    Gated on ownership alone: the calling view has already checked the tool's own
    permission (p_data_export / p_data_import), and the task browsing views are
    themselves permission-free for one's own tasks, so there is no task
    permission left to require here. p_task_all still widens it to anyone's
    task, same rule as views_task.
    """

    task = DataTask.objects.filter(handle=handle).first()
    if not task:
        return None
    if task.owner_id != aaa.get("username", "") and "p_task_all" not in aaa.get("perms", []):
        return None
    return task


def progress_context(context, task):
    """Derive a progress modal's live figures from the task row.

    rate and eta are computed here rather than stored: DataTask already carries
    the elapsed duration and the progress counters, so deriving keeps the task's
    write path cheap and the stored payload small (same approach as
    task_detail's `estimated`).

    Fallback-first, and that is not a detail: a run shorter than its task's
    batch size never calls on_batch, so it has no `progress` at all and every
    figure has to come from the final stats in `output`, which finish_task()
    always writes. Import batches every 20 rows and export every 100, so most
    runs are in that case.
    """

    progress = task.progress or {}
    output = task.output or {}
    is_running = task.state in ("QUEUED", "RUNNING")
    duration = task.duration or 0

    percent = progress.get("percent") or (0 if is_running else 100)
    count_done = progress.get("count") or output.get("count_done") or 0

    context["task"] = task
    context["percent"] = percent
    # the bar's width comes from a .cv-bar-NN class (5% steps) rather than an
    # inline style, so the template keeps all styling in cavaliba.css
    context["percent_step"] = min(100, max(0, int(round(percent / 5.0)) * 5))
    context["total"] = progress.get("total") or output.get("total") or 0
    context["count_done"] = count_done
    context["count_ok"] = progress.get("count_ok", output.get("count_ok", 0))
    context["count_ko"] = progress.get("count_ko", output.get("count_ko", 0))
    context["rate"] = round(count_done / duration, 1) if duration and count_done else None
    context["eta"] = (
        round(duration * (100 - percent) / percent, 1)
        if is_running and duration and percent
        else None
    )
    errors = output.get("errors") or progress.get("errors_sample") or []
    context["errors_sample"] = errors[:5]
    # the whole list is downloadable, so say when the sample is only a sample
    context["errors_total"] = len(errors)
    # a FAILED task stores its reason under "error" (singular), which is a
    # different thing from the per-row "errors" list: it is the one message
    # explaining why the run never produced stats at all - an unreachable broker
    # being the common case. Without it the modal title is the only clue.
    context["error_message"] = output.get("error") or ""
    context["attachment"] = task.attachment or None
    context["is_running"] = is_running
    return context
