# Copyright © The Debusine Developers
# See the AUTHORS file at the top-level directory of this distribution
#
# This file is part of Debusine. It is subject to the license terms
# in the LICENSE file found in the top-level directory of this
# distribution. No part of Debusine, including this file, may be copied,
# modified, propagated, or distributed except according to the terms
# contained in the LICENSE file.

"""UI helpers for work requests."""

from collections import defaultdict
from functools import cached_property
from typing import Any, override

from django.utils.formats import date_format
from django.utils.html import format_html
from django.utils.timesince import timesince

from debusine.artifacts.models import TaskTypes
from debusine.db.models import WorkRequest, WorkflowTemplate
from debusine.db.models.work_requests import InternalTaskError
from debusine.db.tasks import DBTask
from debusine.tasks import TaskConfigError
from debusine.web.helps import DOCS_BASE_URL
from debusine.web.icons import Icons
from debusine.web.views import ui_shortcuts
from debusine.web.views.places import PlaceValue, ResourceMainPlace
from debusine.web.views.sidebar import (
    SidebarAction,
    SidebarElement,
    SidebarItem,
)
from debusine.web.views.ui.base import UI


class WorkflowTemplateUI(UI[WorkflowTemplate]):
    """UI helpers for WorkflowTemplate instances."""

    @cached_property
    def place(self) -> ResourceMainPlace[WorkflowTemplate]:
        """Return a place to show this workflow template."""
        return ResourceMainPlace(
            ui=self,
            title=PlaceValue(
                self.instance.name,
                page=f"Workflow template {self.instance.name}",
            ),
            url=self.instance.get_absolute_url(),
            parent=self.instance.workspace.ui(self.request).place,
        )

    def get_help_url(self) -> str:
        """Return a URL to the appropriate workflow documentation."""
        # We could avoid hardcoding this URL structure by using Sphinx to
        # fetch and parse the intersphinx inventory.  However, Sphinx seems
        # like too heavy a dependency to pull in just for this.
        return (
            f"{DOCS_BASE_URL}/reference/workflows/specs/"
            f"{self.instance.task_name.replace('_', '-')}.html"
        )


