from __future__ import annotations

import csv
import json
import mimetypes
from urllib.parse import urlencode

from django.contrib import messages
from django.contrib.auth import get_user_model
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from django.db.models import Count, Prefetch, Q, Sum
from django.http import FileResponse, HttpResponse, JsonResponse
from django.urls import reverse
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.decorators.http import require_GET, require_http_methods, require_POST
from django.utils.dateparse import parse_date
from django.utils.datastructures import MultiValueDict
from django.utils import timezone

from accounts.decorators import super_admin_required
from .forms import (
    DraftVerificationClaimForm,
    DraftVerificationInvoiceFormSet,
    VerificationClaimForm,
    VerificationDescriptionOptionForm,
    VerificationInvoiceInlineEditForm,
    VerificationInvoiceDocumentBundleUploadForm,
    VerificationInvoiceDocumentUploadForm,
    VerificationInvoiceFormSet,
)
from .exports import ClaimExportError, build_claim_export
from .models import (
    VerificationActivityLog,
    VerificationClaim,
    VerificationDescriptionOption,
    VerificationInvoice,
    VerificationInvoiceDocument,
)
from .permissions import accessible_claim_queryset
from .services import (
    format_changed_labels,
    log_activity,
    save_claim_with_invoices,
    save_invoice_documents,
)

User = get_user_model()


def _active_description_option_labels() -> list[str]:
    return list(
        VerificationDescriptionOption.objects.filter(is_active=True)
        .order_by("sort_order", "label", "id")
        .values_list("label", flat=True)
    )


def _claim_queryset():
    return VerificationClaim.objects.filter(status=VerificationClaim.Status.SUBMITTED).annotate(
        dashboard_invoice_count=Count("invoices", distinct=True),
        dashboard_total_amount_paid=Sum("invoices__total_amount_paid"),
        dashboard_total_vat_approved=Sum("invoices__vat_approved"),
        dashboard_total_vat_paid=Sum("invoices__vat_paid"),
        dashboard_document_count=Count("invoices__documents", distinct=True),
        dashboard_invoice_document_count=Count(
            "invoices__documents",
            filter=Q(invoices__documents__category=VerificationInvoiceDocument.Category.INVOICE),
            distinct=True,
        ),
        dashboard_payment_proof_count=Count(
            "invoices__documents",
            filter=Q(invoices__documents__category=VerificationInvoiceDocument.Category.PAYMENT_PROOF),
            distinct=True,
        ),
    ).select_related("created_by", "updated_by").prefetch_related(
        Prefetch("activity_logs", queryset=VerificationActivityLog.objects.only("id", "action", "claim_id")),
        Prefetch(
            "invoices",
            queryset=VerificationInvoice.objects.only(
                "id",
                "claim_id",
                "transaction_number",
                "line_item",
                "sort_order",
            )
            .order_by("sort_order", "id")
            .prefetch_related(
                Prefetch(
                    "documents",
                    queryset=VerificationInvoiceDocument.objects.only("id", "invoice_id", "category"),
                )
            ),
        ),
    )


def _draft_claim_queryset():
    return VerificationClaim.objects.filter(status=VerificationClaim.Status.DRAFT).annotate(
        draft_invoice_count=Count("invoices", distinct=True),
        draft_document_count=Count("invoices__documents", distinct=True),
    ).select_related("created_by", "updated_by")


def _user_draft_queryset(user):
    if not getattr(user, "is_authenticated", False):
        return _draft_claim_queryset().none()
    return _draft_claim_queryset().filter(Q(created_by=user) | Q(updated_by=user))


def _document_badge_meta(filename: str) -> tuple[str, str]:
    extension = filename.rsplit(".", 1)[-1].lower() if "." in filename else ""
    if extension == "pdf":
        return "PDF", "bi-filetype-pdf"
    if extension in {"jpg", "jpeg", "png"}:
        return "IMG", "bi-file-earmark-image"
    return (extension.upper() or "FILE"), "bi-file-earmark"


def _review_badge_meta(status: str) -> tuple[str, str, str]:
    if status == VerificationInvoiceDocument.ReviewStatus.APPROVED:
        return "Approved", "bi-check-circle-fill", "is-approved"
    if status == VerificationInvoiceDocument.ReviewStatus.DECLINED:
        return "Declined", "bi-x-circle-fill", "is-declined"
    return "Pending", "bi-dash-circle-fill", "is-pending"


def _document_collection_review_status(documents: list[VerificationInvoiceDocument]) -> str:
    if not documents:
        return VerificationInvoiceDocument.ReviewStatus.PENDING

    statuses = {document.review_status for document in documents}
    if VerificationInvoiceDocument.ReviewStatus.DECLINED in statuses:
        return VerificationInvoiceDocument.ReviewStatus.DECLINED
    if statuses == {VerificationInvoiceDocument.ReviewStatus.APPROVED}:
        return VerificationInvoiceDocument.ReviewStatus.APPROVED
    return VerificationInvoiceDocument.ReviewStatus.PENDING


def _document_collection_review_reason_label(documents: list[VerificationInvoiceDocument]) -> str:
    declined_documents = [
        document
        for document in documents
        if document.review_status == VerificationInvoiceDocument.ReviewStatus.DECLINED and document.review_reason
    ]
    if not declined_documents:
        return ""

    declined_documents.sort(key=lambda document: (document.reviewed_at or timezone.now(), document.pk or 0), reverse=True)
    return declined_documents[0].get_review_reason_display()


def _invoice_review_summary(invoice: VerificationInvoice) -> dict[str, str]:
    invoice_status = _document_collection_review_status(getattr(invoice, "invoice_documents", []))
    proof_status = _document_collection_review_status(getattr(invoice, "payment_proof_documents", []))
    invoice_reason_label = _document_collection_review_reason_label(getattr(invoice, "invoice_documents", []))
    proof_reason_label = _document_collection_review_reason_label(getattr(invoice, "payment_proof_documents", []))

    invoice_label, invoice_icon, invoice_class = _review_badge_meta(invoice_status)
    proof_label, proof_icon, proof_class = _review_badge_meta(proof_status)

    return {
        "invoice_status": invoice_status,
        "invoice_label": invoice_label,
        "invoice_icon": invoice_icon,
        "invoice_class": invoice_class,
        "invoice_reason_label": invoice_reason_label,
        "proof_status": proof_status,
        "proof_label": proof_label,
        "proof_icon": proof_icon,
        "proof_class": proof_class,
        "proof_reason_label": proof_reason_label,
    }


def _serialize_invoice_edit_state(invoice: VerificationInvoice) -> dict[str, str]:
    return {
        "goods_services": invoice.goods_services or "",
        "transaction_type": invoice.transaction_type or "",
        "transaction_number": invoice.transaction_number or "",
        "transaction_date_value": invoice.transaction_date.strftime("%Y-%m-%d") if invoice.transaction_date else "",
        "transaction_date_label": invoice.transaction_date.strftime("%d %b %Y") if invoice.transaction_date else "",
        "supplier_trn": invoice.supplier_trn or "",
        "supplier_name": invoice.supplier_name or "",
        "plot_number": invoice.plot_number or "-",
        "tos": invoice.get_tos_display() if invoice.tos else "-",
        "total_amount_paid": f"{invoice.total_amount_paid:.2f}",
        "vat_amount": f"{invoice.vat_amount:.2f}",
        "vat_approved": f"{invoice.vat_approved:.2f}",
        "vat_paid": f"{invoice.vat_paid:.2f}",
        "description": invoice.description or "-",
    }


