from __future__ import annotations

import argparse
import json
from copy import copy
from datetime import datetime
from decimal import Decimal
from html import escape
from pathlib import Path

from openpyxl import load_workbook
from pypdf import PdfWriter
from reportlab.lib import colors
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.lib.pagesizes import A4, letter, landscape
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import mm
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.pdfgen import canvas as pdfcanvas
from reportlab.platypus import Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle


class ExportRuntimeError(RuntimeError):
    pass


INVOICE_START_ROW = 17
TEMPLATE_CAPACITY = 5
INSERTION_ROW = 22
TOTAL_ROW = 22

BRAND_BLUE = colors.HexColor("#3C5DAA")
BRAND_ORANGE = colors.HexColor("#EF9A23")
SURFACE = colors.HexColor("#F5F8FF")
GRID = colors.HexColor("#D7DFEC")
TEXT = colors.HexColor("#1F2A3D")
MUTED = colors.HexColor("#6B7280")
INK = colors.HexColor("#0F172A")
SOFT_PAPER = colors.HexColor("#F8FAFC")
SOFT_LINE = colors.HexColor("#CBD5E1")
SOFT_ACCENT = colors.HexColor("#EFF6FF")
SOFT_WARNING = colors.HexColor("#FFF7ED")
PAGE_BG = colors.white
TABLE_DARK_BG = colors.HexColor("#111111")
LIGHT_TEXT = colors.HexColor("#F4EFE6")
PAGE_MUTED = colors.HexColor("#8B837A")
TITLE_SHADOW = colors.HexColor("#24211D")
ACCENT_LINE = colors.HexColor("#E34F35")
CREAM_PANEL = colors.HexColor("#F1ECE1")
SUCCESS_TEXT = colors.HexColor("#4F8E4A")

ARABIC_FONT_CANDIDATES = [
    Path(__file__).resolve().parent / "resources" / "fonts" / "SFArabic.ttf",
    Path("/System/Library/Fonts/SFArabic.ttf"),
    Path("/System/Library/Fonts/Supplemental/Arial Unicode.ttf"),
    Path("/Library/Fonts/Arial Unicode.ttf"),
]
_ARABIC_FONT_NAME: str | None = None

ARABIC_PRESENTATION_FORMS = {
    "ا": ("\uFE8D", "\uFE8E", None, None),
    "أ": ("\uFE83", "\uFE84", None, None),
    "إ": ("\uFE87", "\uFE88", None, None),
    "آ": ("\uFE81", "\uFE82", None, None),
    "ب": ("\uFE8F", "\uFE90", "\uFE91", "\uFE92"),
    "ت": ("\uFE95", "\uFE96", "\uFE97", "\uFE98"),
    "ث": ("\uFE99", "\uFE9A", "\uFE9B", "\uFE9C"),
    "ج": ("\uFE9D", "\uFE9E", "\uFE9F", "\uFEA0"),
    "ح": ("\uFEA1", "\uFEA2", "\uFEA3", "\uFEA4"),
    "خ": ("\uFEA5", "\uFEA6", "\uFEA7", "\uFEA8"),
    "د": ("\uFEA9", "\uFEAA", None, None),
    "ذ": ("\uFEAB", "\uFEAC", None, None),
    "ر": ("\uFEAD", "\uFEAE", None, None),
    "ز": ("\uFEAF", "\uFEB0", None, None),
    "س": ("\uFEB1", "\uFEB2", "\uFEB3", "\uFEB4"),
    "ش": ("\uFEB5", "\uFEB6", "\uFEB7", "\uFEB8"),
    "ص": ("\uFEB9", "\uFEBA", "\uFEBB", "\uFEBC"),
    "ض": ("\uFEBD", "\uFEBE", "\uFEBF", "\uFEC0"),
    "ط": ("\uFEC1", "\uFEC2", "\uFEC3", "\uFEC4"),
    "ظ": ("\uFEC5", "\uFEC6", "\uFEC7", "\uFEC8"),
    "ع": ("\uFEC9", "\uFECA", "\uFECB", "\uFECC"),
    "غ": ("\uFECD", "\uFECE", "\uFECF", "\uFED0"),
    "ف": ("\uFED1", "\uFED2", "\uFED3", "\uFED4"),
    "ق": ("\uFED5", "\uFED6", "\uFED7", "\uFED8"),
    "ك": ("\uFED9", "\uFEDA", "\uFEDB", "\uFEDC"),
    "ل": ("\uFEDD", "\uFEDE", "\uFEDF", "\uFEE0"),
    "م": ("\uFEE1", "\uFEE2", "\uFEE3", "\uFEE4"),
    "ن": ("\uFEE5", "\uFEE6", "\uFEE7", "\uFEE8"),
    "ه": ("\uFEE9", "\uFEEA", "\uFEEB", "\uFEEC"),
    "و": ("\uFEED", "\uFEEE", None, None),
    "ى": ("\uFEEF", "\uFEF0", None, None),
    "ي": ("\uFEF1", "\uFEF2", "\uFEF3", "\uFEF4"),
    "ئ": ("\uFE89", "\uFE8A", "\uFE8B", "\uFE8C"),
    "ؤ": ("\uFE85", "\uFE86", None, None),
    "ء": ("\uFE80", "\uFE80", None, None),
    "ة": ("\uFE93", "\uFE94", None, None),
}


def _parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser()
    parser.add_argument("kind")
    parser.add_argument("payload_path")
    parser.add_argument("output_path")
    parser.add_argument("template_path", nargs="?")
    return parser.parse_args()


def _to_decimal(value) -> Decimal:
    return Decimal(str(value or "0"))


def _to_float(value) -> float:
    return float(_to_decimal(value))


def _parse_date(value: str) -> datetime:
    return datetime.fromisoformat(value)


def _format_date(value: str | None) -> str:
    if not value:
        return ""
    return _parse_date(value).strftime("%d %b %Y")


def _format_datetime(value: str) -> str:
    return _parse_date(value).strftime("%d %b %Y %H:%M")


def _format_money(value) -> str:
    return f"{_to_decimal(value):,.2f}"


