"""Service-backed evidence review and history-preserving coverage administration."""
from django import forms
from django.contrib import admin, messages
from django.core import signing
from django.core.exceptions import ValidationError
from django.http import HttpResponseRedirect

from . import services
from .models import DevicePurchaseEvidence, DeviceWarrantyCoverage


FACTS = ("purchase_date", "seller_name", "seller_reference", "invoice_number")
COVERAGE_FACTS = ("coverage_start_date", "coverage_end_date", "coverage_source", "reference", "note")


def revision_token(obj):
    return signing.dumps([obj._meta.label_lower, str(obj.pk), obj.updated_at.isoformat()], salt="device-evidence-revision")


class RevisionForm(forms.ModelForm):
    revision = forms.CharField(required=False, widget=forms.HiddenInput)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.instance._state.adding and "revision" in self.fields:
            self.fields["revision"].required = True
            self.initial["revision"] = revision_token(self.instance)

    def clean(self):
        data = super().clean()
        if not self.instance._state.adding:
            try:
                supplied = signing.loads(data.get("revision", ""), salt="device-evidence-revision")
            except signing.BadSignature as error:
                raise ValidationError("Reload this record before making changes.") from error
            expected = [self.instance._meta.label_lower, str(self.instance.pk), self.instance.updated_at.isoformat()]
            if supplied != expected:
                raise ValidationError("This record changed since the form was opened. Reload and review it.")
        return data


class PurchaseEvidenceForm(RevisionForm):
    class Meta:
        model = DevicePurchaseEvidence
        fields = ("device", *FACTS)


class CoverageCreateForm(forms.ModelForm):
    class Meta:
        model = DeviceWarrantyCoverage
        fields = ("device", *COVERAGE_FACTS)


class CoverageReplaceForm(RevisionForm):
    replacement_start_date = forms.DateField(required=False)
    replacement_end_date = forms.DateField(required=False)
    replacement_source = forms.ChoiceField(required=False, choices=[("", "---------"), *DeviceWarrantyCoverage.Source.choices])
    replacement_reference = forms.CharField(required=False, max_length=200)
    replacement_note = forms.CharField(required=False, max_length=2000, widget=forms.Textarea)

    class Meta:
        model = DeviceWarrantyCoverage
        fields = ()

    def clean(self):
        data = super().clean()
        fields = ["replacement_start_date", "replacement_end_date", "replacement_source"]
        if any(data.get(name) for name in fields + ["replacement_reference", "replacement_note"]):
            if not all(data.get(name) for name in fields):
                raise ValidationError("A replacement requires start, end and source together.")
        return data


class EvidenceAdmin(admin.ModelAdmin):
    readonly_fields = ("id", "created_at", "updated_at")
    list_select_related = ("device__product_model__brand",)

    def has_delete_permission(self, request, obj=None):
        return False

    def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
        try:
            return super().changeform_view(request, object_id, form_url, extra_context)
        except ValidationError:
            self.message_user(request, "Evidence operation rejected. Reload and review current evidence, coverage and Device state.", messages.ERROR)
            return HttpResponseRedirect(request.path)

    def construct_change_message(self, request, form, formsets, add=False):
        return "Recorded or reviewed device evidence."

    def _copy(self, obj, current):
        for field in obj._meta.concrete_fields:
            setattr(obj, field.attname, getattr(current, field.attname))
        obj._state = current._state


@admin.register(DevicePurchaseEvidence)
class DevicePurchaseEvidenceAdmin(EvidenceAdmin):
    form = PurchaseEvidenceForm
    list_select_related = (*EvidenceAdmin.list_select_related, "reviewed_by")
    list_display = ("device", "purchase_date", "seller_name", "invoice_number", "evidence_status", "reviewed_by", "reviewed_at")
    list_filter = ("evidence_status",)
    search_fields = ("seller_name", "seller_reference", "invoice_number", "device__product_model__code")
    actions = ("verify_selected", "reject_selected")

    def get_readonly_fields(self, request, obj=None):
        return self.readonly_fields + ("evidence_status", "reviewed_by", "reviewed_at", "verification_note") + (("device",) if obj else ())

    def get_fields(self, request, obj=None):
        return ("device", *FACTS, * (("revision",) if obj else ()),
                "evidence_status", "reviewed_by", "reviewed_at", "verification_note", "id", "created_at", "updated_at")

    def save_model(self, request, obj, form, change):
        fields = set(FACTS).intersection(form.changed_data) if change else set(FACTS)
        current = services.set_device_purchase_evidence(device=obj.device,
            expected_updated_at=obj.updated_at if change else None,
            **{name: form.cleaned_data[name] for name in fields})
        self._copy(obj, current)

    def _review(self, request, queryset, operation):
        for evidence in queryset.order_by("device_id", "pk"):
            try:
                operation(evidence=evidence, reviewed_by=request.user)
            except ValidationError:
                self.message_user(request, "Review rejected; reload current evidence and check reviewer status.", messages.ERROR)

    @admin.action(description="Verify selected purchase evidence", permissions=["change"])
    def verify_selected(self, request, queryset):
        self._review(request, queryset, services.verify_device_purchase_evidence)

    @admin.action(description="Reject selected purchase evidence", permissions=["change"])
    def reject_selected(self, request, queryset):
        self._review(request, queryset, services.reject_device_purchase_evidence)


@admin.register(DeviceWarrantyCoverage)
class DeviceWarrantyCoverageAdmin(EvidenceAdmin):
    list_display = ("device", "coverage_start_date", "coverage_end_date", "coverage_source", "is_active", "reference", "created_at")
    list_filter = ("is_active", "coverage_source")
    search_fields = ("reference", "device__product_model__code")
    actions = ("clear_selected",)

    def has_change_permission(self, request, obj=None):
        return super().has_change_permission(request, obj) and (obj is None or obj.is_active)

    def get_form(self, request, obj=None, **kwargs):
        kwargs["form"] = CoverageReplaceForm if obj else CoverageCreateForm
        return super().get_form(request, obj, **kwargs)

    def get_readonly_fields(self, request, obj=None):
        return self.readonly_fields + ("is_active",) + (("device", *COVERAGE_FACTS) if obj else ())

    def get_fields(self, request, obj=None):
        fields = ("device", *COVERAGE_FACTS, "is_active", "id", "created_at", "updated_at")
        if obj and obj.is_active:
            fields += ("revision", "replacement_start_date", "replacement_end_date", "replacement_source",
                       "replacement_reference", "replacement_note")
        return fields

    def save_model(self, request, obj, form, change):
        if change and not form.cleaned_data.get("replacement_source"):
            self._copy(obj, DeviceWarrantyCoverage.objects.get(pk=obj.pk))
            return
        values = ({name: form.cleaned_data[name] for name in COVERAGE_FACTS} if not change else {
            "coverage_start_date": form.cleaned_data["replacement_start_date"],
            "coverage_end_date": form.cleaned_data["replacement_end_date"],
            "coverage_source": form.cleaned_data["replacement_source"],
            "reference": form.cleaned_data["replacement_reference"], "note": form.cleaned_data["replacement_note"],
        })
        current = services.set_device_warranty_coverage(device=obj.device, expected_current_id=obj.pk if change else None, **values)
        self._copy(obj, current)

    @admin.action(description="Clear selected current coverage (retain history)", permissions=["change"])
    def clear_selected(self, request, queryset):
        for coverage in queryset.order_by("device_id", "pk"):
            try:
                services.clear_device_warranty_coverage(device=coverage.device, expected_current_id=coverage.pk)
            except ValidationError:
                self.message_user(request, "Coverage changed; reload before clearing it.", messages.ERROR)
