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.template.response import TemplateResponse
from django.db.models import Q
from apps.access.authorization import authorized_queryset
from apps.organization.models import Company
from apps.parts.admin import RevisionForm
from apps.parts.models import SparePart
from .admin import InventoryAdmin, _adopt
from .models import StockAdjustment, StockCount, StockCountUnit, SerializedStockUnit
from . import control_services as services, control_queries as queries
from .queries import authorized_locations


class AdjustmentForm(forms.ModelForm):
    reference = forms.CharField(max_length=128)
    note = forms.CharField(max_length=2000, widget=forms.Textarea)
    units = forms.ModelMultipleChoiceField(queryset=SerializedStockUnit.objects.none(), required=False)
    command_key = forms.UUIDField(initial=uuid.uuid4, widget=forms.HiddenInput)
    revisions = forms.CharField(widget=forms.HiddenInput)

    class Meta:
        model = StockAdjustment
        fields = ("location", "spare_part", "quantity_delta", "reason")

    def clean(self):
        cleaned = super().clean()
        try:
            payload = signing.loads(cleaned.get("revisions", ""), salt="inventory-adjustment")
            location, part = cleaned.get("location"), cleaned.get("spare_part")
            if location and part:
                loc_revision, part_revision = payload["locations"][str(location.pk)], payload["parts"][str(part.pk)]
                values = payload["positions"].get(f"{location.pk}:{part.pk}", [0, 0, 0])
                self.expected_revision = f"{loc_revision}:{part_revision}:{values[0]}:{values[1]}:{values[2]}"
        except (signing.BadSignature, KeyError, TypeError, IndexError):
            raise ValidationError("Stock selections changed; reload and review.")
        return cleaned


@admin.register(StockAdjustment)
class StockAdjustmentAdmin(InventoryAdmin):
    form = AdjustmentForm
    actions = None
    list_display = ("created_at", "location", "spare_part", "quantity_delta", "reason", "actor")

    def has_change_permission(self, request, obj=None):
        return False

    def get_queryset(self, request):
        return queries.stock_adjustments(actor=request.user)

    def get_readonly_fields(self, request, obj=None):
        editable = {"location", "spare_part", "quantity_delta", "reason"}
        return tuple(field.name for field in self.model._meta.concrete_fields if obj or field.name not in editable)

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "location":
            kwargs["queryset"] = authorized_locations(actor=request.user, permission="inventory.adjust_stock").filter(is_active=True).exclude(location_type__in=["CUSTODY", "TRANSIT"])
        if db_field.name == "spare_part":
            kwargs["queryset"] = SparePart.objects.filter(is_active=True, category__is_active=True).select_related("category")
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

    def get_form(self, request, obj=None, **kwargs):
        if obj:
            return forms.modelform_factory(self.model, fields=[])
        base = super().get_form(request, obj, **kwargs)
        class ScopedAdjustmentForm(base):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                locations = self.fields["location"].queryset
                companies = authorized_queryset(user=request.user, permission="inventory.adjust_stock", queryset=Company.objects.all())
                self.fields["units"].queryset = SerializedStockUnit.objects.filter(Q(current_location_id__in=locations.values("pk"))
                    | Q(company_id__in=companies.values("pk"), state__in=["REGISTERED", "REMOVED"])
                    | Q(state="REMOVED", current_movement__source_id__in=locations.values("pk"))).select_related("spare_part", "current_location")
                self.fields["reason"].choices = [(key, label) for key, label in self.fields["reason"].choices if key != "COUNT_VARIANCE"]
                if not self.is_bound:
                    payload = {"locations": {str(row.pk): row.updated_at.isoformat() for row in locations},
                        "parts": {str(row.pk): row.updated_at.isoformat() for row in self.fields["spare_part"].queryset},
                        "positions": {f"{row.location_id}:{row.spare_part_id}": [row.ledger_count, row.reservation_count, row.reserved]
                            for row in queries.control_positions(actor=request.user, permission="inventory.adjust_stock")}}
                    self.initial["revisions"] = signing.dumps(payload, salt="inventory-adjustment")
        return ScopedAdjustmentForm

    def save_model(self, request, obj, form, change):
        row = services.adjust_stock(actor=request.user, location=obj.location, spare_part=obj.spare_part,
            quantity_delta=obj.quantity_delta, reason=obj.reason, reference=form.cleaned_data["reference"],
            note=form.cleaned_data["note"], command_key=form.cleaned_data["command_key"], units=list(form.cleaned_data["units"]), expected_revision=form.expected_revision)
        _adopt(obj, row)


class CountForm(RevisionForm):
    units = forms.ModelMultipleChoiceField(queryset=SerializedStockUnit.objects.none(), required=False)

    class Meta:
        model = StockCount
        fields = ("location", "spare_part", "counted_quantity", "note")

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        if not self.instance._state.adding and "units" in self.fields:
            self.initial["units"] = list(self.instance.units.filter(counted=True).values_list("unit_id", flat=True))
            self.fields["counted_quantity"].required = True


