from django import forms
from django.contrib import admin, messages
from django.core import signing
from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.http import HttpResponseRedirect

from apps.catalog.models import ProductModel, ProductVariant
from . import services
from .models import PartCategory, SparePart, SparePartCompatibility

REVISION_SALT = "parts-master-revision"


class RevisionForm(forms.ModelForm):
    revision = forms.CharField(required=False, widget=forms.HiddenInput)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.expected_revision = None
        if not self.instance._state.adding:
            self.initial["revision"] = signing.dumps(self._snapshot(), salt=REVISION_SALT)

    def _snapshot(self):
        return [self.instance._meta.label_lower, str(self.instance.pk), services.revision(self.instance)]

    def clean(self):
        data = super().clean()
        if not self.instance._state.adding:
            try:
                supplied = signing.loads(data.get("revision", ""), salt=REVISION_SALT)
            except signing.BadSignature as error:
                raise ValidationError("Reload the record before saving.") from error
            if supplied != self._snapshot():
                raise ValidationError("The record changed. Reload and review before saving.")
            self.expected_revision = supplied[2]
        return data


class PartCategoryForm(RevisionForm):
    class Meta:
        model = PartCategory
        fields = ("code", "name", "description")


class SparePartForm(RevisionForm):
    product_models = forms.ModelMultipleChoiceField(queryset=ProductModel.objects.select_related("brand"), required=False,
        help_text="Model-wide compatibility includes all valid variants. Inactive configuration is retained but not operational.")
    product_variants = forms.ModelMultipleChoiceField(queryset=ProductVariant.objects.select_related("product_model__brand"), required=False,
        help_text="Exact variants only. Do not also select their parent model above.")

    class Meta:
        model = SparePart
        fields = ("part_code", "name", "description", "category", "manufacturer_part_number", "serialization_policy")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.instance._state.adding:
            mappings = list(self.instance.compatibilities.filter(is_active=True))
            self.initial["product_models"] = [obj.product_model_id for obj in mappings if obj.product_model_id]
            self.initial["product_variants"] = [obj.product_variant_id for obj in mappings if obj.product_variant_id]

    def clean(self):
        data = super().clean()
        models = {obj.pk for obj in data.get("product_models", [])}
        if any(obj.product_model_id in models for obj in data.get("product_variants", [])):
            raise ValidationError("Choose model-wide or variant-specific compatibility for each model, not both.")
        return data


class PartsAdmin(admin.ModelAdmin):
    readonly_fields = ("id", "is_active", "created_at", "updated_at")
    actions = ("deactivate_selected", "reactivate_selected")

    def get_readonly_fields(self, request, obj=None):
        return self.readonly_fields + ((self.code_field,) if obj else ())

    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, IntegrityError):
            # Outside Django Admin's atomic change form: metadata and mappings
            # have rolled back together, including a race detected after clean().
            self.message_user(request, "Save rejected. Review current references, unique codes and revision; reload and retry.", messages.ERROR)
            return HttpResponseRedirect(request.path)

    def _lifecycle(self, request, queryset, active):
        if self.model is PartCategory:
            operation = services.reactivate_part_category if active else services.deactivate_part_category
            argument = "part_category"
        else:
            operation = services.reactivate_spare_part if active else services.deactivate_spare_part
            argument = "spare_part"
        for obj in queryset.order_by("pk"):
            try:
                operation(**{argument: obj}, expected_revision=services.revision(obj))
                self.log_change(request, obj, "Reactivated." if active else "Deactivated.")
            except ValidationError:
                self.message_user(request, "A record changed; reload and retry its lifecycle operation.", messages.ERROR)

    @admin.action(description="Deactivate selected records", permissions=["change"])
    def deactivate_selected(self, request, queryset):
        self._lifecycle(request, queryset, False)

    @admin.action(description="Reactivate selected records", permissions=["change"])
    def reactivate_selected(self, request, queryset):
        self._lifecycle(request, queryset, True)

    @staticmethod
    def _adopt(obj, current):
        for field in obj._meta.concrete_fields:
            setattr(obj, field.attname, getattr(current, field.attname))
        obj._state = current._state


@admin.register(PartCategory)
class PartCategoryAdmin(PartsAdmin):
    form = PartCategoryForm
    code_field = "code"
    list_display = ("code", "name", "is_active")
    list_filter = ("is_active",)
    search_fields = ("code", "name", "description")

    def save_model(self, request, obj, form, change):
        data = {field: form.cleaned_data[field] for field in ("name", "description")}
        current = (services.update_part_category(part_category=obj, expected_revision=form.expected_revision, **data)
            if change else services.create_part_category(code=obj.code, **data))
        self._adopt(obj, current)


@admin.register(SparePart)
class SparePartAdmin(PartsAdmin):
    form = SparePartForm
    code_field = "part_code"
    list_display = ("part_code", "name", "category", "serialization_policy", "is_active")
    list_filter = ("is_active", "category", "serialization_policy")
    list_select_related = ("category",)
    search_fields = ("part_code", "name", "manufacturer_part_number", "description")

    def save_model(self, request, obj, form, change):
        data = {field: form.cleaned_data[field] for field in ("name", "description", "category",
            "manufacturer_part_number", "serialization_policy", "product_models", "product_variants")}
        current = (services.update_spare_part(spare_part=obj, expected_revision=form.expected_revision, **data)
            if change else services.create_spare_part(part_code=obj.part_code, **data))
        self._adopt(obj, current)


@admin.register(SparePartCompatibility)
class CompatibilityAdmin(admin.ModelAdmin):
    list_display = ("spare_part", "product_model", "product_variant", "is_active", "updated_at")
    list_filter = ("is_active",)
    list_select_related = ("spare_part", "product_model__brand", "product_variant__product_model__brand")
    search_fields = ("spare_part__part_code", "product_model__code", "product_variant__code")
    readonly_fields = ("id", "spare_part", "product_model", "product_variant", "is_active", "created_at", "updated_at")
    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
