from __future__ import annotations

from datetime import timedelta

from plain import postgres
from plain.admin.cards import Card, TrendCard
from plain.admin.views import (
    AdminModelDetailView,
    AdminModelListView,
    AdminViewset,
    register_viewset,
)
from plain.http import RedirectResponse
from plain.postgres.expressions import Case, When
from plain.runtime import settings

from .models import JobProcess, JobRequest, JobResult, JobResultQuerySet


def _td_format(td_object: timedelta) -> str:
    seconds = int(td_object.total_seconds())
    periods = [
        ("year", 60 * 60 * 24 * 365),
        ("month", 60 * 60 * 24 * 30),
        ("day", 60 * 60 * 24),
        ("hour", 60 * 60),
        ("minute", 60),
        ("second", 1),
    ]

    strings = []
    for period_name, period_seconds in periods:
        if seconds > period_seconds:
            period_value, seconds = divmod(seconds, period_seconds)
            has_s = "s" if period_value > 1 else ""
            strings.append(f"{period_value} {period_name}{has_s}")

    return ", ".join(strings)


class JobResultsTrendCard(TrendCard):
    title = "Results trend"
    model = JobResult
    datetime_field = "created_at"
    size = TrendCard.Sizes.FULL
    group_field = "status"
    group_labels = {
        "SUCCESSFUL": "Successful",
        "ERRORED": "Errored",
        "CANCELLED": "Cancelled",
        "DEFERRED": "Deferred",
        "LOST": "Lost",
    }
    group_colors = {
        "SUCCESSFUL": {"bg": "rgba(34, 197, 94, 0.7)", "hover": "rgba(34, 197, 94, 1)"},
        "ERRORED": {"bg": "rgba(239, 68, 68, 0.7)", "hover": "rgba(239, 68, 68, 1)"},
        "CANCELLED": {
            "bg": "rgba(168, 162, 158, 0.7)",
            "hover": "rgba(168, 162, 158, 1)",
        },
        "DEFERRED": {"bg": "rgba(59, 130, 246, 0.7)", "hover": "rgba(59, 130, 246, 1)"},
        "LOST": {"bg": "rgba(245, 158, 11, 0.7)", "hover": "rgba(245, 158, 11, 1)"},
    }


class SuccessfulJobsCard(Card):
    title = "Successful"
    text = "View"

    def get_metric(self) -> int:
        return JobResult.query.successful().count()

    def get_link(self) -> str:
        return JobResultViewset.ListView.get_view_url() + "?display=Successful"


class ErroredJobsCard(Card):
    title = "Errored"
    text = "View"

    def get_metric(self) -> int:
        return JobResult.query.errored().count()

    def get_link(self) -> str:
        return JobResultViewset.ListView.get_view_url() + "?display=Errored"


class LostJobsCard(Card):
    title = "Lost"
    text = "View"  # TODO make not required - just an icon?

    def get_description(self) -> str:
        delta = timedelta(seconds=settings.JOBS_TIMEOUT)
        return f"Jobs are considered lost after {_td_format(delta)}"

    def get_metric(self) -> int:
        return JobResult.query.lost().count()

    def get_link(self) -> str:
        return JobResultViewset.ListView.get_view_url() + "?display=Lost"


class RetriedJobsCard(Card):
    title = "Retried"
    text = "View"  # TODO make not required - just an icon?

    def get_metric(self) -> int:
        return JobResult.query.retried().count()

    def get_link(self) -> str:
        return JobResultViewset.ListView.get_view_url() + "?display=Retried"


class WaitingJobsCard(Card):
    title = "Waiting"

    def get_metric(self) -> int:
        return JobProcess.query.waiting().count()


class RunningJobsCard(Card):
    title = "Running"

    def get_metric(self) -> int:
        return JobProcess.query.running().count()


