import uuid
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.db.models import Q
from django.http import HttpResponseRedirect
from django.template.response import TemplateResponse

from apps.access.authorization import authorized_queryset
from apps.organization.models import Company, ServiceCenter
from apps.parts.admin import RevisionForm
from . import services
from .models import InventoryLocation, SerializedStockUnit, StockMovement, StockLedgerEntry, StockMovementUnit
from .queries import authorized_locations


def _adopt(obj, current):
    for field in obj._meta.concrete_fields:
        setattr(obj, field.attname, getattr(current, field.attname))
    obj._state = current._state


class InventoryAdmin(admin.ModelAdmin):
    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):
            self.message_user(request, "Operation rejected. Reload and review scope, stock, identifiers and revisions.", messages.ERROR)
            return HttpResponseRedirect(request.path)


class LocationForm(RevisionForm):
    class Meta:
        model = InventoryLocation
        fields = ("company", "service_center", "code", "name", "location_type")

    def clean_code(self):
        return self.cleaned_data["code"].strip().upper()


@admin.register(InventoryLocation)
class LocationAdmin(InventoryAdmin):
    form = LocationForm
    list_display = ("company", "code", "name", "service_center", "location_type", "is_active")
    list_filter = ("location_type", "is_active")
    search_fields = ("code", "name")
    readonly_fields = ("id", "is_active", "created_at", "updated_at")
    actions = ("deactivate_selected", "reactivate_selected")

    def get_queryset(self, request):
        return authorized_locations(actor=request.user)

    def get_readonly_fields(self, request, obj=None):
        return self.readonly_fields + (("company", "service_center", "code", "location_type") if obj else ())

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        centers = authorized_queryset(user=request.user, permission="inventory.manage_inventory", queryset=ServiceCenter.objects.all())
        companies = authorized_queryset(user=request.user, permission="inventory.manage_inventory", queryset=Company.objects.all())
        if db_field.name == "company":
            kwargs["queryset"] = Company.objects.filter(Q(pk__in=companies.values("pk")) | Q(pk__in=centers.values("company_id")))
        elif db_field.name == "service_center":
            kwargs["queryset"] = centers
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

    def save_model(self, request, obj, form, change):
        if change:
            current = services.update_location(actor=request.user, location=obj, name=form.cleaned_data["name"], expected_revision=form.expected_revision)
        else:
            current = services.create_location(actor=request.user, **{field: form.cleaned_data[field]
                for field in ("company", "service_center", "code", "name", "location_type")})
        _adopt(obj, current)

    def _lifecycle(self, request, queryset, operation, action_name):
        rows = list(queryset.order_by("pk"))
        payload = [[[str(row.pk), row.updated_at.isoformat()] for row in rows], action_name, str(request.user.pk)]
        if not request.POST.get("confirm_transition"):
            return TemplateResponse(request, "admin/inventory/confirm_locations.html", {
                **self.admin_site.each_context(request), "opts": self.model._meta,
                "title": "Review inventory location lifecycle", "locations": rows, "action_name": action_name,
                "transition_revision": signing.dumps(payload, salt="inventory-location-lifecycle")})
        try:
            if signing.loads(request.POST.get("transition_revision", ""), salt="inventory-location-lifecycle") != payload:
                raise ValidationError("Locations changed; reload and review them.")
        except (signing.BadSignature, ValidationError):
            self.message_user(request, "Location selection or revision changed; review again.", messages.ERROR)
            return None
        for row in rows:
            try:
                current = operation(actor=request.user, location=row, expected_revision=row.updated_at.isoformat())
                self.log_change(request, current, "Reactivated location." if current.is_active else "Deactivated location.")
            except ValidationError:
                self.message_user(request, "Location lifecycle rejected; review current stock and revision.", messages.ERROR)

    @admin.action(description="Deactivate empty locations", permissions=["change"])
    def deactivate_selected(self, request, queryset):
        return self._lifecycle(request, queryset, services.deactivate_location, "deactivate_selected")

    @admin.action(description="Reactivate locations", permissions=["change"])
    def reactivate_selected(self, request, queryset):
        return self._lifecycle(request, queryset, services.reactivate_location, "reactivate_selected")


class MovementForm(forms.ModelForm):
    idempotency_key = forms.UUIDField(initial=uuid.uuid4, widget=forms.HiddenInput)
    location_revisions = forms.CharField(widget=forms.HiddenInput, required=False)
    units = forms.ModelMultipleChoiceField(queryset=SerializedStockUnit.objects.none(), required=False)

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if "destination" in self.fields:
            self.fields["destination"].required = True

    class Meta:
        model = StockMovement
        fields = ("source", "destination", "spare_part", "quantity", "reference", "note", "idempotency_key")


