"""Commercial presentation only; frozen queries own balances and services own writes."""
from urllib.parse import urlencode

from django import forms
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.core.paginator import Paginator
from django.db.models import Exists, OuterRef
from django.http import Http404
from django.shortcuts import get_object_or_404, render
from django.urls import reverse
from django.utils import timezone
from django.views.decorators.cache import never_cache
from django.views.decorators.http import require_GET

from apps.commercial import queries as quotes, invoice_queries as invoices, payment_queries as payments
from apps.commercial.models import ServiceFinancialRelease
from . import queries, commercial_workflows

KINDS = {
    "quotations": ("Quotations", quotes.VIEW),
    "invoices": ("Invoices", invoices.VIEW),
    "payments": ("Payments", payments.VIEW),
    "outstanding": ("Outstanding", payments.VIEW),
    "receipts": ("Receipts", payments.VIEW),
    "releases": ("Due-release history", payments.VIEW),
}
CASE_PATH = {"quotations": "family__service_case", "invoices": "service_case",
             "outstanding": "service_case", "payments": "invoice__service_case",
             "receipts": "payment__invoice__service_case", "releases": "invoice__service_case"}
TIME = {"quotations": "created_at", "invoices": "created_at", "outstanding": "finalized_at",
        "payments": "received_at", "receipts": "issued_at", "releases": "authorized_at"}


def url(kind="overview", pk=None):
    return reverse("operations:commercial_record", args=[kind, pk]) if pk else reverse("operations:commercial_workspace", args=[kind])


def source(actor, kind):
    if kind == "quotations": rows = quotes.service_quotations(actor=actor)
    elif kind == "invoices": rows = invoices.service_invoices(actor=actor)
    elif kind == "payments": rows = payments.service_payments(actor=actor)
    elif kind == "outstanding": rows = payments.unsettled_invoices(actor=actor)
    elif kind == "receipts":
        rows = payments.receipt_history(actor=actor).select_related("payment__invoice__service_case__customer", "payment__reversal__reversed_by")
    elif kind == "releases":
        rows = ServiceFinancialRelease.objects.filter(invoice_id__in=payments.settlement_invoices(actor=actor).values("pk")).select_related("invoice__service_case__customer", "authorized_by")
    else: raise Http404
    path = CASE_PATH[kind]
    return rows.select_related(path + "__service_center").annotate(
        job_visible=Exists(queries.cases(actor).filter(pk=OuterRef(path + "_id"))),
        customer_visible=Exists(queries.customers(actor).filter(pk=OuterRef(path + "__customer_id"))),
        invoice_visible=Exists(invoices.service_invoices(actor=actor).filter(service_case_id=OuterRef(path + "_id"))),
        quotation_visible=Exists(quotes.quotation_cases(actor=actor).filter(pk=OuterRef(path + "_id"))),
        payment_visible=Exists(invoices.invoice_cases(actor=actor, permission=payments.VIEW).filter(pk=OuterRef(path + "_id"))),
    ).order_by("-" + TIME[kind], "-pk")


class Filters(forms.Form):
    reference = forms.CharField(required=False, max_length=100)
    case = forms.UUIDField(required=False, label="Service Job ID")
    start = forms.DateField(required=False, widget=forms.DateInput(attrs={"type": "date"}), label="From local date")
    end = forms.DateField(required=False, widget=forms.DateInput(attrs={"type": "date"}), label="Through local date")

    def clean(self):
        data = super().clean()
        if data.get("start") and data.get("end") and data["start"] > data["end"]:
            raise forms.ValidationError("End date must not precede start date.")
        return data


def case_for(kind, row):
    if kind == "quotations": return row.family.service_case
    if kind in ("invoices", "outstanding"): return row.service_case
    if kind == "receipts": return row.payment.invoice.service_case
    return row.invoice.service_case


def settlement_facts(summary):
    # This serializes the authoritative query result; it does not derive debt.
    clearance = "Financially blocked"
    if summary["financially_clear"]:
        clearance = "No outstanding customer balance" if summary["balance"] == 0 else "Authorized due release; balance remains due"
    return [("Settlement currency", summary["currency"]), ("Customer liability", summary["customer_liability"]),
            ("Posted payment allocations", summary["paid"]), ("Outstanding balance", summary["balance"]),
            ("Settlement state", summary["state"]), ("Financial clearance", clearance)]