def _get_arabic_font_name() -> str | None:
    global _ARABIC_FONT_NAME
    if _ARABIC_FONT_NAME:
        return _ARABIC_FONT_NAME

    for candidate in ARABIC_FONT_CANDIDATES:
        if not candidate.exists():
            continue
        try:
            font_name = "VeritasArabic"
            pdfmetrics.registerFont(TTFont(font_name, str(candidate)))
            _ARABIC_FONT_NAME = font_name
            return _ARABIC_FONT_NAME
        except Exception:
            continue
    return None


def _contains_arabic(text: str | None) -> bool:
    if not text:
        return False
    return any("\u0600" <= char <= "\u06FF" for char in text)


def _arabic_can_connect_next(char: str) -> bool:
    forms = ARABIC_PRESENTATION_FORMS.get(char)
    return bool(forms and forms[2] and forms[3])


def _arabic_can_connect_prev(char: str) -> bool:
    forms = ARABIC_PRESENTATION_FORMS.get(char)
    return bool(forms and forms[1])


def _shape_arabic_text(text: str | None) -> str:
    if not text:
        return ""
    characters = list(text)
    shaped: list[str] = []
    for index, char in enumerate(characters):
        forms = ARABIC_PRESENTATION_FORMS.get(char)
        if not forms:
            shaped.append(char)
            continue

        prev_char = characters[index - 1] if index > 0 else ""
        next_char = characters[index + 1] if index + 1 < len(characters) else ""
        joins_prev = prev_char in ARABIC_PRESENTATION_FORMS and _arabic_can_connect_next(prev_char) and _arabic_can_connect_prev(char)
        joins_next = next_char in ARABIC_PRESENTATION_FORMS and _arabic_can_connect_next(char) and _arabic_can_connect_prev(next_char)

        isolated, final, initial, medial = forms
        if joins_prev and joins_next and medial:
            shaped.append(medial)
        elif joins_prev and final:
            shaped.append(final)
        elif joins_next and initial:
            shaped.append(initial)
        else:
            shaped.append(isolated)

    return "".join(reversed(shaped))


def _clear_row_values(ws, row: int):
    for column in range(1, 13):
        ws.cell(row=row, column=column).value = None


def _copy_row_style(ws, source_row: int, target_row: int):
    ws.row_dimensions[target_row].height = ws.row_dimensions[source_row].height
    for column in range(1, 13):
        source = ws.cell(row=source_row, column=column)
        target = ws.cell(row=target_row, column=column)
        if source.has_style:
            target._style = copy(source._style)
        if source.number_format:
            target.number_format = source.number_format
        if source.font:
            target.font = copy(source.font)
        if source.fill:
            target.fill = copy(source.fill)
        if source.border:
            target.border = copy(source.border)
        if source.alignment:
            target.alignment = copy(source.alignment)
        if source.protection:
            target.protection = copy(source.protection)


def _apply_excel_arabic_cell(cell, *, value: str, horizontal: str | None = None):
    cell.value = value
    copied_font = copy(cell.font)
    copied_font.name = "Arial"
    cell.font = copied_font
    copied_alignment = copy(cell.alignment)
    copied_alignment.readingOrder = 2
    if horizontal:
        copied_alignment.horizontal = horizontal
    cell.alignment = copied_alignment


def _prepare_template_rows(ws, invoice_count: int):
    if invoice_count > TEMPLATE_CAPACITY:
        extra_rows = invoice_count - TEMPLATE_CAPACITY
        ws.insert_rows(INSERTION_ROW, amount=extra_rows)
        for offset in range(extra_rows):
            _copy_row_style(ws, INVOICE_START_ROW + TEMPLATE_CAPACITY - 1, INSERTION_ROW + offset)

    total_row = TOTAL_ROW + max(0, invoice_count - TEMPLATE_CAPACITY)
    for row in range(INVOICE_START_ROW, total_row):
        _clear_row_values(ws, row)
    return total_row


def _write_invoice_row(ws, row: int, invoice: dict):
    ws.cell(row=row, column=1, value=invoice["goods_services"])
    ws.cell(row=row, column=2, value=invoice["transaction_type"])
    ws.cell(row=row, column=3, value=invoice["transaction_number"])
    ws.cell(row=row, column=4, value=_parse_date(invoice["transaction_date"]))
    ws.cell(row=row, column=5, value=invoice["line_item"])
    ws.cell(row=row, column=6, value=invoice["supplier_trn"])
    ws.cell(row=row, column=7, value=invoice["supplier_name"])
    ws.cell(row=row, column=8, value=_to_float(invoice["total_amount_paid"]))
    ws.cell(row=row, column=9, value=f"=H{row}*0.05")
    ws.cell(row=row, column=10, value=_to_float(invoice["vat_approved"]))
    ws.cell(row=row, column=11, value=_to_float(invoice["vat_paid"]))
    ws.cell(row=row, column=12, value=invoice["description"])


def _set_total_row(ws, total_row: int, invoices: list[dict]):
    last_invoice_row = max(INVOICE_START_ROW + len(invoices) - 1, INVOICE_START_ROW + TEMPLATE_CAPACITY - 1)

    ws.cell(row=total_row, column=7, value="Total")
    ws.cell(row=total_row, column=8, value=f"=SUM(H{INVOICE_START_ROW}:H{last_invoice_row})")
    ws.cell(row=total_row, column=9, value=f"=SUM(I{INVOICE_START_ROW}:I{last_invoice_row})")
    ws.cell(row=total_row, column=10, value=f"=SUM(J{INVOICE_START_ROW}:J{last_invoice_row})")
    ws.cell(row=total_row, column=11, value=f"=SUM(K{INVOICE_START_ROW}:K{last_invoice_row})")
    ws.cell(row=total_row, column=12, value=None)


