"""Weekly run-sheet PDF generator (INDL-36) using ReportLab.

Mirrors the ReportLab pattern in src/apps/sales/pdf/generator.py
(SimpleDocTemplate/Table/Paragraph -> io.BytesIO).

Security posture:
  * CSV/formula-injection guard applied to EVERY string cell: a value that
    begins with any of  =  +  -  @  \t  \r  is prefixed with a single space so
    a spreadsheet importing the exported PDF (via copy/paste) cannot execute it.
  * PDF metadata (author/creator/subject/title) is blanked so no server-side or
    library identity leaks into the document.
  * Pure sync function — the router invokes it via asyncio.to_thread.
"""
from __future__ import annotations

import io
from datetime import date, timedelta
from typing import Any

# Characters that trigger formula evaluation in spreadsheet software.
_FORMULA_PREFIXES = ("=", "+", "-", "@", "\t", "\r")


def _guard(value: Any) -> str:
    """Coerce to a string cell, neutralising spreadsheet formula-injection AND
    escaping XML entities so ReportLab's ``Paragraph`` parser never chokes on a
    stored value containing ``&``, ``<`` or ``>`` (mirrors sales/pdf/generator).
    """
    if value is None:
        text = ""
    else:
        text = str(value)
    if text and text[0] in _FORMULA_PREFIXES:
        text = " " + text
    # Escape XML entities — ampersand FIRST so we don't double-escape the others.
    text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
    return text


def _fmt_date_time(svc: dict) -> str:
    d = svc.get("date")
    t = svc.get("start_time")
    date_str = d.strftime("%a %b %d") if d else "-"
    time_str = t.strftime("%H:%M") if t else "-"
    return f"{date_str}\n{time_str}"


def build_week_runsheet_pdf_bytes(services: list, week_start: date) -> bytes:
    """Build a weekly run-sheet PDF and return the raw bytes.

    ``services`` is a list of dicts (tenant-scoped, non-draft) with keys:
    date, start_time, service_type, decedent_name, family_contact_name,
    plot_location, section, officiant, crew (list[str]) or crew_count, status.
    """
    from reportlab.lib import colors
    from reportlab.lib.pagesizes import A4, landscape
    from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
    from reportlab.lib.units import cm
    from reportlab.platypus import (
        HRFlowable,
        Paragraph,
        SimpleDocTemplate,
        Spacer,
        Table,
        TableStyle,
    )

    week_end = week_start + timedelta(days=6)

    buf = io.BytesIO()
    doc = SimpleDocTemplate(
        buf,
        pagesize=landscape(A4),
        rightMargin=1.5 * cm,
        leftMargin=1.5 * cm,
        topMargin=1.5 * cm,
        bottomMargin=1.5 * cm,
        title="",
        author="",
        subject="",
        creator="",
    )

    PRIMARY = colors.HexColor("#1a4a6e")
    styles = getSampleStyleSheet()
    title_style = ParagraphStyle(
        "Title", parent=styles["Normal"], fontSize=16, fontName="Helvetica-Bold",
        textColor=PRIMARY, spaceAfter=4,
    )
    small = ParagraphStyle(
        "Small", parent=styles["Normal"], fontSize=8, leading=11,
        textColor=colors.HexColor("#6B7280"),
    )
    cell = ParagraphStyle("Cell", parent=styles["Normal"], fontSize=8, leading=10)

    story: list = []
    story.append(Paragraph("Weekly Service Run-Sheet", title_style))
    story.append(
        Paragraph(
            f"Week of {week_start.strftime('%B %d, %Y')} – "
            f"{week_end.strftime('%B %d, %Y')}  ·  {len(services)} service(s)",
            small,
        )
    )
    story.append(HRFlowable(width="100%", thickness=2, color=PRIMARY, spaceAfter=10))

    header = [
        "Date/Time", "Type", "Decedent", "Family",
        "Plot", "Officiant", "Crew", "Status",
    ]
    tdata: list = [header]

    for svc in services:
        crew = svc.get("crew")
        if crew is None:
            crew_display = svc.get("crew_count", 0)
        elif isinstance(crew, (list, tuple)):
            crew_display = ", ".join(str(c) for c in crew) if crew else "-"
        else:
            crew_display = crew
        row = [
            Paragraph(_guard(_fmt_date_time(svc)).replace("\n", "<br/>"), cell),
            Paragraph(_guard(svc.get("service_type")), cell),
            Paragraph(_guard(svc.get("decedent_name")), cell),
            Paragraph(_guard(svc.get("family_contact_name")), cell),
            Paragraph(_guard(svc.get("plot_location")), cell),
            Paragraph(_guard(svc.get("officiant")), cell),
            Paragraph(_guard(crew_display), cell),
            Paragraph(_guard(svc.get("status")), cell),
        ]
        tdata.append(row)

    if len(tdata) == 1:
        tdata.append([Paragraph(_guard("No services scheduled this week."), cell)]
                     + ["" for _ in range(len(header) - 1)])

    col_widths = [
        2.6 * cm, 2.6 * cm, 4.2 * cm, 4.2 * cm,
        3.0 * cm, 3.2 * cm, 4.2 * cm, 2.8 * cm,
    ]
    table = Table(tdata, colWidths=col_widths, repeatRows=1)
    table.setStyle(
        TableStyle([
            ("FONTNAME", (0, 0), (-1, 0), "Helvetica-Bold"),
            ("FONTSIZE", (0, 0), (-1, 0), 9),
            ("TEXTCOLOR", (0, 0), (-1, 0), PRIMARY),
            ("LINEBELOW", (0, 0), (-1, 0), 1, PRIMARY),
            ("ROWBACKGROUNDS", (0, 1), (-1, -1), [colors.white, colors.HexColor("#F9FAFB")]),
            ("VALIGN", (0, 0), (-1, -1), "TOP"),
            ("TOPPADDING", (0, 0), (-1, -1), 5),
            ("BOTTOMPADDING", (0, 0), (-1, -1), 5),
            ("LEFTPADDING", (0, 0), (-1, -1), 5),
            ("RIGHTPADDING", (0, 0), (-1, -1), 5),
            ("GRID", (0, 0), (-1, -1), 0.25, colors.HexColor("#E5E7EB")),
        ])
    )
    story.append(table)
    story.append(Spacer(1, 12))
    story.append(
        Paragraph("Generated by INDELIS Cemetery Management Platform", small)
    )

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


