"""Confirmation forms delegating every write to frozen inventory services."""
from django import forms
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core import signing
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import IntegrityError, transaction
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_http_methods
from apps.inventory import document_services as documents, control_services as control, request_services
from apps.inventory.queries import authorized_locations
from apps.inventory.models import SerializedStockUnit
from apps.parts.models import SparePart
from . import inventory_workspace as workspace, queries

PERMISSIONS = {"receipts": "receive_stock", "transfers": "transfer_stock", "counts": "count_stock", "requests": "approve_parts"}
LABELS = {"create": "Create draft", "lines": "Replace draft lines", "post": "Post receipt", "dispatch": "Dispatch transfer",
          "receive": "Receive dispatched transfer", "cancel": "Cancel draft / count", "start": "Start physical count",
          "record": "Record observed count", "reconcile": "Reconcile count and post variance", "approve": "Approve request", "reject": "Reject request"}


def action_url(kind, operation, pk=None):
    return reverse("operations:inventory_action", args=[kind, pk, operation]) if pk else reverse("operations:inventory_create", args=[kind])


def locations(actor, kind):
    return authorized_locations(actor=actor, permission="inventory." + PERMISSIONS[kind]).filter(
        pk__in=authorized_locations(actor=actor).values("pk"))


def create_links(actor, kind):
    if kind not in ("receipts", "transfers", "counts"): return []
    if not locations(actor, kind).filter(is_active=True).exclude(location_type__in=["TRANSIT", "CUSTODY"]).exists(): return []
    return [("Create draft", action_url(kind, "create"))]


def allowed(actor, kind, row):
    if kind == "requests":
        if not queries.cases(actor).filter(pk=row.service_case_id).exists(): return []
        if not queries.centers(actor, "inventory.approve_parts").filter(pk=row.service_case.service_center_id).exists(): return []
        return ["approve", "reject"] if row.status == "REQUESTED" else []
    ids = [row.source_id, row.destination_id] if kind == "transfers" else [row.destination_id] if kind == "receipts" else [row.location_id]
    if locations(actor, kind).filter(pk__in=ids).count() != len(set(ids)): return []
    if kind in ("receipts", "transfers"):
        if row.status == "DRAFT": return ["lines", "post" if kind == "receipts" else "dispatch", "cancel"]
        return ["receive"] if kind == "transfers" and row.status == "DISPATCHED" else []
    choices = ["start", "cancel"] if row.status == "DRAFT" else ["record", "cancel"] if row.status == "COUNTING" else []
    if row.status == "COUNTING" and row.counted_quantity is not None and authorized_locations(actor=actor, permission="inventory.adjust_stock").filter(pk=row.location_id).exists():
        choices.append("reconcile")
    return choices


def record_links(actor, kind, row):
    links = [("Review " + LABELS[op].lower(), action_url(kind, op, row.pk)) for op in allowed(actor, kind, row)]
    if kind == "requests" and queries.cases(actor).filter(pk=row.service_case_id).exists():
        from .parts_workflows import available, OPERATIONS
        for op in available(actor, row.service_case):
            if op in ("reserve", "issue", "release", "consume", "return"):
                links.append(("Review " + OPERATIONS[op][0].lower(), reverse("operations:parts_workflow", args=[row.service_case_id, op])))
    return links