def cards(actor, kind, rows):
    rows = list(rows)
    invoice_ids = [row.pk if kind in ("invoices", "outstanding") else row.payment.invoice_id if kind == "receipts" else row.invoice_id
                   for row in rows] if kind != "quotations" else []
    summaries = {row.pk: payments._summary(row) for row in payments.settlement_invoices(actor=actor).filter(pk__in=invoice_ids)} if invoice_ids else {}
    result = []
    for row in rows:
        case = case_for(kind, row)
        facts, links = [("Service center", case.service_center.name)], []
        title = str(row.pk)
        if row.job_visible:
            facts.append(("Service Job", case.job_number))
            links.append((case.job_number, reverse("operations:detail", args=["cases", case.pk])))
        if row.customer_visible:
            facts.append(("Customer", row.customer_snapshot.get("name", "Not recorded") if kind == "receipts" else case.customer.display_name))
            links.append(("Customer record", reverse("operations:detail", args=["customers", case.customer_id])))
        if kind == "quotations":
            title = f"{row.family.number} / revision {row.revision}"
            facts += [("Status", row.status), ("Current revision", row.is_current), ("Currency", row.currency), ("Quoted value", row.grand_total),
                      ("Created", row.created_at), ("Created by", row.created_by), ("Submitted", row.submitted_at), ("Submitted by", row.submitted_by)]
            decision = getattr(row, "decision", None)
            if decision:
                facts += [("Recorded customer decision", decision.outcome), ("Decision recorded", decision.created_at),
                          ("Decision recorded by", decision.recorded_by), ("Decision channel", decision.channel)]
        elif kind in ("invoices", "outstanding"):
            title = row.number
            facts += [("Status", row.status), ("Currency", row.currency), ("Invoice value (not revenue)", row.grand_total),
                      ("Finalized", row.finalized_at)]
            if kind == "invoices":
                facts += [("Prepared", row.created_at), ("Prepared by", row.created_by), ("Finalized by", row.finalized_by)]
        elif kind == "payments":
            title = row.receipt.number
            facts += [("Payment reference", row.reference), ("Payment status", row.status), ("Currency", row.currency),
                      ("Amount recorded", row.amount), ("Method", row.get_method_display()), ("Received", row.received_at),
                      ("Received by", row.received_by), ("Original payment allocation", row.allocation.amount)]
            reversal = getattr(row, "reversal", None)
            if reversal:
                facts += [("Reversed", reversal.reversed_at), ("Reversed by", reversal.reversed_by), ("Reversal reason", reversal.reason)]
            links.append(("Printable receipt", url("receipts", row.receipt.pk)))
        elif kind == "receipts":
            title = row.number
            facts += [("Receiving center (snapshot)", row.center_snapshot.get("name", "Not recorded")),
                      ("Invoice reference (snapshot)", row.invoice_number), ("Payment reference", row.reference),
                      ("Issued", row.issued_at), ("Recorded by", row.issued_by), ("Currency", row.currency),
                      ("Receipt amount", row.amount), ("Method", row.method), ("Payment status now", row.payment.status)]
            if row.payment.status == "VOIDED": facts.append(("Receipt status", "Original receipt retained; payment reversed, not deleted"))
            links.append(("Payment evidence", url("payments", row.payment_id)))
        else:
            title = "Due-release authorization"
            facts += [("Outstanding amount authorized", row.outstanding_amount), ("Currency", row.invoice.currency),
                      ("Authorized", row.authorized_at), ("Authorized by", row.authorized_by), ("Reason", row.reason), ("Reference", row.reference)]
        if kind != "quotations":
            invoice_id = row.pk if kind in ("invoices", "outstanding") else row.payment.invoice_id if kind == "receipts" else row.invoice_id
            if invoice_id in summaries: facts += settlement_facts(summaries[invoice_id])
            if row.invoice_visible and kind not in ("invoices",): links.append(("Invoice", url("invoices", invoice_id)))
        result.append(dict(title=title, facts=facts, links=links, url=url(kind, row.pk)))
    return result


def navigation(request):
    from .context import shell
    flags = shell(request)["workspace"]["capabilities"]
    return [(kind, label, url(kind)) for kind, (label, permission) in KINDS.items() if flags.get((permission, False))]