def _review_status_badge_class(status_class: str) -> str:
    return f"verification-review-badge verification-review-badge--{status_class}"


def _document_review_meta_text(document: VerificationInvoiceDocument) -> str:
    if not document.reviewed_at:
        return "Pending review"

    reviewed_by_label = "System"
    if document.reviewed_by:
        reviewed_by_label = document.reviewed_by.get_full_name() or document.reviewed_by.email

    meta_text = (
        f"{document.review_label} "
        f"{timezone.localtime(document.reviewed_at).strftime('%d %b %Y %H:%M')} · {reviewed_by_label}"
    )
    if document.review_status == VerificationInvoiceDocument.ReviewStatus.DECLINED and document.review_reason:
        meta_text = f"{meta_text} · {document.get_review_reason_display()}"
    return meta_text


def _claim_detail_redirect_url(claim_id: int, invoice_id: int) -> str:
    return f"{reverse('verification:claim_detail', kwargs={'pk': claim_id})}#invoice-documents-{invoice_id}"


def _claim_detail_queryset():
    return VerificationClaim.objects.prefetch_related(
        Prefetch(
            "invoices",
            queryset=VerificationInvoice.objects.order_by("sort_order", "id").prefetch_related(
                Prefetch(
                    "documents",
                    queryset=VerificationInvoiceDocument.objects.select_related("uploaded_by", "reviewed_by").order_by(
                        "category", "-created_at", "-id"
                    ),
                )
            ),
        ),
        Prefetch(
            "activity_logs",
            queryset=VerificationActivityLog.objects.select_related("actor", "invoice").order_by("-created_at"),
        ),
    ).select_related("created_by", "updated_by")


def _claim_form_field_names() -> list[str]:
    return [
        "claim_reference_number",
        "date_received",
        "applicant_name_arabic",
        "applicant_name_english",
        "first_document_request_date",
        "second_reminder_date",
        "final_report_without_missing_docs_date",
        "initial_report_with_missing_docs_date",
        "followup_reminder_date",
        "final_report_submission_date",
    ]


def _invoice_form_field_names() -> list[str]:
    return [
        "goods_services",
        "transaction_type",
        "transaction_number",
        "transaction_date",
        "supplier_trn",
        "supplier_name",
        "plot_number",
        "tos",
        "total_amount_paid",
        "vat_approved",
        "vat_paid",
        "description",
    ]


def _build_autosave_snapshot(request, *, prefix: str = "invoices") -> dict:
    claim_snapshot = {
        field_name: (request.POST.get(field_name) or "").strip()
        for field_name in _claim_form_field_names()
    }

    try:
        total_forms = int(request.POST.get(f"{prefix}-TOTAL_FORMS") or 0)
    except (TypeError, ValueError):
        total_forms = 0

    invoice_snapshot = []
    for index in range(total_forms):
        row = {
            field_name: (request.POST.get(f"{prefix}-{index}-{field_name}") or "").strip()
            for field_name in _invoice_form_field_names()
        }
        row["DELETE"] = bool(request.POST.get(f"{prefix}-{index}-DELETE"))
        row["form_index"] = index
        if any(value for key, value in row.items() if key not in {"DELETE", "form_index"}) or row["DELETE"]:
            invoice_snapshot.append(row)

    return {
        "claim": claim_snapshot,
        "invoices": invoice_snapshot,
    }


def _snapshot_has_meaningful_data(snapshot: dict) -> bool:
    if any((snapshot.get("claim") or {}).values()):
        return True
    for row in snapshot.get("invoices") or []:
        if any(
            value
            for key, value in row.items()
            if key not in {"DELETE", "form_index", "transaction_type"} and str(value or "").strip()
        ):
            return True
        if row.get("transaction_type") and row.get("transaction_type") != "TAX INVOICE":
            return True
    return False


def _apply_draft_snapshot_to_forms(*, claim: VerificationClaim, claim_form, invoice_formset) -> None:
    snapshot = claim.autosave_snapshot or {}
    claim_snapshot = snapshot.get("claim") or {}
    for field_name, value in claim_snapshot.items():
        if field_name in claim_form.fields:
            claim_form.initial[field_name] = value

    invoice_rows = [row for row in (snapshot.get("invoices") or []) if not row.get("DELETE")]
    for form, row in zip(invoice_formset.forms, invoice_rows):
        for field_name in _invoice_form_field_names():
            if field_name in form.fields:
                form.initial[field_name] = row.get(field_name, "")


def _attach_saved_document_counts(*, claim: VerificationClaim, invoice_formset) -> None:
    documents_by_invoice: dict[int, dict[str, list[dict[str, str]]]] = {}
    for document in VerificationInvoiceDocument.objects.filter(invoice__claim=claim).order_by("category", "-created_at", "-id"):
        bucket = documents_by_invoice.setdefault(
            document.invoice_id,
            {"invoice": [], "payment_proof": []},
        )
        bucket[document.category].append(
            {
                "name": document.original_name,
                "url": document.file.url if document.file else "",
            }
        )

    for form in invoice_formset.forms:
        invoice_id = getattr(form.instance, "pk", None)
        saved = documents_by_invoice.get(invoice_id, {"invoice": [], "payment_proof": []})
        form.saved_invoice_documents = saved["invoice"]
        form.saved_payment_proof_documents = saved["payment_proof"]
        form.saved_invoice_documents_json = json.dumps(saved["invoice"])
        form.saved_payment_proof_documents_json = json.dumps(saved["payment_proof"])
        form.saved_invoice_document_count = len(saved["invoice"])
        form.saved_payment_proof_count = len(saved["payment_proof"])


def _dashboard_querystring(
    *,
    query: str,
    date_from: str,
    date_to: str,
    doc_filter: str | None = None,
    rejection_reason: str | None = None,
    sort: str | None = None,
    page_size: int | None = None,
    page: int | str | None = None,
    export: str | None = None,
) -> str:
    params: dict[str, str] = {}
    if query:
        params["q"] = query
    if date_from:
        params["date_from"] = date_from
    if date_to:
        params["date_to"] = date_to
    if doc_filter and doc_filter != "all":
        params["doc_filter"] = doc_filter
    if rejection_reason:
        params["rejection_reason"] = rejection_reason
    if sort:
        params["sort"] = sort
    if page_size:
        params["page_size"] = str(page_size)
    if page and str(page) != "1":
        params["page"] = str(page)
    if export:
        params["export"] = export
    return urlencode(params)


def _format_dashboard_date(value) -> str:
    if not value:
        return ""
    return value.strftime("%d %b %Y")


