"""Build the signed record: original PDF + one appended signature page."""
from __future__ import annotations

import base64
import io
from datetime import datetime
from pathlib import Path

from pypdf import PdfReader, PdfWriter
from reportlab.lib import colors
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import mm
from reportlab.lib.utils import ImageReader
from reportlab.pdfgen import canvas

# Brand, per lfcs-submission-pack/assets/brand.md — the same palette tools/prestart.py draws with,
# repeated here rather than imported because app/ must not reach into tools/.
RED = colors.HexColor("#D72229")
NAVY = colors.HexColor("#1F3A5F")
GREY = colors.HexColor("#5F5E5A")
STRIPE = colors.HexColor("#F5F5F5")
LINE = colors.HexColor("#D9D9D9")
LOGO = Path(__file__).parent / "static" / "lfcs-logo.jpeg"


def _sig_page(job: str, doc_name: str, rev: str, name: str, when: datetime,
              sig_png: bytes, user_agent: str) -> bytes:
    buf = io.BytesIO()
    c = canvas.Canvas(buf, pagesize=A4)
    w, h = A4
    y = h - 30 * mm
    c.setFont("Helvetica-Bold", 16)
    c.drawString(20 * mm, y, "Sign-on record")
    y -= 12 * mm
    c.setFont("Helvetica", 11)
    for label, val in [
        ("Job", job),
        ("Document", doc_name),
        ("Revision", rev or "-"),
        ("Name", name),
        ("Signed at", when.strftime("%d/%m/%Y %H:%M %Z").strip()),
    ]:
        c.drawString(20 * mm, y, f"{label}:")
        c.drawString(55 * mm, y, val)
        y -= 7 * mm
    y -= 4 * mm
    c.drawString(20 * mm, y, "I confirm I have read and understood this document.")
    y -= 10 * mm
    c.drawString(20 * mm, y, "Signature:")
    y -= 45 * mm
    c.rect(20 * mm, y, 120 * mm, 40 * mm)
    c.drawImage(ImageReader(io.BytesIO(sig_png)), 22 * mm, y + 2 * mm,
                width=116 * mm, height=36 * mm, preserveAspectRatio=True, mask="auto")
    c.setFont("Helvetica", 7)
    c.drawString(20 * mm, 15 * mm, f"Device: {user_agent[:150]}")
    c.showPage()
    c.save()
    return buf.getvalue()


def build_signed_pdf(original: bytes, *, job: str, doc_name: str, rev: str, name: str,
                     when: datetime, sig_data_url: str, user_agent: str) -> bytes:
    sig_png = base64.b64decode(sig_data_url.split(",", 1)[1])
    out = PdfWriter()
    for p in PdfReader(io.BytesIO(original)).pages:
        out.add_page(p)
    for p in PdfReader(io.BytesIO(_sig_page(job, doc_name, rev, name, when, sig_png, user_agent))).pages:
        out.add_page(p)
    buf = io.BytesIO()
    out.write(buf)
    return buf.getvalue()