def _build_workbook(*, payload: dict, output_path: Path, template_path: Path, invoices: list[dict]):
    workbook = load_workbook(template_path)
    sheet = workbook[workbook.sheetnames[0]]
    total_row = _prepare_template_rows(sheet, max(len(invoices), 1))

    claim = payload["claim"]
    sheet["D7"] = claim["reference_number"]
    sheet["D8"] = _parse_date(claim["date_received"])
    _apply_excel_arabic_cell(sheet["D9"], value=claim["applicant_name_arabic"], horizontal="right")
    sheet["D10"] = claim["applicant_name_english"]
    _apply_excel_arabic_cell(sheet["G7"], value="الخطوة", horizontal="right")
    _apply_excel_arabic_cell(sheet["L7"], value="التاريخ", horizontal="center")
    _apply_excel_arabic_cell(sheet["G8"], value="البريد الالكتروني الأول لطلب المستندات", horizontal="right")
    _apply_excel_arabic_cell(sheet["G9"], value="البريد الالكتروني الثاني للتذكير", horizontal="right")
    _apply_excel_arabic_cell(
        sheet["G10"],
        value="اعداد / ارسال التقرير النهائي الي الهيئة ومقدم الطلب ( في حال عدم وجود مستندات ناقصة )",
        horizontal="right",
    )
    _apply_excel_arabic_cell(
        sheet["G11"],
        value="اعداد / ارسال التقرير المبدئي الي مقدم الطلب ( في حال وجود مستندات ناقصة )",
        horizontal="right",
    )
    _apply_excel_arabic_cell(sheet["G12"], value="بريد الكتروني للتذكير", horizontal="right")
    _apply_excel_arabic_cell(sheet["G13"], value="اعداد / ارسال التقرير النهائي الي الهيئة ومقدم الطلب", horizontal="right")

    workflow_date_cells = {
        "L8": claim.get("first_document_request_date"),
        "L9": claim.get("second_reminder_date"),
        "L10": claim.get("final_report_without_missing_docs_date"),
        "L11": claim.get("initial_report_with_missing_docs_date"),
        "L12": claim.get("followup_reminder_date"),
        "L13": claim.get("final_report_submission_date"),
    }
    for cell_ref, value in workflow_date_cells.items():
        sheet[cell_ref] = _parse_date(value) if value else None

    for row_index, invoice in enumerate(invoices or [{}], start=INVOICE_START_ROW):
        if invoice:
            _write_invoice_row(sheet, row_index, invoice)

    _set_total_row(sheet, total_row, invoices)
    if getattr(workbook, "calculation", None) is not None:
        workbook.calculation.fullCalcOnLoad = True
        workbook.calculation.forceFullCalc = True
    workbook.save(output_path)


def _table_paragraph(text: str, *, style: ParagraphStyle) -> Paragraph:
    return Paragraph(escape(text or ""), style)


def _pdf_styles():
    styles = getSampleStyleSheet()
    styles.add(
        ParagraphStyle(
            name="ReportTitle",
            parent=styles["Title"],
            fontName="Helvetica-Bold",
            fontSize=18,
            leading=22,
            textColor=TEXT,
            alignment=TA_LEFT,
            spaceAfter=10,
        )
    )
    styles.add(
        ParagraphStyle(
            name="ReportSubtle",
            parent=styles["BodyText"],
            fontName="Helvetica",
            fontSize=9,
            leading=12,
            textColor=MUTED,
        )
    )
    styles.add(
        ParagraphStyle(
            name="ReportBody",
            parent=styles["BodyText"],
            fontName="Helvetica",
            fontSize=9,
            leading=12,
            textColor=TEXT,
        )
    )
    styles.add(
        ParagraphStyle(
            name="ReportLabel",
            parent=styles["BodyText"],
            fontName="Helvetica-Bold",
            fontSize=7,
            leading=9,
            textColor=MUTED,
            alignment=TA_LEFT,
        )
    )
    styles.add(
        ParagraphStyle(
            name="ReportTableHeader",
            parent=styles["BodyText"],
            fontName="Helvetica-Bold",
            fontSize=6.5,
            leading=7.5,
            textColor=TEXT,
            alignment=TA_CENTER,
        )
    )
    styles.add(
        ParagraphStyle(
            name="ReportCell",
            parent=styles["BodyText"],
            fontName="Helvetica",
            fontSize=6.4,
            leading=7.4,
            textColor=TEXT,
            alignment=TA_LEFT,
        )
    )
    return styles


def _wrap_paragraph(text: str, *, style: ParagraphStyle, fallback: str = "-") -> Paragraph:
    return Paragraph(escape(text or fallback), style)


def _row_height_for_cells(
    cells: list,
    col_widths: list[float],
    *,
    min_height: float,
    vertical_padding: float = 6,
) -> float:
    measured_height = min_height
    for cell, width in zip(cells, col_widths):
        if isinstance(cell, Paragraph):
            _, cell_height = cell.wrap(width, 10_000)
            measured_height = max(measured_height, cell_height + vertical_padding)
    return measured_height


def _format_generated_stamp(value: datetime | None = None) -> str:
    stamp = value or datetime.now()
    return stamp.strftime("%d/%m/%Y, %H:%M:%S")