def _format_dashboard_datetime(value) -> str:
    if not value:
        return ""
    if timezone.is_aware(value):
        value = timezone.localtime(value)
    return value.strftime("%d %b %Y %H:%M")


def _sort_dashboard_claims(claims: list[VerificationClaim], sort_key: str) -> list[VerificationClaim]:
    descending = sort_key.startswith("-")
    normalized_key = sort_key[1:] if descending else sort_key

    def documents_sort_value(claim: VerificationClaim):
        return (
            int(claim.dashboard_is_fully_documented),
            int(claim.dashboard_has_any_uploads),
            claim.dashboard_document_count or 0,
            claim.dashboard_complete_invoice_count or 0,
        )

    def status_sort_value(claim: VerificationClaim):
        return 1 if claim.current_status == "Updated" else 0

    sort_map = {
        "claim": lambda claim: claim.claim_reference_number.lower(),
        "documents": documents_sort_value,
        "total_paid": lambda claim: claim.dashboard_total_amount_paid or 0,
        "vat_approved": lambda claim: claim.dashboard_total_vat_approved or 0,
        "status": status_sort_value,
        "updated": lambda claim: claim.updated_at,
    }

    sort_fn = sort_map.get(normalized_key, sort_map["updated"])
    return sorted(claims, key=sort_fn, reverse=descending)


def _export_dashboard_claims_csv(claims: list[VerificationClaim]) -> HttpResponse:
    timestamp = timezone.localtime().strftime("%Y%m%d-%H%M")
    response = HttpResponse(content_type="text/csv")
    response["Content-Disposition"] = f'attachment; filename="verification-reports-{timestamp}.csv"'

    writer = csv.writer(response)
    writer.writerow(
        [
            "Claim Reference",
            "Applicant Name (English)",
            "Applicant Name (Arabic)",
            "Date Received",
            "Document State",
            "Total Files",
            "Invoice Files",
            "Payment Proofs",
            "Pending Invoices",
            "Total Paid",
            "VAT Approved",
            "Status",
            "Updated",
        ]
    )

    for claim in claims:
        writer.writerow(
            [
                claim.claim_reference_number,
                claim.applicant_name_english,
                claim.applicant_name_arabic,
                _format_dashboard_date(claim.date_received),
                claim.dashboard_document_state_label,
                claim.dashboard_document_count or 0,
                claim.dashboard_invoice_document_count or 0,
                claim.dashboard_payment_proof_count or 0,
                claim.dashboard_missing_document_invoice_count or 0,
                claim.dashboard_total_amount_paid or 0,
                claim.dashboard_total_vat_approved or 0,
                claim.current_status,
                _format_dashboard_datetime(claim.updated_at),
            ]
        )

    return response


