"""Scoped commercial forms calling the frozen quotation, invoice and payment APIs."""
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.commercial import services as qs, queries as qq, invoice_services as ins, invoice_queries as iq
from apps.commercial import payment_services as ps, payment_queries as pq
from apps.commercial.admin import MetadataForm, LineForm, DecisionForm
from apps.parts import queries as compatible
from apps.parts.models import SparePart
from apps.commercial.invoice_admin import AllocationForm, ConfirmationForm, AdjustmentForm
from apps.commercial.payment_admin import ReceiveForm, ReleaseForm, ReverseForm
from .queries import cases


OPERATIONS = {
    "quote-create": ("Create quotation", "manage_servicequotation", "view_servicequotation", MetadataForm),
    "quote-lines": ("Edit quotation lines", "manage_servicequotation", "view_servicequotation", None),
    "quote-submit": ("Submit quotation", "manage_servicequotation", "view_servicequotation", forms.Form),
    "quote-decision": ("Record customer decision", "record_quotationdecision", "view_servicequotation", DecisionForm),
    "quote-revise": ("Create replacement quotation revision", "manage_servicequotation", "view_servicequotation", forms.Form),
    "invoice-prepare": ("Prepare invoice", "manage_serviceinvoice", "view_serviceinvoice", forms.Form),
    "invoice-reconcile": ("Reconcile invoice", "manage_serviceinvoice", "view_serviceinvoice", None),
    "invoice-finalize": ("Finalize invoice", "finalize_serviceinvoice", "view_serviceinvoice", forms.Form),
    "payment-receive": ("Receive payment", "receive_servicepayment", "view_servicepayment", ReceiveForm),
    "payment-release": ("Authorize outstanding-balance delivery", "authorize_due_release", "view_servicepayment", ReleaseForm),
    "payment-reverse": ("Reverse payment", "reverse_servicepayment", "view_servicepayment", ReverseForm),
}


def context(actor, case):
    quote=qq.service_quotations(actor=actor).filter(family__service_case=case,is_current=True).first()
    invoice=iq.service_invoices(actor=actor).filter(service_case=case).first()
    return quote,invoice


def available(actor,case,quote,invoice):
    candidates=[]
    if quote is None and case.status in ("DIAGNOSED","REPAIRING"): candidates.append("quote-create")
    if quote:
        if quote.status=="DRAFT": candidates.extend(("quote-lines","quote-submit"))
        if quote.status=="SUBMITTED": candidates.append("quote-decision")
        if quote.status in ("APPROVED","REJECTED","SUBMITTED") and case.status in ("DIAGNOSED","REPAIRING"):
            candidates.append("quote-revise")
        if quote.status=="APPROVED" and invoice is None and case.status in ins.DRAFT_STATES:
            candidates.append("invoice-prepare")
    if invoice:
        if invoice.status=="DRAFT": candidates.extend(("invoice-reconcile","invoice-finalize"))
        else: candidates.extend(("payment-receive","payment-release","payment-reverse"))
    return [op for op in candidates if all(is_authorized(user=actor,permission="commercial."+p,target=case.service_center)
        for p in OPERATIONS[op][1:3])]


def forms_for(op,data,case,quote,invoice,actor):
    cls=OPERATIONS[op][3]
    if cls:
        form=cls(data)
        if op=="payment-reverse":
            form.fields["payment"].queryset=pq.payments_for_invoice(actor=actor,invoice=invoice).filter(status="POSTED")
        return form,[]
    groups=[]
    if op=="quote-lines":
        initial=[{field:getattr(line,field) for field in LineForm.base_fields} for line in quote.lines.filter(is_active=True)]
        # _part_valid refuses parts that are not compatible with this device, so
        # offer only those plus the ones this draft already priced.
        parts=compatible.compatible_spare_parts_for_variant(case.device.product_variant) if case.device.product_variant_id else compatible.compatible_spare_parts_for_model(case.device.product_model)
        parts=parts | SparePart.objects.filter(pk__in=quote.lines.filter(is_active=True).values("spare_part_id"))
        choices={name:list(LineForm(service_case=case).fields[name].choices) for name in ("repair_action","finding")}
        choices["spare_part"]=[(part.pk,str(part)) for part in parts]
        Set=forms.formset_factory(LineForm,extra=0,can_delete=True,max_num=100,validate_max=True)
        # One spare blank line is always rendered. Seed it with explicit empty
        # initials so an untouched row is recognised as empty instead of failing
        # required-field validation; a completed row still states every value.
        groups.append(("Quotation lines",Set(data,initial=initial+[dict.fromkeys(LineForm.base_fields)],
            prefix="lines",form_kwargs={"service_case":case,"display_choices":choices})))
    else:
        # Reconciliation replaces the complete draft set, so prefill it with the
        # current evidence exactly as the trusted Admin does; an empty
        # submission would silently discard the prepared allocations.
        current_lines=list(iq.invoice_lines(actor=actor,invoice=invoice))
        current_allocations=list(iq.invoice_allocations(actor=actor,invoice=invoice))
        initial={
            "allocations":[dict(consumption=a.consumption_id,quotation_line=a.line.quotation_line_id,quantity=a.quantity,
                reason=a.reason or "Confirm previously automatic allocation") for a in current_allocations],
            "confirmations":[dict(quotation_line=l.quotation_line_id,quantity=l.quantity,reason=l.confirmation_note) for l in current_lines if l.kind!="PART"],
            "adjustments":[dict(quotation_line=l.quotation_line_id,additional_discount=l.additional_discount,reason=l.confirmation_note[:1000]) for l in current_lines if l.additional_discount],
        }
        for name,cls in (("allocations",AllocationForm),("confirmations",ConfirmationForm),("adjustments",AdjustmentForm)):
            Set=forms.formset_factory(cls,extra=1,can_delete=True,max_num=1000,validate_max=True,absolute_max=1000)
            example=cls(invoice=invoice)
            choices={key:list(field.choices) for key,field in example.fields.items() if isinstance(field,forms.ModelChoiceField)}
            groups.append((name.title(),Set(data,prefix=name,initial=initial[name],form_kwargs={"invoice":invoice,"display_choices":choices})))
    form=forms.Form(data)
    if op=="invoice-reconcile":
        form.fields["note"]=forms.CharField(required=False,max_length=4000,widget=forms.Textarea,label="Reconciliation note")
    return form,groups