@admin.register(StockMovement)
class MovementAdmin(InventoryAdmin):
    form = MovementForm
    list_display = ("posted_at", "company", "spare_part", "kind", "source", "destination", "quantity", "actor")
    readonly_fields = ("id", "company", "kind", "actor", "posted_at", "created_at", "updated_at")
    actions = None

    def get_queryset(self, request):
        locations = authorized_locations(actor=request.user).values("pk")
        return super().get_queryset(request).filter(Q(source_id__in=locations) | Q(destination_id__in=locations)).select_related(
            "company", "spare_part", "source", "destination", "actor")

    def has_change_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.concrete_fields) if obj else self.readonly_fields

    def get_form(self, request, obj=None, **kwargs):
        if obj is not None:
            return forms.modelform_factory(StockMovement, fields=[])
        form_class = super().get_form(request, obj, **kwargs)
        destinations = authorized_locations(actor=request.user, permission="inventory.receive_stock")
        movable = authorized_locations(actor=request.user, permission="inventory.transfer_stock")
        choices = InventoryLocation.objects.filter(Q(pk__in=destinations.values("pk")) | Q(pk__in=movable.values("pk")))
        class ScopedMovementForm(form_class):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                if obj is None:
                    self.fields["source"].queryset = movable
                    self.fields["destination"].queryset = choices
                    self.initial["location_revisions"] = signing.dumps(
                        {str(row.pk): row.updated_at.isoformat() for row in choices}, salt="inventory-movement-locations")
                    companies = authorized_queryset(user=request.user, permission="inventory.receive_stock", queryset=Company.objects.all())
                    self.fields["units"].queryset = SerializedStockUnit.objects.filter(
                        Q(current_location_id__in=choices.values("pk")) | Q(state="REGISTERED", company_id__in=companies.values("pk"))).select_related("spare_part")
            def clean(self):
                data = super().clean()
                if obj is None:
                    try:
                        supplied = signing.loads(data.get("location_revisions", ""), salt="inventory-movement-locations")
                    except signing.BadSignature as error:
                        raise ValidationError("Reload inventory locations before posting.") from error
                    for field in ("source", "destination"):
                        location = data.get(field)
                        if location and supplied.get(str(location.pk)) != location.updated_at.isoformat():
                            raise ValidationError("A location changed; reload before posting.")
                    self.expected_locations = supplied
                return data
        return ScopedMovementForm

    def save_model(self, request, obj, form, change):
        data = {key: form.cleaned_data[key] for key in ("destination", "spare_part", "quantity", "reference", "note", "idempotency_key", "units")}
        destination_revision = form.expected_locations[str(data["destination"].pk)]
        source = form.cleaned_data["source"]
        if source:
            current = services.move_stock(actor=request.user, source=source, expected_source_revision=form.expected_locations[str(source.pk)],
                expected_destination_revision=destination_revision, **data)
        else:
            current = services.receive_stock(actor=request.user, expected_revision=destination_revision, **data)
        _adopt(obj, current)


class EvidenceAdmin(InventoryAdmin):
    actions = None

    def has_add_permission(self, request):
        return False

    def has_change_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.concrete_fields)


@admin.register(StockLedgerEntry)
class LedgerAdmin(EvidenceAdmin):
    list_display = ("created_at", "company", "location", "spare_part", "quantity_delta", "movement")

    def get_queryset(self, request):
        return super().get_queryset(request).filter(location_id__in=authorized_locations(actor=request.user).values("pk")).select_related(
            "company", "location", "spare_part", "movement")


@admin.register(SerializedStockUnit)
class UnitAdmin(EvidenceAdmin):
    list_display = ("company", "spare_part", "identifier", "state", "current_location")

    def has_add_permission(self, request):
        return request.user.has_perm("inventory.add_serializedstockunit")

    def get_readonly_fields(self, request, obj=None):
        return super().get_readonly_fields(request, obj) if obj else (
            "id", "state", "current_location", "current_movement", "registered_by", "created_at", "updated_at")

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "company":
            kwargs["queryset"] = authorized_queryset(user=request.user, permission="inventory.receive_stock", queryset=Company.objects.all())
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

    def save_model(self, request, obj, form, change):
        current = services.register_serialized_unit(actor=request.user, company=obj.company,
            spare_part=obj.spare_part, identifier=obj.identifier)
        _adopt(obj, current)

    def get_queryset(self, request):
        companies = authorized_queryset(user=request.user, permission="inventory.view_stock", queryset=Company.objects.all())
        return super().get_queryset(request).filter(Q(company_id__in=companies.values("pk"))
            | Q(current_location_id__in=authorized_locations(actor=request.user).values("pk"))).select_related(
                "company", "spare_part", "current_location", "current_movement", "registered_by")


from . import document_admin, request_admin, usage_admin, control_admin  # noqa: E402,F401
