"""Case-bound stock operations; authoritative services own all inventory effects."""
from uuid import UUID, uuid4
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
from django.shortcuts import get_object_or_404, render, redirect
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_http_methods
from apps.access.authorization import is_authorized
from apps.parts import queries as compatible
from apps.inventory import services as stock, request_services as rs, usage_services as us
from apps.inventory import queries as iq, request_queries as rq, usage_queries as uq
from apps.inventory.models import PartsRequestLine, SerializedStockUnit
from apps.service.models import ServiceRepairAction
from apps.service.engineer_queries import current_engineer_assignment
from .queries import cases

OPERATIONS={
    "receive":("Receive nonserialized stock","receive_stock"),
    "request":("Request compatible parts","request_parts"),
    "approve":("Approve parts request","approve_parts"),
    "reserve":("Reserve requested parts","reserve_parts"),
    "release":("Release reservation","reserve_parts"),
    "issue":("Issue reserved parts","issue_parts"),
    "consume":("Consume issued parts","consume_parts"),
    "return":("Return unused issued parts","return_parts"),
}


def available(actor,case):
    if not is_authorized(user=actor,permission="inventory.view_stock",target=case.service_center): return []
    assignment=current_engineer_assignment(case)
    return [op for op,(_,permission) in OPERATIONS.items()
        if is_authorized(user=actor,permission="inventory."+permission,target=case.service_center)
        and (op not in ("request","consume") or assignment and assignment.engineer_id==actor.pk)
        and (op not in ("request","approve","reserve","issue","consume") or case.status in ("DIAGNOSED","REPAIRING"))
        and (case.status not in ("CLOSED","CANCELLED","DELIVERED") or op in ("return","release"))]


def compatible_parts(case):
    if case.device.product_variant_id:
        return compatible.compatible_spare_parts_for_variant(case.device.product_variant)
    return compatible.compatible_spare_parts_for_model(case.device.product_model)


def build_form(op,actor,case,data):
    form=forms.Form(data)
    def choice(name,rows,required=True):
        field=forms.ModelChoiceField(queryset=rows,required=required)
        if name=="request": field.label_from_instance=lambda r:f"{r.requested_at:%Y-%m-%d %H:%M} · {r.requester} · {r.status}"
        if name=="line": field.label_from_instance=lambda r:f"{r.spare_part} · requested {r.quantity} · {r.request.requested_at:%Y-%m-%d %H:%M}"
        if name=="reservation": field.label_from_instance=lambda r:f"{r.spare_part} · {r.quantity} · {r.location}"
        if name=="issue": field.label_from_instance=lambda r:f"{r.reservation.spare_part} · issued {r.reservation.quantity}, resolved {r.resolved_quantity} · {r.recipient}"
        if name=="repair_action": field.label_from_instance=lambda r:f"{r.repair_action} · {r.performed_at}"
        form.fields[name]=field
    parts=compatible_parts(case)
    requests=rq.parts_requests(actor=actor).filter(service_case=case)
    reservations=rq.stock_reservations(actor=actor,active_only=True).filter(service_case=case)
    issues=uq.parts_issues(actor=actor,unresolved_only=True).filter(service_case=case)
    locations=iq.authorized_locations(actor=actor,permission="inventory."+OPERATIONS[op][1]).filter(company_id=case.company_id,is_active=True,location_type__in=iq.USABLE_TYPES)
    if op in ("request","receive"): choice("spare_part",parts.filter(serialization_policy="NOT_SERIALIZED") if op=="receive" else parts)
    if op=="approve": choice("request",requests.filter(status="REQUESTED"))
    if op=="reserve": choice("line",PartsRequestLine.objects.filter(request__in=requests.filter(status="APPROVED")).select_related("spare_part","request"))
    if op in ("release","issue"): choice("reservation",reservations)
    if op in ("consume","return"): choice("issue",issues.filter(recipient=actor) if op=="consume" else issues)
    if op=="consume": choice("repair_action",ServiceRepairAction.objects.filter(repair_execution__service_case=case,performed_at__isnull=False,is_active=True))
    if op in ("reserve","receive","return"): choice("location",locations)
    if op in ("reserve","consume","return"):
        visible=iq.authorized_locations(actor=actor).filter(company_id=case.company_id)
        form.fields["units"]=forms.ModelMultipleChoiceField(queryset=SerializedStockUnit.objects.filter(current_location__in=visible),required=False,
            help_text="Select exact serialized units where required. Leave empty for nonserialized parts.")
    if op in ("request","receive","reserve","consume","return"):
        form.fields["quantity"]=forms.IntegerField(min_value=1,max_value=1000000)
    if op in ("receive","release"):
        form.fields["reference" if op=="receive" else "reason"]=forms.CharField(max_length=200)
    form.fields["note"]=forms.CharField(max_length=2000,required=False,widget=forms.Textarea)
    # Sign every displayed record revision and disposition total; selections cannot
    # be silently refreshed between the review and the authoritative operation.
    facts=[str(case.pk),str(actor.pk),op,case.updated_at.isoformat(),
        list(requests.values_list("id","updated_at")),list(reservations.values_list("id","updated_at")),
        [(str(r.pk),r.updated_at.isoformat(),r.resolved_quantity) for r in issues]]
    normalized=str(facts)
    return form,normalized