def build_forms(actor, kind, operation, row, data):
    form = forms.Form(data)
    lines = None
    parts = SparePart.objects.filter(is_active=True, category__is_active=True).select_related("category")
    if operation == "create":
        physical = locations(actor, kind).filter(is_active=True).exclude(location_type__in=["TRANSIT", "CUSTODY"])
        for key in (["source", "destination"] if kind == "transfers" else ["destination"] if kind == "receipts" else ["location"]):
            form.fields[key] = workspace.LocationChoice(queryset=physical)
        if kind == "counts": form.fields["spare_part"] = forms.ModelChoiceField(queryset=parts)
        if kind == "receipts": form.fields["external_reference"] = forms.CharField(max_length=128, required=False)
        form.fields["note"] = forms.CharField(required=False, max_length=2000, widget=forms.Textarea)
    elif operation == "lines":
        # Full explicit replacement, just as in the existing document Admin.
        # An empty list is allowed for a draft; posting still requires valid lines.
        row = workspace.documents.goods_receipt_detail(actor=actor, receipt=row) if kind == "receipts" else workspace.documents.stock_transfer_detail(actor=actor, transfer=row)
        current = [line for line in row.lines.all() if line.is_active]
        parts = SparePart.objects.filter(pk__in=[line.spare_part_id for line in current]) | parts
        parts = parts.select_related("category")
        part_choices = [(p.pk, str(p)) for p in parts]
        units = SerializedStockUnit.objects.filter(current_location_id=row.source_id, state="IN_STOCK").select_related("spare_part") if kind == "transfers" else SerializedStockUnit.objects.none()
        unit_choices = [(u.pk, str(u)) for u in units]
        class Line(forms.Form):
            spare_part = forms.ModelChoiceField(queryset=parts)
            quantity = forms.IntegerField(min_value=1, max_value=1000000000)
            identifiers = forms.CharField(required=False, widget=forms.Textarea(attrs={"rows": 3}), help_text="One serialized identifier per line; draft text does not create stock.")
            selected_units = forms.ModelMultipleChoiceField(queryset=units, required=False)
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                self.fields["spare_part"].choices = [("", "---------"), *part_choices]
                if kind == "receipts": self.fields.pop("selected_units")
                else:
                    self.fields.pop("identifiers")
                    self.fields["selected_units"].choices = unit_choices
        initial = [dict(spare_part=line.spare_part_id, quantity=line.quantity,
            **({"identifiers": "\n".join(r.identifier for r in line.identifiers.all() if r.is_active)} if kind == "receipts" else
               {"selected_units": [r.unit_id for r in line.units.all() if r.is_active]})) for line in current]
        Set = forms.formset_factory(Line, extra=1, can_delete=True, max_num=100, validate_max=True)
        lines = Set(data, initial=initial, prefix="lines")
    elif operation in ("cancel", "reject"):
        form.fields["reason"] = forms.CharField(max_length=500, widget=forms.Textarea)
    elif operation == "record":
        form.fields["counted_quantity"] = forms.IntegerField(min_value=0, max_value=1000000000)
        form.fields["note"] = forms.CharField(required=False, max_length=2000, widget=forms.Textarea)
        # Conservative selection: present identities currently at this physical
        # location. Reintroducing removed/registered units stays in existing Admin.
        form.fields["units"] = forms.ModelMultipleChoiceField(queryset=SerializedStockUnit.objects.filter(
            current_location_id=row.location_id, spare_part_id=row.spare_part_id).select_related("spare_part"), required=False)
        form.initial.update(counted_quantity=row.counted_quantity, note=row.note,
            units=list(row.units.filter(counted=True).values_list("unit_id", flat=True)))
    return form, lines


def perform(actor, kind, operation, row, data, line_forms):
    if operation == "create":
        function = {"receipts": documents.create_goods_receipt, "transfers": documents.create_stock_transfer, "counts": control.create_stock_count}[kind]
        return function(actor=actor, **data)
    args = dict(actor=actor, expected_revision=row.updated_at.isoformat())
    args[{"receipts": "receipt", "transfers": "transfer", "counts": "count", "requests": "request"}[kind]] = row
    if operation == "lines":
        args["lines"] = [dict(spare_part=f.cleaned_data["spare_part"], quantity=f.cleaned_data["quantity"],
            **({"identifiers": [s.strip() for s in f.cleaned_data.get("identifiers", "").splitlines() if s.strip()]} if kind == "receipts" else
               {"units": list(f.cleaned_data.get("selected_units", []))})) for f in line_forms if f.cleaned_data and not f.cleaned_data.get("DELETE")]
        function = documents.set_goods_receipt_lines if kind == "receipts" else documents.set_stock_transfer_lines
    else:
        functions = {("receipts", "post"): documents.post_goods_receipt, ("receipts", "cancel"): documents.cancel_goods_receipt,
            ("transfers", "dispatch"): documents.dispatch_stock_transfer, ("transfers", "receive"): documents.receive_stock_transfer,
            ("transfers", "cancel"): documents.cancel_stock_transfer, ("counts", "start"): control.start_stock_count,
            ("counts", "record"): control.record_stock_count, ("counts", "cancel"): control.cancel_stock_count,
            ("counts", "reconcile"): control.reconcile_stock_count, ("requests", "approve"): request_services.approve_parts_request,
            ("requests", "reject"): request_services.reject_parts_request}
        function = functions[kind, operation]
        args.update(data)
        if "units" in args: args["units"] = list(args["units"])
    return function(**args)


