"""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 diagnostic_queries as queries, diagnostic_services as services
from .engineer_queries import current_engineer_assignment
from .models import ServiceDiagnosticAssessment, ServiceDiagnosticFinding, NO_CREDENTIALS

SALT = "service-diagnostic-revision"


class DiagnosisForm(forms.Form):
    operation = forms.ChoiceField(choices=[("begin", "Begin diagnosis"), ("note", "Save assessment note"),
        ("add", "Add finding"), ("update", "Update finding"), ("remove", "Remove finding"),
        ("complete", "Complete diagnosis"), ("abandon", "Abandon diagnosis")])
    finding = forms.ModelChoiceField(queryset=None, required=False)
    fault_diagnosis = forms.ModelChoiceField(queryset=None, required=False)
    root_cause = forms.ModelChoiceField(queryset=None, required=False, help_text="Empty means unknown/unconfirmed; completion is allowed.")
    note = forms.CharField(max_length=2000, required=False, widget=forms.Textarea, help_text=NO_CREDENTIALS)
    technical_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)
    revision = forms.CharField(widget=forms.HiddenInput)

    def __init__(self, *args, case, assessment, assignment, **kwargs):
        super().__init__(*args, **kwargs)
        self.case, self.assessment, self.assignment = case, assessment, assignment
        choices = self.fields["operation"].choices
        self.fields["operation"].choices = [item for item in choices if
            (case.status == "ASSIGNED" and item[0] == "begin") or (case.status == "DIAGNOSING" and item[0] != "begin")]
        self.fields["finding"].queryset = queries.diagnostic_findings_for_assessment(assessment).filter(removed_at=None)
        category = case.device.product_model.category
        self.fields["fault_diagnosis"].queryset = taxonomy.applicable_fault_diagnoses_for_category(category)
        self.fields["root_cause"].queryset = taxonomy.applicable_root_causes_for_category(category)
        self.initial["revision"] = signing.dumps(self.facts(), salt=SALT)
        self.initial["operation"] = "note" if assessment else "begin"
        self.initial["technical_note"] = assessment.technical_note if assessment 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.assessment.pk), self.assessment.updated_at.isoformat()] if self.assessment 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 diagnosis form.") from error
        if supplied != self.facts():
            raise ValidationError("Diagnosis changed; reload and review it.")
        operation = data.get("operation")
        if operation in ("add", "update") and not data.get("fault_diagnosis"):
            self.add_error("fault_diagnosis", "Select a FaultDiagnosis.")
        if operation in ("update", "remove") and not data.get("finding"):
            self.add_error("finding", "Select a finding from this assessment.")
        if operation == "abandon" and not data.get("reason"):
            self.add_error("reason", "A reason is required.")
        if operation != "begin" and self.assessment is None:
            raise ValidationError("No open assessment exists.")
        return data


class DiagnosisWorkflowMixin:
    def get_urls(self):
        return [path("<path:object_id>/diagnosis/", self.admin_site.admin_view(self.diagnosis_view),
                     name="service_servicecase_diagnosis")] + super().get_urls()

    @admin.display(description="Diagnosis workflow and history")
    def diagnosis_workflow(self, obj):
        return format_html('<a href="{}">Diagnosis and technical findings</a>', reverse("admin:service_servicecase_diagnosis", args=[obj.pk]))

    def diagnosis_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
        assessment = queries.current_diagnostic_assessment(case)
        assignment = current_engineer_assignment(case)
        form = DiagnosisForm(request.POST if request.method == "POST" else None, case=case, assessment=assessment, 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_diagnosis(service_case=case, actor=request.user,
                        expected_engineer_assignment_id=assignment.pk if assignment else None, expected_updated_at=case.updated_at)
                elif operation == "note":
                    services.update_diagnostic_assessment(assessment=assessment, actor=request.user,
                        technical_note=data["technical_note"], expected_updated_at=assessment.updated_at)
                elif operation == "add":
                    services.add_diagnostic_finding(assessment=assessment, actor=request.user, fault_diagnosis=data["fault_diagnosis"],
                        root_cause=data["root_cause"], note=data["note"], expected_updated_at=assessment.updated_at)
                elif operation == "update":
                    services.update_diagnostic_finding(finding=data["finding"], actor=request.user,
                        fault_diagnosis=data["fault_diagnosis"], root_cause=data["root_cause"], note=data["note"],
                        expected_updated_at=data["finding"].updated_at, expected_assessment_updated_at=assessment.updated_at)
                elif operation == "remove":
                    services.remove_diagnostic_finding(finding=data["finding"], actor=request.user,
                        expected_updated_at=data["finding"].updated_at, expected_assessment_updated_at=assessment.updated_at)
                elif operation == "complete":
                    services.complete_service_case_diagnosis(assessment=assessment, actor=request.user,
                        technical_note=data["technical_note"], expected_updated_at=assessment.updated_at)
                elif operation == "abandon":
                    services.abandon_service_case_diagnosis(assessment=assessment, actor=request.user, reason=data["reason"], expected_updated_at=assessment.updated_at)
            except ValidationError as error:
                form.add_error(None, ValidationError(error.messages))
            else:
                self.log_change(request, case, "Recorded a diagnostic workflow operation.")
                self.message_user(request, "Diagnosis updated.", messages.SUCCESS)
                return HttpResponseRedirect(request.path)
        history = queries.diagnostic_assessment_history(case).prefetch_related("findings__fault_diagnosis", "findings__root_cause")
        return TemplateResponse(request, "admin/service/diagnosis.html", {**self.admin_site.each_context(request),
            "opts": self.model._meta, "title": "Diagnosis and technical findings", "case": case, "form": form,
            "history": history, "can_change": self.has_change_permission(request, case) and case.status in ("ASSIGNED", "DIAGNOSING")})


class DiagnosticHistoryAdmin(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(ServiceDiagnosticAssessment)
class DiagnosticAssessmentAdmin(DiagnosticHistoryAdmin):
    list_display = ("service_case", "engineer_assignment", "started_at", "completed_at", "abandoned_at")
    list_select_related = ("service_case", "engineer_assignment")
    list_filter = ("service_case__company", "service_case__service_center", "completed_at", "abandoned_at")
    search_fields = ("service_case__job_number", "engineer_assignment__engineer__username")


@admin.register(ServiceDiagnosticFinding)
class DiagnosticFindingAdmin(DiagnosticHistoryAdmin):
    list_display = ("assessment", "fault_diagnosis", "root_cause", "removed_at")
    list_select_related = ("assessment", "fault_diagnosis", "root_cause")
    list_filter = ("assessment__service_case__company", "assessment__service_case__service_center")
    search_fields = ("assessment__service_case__job_number", "fault_diagnosis__code")
