"""
Server-side one-page directions PDF for INDL-43 (ReportLab).

Mirrors the sales/billing PDF pattern (in-memory BytesIO buffer, no filesystem
writes, no outbound HTTP). All DB-sourced strings are control-character stripped and
HTML-escaped before reaching ReportLab so a poisoned cemetery/section/plot value
cannot corrupt the document or inject markup (A03 / SAC-04). The mini-map is drawn
purely from in-memory coordinates using ReportLab drawing primitives — it fetches no
external assets, closing the SSRF vector (A10 / API7 / SAC-09).
"""
from __future__ import annotations

import html
import io
from typing import TYPE_CHECKING

from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
from reportlab.lib.units import cm
from reportlab.platypus import (
    HRFlowable,
    Paragraph,
    SimpleDocTemplate,
    Spacer,
    Table,
    TableStyle,
)
from reportlab.graphics.shapes import (
    Circle,
    Drawing,
    Line,
    PolyLine,
    Rect,
    String,
)

if TYPE_CHECKING:
    from src.apps.public.services.directions_service import MemorialDirections


def _clean(value: object, max_len: int = 300) -> str:
    """Strip control characters, cap length, and HTML-escape a DB-sourced string."""
    if value is None:
        return ""
    s = str(value)
    # Drop C0/C1 control chars (incl. CR/LF, null) that could corrupt PDF layout.
    s = "".join(ch for ch in s if ch == " " or (ord(ch) >= 32 and ord(ch) != 127))
    s = s[:max_len].strip()
    return html.escape(s)


def _mini_map_drawing(section_label: str) -> Drawing:
    """
    Decorative schematic: four labelled sections, a stylised path, an entrance icon,
    a dashed route curve, and the highlighted plot pin. Static/illustrative — not a
    real routed path (matches the on-screen mini-map's promise).
    """
    w, h = 15 * cm, 7 * cm
    d = Drawing(w, h)

    # Backdrop
    d.add(Rect(0, 0, w, h, fillColor=colors.HexColor("#E8F4E8"), strokeColor=None))

    # Section quadrants A–D
    labels = ["A", "B", "C", "D"]
    cell_w, cell_h = w / 2, h / 2
    idx = 0
    for row in range(2):
        for col in range(2):
            x = col * cell_w
            y = row * cell_h
            d.add(
                Rect(
                    x + 6,
                    y + 6,
                    cell_w - 12,
                    cell_h - 12,
                    fillColor=colors.HexColor("#DCEEDC"),
                    strokeColor=colors.HexColor("#B7D8B7"),
                    strokeWidth=0.8,
                )
            )
            d.add(
                String(
                    x + 14,
                    y + cell_h - 20,
                    labels[idx],
                    fontName="Helvetica-Bold",
                    fontSize=11,
                    fillColor=colors.HexColor("#5A8A5A"),
                )
            )
            idx += 1

    # Dashed central path
    path = Line(
        20, h / 2, w - 20, h / 2, strokeColor=colors.HexColor("#9CC29C"), strokeWidth=1.4
    )
    path.strokeDashArray = [4, 3]
    d.add(path)

    # Entrance icon (bottom-left)
    ent_x, ent_y = 26, 26
    d.add(Circle(ent_x, ent_y, 7, fillColor=colors.HexColor("#2A85B8"), strokeColor=None))
    d.add(
        String(
            ent_x + 12,
            ent_y - 3,
            "Entrance",
            fontName="Helvetica",
            fontSize=8,
            fillColor=colors.HexColor("#2A85B8"),
        )
    )

    # Plot pin (upper-right area)
    pin_x, pin_y = w - 70, h - 40

    # Dashed route curve from entrance to pin (decorative bezier approximation).
    route = PolyLine(
        [
            ent_x,
            ent_y,
            w * 0.35,
            h * 0.30,
            w * 0.55,
            h * 0.62,
            pin_x,
            pin_y,
        ],
        strokeColor=colors.HexColor("#3F7A35"),
        strokeWidth=1.6,
    )
    route.strokeDashArray = [5, 3]
    d.add(route)

    d.add(Circle(pin_x, pin_y, 8, fillColor=colors.HexColor("#1E4A6E"), strokeColor=colors.white, strokeWidth=1.5))
    d.add(
        String(
            pin_x - 4,
            pin_y - 22,
            section_label or "Plot",
            fontName="Helvetica-Bold",
            fontSize=9,
            fillColor=colors.HexColor("#1E4A6E"),
        )
    )
    return d


