from __future__ import annotations

import json
import subprocess
import sys
import tempfile
from pathlib import Path

from django.conf import settings
from django.utils import timezone
from django.utils.text import slugify

from .models import VerificationClaim, VerificationInvoiceDocument


class ClaimExportError(RuntimeError):
    pass


class ClaimExportKind:
    VERIFICATION_REPORT_PDF = "verification_report_pdf"
    VERIFICATION_REPORT_XLSX = "verification_report_xlsx"
    SUPPORTING_DOCUMENTS_PDF = "supporting_documents_pdf"
    TOP5_SUPPORTING_DOCUMENTS_PDF = "top5_supporting_documents_pdf"
    VAT_TOP5_XLSX = "vat_top5_xlsx"


EXPORT_CONTENT_TYPES = {
    ClaimExportKind.VERIFICATION_REPORT_PDF: "application/pdf",
    ClaimExportKind.VERIFICATION_REPORT_XLSX: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
    ClaimExportKind.SUPPORTING_DOCUMENTS_PDF: "application/pdf",
    ClaimExportKind.TOP5_SUPPORTING_DOCUMENTS_PDF: "application/pdf",
    ClaimExportKind.VAT_TOP5_XLSX: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
}


def _bundled_python_path() -> Path:
    candidates = [
        Path.home() / ".cache/codex-runtimes/codex-primary-runtime/dependencies/python/bin/python3",
        Path(sys.executable),
    ]
    for candidate in candidates:
        if candidate.exists():
            return candidate
    raise ClaimExportError("No compatible Python runtime is available for report exports.")


def _template_workbook_path() -> Path:
    return Path(settings.BASE_DIR) / "verification" / "resources" / "verification-report-template.xlsx"


def _export_runtime_path() -> Path:
    return Path(settings.BASE_DIR) / "verification" / "export_runtime.py"


def _serialize_claim(claim: VerificationClaim) -> dict:
    def _serialize_date(value):
        return value.isoformat() if value else ""

    payload = {
        "claim": {
            "id": claim.pk,
            "reference_number": claim.claim_reference_number,
            "date_received": _serialize_date(claim.date_received),
            "applicant_name_english": claim.applicant_name_english,
            "applicant_name_arabic": claim.applicant_name_arabic,
            "first_document_request_date": _serialize_date(claim.first_document_request_date),
            "second_reminder_date": _serialize_date(claim.second_reminder_date),
            "final_report_without_missing_docs_date": _serialize_date(claim.final_report_without_missing_docs_date),
            "initial_report_with_missing_docs_date": _serialize_date(claim.initial_report_with_missing_docs_date),
            "followup_reminder_date": _serialize_date(claim.followup_reminder_date),
            "final_report_submission_date": _serialize_date(claim.final_report_submission_date),
            "created_at": timezone.localtime(claim.created_at).isoformat() if timezone.is_aware(claim.created_at) else claim.created_at.isoformat(),
            "updated_at": timezone.localtime(claim.updated_at).isoformat() if timezone.is_aware(claim.updated_at) else claim.updated_at.isoformat(),
            "created_by": (
                claim.created_by.get_full_name() or claim.created_by.email
                if claim.created_by
                else "System"
            ),
            "updated_by": (
                claim.updated_by.get_full_name() or claim.updated_by.email
                if claim.updated_by
                else "System"
            ),
            "current_status": claim.current_status,
            "totals": {
                key: str(value)
                for key, value in claim.totals().items()
            },
        },
        "invoices": [],
    }

    for line_item_counter, invoice in enumerate(claim.invoices.all(), start=1):
        documents = []
        for document in invoice.documents.all():
            documents.append(
                {
                    "id": document.pk,
                    "category": document.category,
                    "label": document.get_category_display(),
                    "original_name": document.original_name,
                    "file_path": document.file.path,
                    "created_at": timezone.localtime(document.created_at).isoformat()
                    if timezone.is_aware(document.created_at)
                    else document.created_at.isoformat(),
                    "uploaded_by": (
                        document.uploaded_by.get_full_name() or document.uploaded_by.email
                        if document.uploaded_by
                        else "System"
                    ),
                }
            )

        payload["invoices"].append(
            {
                "id": invoice.pk,
                "goods_services": invoice.goods_services,
                "transaction_type": invoice.transaction_type,
                "transaction_number": invoice.transaction_number,
                "transaction_date": invoice.transaction_date.isoformat(),
                "line_item": str(line_item_counter),
                "supplier_trn": invoice.supplier_trn,
                "supplier_name": invoice.supplier_name,
                "plot_number": invoice.plot_number,
                "tos": invoice.tos,
                "total_amount_paid": str(invoice.total_amount_paid),
                "vat_amount": str(invoice.vat_amount),
                "vat_approved": str(invoice.vat_approved),
                "vat_paid": str(invoice.vat_paid),
                "description": invoice.description,
                "sort_order": invoice.sort_order,
                "documents": documents,
                "invoice_document_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
                ),
            }
        )
    return payload


def _filename_for_export(kind: str, *, claim_reference: str) -> str:
    claim_slug = slugify(claim_reference) or f"claim-{claim_reference}"
    filenames = {
        ClaimExportKind.VERIFICATION_REPORT_PDF: f"verification-report-{claim_slug}.pdf",
        ClaimExportKind.VERIFICATION_REPORT_XLSX: f"verification-report-{claim_slug}.xlsx",
        ClaimExportKind.SUPPORTING_DOCUMENTS_PDF: f"all-invoices-and-payment-proofs-{claim_slug}.pdf",
        ClaimExportKind.TOP5_SUPPORTING_DOCUMENTS_PDF: f"top-5-invoices-and-payment-proofs-{claim_slug}.pdf",
        ClaimExportKind.VAT_TOP5_XLSX: f"vat-verification-top-5-{claim_slug}.xlsx",
    }
    return filenames[kind]


def build_claim_export(*, claim: VerificationClaim, kind: str) -> tuple[bytes, str, str]:
    if kind not in EXPORT_CONTENT_TYPES:
        raise ClaimExportError("Unsupported export type requested.")

    template_path = _template_workbook_path()
    runtime_path = _export_runtime_path()
    python_path = _bundled_python_path()

    if kind.endswith("xlsx") and not template_path.exists():
        raise ClaimExportError("The verification workbook template is missing.")
    if not runtime_path.exists():
        raise ClaimExportError("The export runtime is missing.")

    payload = _serialize_claim(claim)
    output_filename = _filename_for_export(kind, claim_reference=claim.claim_reference_number)

    with tempfile.TemporaryDirectory(prefix="verification-export-") as temp_dir:
        temp_dir_path = Path(temp_dir)
        payload_path = temp_dir_path / "claim-export-payload.json"
        output_path = temp_dir_path / output_filename
        payload_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")

        command = [
            str(python_path),
            str(runtime_path),
            kind,
            str(payload_path),
            str(output_path),
        ]
        if kind.endswith("xlsx"):
            command.append(str(template_path))

        result = subprocess.run(command, capture_output=True, text=True)
        if result.returncode != 0:
            error_output = (result.stderr or result.stdout or "").strip()
            raise ClaimExportError(error_output or "Export generation failed.")
        if not output_path.exists():
            raise ClaimExportError("The export file was not created.")

        return output_path.read_bytes(), EXPORT_CONTENT_TYPES[kind], output_filename