@login_required
@require_GET
def dashboard_view(request):
    query = (request.GET.get("q") or "").strip()
    date_from_raw = (request.GET.get("date_from") or "").strip()
    date_to_raw = (request.GET.get("date_to") or "").strip()
    page_number = (request.GET.get("page") or "1").strip()
    sort = (request.GET.get("sort") or "-updated").strip()
    export = (request.GET.get("export") or "").strip().lower()
    page_size_raw = (request.GET.get("page_size") or "10").strip()
    doc_filter = (request.GET.get("doc_filter") or "all").strip().lower()
    rejection_reason = (request.GET.get("rejection_reason") or "").strip()
    if doc_filter not in {"all", "complete", "incomplete", "rejected", "draft"}:
        doc_filter = "all"
    allowed_rejection_reasons = {choice[0] for choice in VerificationInvoiceDocument.ReviewReason.choices}
    if rejection_reason not in allowed_rejection_reasons:
        rejection_reason = ""
    elif doc_filter == "all":
        doc_filter = "rejected"
    if sort not in {"claim", "-claim", "documents", "-documents", "total_paid", "-total_paid", "vat_approved", "-vat_approved", "status", "-status", "updated", "-updated"}:
        sort = "-updated"
    try:
        page_size = int(page_size_raw)
    except ValueError:
        page_size = 10
    if page_size not in {10, 25, 50, 100}:
        page_size = 10

    date_from = parse_date(date_from_raw) if date_from_raw else None
    date_to = parse_date(date_to_raw) if date_to_raw else None
    if date_from_raw and date_from is None:
        messages.error(request, "Start date is invalid.")
        date_from_raw = ""
    if date_to_raw and date_to is None:
        messages.error(request, "End date is invalid.")
        date_to_raw = ""
    if date_from and date_to and date_from > date_to:
        messages.error(request, "Start date must be before or equal to end date.")
        date_from = None
        date_to = None
        date_from_raw = ""
        date_to_raw = ""

    claims_queryset = accessible_claim_queryset(user=request.user, queryset=_claim_queryset())
    draft_claims_queryset = _user_draft_queryset(request.user)

    if query:
        claims_queryset = claims_queryset.filter(
            Q(claim_reference_number__icontains=query)
            | Q(applicant_name_arabic__icontains=query)
            | Q(applicant_name_english__icontains=query)
        )
        draft_claims_queryset = draft_claims_queryset.filter(
            Q(claim_reference_number__icontains=query)
            | Q(applicant_name_arabic__icontains=query)
            | Q(applicant_name_english__icontains=query)
        )
    if date_from:
        claims_queryset = claims_queryset.filter(date_received__gte=date_from)
        draft_claims_queryset = draft_claims_queryset.filter(date_received__gte=date_from)
    if date_to:
        claims_queryset = claims_queryset.filter(date_received__lte=date_to)
        draft_claims_queryset = draft_claims_queryset.filter(date_received__lte=date_to)

    claims = list(claims_queryset.order_by("-updated_at"))
    draft_claims_all = list(draft_claims_queryset.order_by("-last_autosaved_at", "-updated_at"))
    draft_claims_preview = draft_claims_all[:6]

    for claim in claims:
        document_rows = []
        complete_invoice_count = 0
        invoice_with_uploads_count = 0
        has_rejected_documents = False
        rejected_reason_values: set[str] = set()
        total_invoice_docs = 0
        total_proof_docs = 0
        reviewed_invoice_docs = 0
        reviewed_proof_docs = 0
        rejected_invoice_docs = 0
        rejected_proof_docs = 0

        for invoice in claim.invoices.all():
            documents = list(invoice.documents.all())
            invoice_doc_count = sum(
                1 for document in documents if document.category == VerificationInvoiceDocument.Category.INVOICE
            )
            payment_proof_count = sum(
                1 for document in documents if document.category == VerificationInvoiceDocument.Category.PAYMENT_PROOF
            )
            total_invoice_docs += invoice_doc_count
            total_proof_docs += payment_proof_count
            reviewed_invoice_docs += sum(
                1
                for document in documents
                if document.category == VerificationInvoiceDocument.Category.INVOICE
                and document.review_status != VerificationInvoiceDocument.ReviewStatus.PENDING
            )
            reviewed_proof_docs += sum(
                1
                for document in documents
                if document.category == VerificationInvoiceDocument.Category.PAYMENT_PROOF
                and document.review_status != VerificationInvoiceDocument.ReviewStatus.PENDING
            )
            rejected_invoice_docs += sum(
                1
                for document in documents
                if document.category == VerificationInvoiceDocument.Category.INVOICE
                and document.review_status == VerificationInvoiceDocument.ReviewStatus.DECLINED
            )
            rejected_proof_docs += sum(
                1
                for document in documents
                if document.category == VerificationInvoiceDocument.Category.PAYMENT_PROOF
                and document.review_status == VerificationInvoiceDocument.ReviewStatus.DECLINED
            )
            if any(
                document.review_status == VerificationInvoiceDocument.ReviewStatus.DECLINED
                for document in documents
            ):
                has_rejected_documents = True
            rejected_reason_values.update(
                document.review_reason
                for document in documents
                if document.review_status == VerificationInvoiceDocument.ReviewStatus.DECLINED and document.review_reason
            )

            has_invoice_docs = invoice_doc_count > 0
            has_payment_proofs = payment_proof_count > 0
            is_complete = has_invoice_docs and has_payment_proofs

            if has_invoice_docs or has_payment_proofs:
                invoice_with_uploads_count += 1
            if is_complete:
                complete_invoice_count += 1

            if invoice_doc_count or payment_proof_count:
                document_rows.append(
                    {
                        "transaction_number": invoice.transaction_number,
                        "invoice_count": invoice_doc_count,
                        "payment_proof_count": payment_proof_count,
                    }
                )

        claim.dashboard_document_rows = document_rows
        claim.dashboard_complete_invoice_count = complete_invoice_count
        claim.dashboard_invoice_with_uploads_count = invoice_with_uploads_count
        claim.dashboard_missing_document_invoice_count = max(
            0, (claim.dashboard_invoice_count or 0) - complete_invoice_count
        )
        claim.dashboard_has_rejected_documents = has_rejected_documents
        claim.dashboard_rejected_reason_values = rejected_reason_values
        claim.dashboard_rejected_invoice_count = rejected_invoice_docs
        claim.dashboard_rejected_proof_count = rejected_proof_docs
        claim.dashboard_invoice_review_complete = total_invoice_docs > 0 and reviewed_invoice_docs == total_invoice_docs
        claim.dashboard_proof_review_complete = total_proof_docs > 0 and reviewed_proof_docs == total_proof_docs
        claim.dashboard_is_fully_documented = (
            (claim.dashboard_invoice_count or 0) > 0 and complete_invoice_count == (claim.dashboard_invoice_count or 0)
        )
        claim.dashboard_has_any_uploads = invoice_with_uploads_count > 0
        if claim.dashboard_is_fully_documented:
            claim.dashboard_document_state = "complete"
            claim.dashboard_document_state_label = "Docs Complete"
        elif claim.dashboard_has_any_uploads:
            claim.dashboard_document_state = "attention"
            claim.dashboard_document_state_label = "Needs Docs"
        else:
            claim.dashboard_document_state = "empty"
            claim.dashboard_document_state_label = "No Uploads"

    for draft_claim in draft_claims_all:
        draft_claim.dashboard_draft_saved_at = draft_claim.last_autosaved_at or draft_claim.updated_at

    quick_filter_counts = {
        "all": len(claims),
        "incomplete": sum(1 for claim in claims if not claim.dashboard_is_fully_documented),
        "complete": sum(1 for claim in claims if claim.dashboard_is_fully_documented),
        "rejected": sum(1 for claim in claims if claim.dashboard_has_rejected_documents),
        "draft": len(draft_claims_all),
    }
    rejection_reason_filters = []
    for reason_value, reason_label in VerificationInvoiceDocument.ReviewReason.choices:
        count = sum(1 for claim in claims if reason_value in getattr(claim, "dashboard_rejected_reason_values", set()))
        if count == 0:
            continue
        rejection_reason_filters.append(
            {
                "value": reason_value,
                "label": reason_label,
                "count": count,
                "querystring": _dashboard_querystring(
                    query=query,
                    date_from=date_from_raw,
                    date_to=date_to_raw,
                    doc_filter="rejected",
                    rejection_reason=reason_value,
                    sort=sort,
                    page_size=page_size,
                ),
            }
        )
    dashboard_summary = {
        "total_claims": len(claims),
        "total_invoices": sum(claim.dashboard_invoice_count or 0 for claim in claims),
        "total_documents": sum(claim.dashboard_document_count or 0 for claim in claims),
        "total_vat_approved": sum(claim.dashboard_total_vat_approved or 0 for claim in claims),
        "updated_claims": sum(1 for claim in claims if claim.current_status == "Updated"),
    }

    display_mode = "claims"
    visible_items: list[VerificationClaim] = claims

    if doc_filter == "draft":
        display_mode = "drafts"
        visible_items = draft_claims_all
    elif doc_filter == "complete":
        claims = [claim for claim in claims if claim.dashboard_is_fully_documented]
        visible_items = claims
    elif doc_filter == "incomplete":
        claims = [claim for claim in claims if not claim.dashboard_is_fully_documented]
        visible_items = claims
    elif doc_filter == "rejected":
        claims = [claim for claim in claims if claim.dashboard_has_rejected_documents]
        visible_items = claims
    else:
        visible_items = claims
    if rejection_reason:
        claims = [claim for claim in claims if rejection_reason in getattr(claim, "dashboard_rejected_reason_values", set())]
        visible_items = claims

    if display_mode == "drafts":
        if sort in {"claim", "-claim"}:
            visible_items = sorted(
                visible_items,
                key=lambda claim: ((claim.claim_reference_number or "").lower(), -(claim.pk or 0)),
                reverse=sort.startswith("-"),
            )
        else:
            visible_items = sorted(
                visible_items,
                key=lambda claim: (claim.dashboard_draft_saved_at or claim.updated_at, claim.pk or 0),
                reverse=not sort == "updated",
            )
    else:
        claims = _sort_dashboard_claims(claims, sort)
        visible_items = claims

    if export == "csv":
        return _export_dashboard_claims_csv(claims if display_mode != "drafts" else [])

    paginator = Paginator(visible_items, page_size)
    page_obj = paginator.get_page(page_number)
    pagination_range = paginator.get_elided_page_range(number=page_obj.number, on_each_side=1, on_ends=1)
    pagination_querystring = _dashboard_querystring(
        query=query,
        date_from=date_from_raw,
        date_to=date_to_raw,
        doc_filter=doc_filter,
        rejection_reason=rejection_reason,
        sort=sort,
        page_size=page_size,
    )
    sort_default_map = {
        "claim": "claim",
        "documents": "-documents",
        "total_paid": "-total_paid",
        "vat_approved": "-vat_approved",
        "status": "-status",
        "updated": "-updated",
    }
    sort_querystrings = {}
    sort_states = {}
    for field, default_value in sort_default_map.items():
        if sort == field:
            next_sort = f"-{field}"
            sort_states[field] = "asc"
        elif sort == f"-{field}":
            next_sort = field
            sort_states[field] = "desc"
        else:
            next_sort = default_value
            sort_states[field] = ""

        sort_querystrings[field] = _dashboard_querystring(
            query=query,
            date_from=date_from_raw,
            date_to=date_to_raw,
            doc_filter=doc_filter,
            rejection_reason=rejection_reason,
            sort=next_sort,
            page_size=page_size,
        )
    export_querystring = _dashboard_querystring(
        query=query,
        date_from=date_from_raw,
        date_to=date_to_raw,
        doc_filter=doc_filter,
        rejection_reason=rejection_reason,
        sort=sort,
        page_size=page_size,
        export="csv",
    )

    context = {
        "page_title": "Dashboard",
        "hide_header_page_meta": True,
        "hide_footer_brand_text": True,
        "claims": page_obj.object_list,
        "page_obj": page_obj,
        "pagination_range": pagination_range,
        "pagination_querystring": pagination_querystring,
        "query": query,
        "date_from": date_from_raw,
        "date_to": date_to_raw,
        "doc_filter": doc_filter,
        "rejection_reason": rejection_reason,
        "sort": sort,
        "sort_states": sort_states,
        "sort_querystrings": sort_querystrings,
        "page_size": page_size,
        "page_size_options": [10, 25, 50, 100],
        "export_querystring": export_querystring,
        "has_active_filters": bool(query or date_from_raw or date_to_raw or doc_filter != "all" or rejection_reason or sort != "-updated" or page_size != 10),
        "quick_filter_counts": quick_filter_counts,
        "dashboard_summary": dashboard_summary,
        "draft_claims": draft_claims_preview,
        "draft_count": len(draft_claims_all),
        "display_mode": display_mode,
        "rejection_reason_filters": rejection_reason_filters,
        "filter_querystrings": {
            "all": _dashboard_querystring(query=query, date_from=date_from_raw, date_to=date_to_raw, doc_filter="all", sort=sort, page_size=page_size),
            "complete": _dashboard_querystring(
                query=query,
                date_from=date_from_raw,
                date_to=date_to_raw,
                doc_filter="complete",
                sort=sort,
                page_size=page_size,
            ),
            "rejected": _dashboard_querystring(
                query=query,
                date_from=date_from_raw,
                date_to=date_to_raw,
                doc_filter="rejected",
                sort=sort,
                page_size=page_size,
            ),
            "incomplete": _dashboard_querystring(
                query=query,
                date_from=date_from_raw,
                date_to=date_to_raw,
                doc_filter="incomplete",
                sort=sort,
                page_size=page_size,
            ),
            "draft": _dashboard_querystring(
                query=query,
                date_from=date_from_raw,
                date_to=date_to_raw,
                doc_filter="draft",
                sort=sort,
                page_size=page_size,
            ),
        },
    }
    return render(request, "verification/dashboard.html", context)