def _build_verification_report_pdf(*, payload: dict, output_path: Path):
    claim = payload["claim"]
    invoices = payload["invoices"]
    totals = claim["totals"]
    claim_reference = str(claim["reference_number"]).upper()
    page_size = landscape(letter)
    page_width, page_height = page_size
    left_margin = 12
    right_margin = 12
    top_margin = 10
    bottom_margin = 12
    usable_width = page_width - left_margin - right_margin

    title_style = ParagraphStyle(
        name="VerificationPdfTitle",
        fontName="Helvetica-Bold",
        fontSize=11,
        leading=13,
        textColor=colors.white,
        alignment=TA_CENTER,
    )
    header_label_style = ParagraphStyle(
        name="VerificationPdfHeaderLabel",
        fontName="Helvetica-Bold",
        fontSize=6.3,
        leading=7.2,
        textColor=TEXT,
    )
    header_value_style = ParagraphStyle(
        name="VerificationPdfHeaderValue",
        fontName="Helvetica-Bold",
        fontSize=6.3,
        leading=7.2,
        textColor=TEXT,
    )
    arabic_font_name = _get_arabic_font_name() or "Helvetica-Bold"
    header_arabic_label_style = ParagraphStyle(
        name="VerificationPdfHeaderArabicLabel",
        fontName=arabic_font_name,
        fontSize=7.0,
        leading=8.0,
        textColor=TEXT,
        alignment=TA_RIGHT,
    )
    header_arabic_header_style = ParagraphStyle(
        name="VerificationPdfHeaderArabicHeader",
        fontName=arabic_font_name,
        fontSize=7.0,
        leading=8.0,
        textColor=TEXT,
        alignment=TA_CENTER,
    )
    header_value_arabic_style = ParagraphStyle(
        name="VerificationPdfHeaderValueArabic",
        fontName=arabic_font_name,
        fontSize=7.2,
        leading=8.4,
        textColor=TEXT,
        alignment=TA_RIGHT,
    )
    invoice_header_style = ParagraphStyle(
        name="VerificationPdfInvoiceHeader",
        fontName="Helvetica-Bold",
        fontSize=5.8,
        leading=6.6,
        textColor=TEXT,
        alignment=TA_CENTER,
    )
    invoice_cell_style = ParagraphStyle(
        name="VerificationPdfInvoiceCell",
        fontName="Helvetica",
        fontSize=5.8,
        leading=6.6,
        textColor=TEXT,
    )
    footer_label_style = ParagraphStyle(
        name="VerificationPdfFooterLabel",
        fontName="Helvetica-Bold",
        fontSize=6.2,
        leading=7.2,
        textColor=MUTED,
        alignment=TA_CENTER,
    )
    footer_value_style = ParagraphStyle(
        name="VerificationPdfFooterValue",
        fontName="Helvetica-Bold",
        fontSize=6.5,
        leading=7.6,
        textColor=TEXT,
        alignment=TA_CENTER,
    )

    document = SimpleDocTemplate(
        str(output_path),
        pagesize=page_size,
        leftMargin=left_margin,
        rightMargin=right_margin,
        topMargin=top_margin,
        bottomMargin=bottom_margin,
    )

    title_band = Table(
        [[Paragraph("Local Housing VAT Verification Report", title_style)]],
        colWidths=[usable_width],
        rowHeights=[16],
    )
    title_band.setStyle(
        TableStyle(
            [
                ("BACKGROUND", (0, 0), (-1, -1), BRAND_BLUE),
                ("PADDING", (0, 0), (-1, -1), 0),
                ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
            ]
        )
    )

    def _ar(text: str) -> str:
        return _shape_arabic_text(text)

    info_rows = [
        [
            "A",
            Paragraph("Claim Reference Number", header_label_style),
            Paragraph(claim_reference, header_value_style),
            Paragraph(_ar("الخطوة"), header_arabic_header_style),
            Paragraph(_ar("التاريخ"), header_arabic_header_style),
        ],
        [
            "B",
            Paragraph("Date Received", header_label_style),
            Paragraph(_format_date(claim["date_received"]), header_value_style),
            Paragraph(_ar("البريد الالكتروني الأول لطلب المستندات"), header_arabic_label_style),
            Paragraph(_format_date(claim["first_document_request_date"]), header_value_style),
        ],
        [
            "C",
            Paragraph("Applicant Name (Arabic)", header_label_style),
            Paragraph(
                _shape_arabic_text(claim["applicant_name_arabic"]) if _contains_arabic(claim["applicant_name_arabic"]) else (claim["applicant_name_arabic"] or "-"),
                header_value_arabic_style,
            ),
            Paragraph(_ar("البريد الالكتروني الثاني للتذكير"), header_arabic_label_style),
            Paragraph(_format_date(claim["second_reminder_date"]), header_value_style),
        ],
        [
            "D",
            Paragraph("Applicant Name (English)", header_label_style),
            Paragraph(claim["applicant_name_english"], header_value_style),
            Paragraph(_ar("اعداد / ارسال التقرير النهائي الي الهيئة ومقدم الطلب ( في حال عدم وجود مستندات ناقصة )"), header_arabic_label_style),
            Paragraph(_format_date(claim["final_report_without_missing_docs_date"]), header_value_style),
        ],
        [
            "E",
            Paragraph("", header_label_style),
            Paragraph("", header_value_style),
            Paragraph(_ar("اعداد / ارسال التقرير المبدئي الي مقدم الطلب ( في حال وجود مستندات ناقصة )"), header_arabic_label_style),
            Paragraph(_format_date(claim["initial_report_with_missing_docs_date"]), header_value_style),
        ],
        [
            "F",
            Paragraph("", header_label_style),
            Paragraph("", header_value_style),
            Paragraph(_ar("بريد الكتروني للتذكير"), header_arabic_label_style),
            Paragraph(_format_date(claim["followup_reminder_date"]), header_value_style),
        ],
        [
            "G",
            Paragraph("", header_label_style),
            Paragraph("", header_value_style),
            Paragraph(_ar("اعداد / ارسال التقرير النهائي الي الهيئة ومقدم الطلب"), header_arabic_label_style),
            Paragraph(_format_date(claim["final_report_submission_date"]), header_value_style),
        ],
    ]
    info_table = Table(
        info_rows,
        colWidths=[14, 112, 138, 270, usable_width - (14 + 112 + 138 + 270)],
        rowHeights=[11, 11, 11, 11, 11, 11, 11],
    )
    info_table.setStyle(
        TableStyle(
            [
                ("BACKGROUND", (0, 0), (-1, -1), colors.HexColor("#E7E7E7")),
                ("BACKGROUND", (0, 0), (0, -1), colors.HexColor("#F4F4F4")),
                ("GRID", (0, 0), (-1, -1), 0.45, colors.HexColor("#9A9A9A")),
                ("TEXTCOLOR", (0, 0), (-1, -1), TEXT),
                ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
                ("FONTSIZE", (0, 0), (0, -1), 6.2),
                ("ALIGN", (0, 0), (0, -1), "CENTER"),
                ("PADDING", (0, 0), (-1, -1), 3),
                ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
            ]
        )
    )

    invoice_col_widths = [
        49,
        48,
        50,
        45,
        28,
        60,
        116,
        56,
        45,
        52,
        45,
        usable_width - (49 + 48 + 50 + 45 + 28 + 60 + 116 + 56 + 45 + 52 + 45),
    ]

    table_rows = [[
        Paragraph("Goods and services for which you are claiming VAT", invoice_header_style),
        Paragraph("Transaction Type", invoice_header_style),
        Paragraph("Transaction Number", invoice_header_style),
        Paragraph("Transaction Date", invoice_header_style),
        Paragraph("Line Item", invoice_header_style),
        Paragraph("TRN of the Supplier", invoice_header_style),
        Paragraph("Supplier's Name", invoice_header_style),
        Paragraph("Total Taxable Amount", invoice_header_style),
        Paragraph("VAT Amount", invoice_header_style),
        Paragraph("VAT Approved", invoice_header_style),
        Paragraph("VAT Paid", invoice_header_style),
        Paragraph("Description", invoice_header_style),
    ]]
    invoice_row_heights = [
        _row_height_for_cells(table_rows[0], invoice_col_widths, min_height=36, vertical_padding=8)
    ]

    padded_invoices = list(invoices)
    minimum_visible_rows = 10
    while len(padded_invoices) < minimum_visible_rows:
        padded_invoices.append(None)

    for invoice in padded_invoices:
        if invoice is None:
            table_rows.append([""] * 12)
            invoice_row_heights.append(13)
            continue
        row_cells = [
            Paragraph(str(invoice["goods_services"] or "").upper(), invoice_cell_style),
            Paragraph(invoice["transaction_type"], invoice_cell_style),
            Paragraph(invoice["transaction_number"], invoice_cell_style),
            Paragraph(_format_date(invoice["transaction_date"]), invoice_cell_style),
            Paragraph(str(invoice["line_item"]), invoice_cell_style),
            Paragraph(invoice["supplier_trn"], invoice_cell_style),
            Paragraph(invoice["supplier_name"], invoice_cell_style),
            Paragraph(_format_money(invoice["total_amount_paid"]), invoice_cell_style),
            Paragraph(_format_money(invoice["vat_amount"]), invoice_cell_style),
            Paragraph(_format_money(invoice["vat_approved"]), invoice_cell_style),
            Paragraph(_format_money(invoice["vat_paid"]), invoice_cell_style),
            Paragraph(invoice["description"] or "", invoice_cell_style),
        ]
        table_rows.append(row_cells)
        invoice_row_heights.append(
            _row_height_for_cells(row_cells, invoice_col_widths, min_height=15, vertical_padding=7)
        )

    total_row = [
        "",
        "",
        "",
        "",
        "",
        "",
        Paragraph("Total", invoice_header_style),
        Paragraph(_format_money(totals["total_amount_paid"]), invoice_header_style),
        Paragraph(_format_money(totals["total_vat_amount"]), invoice_header_style),
        Paragraph(_format_money(totals["total_vat_approved"]), invoice_header_style),
        Paragraph(_format_money(totals["total_vat_paid"]), invoice_header_style),
        "",
    ]
    table_rows.append(total_row)
    invoice_row_heights.append(
        _row_height_for_cells(total_row, invoice_col_widths, min_height=14, vertical_padding=6)
    )

    invoice_table = Table(
        table_rows,
        colWidths=invoice_col_widths,
        rowHeights=invoice_row_heights,
    )
    invoice_table.setStyle(
        TableStyle(
            [
                ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#DADADA")),
                ("BACKGROUND", (9, 0), (9, 0), colors.HexColor("#FFF200")),
                ("BACKGROUND", (6, -1), (10, -1), colors.HexColor("#DADADA")),
                ("BACKGROUND", (9, -1), (9, -1), colors.HexColor("#FFF200")),
                ("GRID", (0, 0), (-1, -1), 0.45, colors.HexColor("#9A9A9A")),
                ("TEXTCOLOR", (0, 0), (-1, -1), TEXT),
                ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
                ("FONTNAME", (0, -1), (-1, -1), "Helvetica-Bold"),
                ("LEFTPADDING", (0, 0), (-1, -1), 3),
                ("RIGHTPADDING", (0, 0), (-1, -1), 3),
                ("TOPPADDING", (0, 0), (-1, -1), 3),
                ("BOTTOMPADDING", (0, 0), (-1, -1), 3),
                ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
                ("ALIGN", (7, 1), (10, -1), "RIGHT"),
                ("ALIGN", (7, -1), (10, -1), "RIGHT"),
            ]
        )
    )

    def _paint_white_background(canvas_obj, doc_obj):
        canvas_obj.saveState()
        canvas_obj.setFillColor(colors.white)
        canvas_obj.rect(0, 0, doc_obj.pagesize[0], doc_obj.pagesize[1], stroke=0, fill=1)
        label_y = 150
        left_x = 140
        center_x = (doc_obj.pagesize[0] / 2) - 35
        stamp_center_x = doc_obj.pagesize[0] - 140

        canvas_obj.setFillColor(TEXT)
        canvas_obj.setFont("Helvetica-Bold", 7)
        canvas_obj.drawCentredString(left_x + 35, label_y, "Prepared by:")
        canvas_obj.drawCentredString(center_x + 35, label_y, "Reviewed by:")
        canvas_obj.drawCentredString(stamp_center_x, label_y, "Stamp")
        canvas_obj.restoreState()

    story = [title_band, info_table, Spacer(1, 16), invoice_table]
    document.build(story, onFirstPage=_paint_white_background, onLaterPages=_paint_white_background)