def build_directions_pdf_bytes(directions: "MemorialDirections") -> bytes:
    """Render the one-page directions PDF and return raw bytes."""
    buf = io.BytesIO()
    doc = SimpleDocTemplate(
        buf,
        pagesize=A4,
        rightMargin=2 * cm,
        leftMargin=2 * cm,
        topMargin=2 * cm,
        bottomMargin=2 * cm,
        title="Directions",
    )

    styles = getSampleStyleSheet()
    title_style = ParagraphStyle(
        "DirTitle",
        parent=styles["Title"],
        fontSize=20,
        textColor=colors.HexColor("#1E4A6E"),
        spaceAfter=4,
    )
    sub_style = ParagraphStyle(
        "DirSub", parent=styles["Normal"], fontSize=10, textColor=colors.HexColor("#556677")
    )
    label_style = ParagraphStyle(
        "DirLabel", parent=styles["Normal"], fontSize=10, textColor=colors.HexColor("#6B7280")
    )
    value_style = ParagraphStyle(
        "DirValue", parent=styles["Normal"], fontSize=11, textColor=colors.HexColor("#111827")
    )

    story: list = []

    cemetery = _clean(directions.cemetery_name) or "Cemetery"
    story.append(Paragraph(cemetery, title_style))
    if directions.cemetery_address:
        story.append(Paragraph(_clean(directions.cemetery_address), sub_style))
    story.append(Spacer(1, 6))
    story.append(Paragraph("Directions to the grave", sub_style))
    story.append(Spacer(1, 8))
    story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor("#E5E7EB")))
    story.append(Spacer(1, 12))

    # Mini-map
    section_label = _clean(directions.section_code or directions.section_name)
    story.append(_mini_map_drawing(f"{section_label} · {_clean(directions.plot_ref)}"))
    story.append(Spacer(1, 14))

    # Metadata table — only include walking time / distance rows when available.
    rows = [
        [Paragraph("Section", label_style), Paragraph(_clean(directions.section_name or directions.section_code) or "—", value_style)],
        [Paragraph("Plot", label_style), Paragraph(_clean(directions.plot_ref) or "—", value_style)],
    ]
    if directions.walking_minutes is not None:
        rows.append([Paragraph("Walking time", label_style), Paragraph(f"~{directions.walking_minutes} min", value_style)])
    if directions.distance_m is not None:
        rows.append([Paragraph("Distance", label_style), Paragraph(f"{directions.distance_m} m", value_style)])

    table = Table(rows, colWidths=[4 * cm, 11 * cm])
    table.setStyle(
        TableStyle(
            [
                ("VALIGN", (0, 0), (-1, -1), "MIDDLE"),
                ("TOPPADDING", (0, 0), (-1, -1), 5),
                ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
                ("LINEBELOW", (0, 0), (-1, -2), 0.5, colors.HexColor("#F3F4F6")),
            ]
        )
    )
    story.append(table)
    story.append(Spacer(1, 16))

    # Visiting hours
    story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor("#E5E7EB")))
    story.append(Spacer(1, 10))
    story.append(Paragraph("Visiting hours", label_style))
    story.append(Paragraph(_clean(directions.visiting_hours) or "—", value_style))

    doc.build(story)
    return buf.getvalue()