@super_admin_required
@require_GET
def superuser_dashboard_view(request):
    accountants = (
        User.objects.filter(role=User.Role.ACCOUNTANT)
        .prefetch_related(
            Prefetch(
                "verification_claims_created",
                queryset=VerificationClaim.objects.filter(status=VerificationClaim.Status.SUBMITTED).only("id", "created_by_id", "updated_at").prefetch_related(
                    Prefetch(
                        "invoices",
                        queryset=VerificationInvoice.objects.only("id", "claim_id").prefetch_related(
                            Prefetch(
                                "documents",
                                queryset=VerificationInvoiceDocument.objects.only("id", "invoice_id", "category"),
                            )
                        ),
                    )
                ),
            )
        )
        .order_by("first_name", "last_name", "email")
    )

    rows = []
    summary = {
        "accountants": 0,
        "claims": 0,
        "invoices": 0,
        "completed_invoices": 0,
        "total_files": 0,
    }

    for accountant in accountants:
        claims = list(accountant.verification_claims_created.all())
        claim_count = len(claims)
        invoice_count = 0
        completed_invoice_count = 0
        invoice_file_count = 0
        payment_proof_count = 0
        last_updated = None

        for claim in claims:
            if last_updated is None or claim.updated_at > last_updated:
                last_updated = claim.updated_at

            for invoice in claim.invoices.all():
                invoice_count += 1
                documents = list(invoice.documents.all())
                invoice_docs = sum(
                    1 for document in documents if document.category == VerificationInvoiceDocument.Category.INVOICE
                )
                proof_docs = sum(
                    1 for document in documents if document.category == VerificationInvoiceDocument.Category.PAYMENT_PROOF
                )
                invoice_file_count += invoice_docs
                payment_proof_count += proof_docs
                if invoice_docs > 0 and proof_docs > 0:
                    completed_invoice_count += 1

        total_files = invoice_file_count + payment_proof_count
        completion_rate = round((completed_invoice_count / invoice_count) * 100) if invoice_count else 0

        rows.append(
            {
                "accountant": accountant,
                "claim_count": claim_count,
                "invoice_count": invoice_count,
                "completed_invoice_count": completed_invoice_count,
                "invoice_file_count": invoice_file_count,
                "payment_proof_count": payment_proof_count,
                "total_files": total_files,
                "completion_rate": completion_rate,
                "last_updated": last_updated,
            }
        )

        summary["accountants"] += 1
        summary["claims"] += claim_count
        summary["invoices"] += invoice_count
        summary["completed_invoices"] += completed_invoice_count
        summary["total_files"] += total_files

    context = {
        "page_title": "Overview",
        "hide_header_page_meta": True,
        "hide_footer_brand_text": True,
        "accountant_rows": rows,
        "summary": summary,
    }
    return render(request, "verification/superuser_dashboard.html", context)


@super_admin_required
@require_http_methods(["GET", "POST"])
def description_reason_settings_view(request):
    editing_option = None
    edit_pk = (request.GET.get("edit") or "").strip()
    if edit_pk.isdigit():
        editing_option = get_object_or_404(VerificationDescriptionOption, pk=int(edit_pk))

    if request.method == "POST":
        action = (request.POST.get("settings_action") or "save").strip().lower()
        if action == "delete":
            option = get_object_or_404(VerificationDescriptionOption, pk=request.POST.get("option_id"))
            deleted_label = option.label
            option.delete()
            messages.success(request, f"{deleted_label} deleted.")
            return redirect("verification:description_reason_settings")

        option_id = (request.POST.get("option_id") or "").strip()
        instance = None
        if option_id.isdigit():
            instance = get_object_or_404(VerificationDescriptionOption, pk=int(option_id))
        form = VerificationDescriptionOptionForm(request.POST, instance=instance)
        if form.is_valid():
            saved = form.save()
            messages.success(request, f"{saved.label} saved.")
            return redirect("verification:description_reason_settings")
        editing_option = instance
    else:
        form = VerificationDescriptionOptionForm(instance=editing_option)

    context = {
        "page_title": "Rejection Reasons",
        "hide_header_page_meta": True,
        "hide_footer_brand_text": True,
        "form": form,
        "editing_option": editing_option,
        "options": VerificationDescriptionOption.objects.order_by("sort_order", "label", "id"),
    }
    return render(request, "verification/description_reason_settings.html", context)