def build_take5_pdf(*, job: str, name: str, task: str, when: datetime, hazards: list[tuple[str, str]],
                    other: str, sig_data_url: str, user_agent: str, take5_id: str) -> bytes:
    """One-page Take-5 record: task in the worker's words, hazards ticked + controls, signature."""
    from reportlab.lib.styles import ParagraphStyle
    from reportlab.platypus import Paragraph
    sig_png = base64.b64decode(sig_data_url.split(",", 1)[1])
    buf = io.BytesIO()
    c = canvas.Canvas(buf, pagesize=A4)
    w, h = A4
    body = ParagraphStyle("b", fontName="Helvetica", fontSize=10, leading=13)
    bold = ParagraphStyle("bb", parent=body, fontName="Helvetica-Bold")

    def para(text, style, x, y, width):
        p = Paragraph(text.replace("&", "&amp;").replace("<", "&lt;"), style)
        _, ph = p.wrap(width, 1000)
        p.drawOn(c, x, y - ph)
        return ph

    y = h - 22 * mm
    c.setFillColorRGB(0.843, 0.133, 0.161)   # LFCS red
    c.setFont("Helvetica-Bold", 18)
    c.drawString(20 * mm, y, "Take 5 — task hazard check")
    c.setFillColorRGB(0, 0, 0)
    y -= 10 * mm
    c.setFont("Helvetica", 10)
    c.drawString(20 * mm, y, f"Job: {job}")
    y -= 5.5 * mm
    c.drawString(20 * mm, y, f"Name: {name}")
    y -= 5.5 * mm
    c.drawString(20 * mm, y, f"Started: {when.strftime('%d/%m/%Y %H:%M %Z').strip()}    Ref: {take5_id}")
    y -= 9 * mm
    y -= para("Task (in my words):", bold, 20 * mm, y, 170 * mm) + 1 * mm
    y -= para(task, body, 20 * mm, y, 170 * mm) + 6 * mm
    y -= para("Hazards I identified and how I will control them:", bold, 20 * mm, y, 170 * mm) + 2 * mm
    if not hazards and not other:
        y -= para("— none ticked —", body, 24 * mm, y, 166 * mm) + 2 * mm
    for hz, ctl in hazards:
        y -= para(f"[x]  {hz}", bold, 24 * mm, y, 166 * mm) + 0.5 * mm
        if ctl:
            y -= para(ctl, body, 30 * mm, y, 160 * mm) + 2.5 * mm
        if y < 75 * mm:
            c.showPage(); y = h - 22 * mm
    if other:
        y -= para("[x]  Other / how I'll do it:", bold, 24 * mm, y, 166 * mm) + 0.5 * mm
        y -= para(other, body, 30 * mm, y, 160 * mm) + 2.5 * mm
    if y < 75 * mm:
        c.showPage(); y = h - 22 * mm
    y -= 4 * mm
    c.setFont("Helvetica", 10)
    c.drawString(20 * mm, y, "I have stopped, looked at the task and the area, and will work to the controls above.")
    y -= 8 * mm
    c.drawString(20 * mm, y, "Signature:")
    y -= 40 * mm
    c.rect(20 * mm, y, 110 * mm, 35 * mm)
    c.drawImage(ImageReader(io.BytesIO(sig_png)), 22 * mm, y + 2 * mm,
                width=106 * mm, height=31 * mm, preserveAspectRatio=True, mask="auto")
    c.setFont("Helvetica", 7)
    c.drawString(20 * mm, 15 * mm, f"Signed on worker's own device: {user_agent[:140]}")
    c.showPage()
    c.save()
    return buf.getvalue()


def build_incident_pdf(*, job: str, kind_label: str, name: str, what: str, where: str, who: str, action: str,
                       when: datetime, photo_jpeg: bytes | None, sig_data_url: str, user_agent: str,
                       report_id: str) -> bytes:
    """One/two-page hazard / near-miss / incident report in the reporter's words, photo, optional signature."""
    from reportlab.lib.styles import ParagraphStyle
    from reportlab.platypus import Paragraph
    buf = io.BytesIO()
    c = canvas.Canvas(buf, pagesize=A4)
    w, h = A4
    body = ParagraphStyle("b", fontName="Helvetica", fontSize=10, leading=13)
    bold = ParagraphStyle("bb", parent=body, fontName="Helvetica-Bold")

    def para(text, style, x, y, width):
        p = Paragraph((text or "-").replace("&", "&amp;").replace("<", "&lt;"), style)
        _, ph = p.wrap(width, 1000)
        p.drawOn(c, x, y - ph)
        return ph

    y = h - 22 * mm
    c.setFillColorRGB(0.843, 0.133, 0.161)
    c.setFont("Helvetica-Bold", 18)
    c.drawString(20 * mm, y, "Hazard / Near-miss / Incident report")
    c.setFillColorRGB(0, 0, 0)
    y -= 10 * mm
    c.setFont("Helvetica", 10)
    for line in (f"Job: {job}", f"Type: {kind_label}", f"Reported by: {name}",
                 f"When: {when.strftime('%d/%m/%Y %H:%M %Z').strip()}    Ref: {report_id}"):
        c.drawString(20 * mm, y, line); y -= 5.5 * mm
    y -= 4 * mm
    for label, val in (("What happened (in my words):", what), ("Where:", where), ("Who was involved / saw it:", who),
                       ("What was done straight away:", action)):
        y -= para(label, bold, 20 * mm, y, 170 * mm) + 1 * mm
        y -= para(val, body, 20 * mm, y, 170 * mm) + 5 * mm
    if photo_jpeg:
        try:
            img = ImageReader(io.BytesIO(photo_jpeg))
            iw, ih = img.getSize()
            maxw, maxh = 170 * mm, 90 * mm
            scale = min(maxw / iw, maxh / ih)
            dw, dh = iw * scale, ih * scale
            if y - dh < 60 * mm:
                c.showPage(); y = h - 22 * mm
            c.drawImage(img, 20 * mm, y - dh, width=dw, height=dh)
            y -= dh + 6 * mm
        except Exception:
            y -= para("(photo attached could not be embedded)", body, 20 * mm, y, 170 * mm) + 4 * mm
    if sig_data_url.startswith("data:image/png"):
        if y < 60 * mm:
            c.showPage(); y = h - 22 * mm
        c.setFont("Helvetica", 10)
        c.drawString(20 * mm, y, "Signature:")
        y -= 32 * mm
        c.rect(20 * mm, y, 90 * mm, 28 * mm)
        sig_png = base64.b64decode(sig_data_url.split(",", 1)[1])
        c.drawImage(ImageReader(io.BytesIO(sig_png)), 22 * mm, y + 2 * mm, width=86 * mm, height=24 * mm,
                    preserveAspectRatio=True, mask="auto")
    c.setFont("Helvetica", 7)
    c.drawString(20 * mm, 15 * mm, f"Desk follow-up: MSF22-3 / NCR as required; toolbox next Wednesday. Device: {user_agent[:100]}")
    c.showPage()
    c.save()
    return buf.getvalue()


