#!/usr/bin/env python3
"""
Rebuild the generated appendix of docs/pdfs/TECHNICAL_OVERVIEW_UKINNECT_BACKEND.pdf:

1) End-to-End Technical Architecture — heading plus docs/diagrams/ukinnect-01-overall-topology.png
2) Flutter client appendix (styled like backend typography)

Replaces everything from the first page that contains the Flutter appendix title through
the end of the file (so adding architecture pages does not truncate backend content).

Typography matches the backend overview (sampled from page 1): Calibri-Bold title
#365F91, Calibri-Bold section headings #4F81BD, Cambria 11 pt body black. Uses
Windows fonts when available; otherwise Helvetica fallback.
"""
from pathlib import Path

from PIL import Image as PILImage
from pypdf import PdfReader, PdfWriter
from reportlab.lib import colors
from reportlab.lib.pagesizes import letter
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import inch
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
from reportlab.platypus import Image, PageBreak, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle

# Measured from docs/pdfs/TECHNICAL_OVERVIEW_UKINNECT_BACKEND.pdf page 1 (PyMuPDF span colors).
TITLE_BLUE = colors.HexColor("#365F91")  # main title (Calibri-Bold)
HEADING_BLUE = colors.HexColor("#4F81BD")  # "1. Core stack" style (Calibri-Bold)
BODY_BLACK = colors.black


def _register_ms_fonts() -> dict:
    """
    Register Calibri/Cambria like the backend PDF. Falls back to Helvetica on failure
    (e.g. non-Windows CI).
    """
    names = {
        "title": "Helvetica-Bold",
        "heading": "Helvetica-Bold",
        "body": "Helvetica",
        "body_bold": "Helvetica-Bold",
    }
    win = Path(r"C:\Windows\Fonts")
    if not win.is_dir():
        return names
    try:
        pdfmetrics.registerFont(TTFont("Calibri", str(win / "calibri.ttf")))
        pdfmetrics.registerFont(TTFont("Calibri-Bold", str(win / "calibrib.ttf")))
        pdfmetrics.registerFont(TTFont("Cambria", str(win / "cambria.ttc"), subfontIndex=0))
        pdfmetrics.registerFont(TTFont("Cambria-Bold", str(win / "cambriab.ttf")))
        pdfmetrics.registerFontFamily(
            "Cambria",
            normal="Cambria",
            bold="Cambria-Bold",
            italic="Cambria",
            boldItalic="Cambria-Bold",
        )
        names.update(
            {
                "title": "Calibri-Bold",
                "heading": "Calibri-Bold",
                "body": "Cambria",
                "body_bold": "Cambria-Bold",
            }
        )
    except Exception:
        pass
    return names


def _p(text: str, style: ParagraphStyle) -> Paragraph:
    # ReportLab expects XML-ish escapes for <>&
    safe = (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
    )
    return Paragraph(safe, style)


def _bullet(text: str, bullet_style: ParagraphStyle) -> Paragraph:
    """Single bullet line — no HTML; uses ReportLab bulletText."""
    safe = (
        text.replace("&", "&amp;")
        .replace("<", "&lt;")
        .replace(">", "&gt;")
    )
    return Paragraph(safe, bullet_style, bulletText="\u2022")


def _find_appendix_start_page(reader: PdfReader) -> int:
    """
    First page index of the generated appendix (Flutter section title).
    Falls back to last two pages for legacy PDFs without the marker text.
    """
    n = len(reader.pages)
    scan = min(30, n)
    for back in range(scan):
        i = n - 1 - back
        try:
            text = reader.pages[i].extract_text() or ""
        except Exception:
            text = ""
        if "uKinnect Flutter App" in text and "Technical Architecture" in text:
            return i
    return max(0, n - 2)