@login_required
@require_http_methods(["GET", "POST"])
def claim_create_view(request):
    requested_action = request.POST.get("form_action") or request.POST.get("form_action_intent") or ""
    save_as_draft = request.method == "POST" and requested_action == "draft"
    claim_form_class = DraftVerificationClaimForm if save_as_draft else VerificationClaimForm
    formset_class = DraftVerificationInvoiceFormSet if save_as_draft else VerificationInvoiceFormSet

    claim_form = claim_form_class(request.POST or None)
    formset = formset_class(
        request.POST or None,
        request.FILES or None,
        prefix="invoices",
        queryset=VerificationInvoice.objects.none(),
    )

    if request.method == "POST" and claim_form.is_valid() and formset.is_valid():
        if save_as_draft:
            snapshot = _build_autosave_snapshot(request)
            if not _snapshot_has_meaningful_data(snapshot):
                messages.info(request, "Add some report details before saving a draft.")
            else:
                claim = save_claim_with_invoices(
                    claim_form=claim_form,
                    invoice_formset=formset,
                    actor=request.user,
                    claim_status=VerificationClaim.Status.DRAFT,
                    autosave_snapshot=snapshot,
                    log_invoice_changes=False,
                )
                messages.success(request, "Draft saved.")
                return redirect("accounts:dashboard")
        else:
            claim = save_claim_with_invoices(
                claim_form=claim_form,
                invoice_formset=formset,
                actor=request.user,
                claim_status=VerificationClaim.Status.SUBMITTED,
            )
            log_activity(
                claim=claim,
                actor=request.user,
                action=VerificationActivityLog.Action.CREATE,
                message=f"Claim {claim.claim_reference_number} created with {claim.invoices.count()} invoice(s).",
            )
            messages.success(request, "Verification report created successfully.")
            return redirect("verification:claim_detail", pk=claim.pk)

    context = {
        "page_title": "Create Verification Report",
        "form_title": "Create Verification Report",
        "submit_label": "Create Report",
        "draft_submit_label": "Save as Draft",
        "claim_form": claim_form,
        "invoice_formset": formset,
        "cancel_url": "accounts:dashboard",
        "description_option_labels": _active_description_option_labels(),
    }
    return render(request, "verification/claim_form.html", context)


@login_required
@require_http_methods(["GET", "POST"])
def claim_update_view(request, pk: int):
    claim = get_object_or_404(
        accessible_claim_queryset(user=request.user).prefetch_related("invoices__documents"),
        pk=pk,
    )
    requested_action = request.POST.get("form_action") or request.POST.get("form_action_intent") or ""
    save_as_draft = request.method == "POST" and requested_action == "draft"
    claim_form_class = DraftVerificationClaimForm if save_as_draft else VerificationClaimForm
    formset_class = DraftVerificationInvoiceFormSet if save_as_draft else VerificationInvoiceFormSet

    claim_form = claim_form_class(request.POST or None, instance=claim)
    formset = formset_class(request.POST or None, request.FILES or None, instance=claim, prefix="invoices")

    if request.method == "POST" and claim_form.is_valid() and formset.is_valid():
        if save_as_draft:
            snapshot = _build_autosave_snapshot(request)
            if not _snapshot_has_meaningful_data(snapshot):
                messages.info(request, "Add some report details before saving a draft.")
            else:
                claim = save_claim_with_invoices(
                    claim_form=claim_form,
                    invoice_formset=formset,
                    actor=request.user,
                    claim_status=VerificationClaim.Status.DRAFT,
                    autosave_snapshot=snapshot,
                    log_invoice_changes=False,
                )
                messages.success(request, "Draft saved.")
                return redirect("accounts:dashboard")
        else:
            previous_status = claim.status
            claim_changed = list(claim_form.changed_data)
            claim = save_claim_with_invoices(
                claim_form=claim_form,
                invoice_formset=formset,
                actor=request.user,
                claim_status=VerificationClaim.Status.SUBMITTED,
            )

            if claim_changed:
                changed = format_changed_labels(claim_form)
                log_activity(
                    claim=claim,
                    actor=request.user,
                    action=VerificationActivityLog.Action.UPDATE,
                    message=f"Claim header updated: {changed}.",
                )

            if claim_changed or any(getattr(form, "changed_data", None) for form in formset.forms):
                if previous_status == VerificationClaim.Status.DRAFT:
                    messages.success(request, "Draft report created successfully.")
                else:
                    messages.success(request, "Verification report updated successfully.")
            else:
                messages.info(request, "No changes were detected.")

            return redirect("verification:claim_detail", pk=claim.pk)

    if request.method == "GET" and claim.status == VerificationClaim.Status.DRAFT:
        _apply_draft_snapshot_to_forms(claim=claim, claim_form=claim_form, invoice_formset=formset)

    _attach_saved_document_counts(claim=claim, invoice_formset=formset)

    context = {
        "page_title": "Edit Verification Report",
        "form_title": f"Edit {claim.claim_reference_number}",
        "submit_label": "Create Report" if claim.status == VerificationClaim.Status.DRAFT else "Update Report",
        "draft_submit_label": "Save as Draft",
        "claim_form": claim_form,
        "invoice_formset": formset,
        "claim": claim,
        "cancel_url": "verification:claim_detail",
        "description_option_labels": _active_description_option_labels(),
    }
    return render(request, "verification/claim_form.html", context)


@login_required
@require_POST
def claim_discard_draft_view(request, pk: int):
    claim = get_object_or_404(_user_draft_queryset(request.user), pk=pk)
    claim_reference = claim.claim_reference_number
    claim.delete()
    messages.success(request, f"Draft {claim_reference} discarded.")
    return redirect("accounts:dashboard")


