from datetime import timedelta

from django.contrib import messages
from django.contrib.admin.utils import quote
from django.core.exceptions import ValidationError
from django.db.models import OuterRef, Subquery
from django.shortcuts import redirect
from django.urls import reverse
from django.utils.translation import gettext_lazy as _

from wagtail.admin.widgets.button import ListingButton

from ..choices import COMPLETED_TASK_STATUSUS, LogType, TaskStatus
from ..models import Task, TaskLog
from .readonly import ReadOnlySnippetViewSet
from .registry import action_registry


class TaskInspectView(ReadOnlySnippetViewSet.inspect_view_class):
    template_name = "admin/task/inspect.html"


class TaskIndexView(ReadOnlySnippetViewSet.index_view_class):
    task_url_name = None
    retry_url_name = None
    cancel_url_name = None
    # Retry task after 30 minutes, set to none if retry not needed.
    retry_after = 30

    def retry_allowed(self, instance):
        return instance.status == TaskStatus.FAILED

    def cancel_allowed(self, instance):
        return instance.status == TaskStatus.FAILED

    def get_retry_url(self, instance):
        return reverse(self.retry_url_name, args=(quote(instance.pk),))

    def get_cancel_url(self, instance):
        return reverse(self.cancel_url_name, args=(quote(instance.pk),))

    def get_task_url(self, instance):
        return reverse(self.task_url_name, args=(quote(instance.pk),))

    def get_list_buttons(self, instance):
        buttons = super().get_list_buttons(instance)

        if self.retry_allowed(instance):
            buttons.append(
                ListingButton(
                    _("Retry now"),
                    url=self.get_retry_url(instance),
                )
            )

        if self.cancel_allowed(instance):
            buttons.append(
                ListingButton(
                    _("Cancel"),
                    url=self.get_cancel_url(instance),
                )
            )

        if (
            instance.status == TaskStatus.SUCCESSFUL
            and instance.last_log_message is not None
            and instance.last_log_type in [LogType.FAILED, LogType.ERROR]
        ):
            buttons.append(
                ListingButton(
                    _("Comments"),
                    url=self.get_task_url(instance),
                )
            )

        return buttons

    def get_context_data(self, *args, **kwargs):
        context = super().get_context_data(*args, **kwargs)
        pending_actions = set(
            Task.objects.exclude(status__in=COMPLETED_TASK_STATUSUS).values_list(
                "code", flat=True
            )
        )
        for code, action in action_registry.get_actions().items():
            action.is_disabled = code in pending_actions

        return dict(
            context,
            actions=action_registry.get_actions_to_display(),
        )

    def post(self, request):
        action_string = request.POST.get("action")

        if action := action_registry.get(action_string):
            try:
                action.name = f"Manually Started: {action.name}"
                if action.retry_after is None and self.retry_after:
                    action.retry_after = timedelta(minutes=self.retry_after)
                task = Task.create(action)
                task.run_async()
                messages.success(request, _("Task started successfully."))
            except ValidationError as e:
                messages.error(request, _(f"Task could not be created: {e}"))
        else:
            messages.error(request, _("Action not found."))

        return self.get(request)

    def get_base_queryset(self):
        logs = TaskLog.objects.filter(task_id=OuterRef("pk")).order_by("-timestamp")
        return Task.objects.annotate(
            last_log_type=Subquery(logs.values("log_type")[:1]),
            last_log_message=Subquery(logs.values("message")[:1]),
        )


def retry(request, pk):
    task = Task.objects.get(pk=pk)
    task.set_status(TaskStatus.PENDING, message=_("Manualy retry initialized"))
    task.run_async()

    messages.info(request, f"'{task.name}' retry initialized successfuly")
    return redirect(request.META.get("HTTP_REFERER"))


def cancel(request, pk):
    task = Task.objects.get(pk=pk)
    task.set_status(TaskStatus.CANCELED, message=_("Manualy canceled"))

    messages.info(request, f"'{task.name}' canceled successfuly")
    return redirect(request.META.get("HTTP_REFERER"))
