"""Trusted Admin workflows use the signed-in engineer, never impersonation."""
from django import forms
from django.contrib import admin, messages
from django.core import signing
from django.core.exceptions import PermissionDenied, ValidationError
from django.http import Http404, HttpResponseRedirect
from django.template.response import TemplateResponse
from django.urls import path, reverse
from django.utils.html import format_html

from apps.service_catalog import queries as taxonomy
from . import repair_queries as queries, repair_services as services
from .engineer_queries import current_engineer_assignment
from .models import ServiceRepairExecution, ServiceRepairAction, NO_CREDENTIALS

SALT = "service-repair-revision"


class RepairForm(forms.Form):
    operation = forms.ChoiceField(choices=[("begin", "Begin repair"), ("note", "Save execution note"),
        ("add", "Add action"), ("update", "Update action"), ("remove", "Remove action"),
        ("perform", "Mark action performed"), ("complete", "Complete repair"), ("abandon", "Abandon repair")])
    action = forms.ModelChoiceField(queryset=None, required=False)
    repair_action = forms.ModelChoiceField(queryset=None, required=False)
    note = forms.CharField(max_length=2000, required=False, widget=forms.Textarea, help_text=NO_CREDENTIALS)
    execution_note = forms.CharField(max_length=4000, required=False, widget=forms.Textarea, help_text=NO_CREDENTIALS)
    reason = forms.CharField(max_length=2000, required=False, widget=forms.Textarea, help_text=NO_CREDENTIALS)
    outcome = forms.ChoiceField(choices=ServiceRepairExecution.Outcome.choices, required=False)
    revision = forms.CharField(widget=forms.HiddenInput)

    def __init__(self, *args, case, execution, assignment, **kwargs):
        super().__init__(*args, **kwargs)
        self.case, self.execution, self.assignment = case, execution, assignment
        choices = self.fields["operation"].choices
        self.fields["operation"].choices = [item for item in choices if
            (case.status == "DIAGNOSED" and item[0] == "begin") or (case.status == "REPAIRING" and item[0] != "begin")]
        self.fields["action"].queryset = queries.active_repair_actions(execution)
        category = case.device.product_model.category
        self.fields["repair_action"].queryset = taxonomy.applicable_repair_actions_for_category(category)
        self.initial["revision"] = signing.dumps(self.facts(), salt=SALT)
        self.initial["operation"] = "note" if execution else "begin"
        self.initial["execution_note"] = execution.note if execution else ""

    def facts(self):
        return [str(self.case.pk), self.case.updated_at.isoformat(), str(self.assignment.pk) if self.assignment else None,
                [str(self.execution.pk), self.execution.updated_at.isoformat()] if self.execution else None]

    def clean(self):
        data = super().clean()
        try:
            supplied = signing.loads(data.get("revision", ""), salt=SALT)
        except signing.BadSignature as error:
            raise ValidationError("Reload the repair form.") from error
        if supplied != self.facts():
            raise ValidationError("Repair changed; reload and review it.")
        operation = data.get("operation")
        if operation in ("add", "update") and not data.get("repair_action"):
            self.add_error("repair_action", "Select a RepairAction.")
        if operation in ("update", "remove", "perform") and not data.get("action"):
            self.add_error("action", "Select an action from this execution.")
        if operation == "complete" and not data.get("outcome"):
            self.add_error("outcome", "Choose an outcome.")
        if operation == "abandon" and not data.get("reason"):
            self.add_error("reason", "A reason is required.")
        if operation != "begin" and self.execution is None:
            raise ValidationError("No open execution exists.")
        return data


