from __future__ import annotations

import uuid
from decimal import Decimal

from django.db import transaction
from django.utils import timezone

from .models import (
    VerificationActivityLog,
    VerificationClaim,
    VerificationInvoice,
    VerificationInvoiceDocument,
)


def log_activity(
    *,
    claim: VerificationClaim,
    actor,
    action: str,
    message: str,
    invoice: VerificationInvoice | None = None,
) -> None:
    VerificationActivityLog.objects.create(
        claim=claim,
        invoice=invoice,
        actor=actor if getattr(actor, "is_authenticated", False) else None,
        action=action,
        message=message,
    )


def format_changed_labels(form, *, fallback_labels: dict[str, str] | None = None, field_names=None) -> str:
    labels: list[str] = []
    fallback_labels = fallback_labels or {}
    source_field_names = field_names if field_names is not None else form.changed_data

    for field_name in source_field_names:
        label = fallback_labels.get(field_name)
        if not label and field_name in form.fields:
            label = form.fields[field_name].label
        labels.append(label or field_name.replace("_", " ").title())

    return ", ".join(labels)


@transaction.atomic
def save_claim_with_invoices(
    *,
    claim_form,
    invoice_formset,
    actor,
    claim_status: str | None = None,
    autosave_snapshot: dict | None = None,
    log_invoice_changes: bool = True,
):
    claim = claim_form.save(commit=False)
    if claim.pk is None and getattr(actor, "is_authenticated", False):
        claim.created_by = actor
    if getattr(actor, "is_authenticated", False):
        claim.updated_by = actor
    if claim_status is not None:
        claim.status = claim_status
    if autosave_snapshot is not None:
        claim.autosave_snapshot = autosave_snapshot
        claim.last_autosaved_at = timezone.now()
    elif claim_status == VerificationClaim.Status.SUBMITTED:
        claim.autosave_snapshot = {}
    if claim_status == VerificationClaim.Status.DRAFT:
        prepare_draft_claim_defaults(claim=claim)
    claim.save()

    claim_form.save_m2m()

    invoice_formset.save(commit=False)
    deleted_invoices = list(getattr(invoice_formset, "deleted_objects", []))

    active_index = 0
    row_state = []
    for form in invoice_formset.forms:
        if not hasattr(form, "cleaned_data") or not form.cleaned_data or form.cleaned_data.get("DELETE"):
            row_state.append(
                {
                    "form_prefix": form.prefix,
                    "invoice_id": None,
                    "invoice_document_count": 0,
                    "payment_proof_count": 0,
                }
            )
            continue

        if claim_status == VerificationClaim.Status.DRAFT and not _draft_form_has_meaningful_data(form):
            if form.instance.pk:
                form.instance.delete()
            row_state.append(
                {
                    "form_prefix": form.prefix,
                    "invoice_id": None,
                    "invoice_document_count": 0,
                    "payment_proof_count": 0,
                }
            )
            continue

        active_index += 1
        existing_invoice_pk = form.instance.pk
        invoice = form.save(commit=False)
        invoice.claim = claim
        invoice.sort_order = active_index
        _apply_invoice_save_defaults(invoice=invoice, active_index=active_index, claim=claim, claim_status=claim_status)
        invoice.save()

        invoice_files = form.cleaned_data.get("invoice_files") or []
        payment_proof_files = form.cleaned_data.get("payment_proof_files") or []

        if log_invoice_changes and existing_invoice_pk:
            changed_fields = [
                field_name
                for field_name in form.changed_data
                if field_name not in {"invoice_files", "payment_proof_files"}
            ]
            if changed_fields:
                changed = format_changed_labels(form, field_names=changed_fields)
                log_activity(
                    claim=claim,
                    actor=actor,
                    action=VerificationActivityLog.Action.UPDATE,
                    invoice=invoice,
                    message=f"Invoice {invoice.transaction_number} updated: {changed}.",
                )
        elif log_invoice_changes:
            log_activity(
                claim=claim,
                actor=actor,
                action=VerificationActivityLog.Action.CREATE,
                invoice=invoice,
                message=f"Invoice {invoice.transaction_number} created.",
            )

        if invoice_files:
            save_invoice_documents(
                invoice=invoice,
                category=VerificationInvoiceDocument.Category.INVOICE,
                files=invoice_files,
                actor=actor,
            )
        if payment_proof_files:
            save_invoice_documents(
                invoice=invoice,
                category=VerificationInvoiceDocument.Category.PAYMENT_PROOF,
                files=payment_proof_files,
                actor=actor,
            )

        row_state.append(
            {
                "form_prefix": form.prefix,
                "invoice_id": invoice.pk,
                "invoice_document_count": invoice.documents.filter(
                    category=VerificationInvoiceDocument.Category.INVOICE
                ).count(),
                "payment_proof_count": invoice.documents.filter(
                    category=VerificationInvoiceDocument.Category.PAYMENT_PROOF
                ).count(),
                "invoice_documents": [
                    {
                        "name": document.original_name,
                        "url": document.file.url if document.file else "",
                    }
                    for document in invoice.documents.filter(
                        category=VerificationInvoiceDocument.Category.INVOICE
                    ).order_by("-created_at", "-id")
                ],
                "payment_proof_documents": [
                    {
                        "name": document.original_name,
                        "url": document.file.url if document.file else "",
                    }
                    for document in invoice.documents.filter(
                        category=VerificationInvoiceDocument.Category.PAYMENT_PROOF
                    ).order_by("-created_at", "-id")
                ],
            }
        )

    for invoice in deleted_invoices:
        if log_invoice_changes:
            log_activity(
                claim=claim,
                actor=actor,
                action=VerificationActivityLog.Action.DELETE,
                invoice=None,
                message=f"Invoice {invoice.transaction_number} deleted.",
            )
        invoice.delete()

    invoice_formset.save_m2m()
    claim._saved_form_rows = row_state
    return claim