@never_cache
@login_required(login_url="operations:login")
@require_GET
def workspace(request, kind):
    links = navigation(request)
    permitted = {key for key, _, _ in links}
    if not links or (kind != "overview" and kind not in permitted): raise PermissionDenied
    context = dict(title="Commercial overview" if kind == "overview" else KINDS[kind][0], navigation=links, kind=kind)
    if kind == "overview":
        metrics = []
        for key, state, label in (("quotations", "SUBMITTED", "Current quotations awaiting customer decision"),
                                  ("quotations", "REJECTED", "Current rejected quotations"),
                                  ("invoices", "DRAFT", "Draft invoices awaiting finalization")):
            if key in permitted:
                rows = source(request.user, key).filter(status=state)
                if key == "quotations": rows = rows.filter(is_current=True)
                metrics.append((label, rows.count(), url(key)))
        if "payments" in permitted:
            metrics += [("Finalized invoices with outstanding customer balance", payments.unsettled_invoices(actor=request.user).count(), url("outstanding")),
                        ("Posted payments received today (local date)", payments.service_payments(actor=request.user).filter(status="POSTED", received_at__date=timezone.localdate()).count(), url("payments")),
                        ("Ready-for-delivery invoices lacking financial clearance", payments.settlement_invoices(actor=request.user).filter(service_case__status="READY_FOR_DELIVERY").exclude(pk__in=payments.financially_cleared_delivery_queue(actor=request.user).values("pk")).count(), url("outstanding"))]
        context["metrics"] = metrics
    else:
        form = Filters(request.GET)
        rows = source(request.user, kind)
        if form.is_valid():
            data = form.cleaned_data
            if data.get("case"):
                case = get_object_or_404(queries.cases(request.user), pk=data["case"])
                rows = rows.filter(**{CASE_PATH[kind] + "_id": case.pk})
            if data.get("reference"):
                reference = {"quotations": "family__number", "payments": "reference", "releases": "reference"}.get(kind, "number")
                rows = rows.filter(**{reference + "__icontains": data["reference"]})
            for name, lookup in (("start", "gte"), ("end", "lte")):
                if data.get(name): rows = rows.filter(**{TIME[kind] + "__date__" + lookup: data[name]})
        else: rows = rows.none()
        page = Paginator(rows, 25).get_page(request.GET.get("page"))
        params = request.GET.copy()
        params.pop("page", None)
        context.update(form=form, page=page, cards=cards(request.user, kind, page.object_list), query=params.urlencode())
    return render(request, "operations/commercial_workspace.html", context)


@never_cache
@login_required(login_url="operations:login")
@require_GET
def detail(request, kind, pk):
    if kind not in KINDS: raise Http404
    row = get_object_or_404(source(request.user, kind), pk=pk)
    case = case_for(kind, row)
    card = cards(request.user, kind, [row])[0]
    actions, groups = [], []
    if row.job_visible:
        quote, invoice = commercial_workflows.context(request.user, case)
        # A historical revision never advertises actions on a different revision.
        if kind != "quotations" or row.is_current:
            actions = [("Review: " + commercial_workflows.OPERATIONS[op][0], reverse("operations:commercial_workflow", args=[case.pk, op]))
                       for op in commercial_workflows.available(request.user, case, quote, invoice)]
        from .workflows import permitted
        if permitted(request.user, case, "delivery"):
            actions.append(("Review delivery / handover", reverse("operations:workflow", args=[case.pk, "delivery"])))
    if kind == "quotations" and row.customer_visible:
        decision = getattr(row, "decision", None)
        if decision:
            groups.append(("Customer decision evidence", [[("Recipient", decision.recipient_name),
                ("Reference", decision.reference), ("Note", decision.note)]]))
    if kind == "quotations":
        groups.append(("Recorded quotation lines (up to 100)", [[("Description", line.description), ("Kind", line.kind),
            ("Responsibility", line.responsibility), ("Quantity", line.quantity), ("Unit price", line.unit_price)]
            for line in quotes.quotation_lines(actor=request.user, quotation=row)[:100]]))
        revisions = source(request.user, "quotations").filter(family=row.family).order_by("-revision", "-pk")[:25]
        groups.append(("Retained revisions (latest 25)", [card["facts"] + [("Quotation", card["title"])] for card in cards(request.user, "quotations", revisions)]))
    if kind == "invoices":
        groups.append(("Stored invoice totals", [[(label, getattr(row, field)) for label, field in (
            ("Subtotal", "subtotal"), ("Discount", "discount_total"), ("Tax", "tax_total"), ("Customer liability", "customer_pay_total"),
            ("Warranty covered", "warranty_covered_total"), ("Company covered", "company_covered_total"))]]))
        groups.append(("Current invoice lines (up to 100)", [[("Description", line.description), ("Responsibility", line.responsibility),
            ("Quantity", line.quantity), ("Confirmation", line.confirmation_note)] for line in invoices.invoice_lines(actor=request.user, invoice=row)[:100]]))
        groups.append(("Technical consumption allocations (up to 100; quantities, not payments)", [[("Quantity", allocation.quantity),
            ("Mode", allocation.mode), ("Recorded by", allocation.actor), ("Reason", allocation.reason)]
            for allocation in invoices.invoice_allocations(actor=request.user, invoice=row)[:100]]))
    links = navigation(request)
    if row.job_visible:
        card["links"] += [(label + " for this job", target + "?" + urlencode({"case": case.pk}))
                          for key, label, target in links if (row.quotation_visible if key == "quotations" else
                              row.invoice_visible if key == "invoices" else row.payment_visible)]
    return render(request, "operations/commercial_record.html", dict(title=card["title"], card=card,
        actions=actions, groups=groups, navigation=links, kind=kind, printable=kind == "receipts"))