@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)
    quote,invoice=context(request.user,case)
    if operation not in available(request.user,case,quote,invoice): raise PermissionDenied
    payment_state=pq.invoice_settlement_summary(actor=request.user,invoice=invoice) if operation.startswith("payment-") else None
    token=[str(case.pk),str(request.user.pk),operation,case.updated_at.isoformat(),
        qs.revision(quote) if quote else None,ins.revision(invoice) if invoice else None,
        payment_state["revision"] if payment_state else None]
    form,groups=forms_for(operation,request.POST if request.method=="POST" else None,case,quote,invoice,request.user)
    error=None
    if request.method=="POST":
        try:
            if signing.loads(request.POST.get("revision",""),salt="operational-commercial")!=token:
                raise ValidationError("Commercial evidence changed; reload and review.")
            if not form.is_valid() or not all(group.is_valid() for _,group in groups):
                raise ValidationError("Review the form errors.")
            actor=request.user
            qa=dict(actor=actor,quotation=quote,expected_revision=token[4])
            ia=dict(actor=actor,invoice=invoice,expected_revision=token[5])
            pa=dict(actor=actor,invoice=invoice,expected_revision=token[6])
            if operation=="quote-create": qs.create_service_quotation(actor=actor,service_case=case,**form.cleaned_data)
            elif operation=="quote-lines":
                lines=[{k:v for k,v in f.cleaned_data.items() if k!="DELETE"} for f in groups[0][1] if f.cleaned_data and not f.cleaned_data.get("DELETE")]
                qs.set_quotation_lines(**qa,lines=lines)
            elif operation=="quote-submit": qs.submit_service_quotation(**qa)
            elif operation=="quote-decision": qs.record_quotation_decision(**qa,**form.cleaned_data)
            elif operation=="quote-revise": qs.revise_service_quotation(**qa)
            elif operation=="invoice-prepare": ins.prepare_service_invoice(actor=actor,service_case=case)
            elif operation=="invoice-reconcile":
                values={group.prefix:[{k:v for k,v in f.cleaned_data.items() if k!="DELETE"} for f in group
                    if f.cleaned_data and not f.cleaned_data.get("DELETE")] for _,group in groups}
                ins.reconcile_service_invoice(**ia,**values,note=form.cleaned_data.get("note",""))
            elif operation=="invoice-finalize": ins.finalize_service_invoice(**ia)
            elif operation=="payment-receive": ps.receive_service_payment(**pa,**form.cleaned_data)
            elif operation=="payment-release": ps.authorize_due_release(**pa,**form.cleaned_data)
            else: ps.reverse_service_payment(actor=actor,expected_revision=token[6],**form.cleaned_data)
        except (ValidationError,signing.BadSignature,IntegrityError) as exc:
            error="; ".join(exc.messages) if isinstance(exc,ValidationError) else "Invalid or conflicting submission. Reload and review."
        else:
            messages.success(request,"Commercial operation recorded.")
            return redirect("operations:detail",kind="cases",pk=case.pk)
    return render(request,"operations/commercial_workflow.html",dict(title=OPERATIONS[operation][0],case=case,
        form=form,groups=groups,quote=quote,invoice=invoice,payment_state=payment_state,error=error,
        destructive=operation == "payment-reverse",
        revision=signing.dumps(token,salt="operational-commercial")),status=400 if error else 200)
