"""Scoped, native Admin QC workflow; every mutation delegates to a service."""
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 . import quality_control_queries as queries, quality_control_services as services
from .models import ServiceQualityControl, ServiceQualityControlCheck, ServiceQualityControlComplaintCheck, NO_CREDENTIALS
from .repair_admin import RepairHistoryAdmin

SALT = "service-quality-control-revision"


class TechnicalCheckChoice(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return f"{obj.sequence}. {obj.check_name} ({obj.get_result_display() or 'Unanswered'})"


class ComplaintCheckChoice(forms.ModelChoiceField):
    def label_from_instance(self, obj):
        return f"{obj.complaint.complaint_symptom}: {obj.complaint.customer_description} ({obj.get_result_display() or 'Unanswered'})"


class QualityControlForm(forms.Form):
    operation = forms.ChoiceField(choices=[("submit", "Submit for QC"), ("begin", "Begin QC"),
        ("check", "Record technical check"), ("complaint", "Verify complaint"), ("summary", "Save summary"),
        ("pass", "PASS QC"), ("fail", "FAIL QC / return for rework"), ("abandon", "Abandon QC")])
    check = TechnicalCheckChoice(queryset=None, required=False)
    complaint_check = ComplaintCheckChoice(queryset=None, required=False)
    result = forms.ChoiceField(choices=[("", "---------"), *ServiceQualityControlCheck.Result.choices], required=False)
    complaint_result = forms.ChoiceField(choices=[("", "---------"), *ServiceQualityControlComplaintCheck.Result.choices], required=False)
    note = forms.CharField(max_length=2000, required=False, widget=forms.Textarea, help_text=NO_CREDENTIALS)
    summary = 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, quality_control, **kwargs):
        super().__init__(*args, **kwargs)
        self.case, self.quality_control = case, quality_control
        self.fields["operation"].choices = [item for item in self.fields["operation"].choices if
            (case.status == "REPAIRED" and item[0] == "submit") or (case.status == "QC_PENDING" and item[0] == "begin") or
            (case.status == "QC_IN_PROGRESS" and item[0] not in ("submit", "begin"))]
        self.fields["check"].queryset = queries.quality_control_checks(quality_control)
        self.fields["complaint_check"].queryset = queries.quality_control_complaint_checks(quality_control)
        self.initial["revision"] = signing.dumps(self.facts(), salt=SALT)
        self.initial["summary"] = quality_control.summary if quality_control else ""

    def facts(self):
        row = self.quality_control
        return [str(self.case.pk), self.case.updated_at.isoformat(),
            [str(row.pk), row.updated_at.isoformat(), str(row.repair_execution_id), str(row.inspector_id)] if row else None]

    def clean(self):
        data = super().clean()
        try:
            facts = signing.loads(data.get("revision", ""), salt=SALT)
        except signing.BadSignature as error:
            raise ValidationError("Reload the QC form.") from error
        if facts != self.facts():
            raise ValidationError("QC changed; reload and review it.")
        operation = data.get("operation")
        for op, names in (("check", ("check", "result")), ("complaint", ("complaint_check", "complaint_result")),
                          ("fail", ("reason",)), ("abandon", ("reason",))):
            if operation == op:
                for name in names:
                    if not data.get(name):
                        self.add_error(name, "Required for this operation.")
        return data


class QualityControlWorkflowMixin:
    def get_urls(self):
        return [path("<path:object_id>/quality-control/", self.admin_site.admin_view(self.quality_control_view),
            name="service_servicecase_quality_control")] + super().get_urls()

    @admin.display(description="Independent quality control")
    def quality_control_workflow(self, obj):
        return format_html('<a href="{}">Quality control and rework history</a>', reverse("admin:service_servicecase_quality_control", args=[obj.pk]))

    def quality_control_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) or not queries.quality_control_visible_cases(request.user).filter(pk=case.pk).exists():
            raise PermissionDenied
        can_change = request.user.has_perm("service.change_servicecase") and case.status in ("REPAIRED", "QC_PENDING", "QC_IN_PROGRESS")
        if request.method == "POST" and not can_change:
            raise PermissionDenied
        row = queries.current_quality_control(case)
        form = QualityControlForm(request.POST if request.method == "POST" else None, case=case, quality_control=row)
        if request.method == "POST" and form.is_valid():
            data = form.cleaned_data
            try:
                operation = data["operation"]
                if operation in ("submit", "begin"):
                    method = services.submit_for_quality_control if operation == "submit" else services.begin_quality_control
                    method(service_case=case, inspector=request.user, expected_updated_at=case.updated_at)
                else:
                    args = dict(quality_control=row, inspector=request.user, expected_updated_at=row.updated_at)
                    if operation == "check":
                        services.set_quality_control_check(**args, check_code=data["check"].check_code, result=data["result"], note=data["note"])
                    elif operation == "complaint":
                        services.set_quality_control_complaint_check(**args, complaint=data["complaint_check"].complaint,
                            result=data["complaint_result"], note=data["note"])
                    elif operation == "summary":
                        services.update_quality_control(**args, summary=data["summary"])
                    elif operation == "pass":
                        services.complete_quality_control_pass(**args, summary=data["summary"])
                    elif operation == "fail":
                        services.complete_quality_control_fail(**args, failure_note=data["reason"])
                    elif operation == "abandon":
                        services.abandon_quality_control(quality_control=row, actor=request.user, reason=data["reason"], expected_updated_at=row.updated_at)
            except ValidationError as error:
                form.add_error(None, ValidationError(error.messages))
            else:
                self.log_change(request, case, "Recorded a quality control workflow operation.")
                self.message_user(request, "Quality control updated.", messages.SUCCESS)
                return HttpResponseRedirect(request.path)
        history = queries.quality_control_history(case).prefetch_related("servicequalitycontrolchecks", "servicequalitycontrolcomplaintchecks__complaint__complaint_symptom")
        return TemplateResponse(request, "admin/service/quality_control.html", {**self.admin_site.each_context(request),
            "opts": self.model._meta, "title": "Quality control and rework history", "case": case, "form": form,
            "history": history, "can_change": can_change})


class QualityControlHistoryAdmin(RepairHistoryAdmin):
    def get_queryset(self, request):
        cases = queries.quality_control_visible_cases(request.user)
        lookup = "service_case_id__in" if self.model is ServiceQualityControl else "quality_control__service_case_id__in"
        return super().get_queryset(request).filter(**{lookup: cases.values("pk")})


@admin.register(ServiceQualityControl)
class QualityControlAdmin(QualityControlHistoryAdmin):
    list_display = ("service_case", "repair_execution", "inspector", "status", "outcome", "started_at", "completed_at")
    list_select_related = ("service_case", "repair_execution", "inspector")
    list_filter = ("status", "outcome", "service_case__company", "service_case__service_center")
    search_fields = ("service_case__job_number", "inspector__username")


@admin.register(ServiceQualityControlCheck)
class QualityControlCheckAdmin(QualityControlHistoryAdmin):
    list_display = ("quality_control", "check_code", "check_name", "result")
    list_select_related = ("quality_control",)


@admin.register(ServiceQualityControlComplaintCheck)
class QualityControlComplaintCheckAdmin(QualityControlHistoryAdmin):
    list_display = ("quality_control", "complaint", "result")
    list_select_related = ("quality_control", "complaint")