def _build_evidence_cover_pdf(
    *,
    payload: dict,
    invoices: list[dict],
    output_path: Path,
    evidence_title: str,
    evidence_subtitle: str,
    display_invoice_count: int | None = None,
):
    claim = payload["claim"]
    totals = claim["totals"]
    generated_at = _format_generated_stamp()
    claim_reference = str(claim["reference_number"]).upper()

    page_width, page_height = A4
    left = 18 * mm
    right = page_width - (18 * mm)
    top = page_height - (18 * mm)

    pdf = pdfcanvas.Canvas(str(output_path), pagesize=A4)
    pdf.setTitle(evidence_title)

    _build_evidence_cover_page_on_canvas(
        pdf=pdf,
        claim=claim,
        claim_reference=claim_reference,
        totals=totals,
        invoices=invoices,
        generated_at=generated_at,
        evidence_title=evidence_title,
        evidence_subtitle=evidence_subtitle,
        display_invoice_count=display_invoice_count,
    )
    pdf.save()


def _truncate_label(value: str, limit: int = 32) -> str:
    text = str(value or "").strip()
    if len(text) <= limit:
        return text
    return f"{text[: limit - 3].rstrip()}..."


def _build_summary_intro_pdf(
    *,
    payload: dict,
    invoices: list[dict],
    output_path: Path,
    pdf_title: str,
    page_title: str,
    page_subtitle: str,
    display_invoice_count: int | None = None,
):
    claim = payload["claim"]
    generated_at = _format_generated_stamp()

    pdf = pdfcanvas.Canvas(str(output_path), pagesize=A4)
    pdf.setTitle(pdf_title)
    _build_summary_page_on_canvas(
        pdf=pdf,
        claim=claim,
        invoices=invoices,
        generated_at=generated_at,
        page_title=page_title,
        page_subtitle=page_subtitle,
        display_invoice_count=display_invoice_count,
    )
    pdf.save()