@transaction.atomic
def save_invoice_documents(*, invoice: VerificationInvoice, category: str, files, actor):
    documents = []
    for uploaded_file in files:
        documents.append(
            VerificationInvoiceDocument.objects.create(
                invoice=invoice,
                category=category,
                file=uploaded_file,
                original_name=uploaded_file.name,
                uploaded_by=actor if getattr(actor, "is_authenticated", False) else None,
            )
        )

    if documents:
        category_label = VerificationInvoiceDocument.Category(category).label
        log_activity(
            claim=invoice.claim,
            actor=actor,
            action=VerificationActivityLog.Action.UPDATE,
            invoice=invoice,
            message=f"{len(documents)} {category_label.lower()} document(s) uploaded for invoice {invoice.transaction_number}.",
        )

    return documents


def _draft_form_has_meaningful_data(form) -> bool:
    cleaned = getattr(form, "cleaned_data", {}) or {}
    invoice_files = cleaned.get("invoice_files") or []
    payment_proof_files = cleaned.get("payment_proof_files") or []
    if invoice_files or payment_proof_files:
        return True

    meaningful_values = [
        cleaned.get("goods_services"),
        cleaned.get("transaction_number"),
        cleaned.get("transaction_date"),
        cleaned.get("supplier_trn"),
        cleaned.get("supplier_name"),
        cleaned.get("plot_number"),
        cleaned.get("tos"),
        cleaned.get("description"),
    ]
    if any(str(value or "").strip() for value in meaningful_values):
        return True

    numeric_values = [
        cleaned.get("total_amount_paid"),
        cleaned.get("vat_approved"),
        cleaned.get("vat_paid"),
    ]
    return any(value not in (None, "", Decimal("0"), Decimal("0.00"), 0, 0.0) for value in numeric_values)


def _apply_invoice_save_defaults(*, invoice: VerificationInvoice, active_index: int, claim: VerificationClaim, claim_status: str | None):
    if not invoice.line_item:
        invoice.line_item = str(active_index)

    if claim_status == VerificationClaim.Status.DRAFT and not (invoice.transaction_number or "").strip():
        invoice.transaction_number = f"__draft__{claim.pk or 'new'}_{active_index}"
    elif not invoice.transaction_number:
        invoice.transaction_number = str(active_index)

    if claim_status == VerificationClaim.Status.DRAFT and not invoice.transaction_date:
        invoice.transaction_date = claim.date_received or timezone.localdate()

    if claim_status == VerificationClaim.Status.DRAFT and invoice.total_amount_paid is None:
        invoice.total_amount_paid = Decimal("0.00")
    if claim_status == VerificationClaim.Status.DRAFT and invoice.vat_approved is None:
        invoice.vat_approved = Decimal("0.00")
    if claim_status == VerificationClaim.Status.DRAFT and invoice.vat_paid is None:
        invoice.vat_paid = Decimal("0.00")


def prepare_draft_claim_defaults(*, claim: VerificationClaim):
    if not (claim.claim_reference_number or "").strip():
        claim.claim_reference_number = f"DRAFT-{uuid.uuid4().hex[:10].upper()}"
    if not claim.date_received:
        claim.date_received = timezone.localdate()
    if claim.applicant_name_arabic is None:
        claim.applicant_name_arabic = ""
    if claim.applicant_name_english is None:
        claim.applicant_name_english = ""
    return claim