@register_viewset
class JobRequestViewset(AdminViewset):
    class ListView(AdminModelListView):
        nav_section = "Jobs"
        nav_icon = "inbox"
        model = JobRequest
        title = "Requests"
        description = "Jobs waiting to be picked up by a worker."
        fields = [
            "id",
            "job_class",
            "priority",
            "created_at",
            "start_at",
            "concurrency_key",
        ]
        actions = ["Delete"]
        queryset_order = ["-priority", "-start_at", "-created_at"]

        def perform_action(self, action: str, target_ids: list[int]) -> None:
            if action == "Delete":
                JobRequest.query.filter(id__in=target_ids).delete()

    class DetailView(AdminModelDetailView):
        model = JobRequest
        title = "Request"


@register_viewset
class JobProcessViewset(AdminViewset):
    class ListView(AdminModelListView):
        nav_section = "Jobs"
        nav_icon = "gear"
        model = JobProcess
        title = "Processes"
        description = "Jobs currently being processed by a worker."
        fields = [
            "id",
            "job_class",
            "priority",
            "created_at",
            "started_at",
            "concurrency_key",
        ]
        actions = ["Delete"]
        cards = [
            WaitingJobsCard,
            RunningJobsCard,
        ]

        def perform_action(self, action: str, target_ids: list[int]) -> None:
            if action == "Delete":
                JobProcess.query.filter(id__in=target_ids).delete()

    class DetailView(AdminModelDetailView):
        model = JobProcess
        title = "Process"


@register_viewset
class JobResultViewset(AdminViewset):
    class ListView(AdminModelListView):
        nav_section = "Jobs"
        nav_icon = "clipboard-check"
        model = JobResult
        title = "Results"
        description = "Completed jobs with their success/failure status."
        fields = [
            "id",
            "job_class",
            "priority",
            "created_at",
            "status",
            "retried",
            "is_retry",
        ]
        field_templates = {
            "status": "jobs/values/job_status.html",
        }
        search_fields = [
            "uuid",
            "job_process_uuid",
            "job_request_uuid",
            "job_class",
        ]
        cards = [
            JobResultsTrendCard,
            SuccessfulJobsCard,
            ErroredJobsCard,
            LostJobsCard,
            RetriedJobsCard,
        ]
        filters = [
            "Successful",
            "Errored",
            "Cancelled",
            "Lost",
            "Retried",
        ]
        actions = [
            "Retry",
        ]

        def get_initial_queryset(self) -> JobResultQuerySet:
            queryset: JobResultQuerySet = super().get_initial_queryset()  # type: ignore[assignment]
            return queryset.annotate(
                retried=Case(
                    When(retry_job_request_uuid__isnull=False, then=True),
                    default=False,
                    output_field=postgres.BooleanField(),
                ),
                is_retry=Case(
                    When(retry_attempt__gt=0, then=True),
                    default=False,
                    output_field=postgres.BooleanField(),
                ),
            )

        def filter_queryset(self, queryset: JobResultQuerySet) -> JobResultQuerySet:
            if self.filter == "Successful":
                return queryset.successful()
            if self.filter == "Errored":
                return queryset.errored()
            if self.filter == "Cancelled":
                return queryset.cancelled()
            if self.filter == "Lost":
                return queryset.lost()
            if self.filter == "Retried":
                return queryset.retried()
            return queryset

        def get_fields(self) -> list[str]:
            fields = super().get_fields()
            if self.filter == "Retried":
                fields.append("retries")
                fields.append("retry_attempt")
            return fields

        def perform_action(self, action: str, target_ids: list[int]) -> None:
            if action == "Retry":
                for result in JobResult.query.filter(id__in=target_ids):
                    result.retry_job(delay=0)
            else:
                raise ValueError("Invalid action")

    class DetailView(AdminModelDetailView):
        model = JobResult
        title = "Result"

        def post(self) -> RedirectResponse:
            self.object.retry_job(delay=0)
            return RedirectResponse(".")