def _build_top_five_intro_pdf(*, payload: dict, invoices: list[dict], output_path: Path):
    _build_summary_intro_pdf(
        payload=payload,
        invoices=invoices,
        output_path=output_path,
        pdf_title="Top 5 Invoices & Payment Proofs",
        page_title="Top 5 Evidence",
        page_subtitle="Top 5 invoices with matching payment proofs",
    )


def _build_combined_evidence_intro_pdf(*, payload: dict, invoices: list[dict], output_path: Path):
    _build_summary_intro_pdf(
        payload=payload,
        invoices=invoices,
        output_path=output_path,
        pdf_title="All Invoices & Payment Proofs",
        page_title="Combined Evidence",
        page_subtitle="All tax invoices first, then all payment proofs",
        display_invoice_count=len(invoices),
    )


def _build_separator_pdf(*, output_path: Path, title: str, subtitle: str):
    page_height = A4[1]
    left = 18 * mm
    top = page_height - (18 * mm)

    pdf = pdfcanvas.Canvas(str(output_path), pagesize=A4)
    _draw_separator_page(
        pdf,
        left=left,
        top=top,
        title=title,
        subtitle=subtitle,
    )
    pdf.save()


def _draw_separator_page(pdf, *, left: float, top: float, title: str, subtitle: str):
    page_width, page_height = A4
    _paint_dark_page(pdf)
    title_y = (page_height / 2) - 22
    divider_y = title_y - 18
    subtitle_y = divider_y - 22

    pdf.setFillColor(TITLE_SHADOW)
    pdf.setFont("Helvetica-Bold", 32)
    pdf.drawString(left, title_y, title)
    pdf.setStrokeColor(ACCENT_LINE)
    pdf.setLineWidth(1)
    pdf.line(0, divider_y, page_width, divider_y)
    pdf.setFillColor(PAGE_MUTED)
    pdf.setFont("Helvetica-Bold", 12)
    pdf.drawString(left, subtitle_y, subtitle)
    pdf.showPage()


def _draw_evidence_footer(pdf, left: float, right: float):
    footer_y = 18 * mm
    pdf.setStrokeColor(SOFT_LINE)
    pdf.line(left, footer_y + 8, right, footer_y + 8)
    pdf.setFillColor(PAGE_MUTED)
    pdf.setFont("Helvetica", 8)
    pdf.drawString(left, footer_y, "Generated by Veritas VAT Verification System · For internal departmental use")
    pdf.setFont("Helvetica-Bold", 8)
    pdf.setFillColor(ACCENT_LINE)
    pdf.drawRightString(right, footer_y, "CONFIDENTIAL")


def _paint_dark_page(pdf):
    page_width, page_height = A4
    pdf.saveState()
    pdf.setFillColor(PAGE_BG)
    pdf.rect(0, 0, page_width, page_height, stroke=0, fill=1)
    pdf.restoreState()


def _draw_page_band_header(pdf, *, left: float, right: float, top: float, title: str, right_text: str):
    page_width, _ = A4
    pdf.setFillColor(TEXT)
    pdf.setFont("Helvetica-Bold", 17)
    pdf.drawString(left, top, title)
    pdf.setFont("Helvetica-Bold", 14)
    pdf.drawRightString(right, top - 2, right_text)
    pdf.setStrokeColor(ACCENT_LINE)
    pdf.setLineWidth(1)
    pdf.line(0, top - 30, page_width, top - 30)


def _build_evidence_cover_page_on_canvas(
    *,
    pdf,
    claim: dict,
    claim_reference: str,
    totals: dict,
    invoices: list[dict],
    generated_at: str,
    evidence_title: str,
    evidence_subtitle: str,
    display_invoice_count: int | None = None,
):
    page_width, page_height = A4
    left = 18 * mm
    right = page_width - (18 * mm)
    top = page_height - (18 * mm)

    _paint_dark_page(pdf)
    _draw_page_band_header(pdf, left=left, right=right, top=top, title="VERITAS", right_text=claim_reference)
    pdf.setFillColor(PAGE_MUTED)
    pdf.setFont("Helvetica", 10)
    pdf.drawString(left, top - 16, "VAT Verification System")

    pdf.setFillColor(PAGE_MUTED)
    pdf.setFont("Helvetica-Bold", 14)
    pdf.drawString(left, top - 126, "Local Housing")
    pdf.setFillColor(TITLE_SHADOW)
    pdf.setFont("Helvetica-Bold", 34)
    pdf.drawString(left, top - 168, evidence_title)
    pdf.setFillColor(PAGE_MUTED)
    pdf.setFont("Helvetica-Oblique", 13)
    pdf.drawString(left, top - 194, evidence_subtitle)

    meta_top = top - 284
    label_x = left
    value_x = left + 54 * mm
    row_gap = 23
    meta_items = [
        ("CLAIM REFERENCE", claim_reference),
        ("APPLICANT (ENGLISH)", claim["applicant_name_english"]),
        ("DATE RECEIVED", _format_date(claim["date_received"])),
        ("INVOICES", f"{display_invoice_count if display_invoice_count is not None else len(invoices)} line items"),
        ("GENERATED", generated_at),
    ]
    for index, (label, value) in enumerate(meta_items):
        y = meta_top - (index * row_gap)
        pdf.setFillColor(PAGE_MUTED)
        pdf.setFont("Helvetica-Bold", 8)
        pdf.drawString(label_x, y, label)
        pdf.setFillColor(TEXT)
        pdf.setFont("Helvetica-Bold", 11 if index != 1 else 10)
        pdf.drawString(value_x, y, str(value))

    panel_x = left
    panel_y = 58 * mm
    panel_w = right - left
    panel_h = 36 * mm
    pdf.setFillColor(CREAM_PANEL)
    pdf.setStrokeColor(CREAM_PANEL)
    pdf.roundRect(panel_x, panel_y, panel_w, panel_h, 2, stroke=0, fill=1)

    pdf.setFillColor(colors.HexColor("#6D665E"))
    pdf.setFont("Helvetica-Bold", 9)
    pdf.drawString(panel_x + 12, panel_y + panel_h - 16, "VAT TOTALS · AED")

    totals_items = [
        ("Taxable", _format_money(totals["total_amount_paid"])),
        ("VAT", _format_money(totals["total_vat_amount"])),
        ("Approved", _format_money(totals["total_vat_approved"])),
        ("Paid", _format_money(totals["total_vat_paid"])),
    ]
    column_width = panel_w / 4
    for index, (label, value) in enumerate(totals_items):
        x = panel_x + (index * column_width) + 12
        pdf.setFillColor(colors.HexColor("#6D665E"))
        pdf.setFont("Helvetica", 8.5)
        pdf.drawString(x, panel_y + 18, label)
        pdf.setFillColor(TEXT)
        pdf.setFont("Helvetica-Bold", 15)
        pdf.drawString(x, panel_y + 2, value)

    _draw_evidence_footer(pdf, left, right)
    pdf.showPage()


