"""WeasyPrint PDF layout with repeated legacy headers, footers and common blocks."""
import base64
import io
import re
from pathlib import Path

import nh3
from pypdf import PdfReader, PdfWriter

from flyway.printing.fill import fill

A4 = (595.0, 842.0)  # pt
A4_LANDSCAPE = (842.0, 595.0)
MARGIN = 20.0  # pt，旧值
PX = 96 / 72  # CSS px / pt

LEGACY_LOGO = "http://stock.flyway.anh.co.nz/Content/images/logo.jpg"
LOGO = "data:image/jpeg;base64," + base64.b64encode((Path(__file__).with_name("logo.jpg")).read_bytes()).decode()

# WeasyPrint does not constrain table widths with max-width as browsers do.
BASE_CSS = ("<style>html,body{margin:0;padding:0}body{font-family:Helvetica,Arial,sans-serif;font-size:12pt;"
            "}table{width:100% !important;table-layout:fixed}"
            # 旧引擎排出的段落之间没有 1em 外边距（对照旧版输出得出）
            "p{margin-top:0;margin-bottom:0}"
            # 旧引擎单元格的 height 含内边距
            "td,th{box-sizing:border-box}th{text-align:inherit}"
            # 保留旧 PDF 单元格的 2pt 默认内边距；模板指定 cellpadding 或 padding 时照旧。
            "table:not([cellspacing]){border-spacing:0}"
            "table:not([cellpadding])>tbody>tr>td,table:not([cellpadding])>tbody>tr>th{padding:2pt 0}</style>")


def _doc(body: str) -> str:
    return f"<!doctype html><html><head><meta charset='utf-8'>{BASE_CSS}</head><body>{body}</body></html>"


def _render(body: str, w: float, h: float, margins: tuple = (0, 0, 0, 0), extra_css: str = ""):
    # Missing PDF system libraries must not prevent login or other API routes.
    from weasyprint import HTML
    from weasyprint.urls import URLFetcher

    class LogoFetcher(URLFetcher):
        def fetch(self, url, headers=None):
            if url != LOGO:
                raise ValueError("PDF resources are limited to the bundled logo")
            return super().fetch(url, headers)

    css = f"@page {{ size:{w}pt {h}pt; margin:{' '.join(f'{m}pt' for m in margins)} }}{extra_css}"
    doc = _doc(body).replace("</head>", f"<style>{css}</style></head>")
    return HTML(string=doc, url_fetcher=LogoFetcher()).render(presentational_hints=True)


def _measure(content: str, width: float) -> float:
    if not content:
        return 0.0
    document = _render(f'<div>{content}</div><div id="end"></div>', width, 20000)
    return document.pages[0].anchors['end'][1] / PX


def render_pdf(header: str, footer: str, body: str, common_blocks: list[str] | None = None,
               size: tuple[float, float] = A4) -> bytes:
    """Return a PDF in-process, without a browser or external resource access."""
    from flyway.api.routes_print import ALLOWED_ATTRS, ALLOWED_TAGS

    def safe(html: str) -> str:
        def attribute(tag: str, name: str, value: str) -> str | None:
            if tag == "img" and name == "src":
                return LOGO if value in (LEGACY_LOGO, LOGO) else None
            if name == "style":
                return re.sub(r"url\s*\([^)]*\)", "none", value, flags=re.I)
            return value

        return nh3.clean(html or "", tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS,
                         attribute_filter=attribute, link_rel=None, url_schemes={"http", "https", "data"})

    header, footer, body = safe(header), safe(footer), safe(body)
    w, h = size
    content_w = w - 2 * MARGIN
    blocks = [safe(b) for b in (common_blocks or []) if b]
    sample = {"PageNumber": "1", "PageCount": "1"}
    header_h = _measure(fill(header, sample), content_w)
    footer_h = _measure(fill(footer, sample), content_w)
    gap = 0.0
    if blocks:
        probe = _render(body, w, h)
        # WeasyPrint 70 exposes computed table margins through its layout boxes.
        gap = max((box.margin_top / PX for page in probe.pages
                   for box in page._page_box.descendants()
                   if box.element_tag == 'table' and isinstance(box.margin_top, (int, float))), default=0.0)
        common = _render(''.join(blocks), content_w, 20000)
        left_bottom = max((box.position_y + box.height for page in common.pages
                           for box in page._page_box.descendants()
                           if box.element_tag == 'table' and box.position_x < 1
                           and box.width < content_w * PX - 1), default=0.0)
        gap = max(gap, left_bottom / PX)
    body_doc = _render(body, w, h, (MARGIN + header_h + gap, MARGIN, MARGIN + footer_h, MARGIN),
                       "table { margin-top:0 !important }" if gap else "")
    body_pdf = body_doc.write_pdf()
    reader = PdfReader(io.BytesIO(body_pdf))
    count = len(reader.pages)
    if not (header or footer or blocks):
        return body_pdf
    sheets = []
    for i in range(1, count + 1):
        data = {"PageNumber": str(i), "PageCount": str(count)}
        top = (f"<div style='position:absolute;left:{MARGIN}pt;top:{MARGIN}pt;width:{content_w}pt'>"
               f"{fill(header, data)}{''.join(blocks)}</div>")
        bottom = (f"<div style='position:absolute;left:{MARGIN}pt;bottom:{MARGIN}pt;width:{content_w}pt'>"
                  f"{fill(footer, data)}</div>")
        sheets.append(f"<div style='position:relative;width:{w}pt;height:{h}pt;overflow:hidden;"
                      f"page-break-after:always'>{top}{bottom}</div>")
    overlay = PdfReader(io.BytesIO(_render("".join(sheets), w, h).write_pdf()))
    out = PdfWriter()
    for i, body_page in enumerate(reader.pages):
        out.add_page(body_page).merge_page(overlay.pages[i])
    out.add_metadata({"/Producer": "WeasyPrint"})
    buf = io.BytesIO()
    out.write(buf)
    return buf.getvalue()


def page_texts(pdf: bytes) -> list[str]:
    return [p.extract_text() for p in PdfReader(io.BytesIO(pdf)).pages]