class CountUnitInline(admin.TabularInline):
    model = StockCountUnit
    fields = ("unit", "expected", "counted")
    readonly_fields = fields
    extra = 0

    def has_add_permission(self, request, obj=None):
        return False

    def has_change_permission(self, request, obj=None):
        return False

    def has_delete_permission(self, request, obj=None):
        return False

    def get_queryset(self, request):
        return super().get_queryset(request).select_related("unit__spare_part")


@admin.register(StockCount)
class StockCountAdmin(InventoryAdmin):
    form = CountForm
    inlines = (CountUnitInline,)
    list_display = ("location", "spare_part", "status", "expected_quantity", "counted_quantity", "variance", "started_at", "finished_at")
    list_filter = ("status",)
    actions = ("start_selected", "reconcile_selected", "cancel_selected")

    def get_inlines(self, request, obj=None):
        return (CountUnitInline,) if obj is not None and obj.units.exists() else ()

    def get_queryset(self, request):
        return queries.stock_counts(actor=request.user)

    def has_change_permission(self, request, obj=None):
        return (obj is None or obj.status == "COUNTING") and super().has_change_permission(request, obj)

    def get_readonly_fields(self, request, obj=None):
        editable = {"location", "spare_part", "note"} if obj is None else {"counted_quantity", "note"} if obj.status == "COUNTING" else set()
        return tuple(field.name for field in self.model._meta.concrete_fields if field.name not in editable)

    def get_form(self, request, obj=None, **kwargs):
        if obj and obj.status != "COUNTING":
            return forms.modelform_factory(self.model, fields=[])
        form = super().get_form(request, obj, **kwargs)
        if "units" in form.base_fields:
            if obj is None:
                form.base_fields["units"].widget = forms.HiddenInput()
            else:
                locations = authorized_locations(actor=request.user, permission="inventory.count_stock").values("pk")
                companies = authorized_queryset(user=request.user, permission="inventory.count_stock", queryset=Company.objects.all())
                form.base_fields["units"].queryset = SerializedStockUnit.objects.filter(company_id=obj.company_id, spare_part_id=obj.spare_part_id).filter(
                    Q(current_location_id=obj.location_id, state="IN_STOCK")
                    | Q(state__in=["REGISTERED", "REMOVED"], current_location=None, company_id__in=companies.values("pk"))
                    | Q(state="REMOVED", current_movement__source_id__in=locations)).select_related("spare_part").order_by("identifier")
        return form

    def formfield_for_foreignkey(self, db_field, request, **kwargs):
        if db_field.name == "location":
            kwargs["queryset"] = authorized_locations(actor=request.user, permission="inventory.count_stock").filter(is_active=True).exclude(location_type__in=["CUSTODY", "TRANSIT"])
        if db_field.name == "spare_part":
            kwargs["queryset"] = SparePart.objects.filter(is_active=True, category__is_active=True)
        return super().formfield_for_foreignkey(db_field, request, **kwargs)

    def save_model(self, request, obj, form, change):
        row = (services.record_stock_count(actor=request.user, count=obj, counted_quantity=obj.counted_quantity,
            units=list(form.cleaned_data["units"]), note=obj.note, expected_revision=form.expected_revision) if change
            else services.create_stock_count(actor=request.user, location=obj.location, spare_part=obj.spare_part, note=obj.note))
        _adopt(obj, row)

    def transition(self, request, queryset, action, operation, reason=False):
        rows = list(queryset[:2])
        if len(rows)!=1:
            self.message_user(request, "Select one count to review.", messages.ERROR)
            return None
        row = rows[0]
        payload = [str(row.pk), row.updated_at.isoformat(), action, str(request.user.pk)]
        if request.POST.get("confirm_transition"):
            try:
                if signing.loads(request.POST.get("transition_revision", ""), salt="inventory-count-transition") != payload:
                    raise ValidationError("Count changed; review it again.")
                options = {"reason": request.POST.get("reason", "")} if reason else {}
                result = operation(actor=request.user, count=row, expected_revision=payload[1], **options)
                self.log_change(request, result, action)
                self.message_user(request, "Count operation completed.")
            except (signing.BadSignature, ValidationError, IntegrityError):
                self.message_user(request, "Count operation rejected. Review state, variance, and reservations.", messages.ERROR)
            return None
        return TemplateResponse(request, "admin/inventory/confirm_count_transition.html", {
            **self.admin_site.each_context(request), "opts": self.model._meta, "title": action.replace("_", " ").title(),
            "record": row, "action_name": action, "needs_reason": reason,
            "transition_revision": signing.dumps(payload, salt="inventory-count-transition")})

    @admin.action(description="Start physical count", permissions=["change"])
    def start_selected(self, request, queryset):
        return self.transition(request, queryset, "start_selected", services.start_stock_count)

    @admin.action(description="Approve variance and reconcile count", permissions=["change"])
    def reconcile_selected(self, request, queryset):
        return self.transition(request, queryset, "reconcile_selected", services.reconcile_stock_count)

    @admin.action(description="Cancel unfinished count", permissions=["change"])
    def cancel_selected(self, request, queryset):
        return self.transition(request, queryset, "cancel_selected", services.cancel_stock_count, True)