def _brand_fonts():
    """Calibri if the box has it, Helvetica otherwise. Same rule as tools/prestart.py."""
    try:
        from reportlab.pdfbase import pdfmetrics
        from reportlab.pdfbase.ttfonts import TTFont
        for cand in ("C:/Windows/Fonts/calibri.ttf", "/usr/share/fonts/truetype/calibri.ttf"):
            if Path(cand).exists():
                pdfmetrics.registerFont(TTFont("Calibri", cand))
                pdfmetrics.registerFont(TTFont("Calibri-Bold", cand.replace("calibri.ttf", "calibrib.ttf")))
                return "Calibri", "Calibri-Bold"
    except Exception:
        pass
    return "Helvetica", "Helvetica-Bold"


def build_tm_pdf(*, job: str, docket_no: str, when: datetime, description: str,
                 men: list[tuple[str, float]], plant: str, raised_by: str,
                 signed_by: str, company: str, signed_at: datetime | None,
                 sig_data_url: str, user_agent: str) -> bytes:
    """The T&M docket the head contractor signs on the phone. LFCS letterhead, one page.

    This is the piece of paper the money hangs off, so it says exactly four things: what was done,
    who did it and for how long, what plant and materials went into it, and who signed for it.
    """
    from reportlab.lib.styles import ParagraphStyle
    from reportlab.platypus import Image, KeepTogether, Paragraph, SimpleDocTemplate, Spacer, Table, TableStyle

    F, FB = _brand_fonts()
    base = ParagraphStyle("b", fontName=F, fontSize=9.5, leading=13, textColor=colors.black)
    small = ParagraphStyle("s", parent=base, fontSize=8, leading=10.5, textColor=GREY)
    h1 = ParagraphStyle("h1", parent=base, fontName=FB, fontSize=16, leading=19, textColor=RED)
    h2 = ParagraphStyle("h2", parent=base, fontName=FB, fontSize=10, leading=13, textColor=colors.white)
    lbl = ParagraphStyle("l", parent=base, fontName=FB, textColor=NAVY)

    # Everything on this page is somebody's typing, so it all gets escaped. Nothing carries markup —
    # bold is a style, not a tag, or "<b>10.5</b>" ends up printed on the docket the super signs.
    def P(t, st=base):
        return Paragraph(str(t).replace("&", "&amp;").replace("<", "&lt;").replace("\n", "<br/>"), st)

    right = ParagraphStyle("r", parent=base, alignment=2)
    right_b = ParagraphStyle("rb", parent=base, alignment=2, fontName=FB)

    buf = io.BytesIO()
    doc = SimpleDocTemplate(buf, pagesize=A4, leftMargin=18 * mm, rightMargin=18 * mm,
                            topMargin=16 * mm, bottomMargin=22 * mm, title=f"T&M docket {docket_no}")
    w = doc.width

    def banner(title: str):
        t = Table([[P(title.upper(), h2)]], colWidths=[w])
        t.setStyle(TableStyle([("BACKGROUND", (0, 0), (-1, -1), RED), ("LEFTPADDING", (0, 0), (-1, -1), 8),
                               ("TOPPADDING", (0, 0), (-1, -1), 5), ("BOTTOMPADDING", (0, 0), (-1, -1), 5)]))
        return t

    words = w - 22 * mm if LOGO.exists() else w
    title = Table([[P("T&M / Day Works Docket", h1)],
                   [P("LF Construction Services · Licence No 292303c", small)]], colWidths=[words])
    title.setStyle(TableStyle([("LEFTPADDING", (0, 0), (-1, -1), 0), ("TOPPADDING", (0, 0), (-1, -1), 0),
                               ("BOTTOMPADDING", (0, 0), (-1, -1), 1)]))
    if LOGO.exists():
        ht = Table([[Image(str(LOGO), width=16 * mm, height=16 * mm), title]], colWidths=[22 * mm, words])
    else:
        ht = Table([[title]], colWidths=[words])
    ht.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "MIDDLE"), ("LEFTPADDING", (0, 0), (-1, -1), 0),
                            ("RIGHTPADDING", (0, 0), (-1, -1), 0),
                            ("BOTTOMPADDING", (0, 0), (-1, -1), 0), ("TOPPADDING", (0, 0), (-1, -1), 0)]))

    total = sum(h for _, h in men)
    kv = [("Job", job), ("Docket No", docket_no), ("Date of works", when.strftime("%d/%m/%Y")),
          ("Raised by", raised_by or "-")]
    kvt = Table([[P(k, lbl), P(v)] for k, v in kv], colWidths=[42 * mm, w - 42 * mm])
    kvt.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "TOP"), ("LINEBELOW", (0, 0), (-1, -1), 0.4, LINE),
                             ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4)]))

    labour = [[P("Name", lbl), P("Hours", lbl)]]
    for n, h in men:
        labour.append([P(n), P(f"{h:g}", right)])
    labour.append([P("Total hours", lbl), P(f"{total:g}", right_b)])
    lt = Table(labour, colWidths=[w - 30 * mm, 30 * mm], repeatRows=1)
    st = [("VALIGN", (0, 0), (-1, -1), "TOP"), ("LINEBELOW", (0, 0), (-1, -1), 0.4, LINE),
          ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4),
          ("BACKGROUND", (0, 0), (-1, 0), STRIPE), ("BACKGROUND", (0, -1), (-1, -1), STRIPE)]
    lt.setStyle(TableStyle(st))

    s = [ht, Spacer(1, 8), kvt, Spacer(1, 10),
         KeepTogether([banner("Extra work carried out"), Spacer(1, 4), P(description or "-"), Spacer(1, 8)]),
         KeepTogether([banner("Labour"), Spacer(1, 4), lt, Spacer(1, 8)])]
    if plant.strip():
        s.append(KeepTogether([banner("Plant and materials"), Spacer(1, 4), P(plant), Spacer(1, 8)]))

    sig_rows = [[P("Signed by", lbl), P(signed_by or "-")],
                [P("Company", lbl), P(company or "-")],
                [P("Signed at", lbl), P(signed_at.strftime("%d/%m/%Y %H:%M") if signed_at else "-")]]
    sgt = Table(sig_rows, colWidths=[42 * mm, w - 42 * mm])
    sgt.setStyle(TableStyle([("VALIGN", (0, 0), (-1, -1), "TOP"), ("LINEBELOW", (0, 0), (-1, -1), 0.4, LINE),
                             ("TOPPADDING", (0, 0), (-1, -1), 4), ("BOTTOMPADDING", (0, 0), (-1, -1), 4)]))
    sig_block = [banner("Signed — head contractor representative"), Spacer(1, 4),
                 P("I confirm the work described above was carried out as day works / T&M and the hours "
                   "shown are correct.", small), Spacer(1, 4), sgt, Spacer(1, 4)]
    if sig_data_url.startswith("data:image/png"):
        sig_png = base64.b64decode(sig_data_url.split(",", 1)[1])
        sig_block.append(Image(io.BytesIO(sig_png), width=90 * mm, height=28 * mm, kind="proportional"))
    s.append(KeepTogether(sig_block))

    line = f"{job} · Docket {docket_no}"

    def footer(c, d):
        c.saveState()
        c.setFont(F, 7.5)
        c.setFillColor(GREY)
        c.drawString(d.leftMargin, 12 * mm, f"LF Construction Services · T&M Docket · {line}")
        c.drawRightString(A4[0] - d.rightMargin, 12 * mm, f"Page {d.page}")
        if user_agent:
            c.setFont(F, 6.5)
            c.drawString(d.leftMargin, 8 * mm, f"Signed on site: {user_agent[:110]}")
        c.setStrokeColor(RED)
        c.setLineWidth(1.2)
        c.line(d.leftMargin, 16 * mm, A4[0] - d.rightMargin, 16 * mm)
        c.restoreState()

    doc.build(s, onFirstPage=footer, onLaterPages=footer)
    return buf.getvalue()


def page_count(pdf: bytes) -> int:
    return len(PdfReader(io.BytesIO(pdf)).pages)


def render_page_png(pdf: bytes, n: int, scale: float = 1.6) -> bytes:
    """Render page n (1-based) to PNG. pypdfium2 — fast, no poppler dependency."""
    import pypdfium2 as pdfium
    doc = pdfium.PdfDocument(pdf)
    if n < 1 or n > len(doc):
        raise IndexError(n)
    img = doc[n - 1].render(scale=scale).to_pil()
    buf = io.BytesIO()
    img.save(buf, format="PNG", optimize=True)
    return buf.getvalue()


def qr_data_url(text: str) -> str:
    import qrcode
    img = qrcode.make(text, box_size=6, border=2)
    buf = io.BytesIO()
    img.save(buf, format="PNG")
    return "data:image/png;base64," + base64.b64encode(buf.getvalue()).decode()