class RepairWorkflowMixin:
    def get_urls(self):
        return [path("<path:object_id>/repair/", self.admin_site.admin_view(self.repair_view),
                     name="service_servicecase_repair")] + super().get_urls()

    @admin.display(description="Repair workflow and history")
    def repair_workflow(self, obj):
        return format_html('<a href="{}">Repair and technical actions</a>', reverse("admin:service_servicecase_repair", args=[obj.pk]))

    def repair_view(self, request, object_id):
        case = self.get_object(request, object_id)
        if case is None:
            raise Http404
        if not self.has_view_permission(request, case):
            raise PermissionDenied
        if request.method == "POST" and not self.has_change_permission(request, case):
            raise PermissionDenied
        execution = queries.current_repair_execution(case)
        assignment = current_engineer_assignment(case)
        form = RepairForm(request.POST if request.method == "POST" else None, case=case, execution=execution, assignment=assignment)
        if request.method == "POST" and form.is_valid():
            data = form.cleaned_data
            try:
                operation = data["operation"]
                if operation == "begin":
                    services.begin_service_case_repair(service_case=case, actor=request.user, note=data["execution_note"],
                        expected_engineer_assignment_id=assignment.pk if assignment else None, expected_updated_at=case.updated_at)
                elif operation == "note":
                    services.update_repair_execution(repair_execution=execution, actor=request.user,
                        note=data["execution_note"], expected_updated_at=execution.updated_at)
                elif operation == "add":
                    services.add_repair_action(repair_execution=execution, actor=request.user, repair_action=data["repair_action"],
                        note=data["note"], expected_updated_at=execution.updated_at)
                elif operation in ("update", "remove", "perform"):
                    arguments = dict(action=data["action"], actor=request.user,
                        expected_updated_at=data["action"].updated_at, expected_execution_updated_at=execution.updated_at)
                    if operation == "update":
                        services.update_repair_action(**arguments, repair_action=data["repair_action"], note=data["note"])
                    elif operation == "remove":
                        services.remove_repair_action(**arguments)
                    else:
                        services.mark_repair_action_performed(**arguments)
                elif operation == "complete":
                    services.complete_service_case_repair(repair_execution=execution, actor=request.user,
                        outcome=data["outcome"], note=data["execution_note"], expected_updated_at=execution.updated_at)
                elif operation == "abandon":
                    services.abandon_service_case_repair(repair_execution=execution, actor=request.user,
                        reason=data["reason"], expected_updated_at=execution.updated_at)
            except ValidationError as error:
                form.add_error(None, ValidationError(error.messages))
            else:
                self.log_change(request, case, "Recorded a repair workflow operation.")
                self.message_user(request, "Repair updated.", messages.SUCCESS)
                return HttpResponseRedirect(request.path)
        history = queries.repair_execution_history(case).prefetch_related("actions__repair_action", "actions__performed_by")
        return TemplateResponse(request, "admin/service/repair.html", {**self.admin_site.each_context(request),
            "opts": self.model._meta, "title": "Repair and technical actions", "case": case, "form": form,
            "history": history, "can_change": self.has_change_permission(request, case) and case.status in ("DIAGNOSED", "REPAIRING")})


class RepairHistoryAdmin(admin.ModelAdmin):
    actions = None

    def has_add_permission(self, request):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

    def get_readonly_fields(self, request, obj=None):
        return tuple(field.name for field in self.model._meta.fields)



@admin.register(ServiceRepairExecution)
class RepairExecutionAdmin(RepairHistoryAdmin):
    list_display = ("service_case", "engineer_assignment", "status", "outcome", "started_at", "completed_at")
    list_select_related = ("service_case", "engineer_assignment")
    list_filter = ("service_case__company", "service_case__service_center", "status", "outcome")
    search_fields = ("service_case__job_number", "engineer_assignment__engineer__username")


@admin.register(ServiceRepairAction)
class RepairActionAdmin(RepairHistoryAdmin):
    list_display = ("repair_execution", "repair_action", "is_active", "performed_at", "performed_by")
    list_select_related = ("repair_execution", "repair_action", "performed_by")
    list_filter = ("repair_execution__service_case__company", "is_active")
    search_fields = ("repair_execution__service_case__job_number", "repair_action__code")