@never_cache
@login_required(login_url="operations:login")
@require_http_methods(["GET","POST"])
def workflow(request,pk,operation):
    case=get_object_or_404(cases(request.user),pk=pk)
    if operation not in available(request.user,case): raise PermissionDenied
    form,facts=build_form(operation,request.user,case,request.POST if request.method=="POST" else None)
    error=None
    command=str(uuid4())
    if request.method=="POST":
        try:
            payload=signing.loads(request.POST.get("revision",""),salt="operational-parts")
            if payload[0]!=facts: raise ValidationError("Parts evidence changed; reload and review.")
            command=UUID(payload[1])
            if not form.is_valid(): raise ValidationError("Review the form errors.")
            d=form.cleaned_data; actor=request.user; op=operation
            if op=="request": rs.create_parts_request(actor=actor,service_case=case,lines=[dict(spare_part=d["spare_part"],quantity=d["quantity"])],note=d["note"])
            elif op=="receive": stock.receive_stock(actor=actor,destination=d["location"],spare_part=d["spare_part"],quantity=d["quantity"],reference=d["reference"],idempotency_key=command,note=d["note"])
            elif op=="approve": rs.approve_parts_request(actor=actor,request=d["request"],expected_revision=d["request"].updated_at.isoformat())
            elif op=="reserve": rs.reserve_parts(actor=actor,line=d["line"],location=d["location"],quantity=d["quantity"],units=d["units"],expected_request_revision=d["line"].request.updated_at.isoformat())
            elif op=="release": rs.release_reservation(actor=actor,reservation=d["reservation"],reason=d["reason"],expected_revision=d["reservation"].updated_at.isoformat())
            elif op=="issue": us.issue_reserved_parts(actor=actor,reservation=d["reservation"],expected_revision=d["reservation"].updated_at.isoformat(),note=d["note"])
            else:
                args=dict(actor=actor,issue=d["issue"],quantity=d["quantity"],units=d["units"],command_key=command,note=d["note"],
                    expected_revision=f'{d["issue"].updated_at.isoformat()}:{d["issue"].resolved_quantity}')
                if op=="consume": us.consume_issued_parts(**args,repair_action=d["repair_action"])
                else: us.return_unused_parts(**args,destination=d["location"])
        except (ValidationError,signing.BadSignature,IntegrityError,IndexError,TypeError,ValueError) as exc:
            error="; ".join(exc.messages) if isinstance(exc,ValidationError) else "Invalid or conflicting submission. Reload and review."
        else:
            messages.success(request,"Parts operation recorded.")
            return redirect("operations:detail",kind="cases",pk=case.pk)
    evidence=dict(
        parts_requests=rq.parts_requests(actor=request.user).filter(service_case=case).prefetch_related("lines__spare_part"),
        reservations=rq.stock_reservations(actor=request.user).filter(service_case=case).select_related("spare_part","location"),
        parts_issues=uq.parts_issues(actor=request.user).filter(service_case=case),
        stock_positions=[dict(spare_part=part,location=row["location__code"],on_hand=row["on_hand"])
            for part in compatible_parts(case)
            for row in iq.stock_positions_for_part(actor=request.user,spare_part=part) if row["on_hand"]])
    return render(request,"operations/parts_workflow.html",dict(title=OPERATIONS[operation][0],case=case,form=form,
        error=error,revision=signing.dumps([facts,str(command)],salt="operational-parts"),**evidence),
        status=400 if error else 200)