class WorkRequestUI(UI[WorkRequest]):
    """UI helpers for WorkRequest instances."""

    @cached_property
    def task(self) -> DBTask[Any] | None:
        """Return the task for this work request, if it can be instantiated."""
        try:
            return self.instance.get_task()
        except (TaskConfigError, InternalTaskError):
            return None

    @cached_property
    def label(self) -> str:
        """Get the label for this work request."""
        if self.task is not None:
            return self.task.get_label()
        elif (
            self.instance.is_workflow
            and self.instance.workflow_data.workflow_template_name
        ):
            return self.instance.workflow_data.workflow_template_name
        else:
            return self.instance.task_name

    @cached_property
    def place(self) -> ResourceMainPlace[WorkRequest]:
        """Return a place to show this work request."""
        is_workflow_root = self.instance == self.instance.workflow_root
        place = ResourceMainPlace(
            ui=self,
            title=PlaceValue(
                (
                    f"#{self.instance.id}: {self.label}"
                    if is_workflow_root
                    else f"#{self.instance.id}"
                ),
                page=f"#{self.instance.id}: {self.label}",
            ),
            description=f"Work request #{self.instance.id}: {self.label}",
            url=self.instance.get_absolute_url(),
            icon=Icons.WORK_REQUEST,
        )
        if self.instance.parent is not None:
            place.parent = self.instance.parent.ui(self.request).place
        elif self.instance.task_type == TaskTypes.WORKFLOW:
            place.parent = self.instance.workspace.ui(
                self.request
            ).place_workflow_list
        else:
            place.parent = self.instance.workspace.ui(self.request).place
        return place

    @cached_property
    def workflow_children(self) -> dict[int, list[WorkRequest]]:
        """Mapping from work request IDs to their children in this workflow."""
        children: defaultdict[int, list[WorkRequest]] = defaultdict(list)
        for wr in (
            WorkRequest.objects.filter(id=self.instance.id)
            .workflow_flattened(include_workflows=True)
            .filter(parent__isnull=False)
            .visible_in_workflow()
            .order_by("id")
            .select_related(
                "workspace__scope",
                "aborted_by",
            )
        ):
            assert wr.parent_id is not None
            children[wr.parent_id].append(wr)
        return children

    @override
    def build_ui_shortcuts(self) -> list[ui_shortcuts.UIShortcut]:
        result = super().build_ui_shortcuts()
        if self.request.user.is_authenticated:
            if self.instance.verify_retry():
                result.append(
                    ui_shortcuts.create_work_request_retry(self.instance)
                )
            if self.instance.verify_abort():
                result.append(
                    ui_shortcuts.create_work_request_abort(self.instance)
                )
        return result

    def sidebar_work_request(self) -> SidebarItem:
        """Create sidebar item with work request information."""
        kwargs = {
            "icon": Icons.WORK_REQUEST,
            "label": "Work request",
        }

        kwargs["detail"] = (
            f"{self.instance.task_type} {self.instance.task_name} task"
        )
        kwargs["value"] = self.label

        return SidebarAction(
            url=self.instance.get_absolute_url(),
            **kwargs,
        )

    def sidebar_work_request_status(self) -> SidebarItem:
        """Create information about a work request status and result."""
        kwargs = {
            "icon": Icons.WORK_REQUEST_STATUS,
            "label": "Status and result",
        }

        match self.instance.status:
            case WorkRequest.Statuses.PENDING:
                status_bg = "secondary"
            case WorkRequest.Statuses.RUNNING:
                status_bg = "info"
            case WorkRequest.Statuses.COMPLETED:
                status_bg = "primary"
            case WorkRequest.Statuses.ABORTED | WorkRequest.Statuses.BLOCKED:
                status_bg = "dark"
            case _ as unreachable:  # noqa: F841
                status_bg = "info"

        status = format_html(
            '<span class="badge text-bg-{bg}">{status}</span>',
            bg=status_bg,
            status=self.instance.status.capitalize(),
        )

        result_bg: str | None
        match self.instance.result:
            case WorkRequest.Results.NONE:
                result_bg = None
            case WorkRequest.Results.SUCCESS:
                result_bg = "success"
            case WorkRequest.Results.FAILURE:
                result_bg = "warning"
            case WorkRequest.Results.ERROR:
                result_bg = "danger"
            case WorkRequest.Results.SKIPPED:
                result_bg = "secondary"
            case _ as unreachable:  # noqa: F841
                result_bg = "info"

        if result_bg is None:
            kwargs["value"] = status
        else:
            kwargs["value"] = status + format_html(
                ' <span class="badge text-bg-{bg}">{result}</span>',
                bg=result_bg,
                result=self.instance.result.capitalize(),
            )

        return SidebarElement(**kwargs)

    def sidebar_work_request_superseded(self) -> SidebarItem:
        """Create a link to the work request that supersedes this one."""
        kwargs = {
            "icon": Icons.WORK_REQUEST_SUPERSEDED,
            "label": "Superseded by",
        }
        if (superseded := getattr(self.instance, "superseded", None)) is None:
            return SidebarElement(**kwargs)
        return SidebarAction(
            url=superseded.get_absolute_url(),
            value=str(superseded),
            **kwargs,
        )

    def sidebar_work_request_supersedes(self) -> SidebarItem:
        """Create a link to the work request superseded by this one."""
        kwargs = {
            "icon": Icons.WORK_REQUEST_SUPERSEDES,
            "label": "Supersedes",
        }
        if (supersedes := getattr(self.instance, "supersedes", None)) is None:
            return SidebarElement(**kwargs)

        return SidebarAction(
            url=supersedes.get_absolute_url(),
            value=str(supersedes),
            **kwargs,
        )

    def sidebar_workflow(self) -> SidebarItem:
        """Create a link to the associated workflow."""
        workflow = self.instance.workflow_root
        if workflow is None:
            return SidebarElement(
                icon=Icons.WORKFLOW,
                label="Workflow",
            )

        return SidebarAction(
            icon="diagram-3",
            label="Workflow",
            url=workflow.get_absolute_url(),
            value=str(workflow),
        )

    def sidebar_work_request_started_at(self) -> SidebarItem:
        """Show the queue time of the work request before being started."""
        kwargs = {
            "icon": Icons.STARTED_AT,
            "label": "Time from creation to start",
        }
        if not self.instance.started_at:
            return SidebarElement(**kwargs)

        return SidebarElement(
            detail=date_format(self.instance.started_at, "DATETIME_FORMAT"),
            value=timesince(self.instance.created_at, self.instance.started_at),
            **kwargs,
        )

    def sidebar_work_request_duration(self) -> SidebarItem:
        """Show the duration of the work request run."""
        kwargs = {
            "icon": Icons.DURATION,
            "label": "Duration",
        }

        if (
            self.instance.started_at
            and self.instance.status == WorkRequest.Statuses.RUNNING
        ):
            return SidebarElement(
                value=timesince(self.instance.started_at),
                **kwargs,
            )

        if not self.instance.started_at or not self.instance.completed_at:
            return SidebarElement(**kwargs)

        return SidebarElement(
            value=timesince(
                self.instance.started_at, self.instance.completed_at
            ),
            detail=date_format(self.instance.completed_at, "DATETIME_FORMAT"),
            **kwargs,
        )