@login_required
@require_GET
def claim_detail_view(request, pk: int):
    claim = get_object_or_404(
        accessible_claim_queryset(
            user=request.user,
            queryset=_claim_detail_queryset(),
        ),
        pk=pk,
    )
    totals = claim.totals()
    invoices = list(claim.invoices.all())
    distinct_plot_numbers = {
        str(invoice.plot_number or "").strip().upper()
        for invoice in invoices
        if str(invoice.plot_number or "").strip()
    }
    has_plot_mismatch = len(distinct_plot_numbers) > 1
    summary = {
        "total_invoices": len(invoices),
        "with_invoice_files": 0,
        "with_payment_proofs": 0,
        "complete": 0,
        "missing": 0,
    }

    default_open_assigned = False

    total_review_invoices = len(invoices)

    for index, invoice in enumerate(invoices, start=1):
        documents = list(invoice.documents.all())
        invoice.invoice_documents = [
            doc for doc in documents if doc.category == VerificationInvoiceDocument.Category.INVOICE
        ]
        invoice.payment_proof_documents = [
            doc for doc in documents if doc.category == VerificationInvoiceDocument.Category.PAYMENT_PROOF
        ]
        for document in documents:
            document.file_badge, document.file_icon = _document_badge_meta(document.original_name)
            document.preview_url = reverse(
                "verification:invoice_document_preview",
                kwargs={"claim_pk": claim.pk, "document_pk": document.pk},
            )
            document.review_label, document.review_icon, document.review_class = _review_badge_meta(
                document.review_status
            )
            document.review_badge_class = _review_status_badge_class(document.review_class)
            document.review_action_url = reverse(
                "verification:invoice_document_review",
                kwargs={"claim_pk": claim.pk, "document_pk": document.pk},
            )
            document.review_meta_text = _document_review_meta_text(document)

        invoice.invoice_document_count = len(invoice.invoice_documents)
        invoice.payment_proof_count = len(invoice.payment_proof_documents)
        invoice.total_document_count = len(documents)
        invoice.has_invoice_documents = invoice.invoice_document_count > 0
        invoice.has_payment_proofs = invoice.payment_proof_count > 0
        invoice.is_document_complete = invoice.has_invoice_documents and invoice.has_payment_proofs
        invoice.has_missing_documents = not invoice.is_document_complete
        invoice.plot_warning_required = invoice.tos in {
            VerificationInvoice.TOS.CONTRACTOR,
            VerificationInvoice.TOS.CONSULTANT,
        } and not str(invoice.plot_number or "").strip()
        invoice.has_plot_mismatch = has_plot_mismatch
        invoice.review_summary = _invoice_review_summary(invoice)
        invoice.review_summary["invoice_badge_class"] = _review_status_badge_class(invoice.review_summary["invoice_class"])
        invoice.review_summary["proof_badge_class"] = _review_status_badge_class(invoice.review_summary["proof_class"])

        if invoice.is_document_complete:
            invoice.document_state = "complete"
            invoice.document_state_label = "Complete"
            invoice.document_state_badge_class = "verification-status-badge verification-status-badge--complete"
            summary["complete"] += 1
        else:
            invoice.document_state = "missing"
            if not invoice.has_invoice_documents and not invoice.has_payment_proofs:
                invoice.document_state_label = "Missing All Docs"
            elif not invoice.has_invoice_documents:
                invoice.document_state_label = "Missing Invoice"
            else:
                invoice.document_state_label = "Missing Proof"
            invoice.document_state_badge_class = "verification-status-badge verification-status-badge--missing"
            summary["missing"] += 1

        if invoice.has_invoice_documents:
            summary["with_invoice_files"] += 1
        if invoice.has_payment_proofs:
            summary["with_payment_proofs"] += 1

        invoice.documents_open = False
        if not default_open_assigned and invoice.has_missing_documents:
            invoice.documents_open = True
            default_open_assigned = True

        invoice.review_position = index
        invoice.review_total = total_review_invoices
        invoice.prev_review_invoice_pk = invoices[index - 2].pk if index > 1 else None
        invoice.next_review_invoice_pk = invoices[index].pk if index < total_review_invoices else None
        invoice.review_edit_form = VerificationInvoiceInlineEditForm(instance=invoice, prefix=f"review-{invoice.pk}")
        invoice.review_update_url = reverse(
            "verification:invoice_inline_update",
            kwargs={"claim_pk": claim.pk, "invoice_pk": invoice.pk},
        )

    if invoices and not default_open_assigned:
        invoices[0].documents_open = True

    claim.invoice_rows = invoices

    context = {
        "page_title": claim.claim_reference_number,
        "hide_header_page_meta": True,
        "claim": claim,
        "totals": totals,
        "document_summary": summary,
        "review_reason_choices": VerificationInvoiceDocument.ReviewReason.choices,
        "description_option_labels": _active_description_option_labels(),
    }
    return render(request, "verification/claim_detail.html", context)


@login_required
@require_POST
def invoice_inline_update_view(request, claim_pk: int, invoice_pk: int):
    invoice = get_object_or_404(
        VerificationInvoice.objects.select_related("claim").filter(
            claim__in=accessible_claim_queryset(user=request.user)
        ),
        pk=invoice_pk,
        claim_id=claim_pk,
    )
    form = VerificationInvoiceInlineEditForm(request.POST or None, instance=invoice, prefix=f"review-{invoice.pk}")
    if not form.is_valid():
        return JsonResponse({"ok": False, "errors": form.errors.get_json_data()}, status=400)

    changed_fields = list(form.changed_data)
    invoice = form.save()

    if changed_fields:
        changed = format_changed_labels(form, field_names=changed_fields)
        log_activity(
            claim=invoice.claim,
            actor=request.user,
            action=VerificationActivityLog.Action.UPDATE,
            invoice=invoice,
            message=f"Invoice {invoice.transaction_number} updated from review: {changed}.",
        )

    return JsonResponse(
        {
            "ok": True,
            "invoice": {
                "id": invoice.pk,
                "display": _serialize_invoice_edit_state(invoice),
                "changed_labels": format_changed_labels(form, field_names=changed_fields) if changed_fields else "",
                "saved_message": "Invoice values saved",
            },
        }
    )


@login_required
@require_GET
@xframe_options_sameorigin
def invoice_document_preview_view(request, claim_pk: int, document_pk: int):
    document = get_object_or_404(
        VerificationInvoiceDocument.objects.select_related("invoice", "invoice__claim").filter(
            invoice__claim__in=accessible_claim_queryset(user=request.user)
        ),
        pk=document_pk,
        invoice__claim_id=claim_pk,
    )

    guessed_type, _ = mimetypes.guess_type(document.file.name)
    content_type = guessed_type or "application/octet-stream"
    response = FileResponse(document.file.open("rb"), content_type=content_type)
    response["Content-Disposition"] = f'inline; filename="{document.original_name}"'
    return response