def _build_supporting_documents_pdf(*, payload: dict, output_path: Path):
    invoices = [
        invoice
        for invoice in payload["invoices"]
        if _to_decimal(invoice["vat_approved"]) > 0
    ]
    intro_path = output_path.with_name(f"{output_path.stem}-intro.pdf")
    invoice_paths: list[Path] = []
    proof_paths: list[Path] = []

    for invoice in invoices:
        for document_meta in sorted(
            (
                document
                for document in invoice["documents"]
                if document["category"] == "invoice"
            ),
            key=lambda document: (document["created_at"], document["original_name"].lower()),
        ):
            file_path = Path(document_meta["file_path"])
            if file_path.exists():
                invoice_paths.append(file_path)

        for document_meta in sorted(
            (
                document
                for document in invoice["documents"]
                if document["category"] == "payment_proof"
            ),
            key=lambda document: (document["created_at"], document["original_name"].lower()),
        ):
            file_path = Path(document_meta["file_path"])
            if file_path.exists():
                proof_paths.append(file_path)

    _build_combined_evidence_intro_pdf(payload=payload, invoices=invoices, output_path=intro_path)

    if not invoice_paths and not proof_paths:
        intro_path.replace(output_path)
        return

    writer = PdfWriter()
    writer.append(str(intro_path))
    for pdf_path in invoice_paths:
        writer.append(str(pdf_path))
    for pdf_path in proof_paths:
        writer.append(str(pdf_path))
    with output_path.open("wb") as output_file:
        writer.write(output_file)
    writer.close()
    intro_path.unlink(missing_ok=True)


def _build_document_bundle_pdf(
    *,
    payload: dict,
    invoices: list[dict],
    output_path: Path,
    evidence_title: str,
    evidence_subtitle: str,
    document_order: str,
):
    cover_path = output_path.with_name(f"{output_path.stem}-cover.pdf")
    invoice_separator_path = output_path.with_name(f"{output_path.stem}-invoice-separator.pdf")
    proof_separator_path = output_path.with_name(f"{output_path.stem}-proof-separator.pdf")
    supporting_paths: list[Path] = []
    invoice_paths: list[Path] = []
    proof_paths: list[Path] = []
    if document_order == "grouped":
        for invoice in invoices:
            invoice_documents = sorted(
                (
                    document
                    for document in invoice["documents"]
                    if document["category"] == "invoice"
                ),
                key=lambda document: (document["created_at"], document["original_name"].lower()),
            )
            for document_meta in invoice_documents:
                file_path = Path(document_meta["file_path"])
                if file_path.exists():
                    invoice_paths.append(file_path)

            proof_documents = sorted(
                (
                    document
                    for document in invoice["documents"]
                    if document["category"] == "payment_proof"
                ),
                key=lambda document: (document["created_at"], document["original_name"].lower()),
            )
            for document_meta in proof_documents:
                file_path = Path(document_meta["file_path"])
                if file_path.exists():
                    proof_paths.append(file_path)
    else:
        for invoice in invoices:
            ordered_documents = sorted(
                invoice["documents"],
                key=lambda document: (
                    0 if document["category"] == "invoice" else 1,
                    document["created_at"],
                    document["original_name"].lower(),
                ),
            )
            for document_meta in ordered_documents:
                file_path = Path(document_meta["file_path"])
                if file_path.exists():
                    supporting_paths.append(file_path)

    _build_evidence_cover_pdf(
        payload=payload,
        invoices=invoices,
        output_path=cover_path,
        evidence_title=evidence_title,
        evidence_subtitle=evidence_subtitle,
    )

    if document_order == "grouped":
        if not invoice_paths and not proof_paths:
            cover_path.replace(output_path)
            return
    else:
        if not supporting_paths:
            cover_path.replace(output_path)
            return

    writer = PdfWriter()
    writer.append(str(cover_path))
    if document_order == "grouped":
        if invoice_paths:
            _build_separator_pdf(
                output_path=invoice_separator_path,
                title="TAX INVOICES",
                subtitle=f"{len(invoice_paths)} documents",
            )
            writer.append(str(invoice_separator_path))
            for pdf_path in invoice_paths:
                writer.append(str(pdf_path))
        if proof_paths:
            _build_separator_pdf(
                output_path=proof_separator_path,
                title="PAYMENT PROOFS",
                subtitle=f"{len(proof_paths)} documents",
            )
            writer.append(str(proof_separator_path))
            for pdf_path in proof_paths:
                writer.append(str(pdf_path))
    else:
        for pdf_path in supporting_paths:
            writer.append(str(pdf_path))
    with output_path.open("wb") as output_file:
        writer.write(output_file)
    writer.close()
    cover_path.unlink(missing_ok=True)
    invoice_separator_path.unlink(missing_ok=True)
    proof_separator_path.unlink(missing_ok=True)


