"""Small trusted-admin assignment form; historical rows are never editable."""
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, format_html_join

from . import engineer_queries as queries, engineer_services as services
from .models import ServiceEngineerAssignment, NO_CREDENTIALS

SALT = "service-engineer-revision"


class AssignmentForm(forms.Form):
    operation = forms.ChoiceField(choices=[("assign", "Assign"), ("reassign", "Reassign"), ("unassign", "Unassign")])
    engineer = forms.ModelChoiceField(queryset=None, required=False)
    reason = forms.CharField(max_length=2000, required=False, widget=forms.Textarea, help_text=NO_CREDENTIALS)
    note = 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, current, **kwargs):
        super().__init__(*args, **kwargs)
        self.case = case
        self.current = current
        self.fields["engineer"].queryset = queries.eligible_engineers_for_service_case(case)
        self.initial["revision"] = signing.dumps(self.facts(), salt=SALT)
        self.initial["operation"] = "reassign" if current else "assign"

    def facts(self):
        return [str(self.case.pk), self.case.updated_at.isoformat(), str(self.current.pk) if self.current 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 assignment form.") from error
        if facts != self.facts():
            raise ValidationError("Assignment or case changed; reload and review it.")
        if data.get("operation") in ("assign", "reassign") and not data.get("engineer"):
            self.add_error("engineer", "Choose an eligible engineer.")
        return data


class EngineerWorkflowMixin:
    def get_urls(self):
        return [path("<path:object_id>/engineer/", self.admin_site.admin_view(self.engineer_view),
                     name="service_servicecase_engineer")] + super().get_urls()

    def get_queryset(self, request):
        from django.db.models import Prefetch
        return super().get_queryset(request).prefetch_related(Prefetch("engineer_assignments",
            queryset=ServiceEngineerAssignment.objects.filter(ended_at__isnull=True).select_related("engineer"), to_attr="current_engineer_rows"))

    @admin.display(description="Current engineer")
    def assigned_engineer(self, obj):
        rows = obj.current_engineer_rows
        return rows[0].engineer if rows else "-"

    @admin.display(description="Engineer workflow")
    def engineer_workflow(self, obj):
        if obj.status == "CANCELLED":
            return "Cancelled; history retained below."
        if obj.status in ("DIAGNOSING", "DIAGNOSED", "REPAIRING", "REPAIRED", "QC_PENDING", "QC_IN_PROGRESS", "QC_PASSED", "READY_FOR_DELIVERY", "DELIVERED", "CLOSED"):
            return "Engineer changes are unavailable during or after diagnosis. Abandon an open diagnosis first."
        return format_html('<a href="{}">Assign / reassign / unassign</a>',
                           reverse("admin:service_servicecase_engineer", args=[obj.pk]))

    @admin.display(description="Assignment history")
    def assignment_history(self, obj):
        rows = queries.engineer_assignment_history(obj)
        body = format_html_join("", "<tr><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td><td>{}</td></tr>",
            ((row.engineer, row.assigned_at, row.assigned_by, row.ended_at or "Current", row.ended_by or "-", row.end_reason) for row in rows))
        return format_html("<table><thead><tr><th>Engineer</th><th>Assigned</th><th>By</th><th>Ended</th><th>By</th><th>Reason</th></tr></thead><tbody>{}</tbody></table>", body)

    def engineer_view(self, request, object_id):
        case = self.get_object(request, object_id)
        if case is None:
            raise Http404
        if not self.has_change_permission(request, case):
            raise PermissionDenied
        current = queries.current_engineer_assignment(case)
        form = AssignmentForm(request.POST if request.method == "POST" else None, case=case, current=current)
        if request.method == "POST" and form.is_valid():
            data = form.cleaned_data
            try:
                if data["operation"] == "assign":
                    services.assign_service_case_engineer(service_case=case, engineer=data["engineer"], assigned_by=request.user,
                        note=data["note"], expected_updated_at=case.updated_at)
                elif data["operation"] == "reassign":
                    services.reassign_service_case_engineer(service_case=case, new_engineer=data["engineer"], reassigned_by=request.user,
                        reason=data["reason"], note=data["note"], expected_current_assignment_id=current.pk if current else None)
                else:
                    services.unassign_service_case_engineer(service_case=case, unassigned_by=request.user,
                        reason=data["reason"], expected_current_assignment_id=current.pk if current else None)
            except ValidationError as error:
                form.add_error(None, error)
            else:
                self.log_change(request, case, "Changed engineer assignment through domain service.")
                self.message_user(request, "Engineer assignment updated.", messages.SUCCESS)
                return HttpResponseRedirect(reverse("admin:service_servicecase_change", args=[case.pk]))
        return TemplateResponse(request, "admin/service/engineer_assignment.html", {
            **self.admin_site.each_context(request), "opts": self.model._meta,
            "title": "Manage engineer assignment", "case": case, "current": current, "form": form,
        })


@admin.register(ServiceEngineerAssignment)
class EngineerAssignmentAdmin(admin.ModelAdmin):
    list_display = ("service_case", "engineer", "assigned_at", "assigned_by", "ended_at", "ended_by")
    list_select_related = ("service_case", "engineer", "assigned_by", "ended_by")
    list_filter = ("service_case__company", "service_case__service_center", "ended_at")
    search_fields = ("service_case__job_number", "engineer__username", "engineer__first_name", "engineer__last_name", "engineer__email")
    readonly_fields = tuple(field.name for field in ServiceEngineerAssignment._meta.fields)
    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