@login_required
@require_POST
def invoice_document_review_view(request, claim_pk: int, document_pk: int):
    document = get_object_or_404(
        VerificationInvoiceDocument.objects.select_related("invoice", "invoice__claim", "reviewed_by").filter(
            invoice__claim__in=accessible_claim_queryset(user=request.user)
        ),
        pk=document_pk,
        invoice__claim_id=claim_pk,
    )

    requested_status = (request.POST.get("status") or "").strip().lower()
    requested_reason = (request.POST.get("reason") or "").strip()
    allowed_statuses = {
        VerificationInvoiceDocument.ReviewStatus.APPROVED,
        VerificationInvoiceDocument.ReviewStatus.DECLINED,
    }
    if requested_status not in allowed_statuses:
        return JsonResponse({"ok": False, "error": "Invalid review status."}, status=400)

    if requested_status == VerificationInvoiceDocument.ReviewStatus.DECLINED:
        allowed_reasons = {choice[0] for choice in VerificationInvoiceDocument.ReviewReason.choices}
        if requested_reason not in allowed_reasons:
            return JsonResponse({"ok": False, "error": "Select a rejection reason."}, status=400)
        document.review_reason = requested_reason
    else:
        document.review_reason = ""

    document.review_status = requested_status
    document.reviewed_by = request.user if getattr(request.user, "is_authenticated", False) else None
    document.reviewed_at = timezone.now()
    document.save(update_fields=["review_status", "review_reason", "reviewed_by", "reviewed_at", "updated_at"])

    review_label, review_icon, review_class = _review_badge_meta(document.review_status)
    document.review_label = review_label
    document.review_icon = review_icon
    document.review_class = review_class

    invoice = document.invoice
    invoice_documents = list(
        invoice.documents.select_related("reviewed_by").order_by("category", "-created_at", "-id")
    )
    invoice.invoice_documents = [
        item for item in invoice_documents if item.category == VerificationInvoiceDocument.Category.INVOICE
    ]
    invoice.payment_proof_documents = [
        item for item in invoice_documents if item.category == VerificationInvoiceDocument.Category.PAYMENT_PROOF
    ]
    review_summary = _invoice_review_summary(invoice)

    log_activity(
        claim=invoice.claim,
        actor=request.user,
        action=VerificationActivityLog.Action.UPDATE,
        invoice=invoice,
        message=(
            f"{document.get_category_display()} document '{document.original_name}' "
            f"{document.get_review_status_display().lower()}"
            f"{f' ({document.get_review_reason_display()})' if document.review_status == VerificationInvoiceDocument.ReviewStatus.DECLINED and document.review_reason else ''} "
            f"for invoice {invoice.transaction_number}."
        ),
    )

    reviewed_by_label = "System"
    if document.reviewed_by:
        reviewed_by_label = document.reviewed_by.get_full_name() or document.reviewed_by.email
    review_meta_text = _document_review_meta_text(document)

    if request.headers.get("x-requested-with") != "XMLHttpRequest":
        messages.success(
            request,
            (
                f"{document.get_category_display()} document marked {document.get_review_status_display().lower()}"
                f"{f' ({document.get_review_reason_display()})' if document.review_status == VerificationInvoiceDocument.ReviewStatus.DECLINED and document.review_reason else ''}."
            ),
        )
        return redirect("verification:claim_detail", pk=invoice.claim_id)

    return JsonResponse(
        {
            "ok": True,
            "document": {
                "id": document.pk,
                "status": document.review_status,
                "label": review_label,
                "icon": review_icon,
                "class": review_class,
                "badge_class": _review_status_badge_class(review_class),
                "reviewed_at": timezone.localtime(document.reviewed_at).strftime("%d %b %Y %H:%M")
                if document.reviewed_at
                else "",
                "reviewed_by": reviewed_by_label,
                "review_reason": document.review_reason,
                "review_reason_label": document.get_review_reason_display() if document.review_reason else "",
                "review_meta": review_meta_text,
            },
            "invoice": {
                "id": invoice.pk,
                "summary": {
                    "invoice_status": review_summary["invoice_status"],
                    "invoice_label": review_summary["invoice_label"],
                    "invoice_icon": review_summary["invoice_icon"],
                    "invoice_class": review_summary["invoice_class"],
                    "invoice_badge_class": _review_status_badge_class(review_summary["invoice_class"]),
                    "invoice_reason_label": review_summary["invoice_reason_label"],
                    "proof_status": review_summary["proof_status"],
                    "proof_label": review_summary["proof_label"],
                    "proof_icon": review_summary["proof_icon"],
                    "proof_class": review_summary["proof_class"],
                    "proof_badge_class": _review_status_badge_class(review_summary["proof_class"]),
                    "proof_reason_label": review_summary["proof_reason_label"],
                },
            },
        }
    )


@login_required
@require_GET
def claim_export_view(request, pk: int, export_kind: str):
    claim = get_object_or_404(
        accessible_claim_queryset(user=request.user, queryset=_claim_detail_queryset()),
        pk=pk,
    )
    try:
        file_bytes, content_type, filename = build_claim_export(claim=claim, kind=export_kind)
    except ClaimExportError as error:
        messages.error(request, f"Download could not be generated: {error}")
        return redirect("verification:claim_detail", pk=claim.pk)

    response = HttpResponse(file_bytes, content_type=content_type)
    response["Content-Disposition"] = f'attachment; filename="{filename}"'
    return response


@login_required
@require_POST
def invoice_document_upload_view(request, claim_pk: int, invoice_pk: int):
    invoice = get_object_or_404(
        VerificationInvoice.objects.select_related("claim").filter(
            claim__in=accessible_claim_queryset(user=request.user)
        ),
        pk=invoice_pk,
        claim_id=claim_pk,
    )
    invoice_files = request.FILES.getlist("invoice_files")
    payment_proof_files = request.FILES.getlist("payment_proof_files")

    if invoice_files or payment_proof_files:
        form = VerificationInvoiceDocumentBundleUploadForm(
            request.POST,
            MultiValueDict(
                {
                    "invoice_files": invoice_files,
                    "payment_proof_files": payment_proof_files,
                }
            ),
        )
        if form.is_valid():
            uploaded_labels = []
            invoice_saved = []
            proof_saved = []
            if form.cleaned_data["invoice_files"]:
                invoice_saved = save_invoice_documents(
                    invoice=invoice,
                    category=VerificationInvoiceDocument.Category.INVOICE,
                    files=form.cleaned_data["invoice_files"],
                    actor=request.user,
                )
                uploaded_labels.append(f"{len(invoice_saved)} invoice")
            if form.cleaned_data["payment_proof_files"]:
                proof_saved = save_invoice_documents(
                    invoice=invoice,
                    category=VerificationInvoiceDocument.Category.PAYMENT_PROOF,
                    files=form.cleaned_data["payment_proof_files"],
                    actor=request.user,
                )
                uploaded_labels.append(f"{len(proof_saved)} proof")

            messages.success(
                request,
                f"Uploaded {' and '.join(uploaded_labels)} document(s) successfully.",
            )
        else:
            for errors in form.errors.values():
                for error in errors:
                    messages.error(request, error)
    else:
        files = request.FILES.getlist("files")
        form = VerificationInvoiceDocumentUploadForm(request.POST, MultiValueDict({"files": files}))

        if form.is_valid():
            category = form.cleaned_data["category"]
            saved = save_invoice_documents(
                invoice=invoice,
                category=category,
                files=form.cleaned_data["files"],
                actor=request.user,
            )
            category_label = VerificationInvoiceDocument.Category(category).label
            messages.success(request, f"{len(saved)} {category_label.lower()} document(s) uploaded successfully.")
        else:
            for errors in form.errors.values():
                for error in errors:
                    messages.error(request, error)

    return redirect(_claim_detail_redirect_url(invoice.claim_id, invoice.pk))


@login_required
@require_POST
def invoice_document_delete_view(request, claim_pk: int, document_pk: int):
    document = get_object_or_404(
        VerificationInvoiceDocument.objects.select_related("invoice", "invoice__claim").filter(
            invoice__claim__in=accessible_claim_queryset(user=request.user)
        ),
        pk=document_pk,
        invoice__claim_id=claim_pk,
    )
    invoice = document.invoice
    original_name = document.original_name
    category_label = document.get_category_display()
    document.delete()

    log_activity(
        claim=invoice.claim,
        actor=request.user,
        action=VerificationActivityLog.Action.DELETE,
        invoice=invoice,
        message=f"{category_label} document '{original_name}' removed from invoice {invoice.transaction_number}.",
    )
    messages.success(request, f"{category_label} document removed.")
    return redirect(_claim_detail_redirect_url(invoice.claim_id, invoice.pk))
