from django import forms
from django.contrib import admin, messages
from django.core import signing
from django.core.exceptions import PermissionDenied, ValidationError
from django.db import IntegrityError
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.urls import path, reverse
from django.utils.html import format_html
from apps.inventory.models import PartsDisposition
from apps.service.models import ServiceCase
from .models import ServiceInvoice, QuotationLine
from . import invoice_services as s, invoice_queries as q


class CreationForm(forms.ModelForm):
    service_case = forms.ModelChoiceField(queryset=ServiceCase.objects.none())

    class Meta:
        model = ServiceInvoice
        fields = ("note",)


class AllocationForm(forms.Form):
    consumption = forms.ModelChoiceField(queryset=PartsDisposition.objects.none())
    quotation_line = forms.ModelChoiceField(queryset=QuotationLine.objects.none())
    quantity = forms.IntegerField(min_value=1, max_value=1000000)
    reason = forms.CharField(max_length=2000)

    def __init__(self, *args, invoice, display_choices=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["consumption"].queryset = s.consumption_sources(invoice.service_case)
        self.fields["quotation_line"].queryset = invoice.quotation.lines.filter(is_active=True, kind="PART")
        for name, choices in (display_choices or {}).items():
            self.fields[name].choices = choices


class ConfirmationForm(forms.Form):
    quotation_line = forms.ModelChoiceField(queryset=QuotationLine.objects.none())
    quantity = forms.IntegerField(min_value=1, max_value=1000000)
    reason = forms.CharField(max_length=2000)

    def __init__(self, *args, invoice, display_choices=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["quotation_line"].queryset = invoice.quotation.lines.filter(is_active=True, kind__in=["LABOR", "SERVICE"])
        for name, choices in (display_choices or {}).items():
            self.fields[name].choices = choices


class AdjustmentForm(forms.Form):
    quotation_line = forms.ModelChoiceField(queryset=QuotationLine.objects.none())
    additional_discount = forms.DecimalField(min_value=0, max_digits=14, decimal_places=2)
    reason = forms.CharField(max_length=1000)

    def __init__(self, *args, invoice, display_choices=None, **kwargs):
        super().__init__(*args, **kwargs)
        self.fields["quotation_line"].queryset = invoice.quotation.lines.filter(is_active=True)
        for name, choices in (display_choices or {}).items():
            self.fields[name].choices = choices


@admin.register(ServiceInvoice)
class InvoiceAdmin(admin.ModelAdmin):
    form = CreationForm
    actions = None
    list_display = ("number", "service_case", "status", "currency", "grand_total", "customer_pay_total")
    list_filter = ("status", "currency")

    def get_queryset(self, request):
        return q.service_invoices(actor=request.user)

    def has_delete_permission(self, request, obj=None):
        return False

    def get_readonly_fields(self, request, obj=None):
        return ("workflow", "settlement") + tuple(f.name for f in self.model._meta.concrete_fields) if obj else ()

    def get_fields(self, request, obj=None):
        return self.get_readonly_fields(request, obj) if obj else ("service_case", "note")

    def get_form(self, request, obj=None, **kwargs):
        if obj:
            return forms.modelform_factory(ServiceInvoice, fields=[])
        base = super().get_form(request, obj, **kwargs)
        class ScopedCreation(base):
            def __init__(self, *args, **kw):
                super().__init__(*args, **kw)
                self.fields["service_case"].queryset = q.invoice_cases(actor=request.user, permission=s.MANAGE).filter(
                    status__in=s.DRAFT_STATES, invoice__isnull=True, quotation_family__revisions__is_current=True,
                    quotation_family__revisions__status="APPROVED")
        return ScopedCreation

    def workflow(self, obj):
        return format_html('<a href="{}">Reconcile invoice and inspect provenance</a>', reverse("admin:commercial_invoice_workflow", args=[obj.pk]))

    def settlement(self, obj):
        if obj.status != "FINALIZED":
            return "Finalize before receiving payment."
        return format_html('<a href="{}">Payments, receipts and financial clearance</a>', reverse("admin:commercial_invoice_settlement", args=[obj.pk]))

    def save_model(self, request, obj, form, change):
        if change:
            raise ValidationError("Use the signed invoice workflow.")
        row = s.prepare_service_invoice(actor=request.user, **form.cleaned_data)
        for field in obj._meta.concrete_fields:
            setattr(obj, field.attname, getattr(row, field.attname))
        obj._state = row._state

    def changeform_view(self, request, object_id=None, form_url="", extra_context=None):
        try:
            return super().changeform_view(request, object_id, form_url, extra_context)
        except (ValidationError, IntegrityError):
            self.message_user(request, "Invoice operation rejected; reload and review current state.", messages.ERROR)
            return HttpResponseRedirect(request.path)

    def get_urls(self):
        from .payment_admin import settlement_view
        return [path("<uuid:object_id>/settlement/", self.admin_site.admin_view(settlement_view), name="commercial_invoice_settlement"),
            path("<uuid:object_id>/workflow/", self.admin_site.admin_view(self.workflow_view), name="commercial_invoice_workflow")] + super().get_urls()

    def workflow_view(self, request, object_id):
        row = get_object_or_404(q.service_invoices(actor=request.user), pk=object_id)
        if not self.has_view_or_change_permission(request, row):
            raise PermissionDenied
        token = [str(row.pk), s.revision(row), str(request.user.pk)]
        current_lines = list(q.invoice_lines(actor=request.user, invoice=row))
        current_allocations = list(q.invoice_allocations(actor=request.user, invoice=row))
        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],
        }
        operation = request.POST.get("operation") if request.method == "POST" else None
        sets = {}
        for name, form in (("allocations", AllocationForm), ("confirmations", ConfirmationForm), ("adjustments", AdjustmentForm)):
            factory = forms.formset_factory(form, extra=1, can_delete=True, max_num=1000, validate_max=True, absolute_max=1000)
            example = form(invoice=row)
            choices = {key:list(field.choices) for key, field in example.fields.items() if isinstance(field, forms.ModelChoiceField)}
            sets[name] = factory(request.POST if operation == "reconcile" else None, prefix=name, initial=initial[name], form_kwargs={"invoice": row, "display_choices": choices})
        error = None
        if request.method == "POST":
            if not self.has_change_permission(request, row):
                raise PermissionDenied
            try:
                if signing.loads(request.POST.get("revision_token", ""), salt="invoice-workflow") != token:
                    raise ValidationError("Stale invoice form; reload and review.")
                args = dict(actor=request.user, invoice=row, expected_revision=token[1])
                if operation == "finalize":
                    result = s.finalize_service_invoice(**args)
                elif operation == "rebuild":
                    result = s.prepare_service_invoice(**args, service_case=row.service_case, note=row.note)
                elif operation == "reconcile" and all(formset.is_valid() for formset in sets.values()):
                    inputs = {name: [{k:v for k,v in f.cleaned_data.items() if k != "DELETE"} for f in fs if f.cleaned_data and not f.cleaned_data.get("DELETE")] for name, fs in sets.items()}
                    result = s.reconcile_service_invoice(**args, **inputs, note=request.POST.get("note", ""))
                else:
                    raise ValidationError("Review reconciliation form errors.")
                self.log_change(request, result, f"Invoice workflow: {operation}")
                return HttpResponseRedirect(reverse("admin:commercial_invoice_workflow", args=[row.pk]))
            except (ValidationError, IntegrityError, signing.BadSignature) as exc:
                error = "; ".join(exc.messages) if isinstance(exc, ValidationError) else "Invalid or conflicting invoice submission."
        return TemplateResponse(request, "admin/commercial/invoice_workflow.html", {**self.admin_site.each_context(request),
            "opts": self.model._meta, "title": str(row), "invoice": row, "line_rows": current_lines, "allocation_rows": current_allocations,
            "sources": s.consumption_sources(row.service_case), "history": q.invoice_lines(actor=request.user, invoice=row, include_history=True),
            "quotation_lines": row.quotation.lines.filter(is_active=True), "formsets": sets,
            "revision_token": signing.dumps(token, salt="invoice-workflow"), "error": error}, status=400 if error else 200)