def _top_five_vat_invoices(payload: dict) -> list[dict]:
    invoices = [
        invoice
        for invoice in payload["invoices"]
        if _to_decimal(invoice["vat_approved"]) > 0
    ]
    invoices.sort(
        key=lambda invoice: (
            _to_decimal(invoice["vat_approved"]),
            _to_decimal(invoice["total_amount_paid"]),
            invoice["transaction_date"],
        ),
        reverse=True,
    )
    return invoices[:5]


def _build_top_five_supporting_documents_pdf(*, payload: dict, output_path: Path):
    top_five_invoices = _top_five_vat_invoices(payload)
    intro_path = output_path.with_name(f"{output_path.stem}-intro.pdf")

    invoice_paths: list[Path] = []
    proof_paths: list[Path] = []
    for invoice in top_five_invoices:
      for document_meta in sorted(
          (document for document in invoice["documents"] if document["category"] == "invoice"),
          key=lambda document: (document["created_at"], document["original_name"].lower()),
      ):
          file_path = Path(document_meta["file_path"])
          if file_path.exists():
              invoice_paths.append(file_path)
      for document_meta in sorted(
          (document for document in invoice["documents"] if document["category"] == "payment_proof"),
          key=lambda document: (document["created_at"], document["original_name"].lower()),
      ):
          file_path = Path(document_meta["file_path"])
          if file_path.exists():
              proof_paths.append(file_path)

    _build_top_five_intro_pdf(payload=payload, invoices=top_five_invoices, output_path=intro_path)

    # If there are no supporting documents at all, still return the summary page
    # rather than bubbling an avoidable export error.
    if not invoice_paths and not proof_paths:
        intro_path.replace(output_path)
        return

    writer = PdfWriter()
    writer.append(str(intro_path))
    for pdf_path in invoice_paths:
        writer.append(str(pdf_path))
    for pdf_path in proof_paths:
        writer.append(str(pdf_path))
    with output_path.open("wb") as output_file:
        writer.write(output_file)
    writer.close()
    intro_path.unlink(missing_ok=True)


def _build_summary_page_on_canvas(
    *,
    pdf,
    claim: dict,
    invoices: list[dict],
    generated_at: str,
    page_title: str,
    page_subtitle: str,
    display_invoice_count: int | None = None,
):
    page_width, page_height = A4
    left = 18 * mm
    right = page_width - (18 * mm)
    top = page_height - (22 * mm)

    pdf.setFillColor(colors.white)
    pdf.rect(0, 0, page_width, page_height, stroke=0, fill=1)

    pdf.setFillColor(colors.HexColor("#1F2937"))
    pdf.setFont("Helvetica-Bold", 30)
    pdf.drawString(left, top, page_title)

    pdf.setFillColor(colors.HexColor("#6B7280"))
    pdf.setFont("Helvetica-Oblique", 12)
    pdf.drawString(left, top - 20, page_subtitle)

    summary_rows = [
        ("CLAIM REFERENCE", str(claim["reference_number"])),
        ("APPLICANT (ENGLISH)", str(claim["applicant_name_english"] or "-")),
        ("DATE RECEIVED", _format_date(claim["date_received"])),
        (
            "INVOICES",
            f"{display_invoice_count if display_invoice_count is not None else len(invoices)} line item{'s' if (display_invoice_count if display_invoice_count is not None else len(invoices)) != 1 else ''}",
        ),
        ("GENERATED", generated_at),
    ]

    summary_table = Table(
        summary_rows,
        colWidths=[62 * mm, (right - left) - (62 * mm)],
        rowHeights=[15 * mm] * len(summary_rows),
    )
    summary_table.setStyle(
        TableStyle(
            [
                ("GRID", (0, 0), (-1, -1), 0, colors.white),
                ("BOX", (0, 0), (-1, -1), 0, colors.white),
                ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
                ("PADDING", (0, 0), (-1, -1), 0),
                ("LEFTPADDING", (0, 0), (0, -1), 0),
                ("RIGHTPADDING", (0, 0), (0, -1), 10),
                ("LEFTPADDING", (1, 0), (1, -1), 0),
                ("TEXTCOLOR", (0, 0), (0, -1), colors.HexColor("#5B6B7A")),
                ("TEXTCOLOR", (1, 0), (1, -1), colors.HexColor("#1F2937")),
                ("FONTNAME", (0, 0), (0, -1), "Helvetica-Bold"),
                ("FONTNAME", (1, 0), (1, -1), "Helvetica-Bold"),
                ("FONTSIZE", (0, 0), (0, -1), 9),
                ("FONTSIZE", (1, 0), (1, -1), 11),
            ]
        )
    )
    table_width, table_height = summary_table.wrap(0, 0)
    summary_table.drawOn(pdf, left, top - 55 - table_height)
    pdf.showPage()


def main():
    args = _parse_args()
    payload = json.loads(Path(args.payload_path).read_text(encoding="utf-8"))
    output_path = Path(args.output_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    if args.kind == "verification_report_pdf":
        _build_verification_report_pdf(payload=payload, output_path=output_path)
        return
    if args.kind == "supporting_documents_pdf":
        _build_supporting_documents_pdf(payload=payload, output_path=output_path)
        return
    if args.kind == "top5_supporting_documents_pdf":
        _build_top_five_supporting_documents_pdf(payload=payload, output_path=output_path)
        return

    if not args.template_path:
        raise ExportRuntimeError("Workbook template path is required for Excel exports.")

    template_path = Path(args.template_path)
    if args.kind == "verification_report_xlsx":
        _build_workbook(payload=payload, output_path=output_path, template_path=template_path, invoices=payload["invoices"])
        return
    if args.kind == "vat_top5_xlsx":
        _build_workbook(
            payload=payload,
            output_path=output_path,
            template_path=template_path,
            invoices=_top_five_vat_invoices(payload),
        )
        return

    raise ExportRuntimeError(f"Unsupported export kind: {args.kind}")


if __name__ == "__main__":
    main()