def _xml_escape_and_neutralize(value: Any) -> str:
    """XML-escape for ReportLab ``Paragraph()`` and neutralise formula-injection
    prefixes (``=``, ``+``, ``-``, ``@``) so a spreadsheet copy/paste of the
    PDF text cannot execute a formula. Mirrors ``_guard`` above.
    """
    if value is None:
        text = ""
    elif isinstance(value, str):
        text = value
    else:
        text = str(value)
    if text and text[0] in _FORMULA_PREFIXES:
        text = " " + text
    text = text.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
    return text


_MAX_RUN_SHEET_ITEMS = 50
_MAX_ATTACHMENTS = 50


def build_service_detail_pdf_bytes(detail: dict) -> bytes:
    """Build a single-service detail PDF (INDL-37) and return the raw bytes.

    ``detail`` is the dict returned by ``SchedulingService.get_by_id`` — a
    single tenant's data only, so this always renders exactly one service
    (no cross-tenant loop). Text-only header per the Architect's SSRF-avoidance
    recommendation — no logo/image fetch.
    """
    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,
    )

    esc = _xml_escape_and_neutralize

    buf = io.BytesIO()
    doc = SimpleDocTemplate(
        buf,
        pagesize=A4,
        rightMargin=1.5 * cm,
        leftMargin=1.5 * cm,
        topMargin=1.5 * cm,
        bottomMargin=1.5 * cm,
        title="",
        author="",
        subject="",
        creator="",
    )

    PRIMARY = colors.HexColor("#1a4a6e")
    styles = getSampleStyleSheet()
    title_style = ParagraphStyle(
        "Title", parent=styles["Normal"], fontSize=16, fontName="Helvetica-Bold",
        textColor=PRIMARY, spaceAfter=4,
    )
    heading_style = ParagraphStyle(
        "Heading", parent=styles["Normal"], fontSize=11, fontName="Helvetica-Bold",
        textColor=PRIMARY, spaceBefore=10, spaceAfter=4,
    )
    small = ParagraphStyle(
        "Small", parent=styles["Normal"], fontSize=8, leading=11,
        textColor=colors.HexColor("#6B7280"),
    )
    body = ParagraphStyle("Body", parent=styles["Normal"], fontSize=9, leading=13)

    story: list = []

    # ── Header ──────────────────────────────────────────────────────────────
    cemetery_name = detail.get("cemetery_name")
    if cemetery_name:
        story.append(Paragraph(esc(cemetery_name), title_style))
    story.append(Paragraph("Service Record", title_style))
    story.append(
        Paragraph(f"Generated: {date.today().strftime('%B %d, %Y')}", small)
    )
    story.append(HRFlowable(width="100%", thickness=2, color=PRIMARY, spaceAfter=10))

    # ── Type / Status / Date / Time / Plot / Officiant ─────────────────────
    d = detail.get("date")
    t = detail.get("start_time")
    date_str = d.strftime("%a %b %d, %Y") if d else "-"
    time_str = t.strftime("%I:%M %p") if t else "-"
    plot_line = detail.get("plot_location") or "-"
    if detail.get("section"):
        plot_line = f"{plot_line} · Section {detail.get('section')}"

    story.append(
        Paragraph(
            f"Type: {esc(detail.get('service_type'))} &nbsp;&nbsp; "
            f"Status: {esc(detail.get('status'))}",
            body,
        )
    )
    story.append(
        Paragraph(f"Date: {esc(date_str)} &nbsp;&nbsp; Time: {esc(time_str)}", body)
    )
    story.append(
        Paragraph(
            f"Plot: {esc(plot_line)} &nbsp;&nbsp; "
            f"Officiant: {esc(detail.get('officiant') or '-')}",
            body,
        )
    )

    # ── Decedent Information ────────────────────────────────────────────────
    if detail.get("decedent_name"):
        story.append(Paragraph("Decedent Information", heading_style))
        years = detail.get("decedent_years")
        name_line = esc(detail.get("decedent_name"))
        if years:
            name_line = f"{name_line} ({esc(years)})"
        story.append(Paragraph(f"Name: {name_line}", body))

    # ── Family Contact ──────────────────────────────────────────────────────
    if (
        detail.get("family_contact_name")
        or detail.get("family_contact_phone")
        or detail.get("family_contact_email")
        or detail.get("expected_attendees")
    ):
        story.append(Paragraph("Family Contact", heading_style))
        if detail.get("family_contact_name"):
            story.append(Paragraph(f"Name: {esc(detail.get('family_contact_name'))}", body))
        if detail.get("family_contact_phone"):
            story.append(Paragraph(f"Phone: {esc(detail.get('family_contact_phone'))}", body))
        if detail.get("family_contact_email"):
            story.append(Paragraph(f"Email: {esc(detail.get('family_contact_email'))}", body))
        if detail.get("expected_attendees") is not None:
            story.append(
                Paragraph(f"Expected Guests: {esc(detail.get('expected_attendees'))}", body)
            )

    # ── Assigned Crew + Equipment ────────────────────────────────────────────
    crew = detail.get("crew") or []
    crew_names = [c.get("name") if isinstance(c, dict) else c for c in crew]
    if crew_names or detail.get("equipment_needed"):
        story.append(Paragraph("Assigned Crew", heading_style))
        if crew_names:
            story.append(
                Paragraph(esc(", ".join(str(n) for n in crew_names if n)), body)
            )
        if detail.get("equipment_needed"):
            story.append(
                Paragraph(f"Equipment: {esc(detail.get('equipment_needed'))}", body)
            )

    # ── Run Sheet ────────────────────────────────────────────────────────────
    run_sheet = detail.get("run_sheet_items") or []
    if run_sheet:
        story.append(Paragraph("Run Sheet", heading_style))
        truncated = len(run_sheet) > _MAX_RUN_SHEET_ITEMS
        for idx, item in enumerate(run_sheet[:_MAX_RUN_SHEET_ITEMS], start=1):
            time_label = esc(item.get("time_label")) if isinstance(item, dict) else esc(item)
            details_text = esc(item.get("details")) if isinstance(item, dict) else ""
            story.append(Paragraph(f"{idx}. {time_label} — {details_text}", body))
        if truncated:
            more = len(run_sheet) - _MAX_RUN_SHEET_ITEMS
            story.append(Paragraph(f"+ {more} more items", small))

    # ── Notes ────────────────────────────────────────────────────────────────
    event_note = detail.get("event_note")
    if event_note:
        story.append(Paragraph("Notes", heading_style))
        story.append(Paragraph(esc(event_note), body))

    # ── Attachments ──────────────────────────────────────────────────────────
    documents = detail.get("documents") or []
    if documents:
        truncated = len(documents) > _MAX_ATTACHMENTS
        story.append(
            Paragraph(f"Attachments ({len(documents)} files)", heading_style)
        )
        for doc_item in documents[:_MAX_ATTACHMENTS]:
            file_name = (
                doc_item.get("file_name") if isinstance(doc_item, dict) else doc_item
            )
            story.append(Paragraph(f"· {esc(file_name)}", body))
        if truncated:
            more = len(documents) - _MAX_ATTACHMENTS
            story.append(Paragraph(f"+ {more} more files", small))

    # ── Footer ───────────────────────────────────────────────────────────────
    story.append(Spacer(1, 14))
    story.append(HRFlowable(width="100%", thickness=1, color=colors.HexColor("#E5E7EB")))
    created_by = detail.get("created_by_name") or detail.get("created_by")
    created_at = detail.get("created_at")
    created_str = created_at.strftime("%B %d, %Y") if created_at else "-"
    story.append(
        Paragraph(f"Created by {esc(created_by) if created_by else 'unknown'} on {esc(created_str)}", small)
    )

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