def _append_architecture_section(
    story: list, title: ParagraphStyle, body: ParagraphStyle, repo: Path
) -> None:
    """Heading + topology PNG (same width as text column)."""
    story.append(_p("End-to-End Technical Architecture", title))
    story.append(
        _p(
            "Topology diagram: users, Route 53, load balancer, Node API, Redis/BullMQ, "
            "MongoDB Atlas, app flows (auth, Stripe, booking, notifications, FCM, SMTP). "
            "Source asset: docs/diagrams/ukinnect-01-overall-topology.png.",
            body,
        )
    )
    story.append(Spacer(1, 0.08 * inch))
    img_path = repo / "docs" / "diagrams" / "ukinnect-01-overall-topology.png"
    if not img_path.is_file():
        raise SystemExit(f"Missing topology image: {img_path}")
    with PILImage.open(img_path) as pil:
        w_px, h_px = pil.size
    content_w = letter[0] - 1.5 * inch  # match SimpleDocTemplate side margins
    scale = content_w / w_px
    draw_h = h_px * scale
    story.append(Image(str(img_path), width=content_w, height=draw_h))
    story.append(Spacer(1, 0.2 * inch))
    story.append(PageBreak())


def build_appendix_pdf(path: Path, repo: Path) -> None:
    fonts = _register_ms_fonts()
    styles = getSampleStyleSheet()
    title = ParagraphStyle(
        "title",
        parent=styles["Heading1"],
        fontName=fonts["title"],
        fontSize=14,
        leading=18,
        textColor=TITLE_BLUE,
        spaceAfter=14,
    )
    h1 = ParagraphStyle(
        "h1",
        parent=styles["Heading2"],
        fontName=fonts["heading"],
        fontSize=13,
        leading=17,
        textColor=HEADING_BLUE,
        spaceBefore=10,
        spaceAfter=6,
    )
    body = ParagraphStyle(
        "body",
        parent=styles["Normal"],
        fontName=fonts["body"],
        fontSize=11,
        leading=14,
        textColor=BODY_BLACK,
        spaceAfter=6,
    )
    bullet_body = ParagraphStyle(
        "bullet_body",
        parent=body,
        leftIndent=14,
        bulletIndent=6,
        spaceAfter=3,
    )
    story = []

    _append_architecture_section(story, title, body, repo)

    story.append(_p("uKinnect Flutter App — Technical Architecture", title))
    story.append(
        _p(
            "This section documents the mobile client (Flutter) architecture: navigation, "
            "state, networking, and integrations. Numbered headings match the backend overview "
            "above.",
            body,
        )
    )
    story.append(Spacer(1, 0.12 * inch))

    story.append(_p("1. Project overview", h1))
    story.append(
        _p(
            "uKinnect is a Flutter application connecting families with caregivers. It supports "
            "authentication, dual roles (family and caretaker), booking lifecycle, payments "
            "(Stripe), chat, tasks, disputes, notifications, and maps integration.",
            body,
        )
    )

    story.append(_p("2. Architecture analysis", h1))
    story.append(
        _p(
            "Feature-based monolithic Flutter structure. GetX is used for state management, "
            "dependency injection, and navigation. Controllers manage business logic; "
            "repositories handle API abstraction.",
            body,
        )
    )

    story.append(_p("3. State management", h1))
    story.append(
        _p(
            "GetX is used with global and feature-level controllers. Reactive programming via "
            "Rx and Obx. GlobalVariable is used for session-level data.",
            body,
        )
    )

    story.append(_p("4. Navigation", h1))
    story.append(_bullet("Entry: GetMaterialApp with splash entry.", bullet_body))
    story.append(_bullet("Routing: Get.to, Get.off, Get.offAll.", bullet_body))
    story.append(_bullet("Flows: PageView for multi-step flows.", bullet_body))
    story.append(_bullet("Deep links: app_links.", bullet_body))

    story.append(_p("5. API and data layer", h1))
    story.append(_bullet("Primary HTTP client: http via NetworkApiServices.", bullet_body))
    story.append(_bullet("Dio used in limited cases.", bullet_body))
    story.append(_bullet("Repositories structured per domain.", bullet_body))
    story.append(_bullet("Models follow JSON serialization.", bullet_body))

    story.append(_p("6. Backend communication", h1))
    story.append(
        _bullet(
            "REST APIs with Bearer token authentication; base URLs via Appurls.",
            bullet_body,
        )
    )
    story.append(
        _bullet(
            "Socket.IO for real-time events (messages, booking updates).",
            bullet_body,
        )
    )

    story.append(_p("7. Local storage", h1))
    story.append(
        _bullet("SharedPreferences for tokens, user data, and flags.", bullet_body),
    )

    story.append(_p("8. Dependency management", h1))
    story.append(
        _bullet("Get.put globally and locally within features.", bullet_body),
    )

    story.append(_p("9. Key modules", h1))
    story.append(
        _bullet(
            "Auth, role selection, family and caretaker modules, booking, payments, chat, "
            "tasks, forms, notifications, disputes, support, maps, media, deep links.",
            bullet_body,
        ),
    )

    story.append(_p("10. Third-party integrations", h1))
    story.append(
        _bullet(
            "Firebase, Stripe, Google Maps, Places API, Socket.IO, WebView, UI libraries.",
            bullet_body,
        ),
    )

    story.append(_p("11. Security practices", h1))
    story.append(_bullet("Bearer token authentication.", bullet_body))
    story.append(_bullet("Token refresh handling.", bullet_body))
    story.append(_bullet("FCM token management.", bullet_body))

    story.append(_p("12. Build and environment", h1))
    story.append(_bullet("Configuration via Appurls constants.", bullet_body))
    story.append(_bullet("Portrait orientation.", bullet_body))
    story.append(_bullet("Theming via GetMaterialApp.", bullet_body))
    story.append(_bullet("Responsive layout with ScreenUtil.", bullet_body))

    story.append(_p("13. Folder structure", h1))
    data = [
        ["Path", "Purpose"],
        ["lib/main.dart", "App bootstrap"],
        ["lib/FamilyFolder/", "Family module"],
        ["lib/caretakerfolder/", "Caretaker module"],
        ["lib/Repositories/", "API layer"],
        ["lib/Network/", "Network services"],
        ["lib/models/", "Data models"],
        ["lib/utils/", "Utilities"],
        ["assets/", "Media resources"],
    ]
    tbl = Table(data, colWidths=[2.6 * inch, 3.9 * inch])
    tbl.setStyle(
        TableStyle(
            [
                ("FONT", (0, 0), (-1, 0), fonts["body_bold"], 11),
                ("FONT", (0, 1), (-1, -1), fonts["body"], 11),
                ("TEXTCOLOR", (0, 0), (-1, -1), BODY_BLACK),
                ("BACKGROUND", (0, 0), (-1, 0), colors.HexColor("#f1f5f9")),
                ("GRID", (0, 0), (-1, -1), 0.4, colors.HexColor("#cbd5e1")),
                ("VALIGN", (0, 0), (-1, -1), "TOP"),
                ("LEFTPADDING", (0, 0), (-1, -1), 6),
                ("RIGHTPADDING", (0, 0), (-1, -1), 6),
                ("TOPPADDING", (0, 0), (-1, -1), 4),
                ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
            ]
        )
    )
    story.append(tbl)

    doc = SimpleDocTemplate(
        str(path),
        pagesize=letter,
        leftMargin=0.75 * inch,
        rightMargin=0.75 * inch,
        topMargin=0.7 * inch,
        bottomMargin=0.7 * inch,
    )
    doc.build(story)


def main() -> None:
    repo = Path(__file__).resolve().parents[1]
    merged = repo / "docs" / "pdfs" / "TECHNICAL_OVERVIEW_UKINNECT_BACKEND.pdf"
    if not merged.exists():
        raise SystemExit(f"Missing merged PDF: {merged}")

    tmp_appendix = repo / "docs" / "pdfs" / "_technical_overview_appendix.pdf"
    build_appendix_pdf(tmp_appendix, repo)

    reader = PdfReader(str(merged))
    n = len(reader.pages)
    if n < 1:
        raise SystemExit("Merged PDF is empty; aborting.")

    start_appendix = _find_appendix_start_page(reader)
    appendix_reader = PdfReader(str(tmp_appendix))
    new_appendix_len = len(appendix_reader.pages)

    writer = PdfWriter()
    for i in range(start_appendix):
        writer.add_page(reader.pages[i])
    for p in appendix_reader.pages:
        writer.add_page(p)

    out = merged
    writer.write(str(out))
    tmp_appendix.unlink(missing_ok=True)
    print(
        f"Updated {out} (kept pages 0..{start_appendix - 1}, "
        f"replaced appendix from page {start_appendix + 1} with {new_appendix_len} new pages)."
    )


if __name__ == "__main__":
    main()