@never_cache
@login_required(login_url="operations:login")
@require_http_methods(["GET", "POST"])
def action(request, kind, operation="create", pk=None):
    workspace.gate(request.user)
    if kind not in PERMISSIONS or operation not in LABELS: raise PermissionDenied
    row = get_object_or_404(workspace.source(request.user, kind), pk=pk) if pk else None
    if row:
        if operation not in allowed(request.user, kind, row): raise PermissionDenied
    elif operation != "create" or not create_links(request.user, kind): raise PermissionDenied
    payload = [str(request.user.pk), kind, operation, str(pk), row.updated_at.isoformat() if row else None]
    form, lines = build_forms(request.user, kind, operation, row, request.POST if request.method == "POST" else None)
    error = None
    if request.method == "POST":
        try:
            if signing.loads(request.POST.get("revision", ""), salt="inventory-workspace", max_age=3600) != payload:
                raise ValidationError("Evidence changed. Reload and review before confirming.")
            if not form.is_valid() or (lines is not None and not lines.is_valid()):
                raise ValidationError("Review the field errors below.")
            with transaction.atomic():
                current = perform(request.user, kind, operation, row, form.cleaned_data, lines)
            messages.success(request, "Inventory operation recorded.")
            return redirect("operations:inventory_record", kind=kind, pk=current.pk)
        except (ValidationError, IntegrityError, signing.BadSignature) as exc:
            error = " ".join(exc.messages) if isinstance(exc, ValidationError) else "Operation rejected. Reload and review the current evidence."
    return render(request, "operations/inventory_action.html", dict(title=LABELS[operation] + "  /  " + workspace.KINDS[kind],
        row=row, form=form, lines=lines, error=error, revision=signing.dumps(payload, salt="inventory-workspace"),
        back=workspace.url(kind, pk) if pk else workspace.url(kind)), status=400 if error else 200)


@never_cache
@login_required(login_url="operations:login")
@require_http_methods(["GET", "POST"])
def adjust(request, pk):
    """Single-position confirmation using the existing revision and command key."""
    import uuid
    from apps.inventory.models import StockAdjustment
    workspace.gate(request.user)
    row = get_object_or_404(workspace.source(request.user, "positions"), pk=pk)
    if not authorized_locations(actor=request.user, permission="inventory.adjust_stock").filter(pk=row.location_id).exists():
        raise PermissionDenied
    if row.location.location_type in ("TRANSIT", "CUSTODY") or not row.location.is_active or not row.spare_part.is_active:
        raise PermissionDenied
    form = forms.Form(request.POST if request.method == "POST" else None)
    form.fields["quantity_delta"] = forms.IntegerField(min_value=-1000000000, max_value=1000000000)
    form.fields["reason"] = forms.ChoiceField(choices=[(key, label) for key, label in StockAdjustment.Reason.choices if key != "COUNT_VARIANCE"])
    form.fields["reference"] = forms.CharField(max_length=128)
    form.fields["note"] = forms.CharField(max_length=2000, widget=forms.Textarea)
    form.fields["units"] = forms.ModelMultipleChoiceField(queryset=SerializedStockUnit.objects.filter(
        current_location_id=row.location_id, spare_part_id=row.spare_part_id, state="IN_STOCK").select_related("spare_part"), required=False,
        help_text="Select identities for a negative adjustment. Reintroducing removed/registered identities remains in the existing guarded Admin.")
    payload = dict(actor=str(request.user.pk), position=str(row.pk), revision=control.position_revision(row.location, row.spare_part), command=str(uuid.uuid4()))
    error = None
    if request.method == "POST":
        try:
            submitted = signing.loads(request.POST.get("revision", ""), salt="inventory-position", max_age=3600)
            if not isinstance(submitted, dict) or any(submitted.get(key) != payload[key] for key in ("actor", "position", "revision")):
                raise ValidationError("Stock changed. Reload and review the adjustment.")
            if not form.is_valid():
                raise ValidationError("Review the field errors below.")
            with transaction.atomic():
                control.adjust_stock(actor=request.user, location=row.location, spare_part=row.spare_part,
                    command_key=uuid.UUID(submitted["command"]), expected_revision=submitted["revision"], **form.cleaned_data)
            messages.success(request, "Stock adjustment posted.")
            return redirect(workspace.url("adjustments"))
        except (ValidationError, IntegrityError, signing.BadSignature) as exc:
            error = " ".join(exc.messages) if isinstance(exc, ValidationError) else "Operation rejected. Reload and review current evidence."
    return render(request, "operations/inventory_action.html", dict(title="Post stock adjustment", form=form, row=row,
        error=error, revision=signing.dumps(payload, salt="inventory-position"), back=workspace.url("positions"), adjustment=True), status=400 if error else 200)
