#!/usr/bin/env python3
"""Audit the Pivot Sans specimen in headless Chromium."""

from __future__ import annotations

import argparse
import threading
from functools import partial
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from urllib.parse import unquote, urlparse

from playwright.sync_api import sync_playwright

ROOT = Path(__file__).resolve().parent.parent
DIST = ROOT / "dist"
SAMPLES = {
    "latin-core": "Hello $123 ≠",
    "latin-ext": "ĀŁļḷ",
    "vietnamese": "Ạỹ",
}


class QuietHandler(SimpleHTTPRequestHandler):
    def log_message(self, _format, *args):
        pass


def font_filename(url: str) -> str:
    return Path(unquote(urlparse(url).path)).name


def expected_filename(style: str, group: str) -> str:
    italic = "-Italic" if style == "italic" else ""
    return f"PivotSans{italic}-{group}[wght].woff2"


def serve_dist():
    handler = partial(QuietHandler, directory=DIST)
    server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
    thread = threading.Thread(target=server.serve_forever, daemon=True)
    thread.start()
    return server, f"http://127.0.0.1:{server.server_port}/"


def audit(screenshot: Path) -> None:
    server, base = serve_dist()
    try:
        with sync_playwright() as playwright:
            browser = playwright.chromium.launch(headless=True)
            selective = {}

            for style in ("normal", "italic"):
                for group, sample in SAMPLES.items():
                    context = browser.new_context()
                    page = context.new_page()
                    requested = []
                    page.on(
                        "response",
                        lambda response, bag=requested: (
                            bag.append(font_filename(response.url))
                            if ".woff2" in response.url
                            else None
                        ),
                    )
                    page.goto(base, wait_until="domcontentloaded")
                    page.set_content(
                        f"""<!doctype html>
<base href="{base}">
<link rel="stylesheet" href="pivot-sans-subsets.css">
<p style="font-family:'Pivot Sans';font-style:{style};
          font-weight:400;font-size:64px">{sample}</p>""",
                        wait_until="load",
                    )
                    page.evaluate(
                        """([style, sample]) =>
                          document.fonts.load(
                            `${style} 400 64px 'Pivot Sans'`, sample)""",
                        [style, sample],
                    )
                    page.evaluate("() => document.fonts.ready")
                    page.wait_for_timeout(150)

                    fonts = set(requested)
                    group_font = expected_filename(style, group)
                    core_font = expected_filename(style, "latin-core")
                    assert group_font in fonts, (style, group, sorted(fonts))
                    assert fonts <= {group_font, core_font}, (
                        style,
                        group,
                        sorted(fonts),
                    )
                    if group == "latin-core":
                        assert fonts == {core_font}
                    selective[f"{style}:{group}"] = sorted(fonts)
                    context.close()

            context = browser.new_context(
                viewport={"width": 1440, "height": 1000},
                device_scale_factor=1,
            )
            page = context.new_page()
            console_errors = []
            failed_requests = []
            font_responses = []
            page.on(
                "console",
                lambda message: (
                    console_errors.append(f"{message.type}: {message.text}")
                    if message.type == "error"
                    else None
                ),
            )
            page.on(
                "requestfailed",
                lambda request: failed_requests.append(
                    f"{request.url}: {request.failure}"
                ),
            )
            page.on(
                "response",
                lambda response: (
                    font_responses.append(
                        (response.status, font_filename(response.url))
                    )
                    if ".woff2" in response.url
                    else None
                ),
            )

            response = page.goto(
                base + "specimen.html", wait_until="networkidle"
            )
            assert response and response.status == 200
            page.evaluate("() => document.fonts.ready")
            assert page.title() == "Pivot Sans 1.200 — specimen and QA"
            assert page.locator("h1").inner_text() == "Pivot Sans"
            assert not console_errors, console_errors
            assert not failed_requests, failed_requests
            assert font_responses
            assert all(status == 200 for status, _ in font_responses)

            pin_faces = (
                ("normal", 450, "Pivot Sans Pin 450"),
                ("italic", 450, "Pivot Sans Pin 450"),
                ("normal", 550, "Pivot Sans Pin 550"),
                ("italic", 550, "Pivot Sans Pin 550"),
            )
            for style, weight, family in pin_faces:
                loaded = page.evaluate(
                    """([style, weight, family]) =>
                      document.fonts.load(
                        `${style} ${weight} 48px "${family}"`,
                        "A deliberate in-between.")""",
                    [style, weight, family],
                )
                assert loaded, (style, weight, family)
            page.evaluate("() => document.fonts.ready")
            requested_pin_files = {
                filename
                for _status, filename in font_responses
                if filename.startswith("PivotSans-Wght")
            }
            assert requested_pin_files == {
                "PivotSans-Wght450.woff2",
                "PivotSans-Wght450Italic.woff2",
                "PivotSans-Wght550.woff2",
                "PivotSans-Wght550Italic.woff2",
            }, sorted(requested_pin_files)

            measurements = page.evaluate(
                """async () => {
                  const results = [];
                  const samples = [
                    '$1,111,111.11',
                    '€9,999,999.99'
                  ];
                  for (const style of ['normal', 'italic']) {
                    for (const weight of [300, 400, 500, 600, 700]) {
                      await Promise.all(samples.map(text =>
                        document.fonts.load(
                          `${style} ${weight} 72px "Pivot Sans"`, text)
                      ));
                      const widths = samples.map(text => {
                        const span = document.createElement('span');
                        Object.assign(span.style, {
                          position: 'absolute',
                          visibility: 'hidden',
                          whiteSpace: 'nowrap',
                          fontFamily: 'Pivot Sans',
                          fontStyle: style,
                          fontWeight: String(weight),
                          fontSize: '72px',
                          fontVariantNumeric: 'tabular-nums'
                        });
                        span.textContent = text;
                        document.body.appendChild(span);
                        const width = span.getBoundingClientRect().width;
                        span.remove();
                        return width;
                      });
                      results.push({style, weight, widths});
                    }
                  }
                  return results;
                }"""
            )
            for row in measurements:
                assert abs(row["widths"][0] - row["widths"][1]) < 0.02, row

            ss02_measurements = page.evaluate(
                """async () => {
                  const results = [];
                  const sample = 'acct_Il1l-class l ĺ ļ ľ ŀ ł ḷ ḻ';
                  for (const style of ['normal', 'italic']) {
                    for (const weight of [300, 350, 450, 550, 650, 700]) {
                      await document.fonts.load(
                        `${style} ${weight} 64px "Pivot Sans"`, sample);
                      const widths = [];
                      for (const enabled of [false, true]) {
                        const span = document.createElement('span');
                        Object.assign(span.style, {
                          position: 'absolute',
                          visibility: 'hidden',
                          whiteSpace: 'nowrap',
                          fontFamily: 'Pivot Sans',
                          fontStyle: style,
                          fontWeight: String(weight),
                          fontSize: '64px',
                          fontFeatureSettings: enabled
                            ? '"ss02" 1'
                            : '"ss02" 0'
                        });
                        span.textContent = sample;
                        document.body.appendChild(span);
                        widths.push(span.getBoundingClientRect().width);
                        span.remove();
                      }
                      results.push({style, weight, widths});
                    }
                  }
                  return results;
                }"""
            )
            for row in ss02_measurements:
                assert abs(row["widths"][0] - row["widths"][1]) < 0.02, row

            page.evaluate(
                """() => {
                  for (const [id, enabled] of [
                    ['ss02-probe-default', false],
                    ['ss02-probe-enabled', true]
                  ]) {
                    const probe = document.createElement('span');
                    probe.id = id;
                    probe.lang = 'mh';
                    Object.assign(probe.style, {
                      display: 'inline-block',
                      position: 'fixed',
                      left: '12px',
                      top: enabled ? '120px' : '20px',
                      padding: '8px',
                      background: 'white',
                      whiteSpace: 'nowrap',
                      fontFamily: 'Pivot Sans',
                      fontStyle: 'italic',
                      fontWeight: '475',
                      fontSize: '64px',
                      fontFeatureSettings: enabled
                        ? '"ss02" 1'
                        : '"ss02" 0'
                    });
                    probe.textContent = 'acct_Il1l-class ļ';
                    document.body.appendChild(probe);
                  }
                }"""
            )
            default_probe = page.locator("#ss02-probe-default")
            enabled_probe = page.locator("#ss02-probe-enabled")
            assert abs(
                default_probe.bounding_box()["width"]
                - enabled_probe.bounding_box()["width"]
            ) < 0.02
            assert default_probe.screenshot() != enabled_probe.screenshot()
            default_probe.evaluate("node => node.remove()")
            enabled_probe.evaluate("node => node.remove()")

            initial_features = page.locator("#live-preview").evaluate(
                "node => getComputedStyle(node).fontFeatureSettings"
            )
            assert initial_features == "normal"
            page.locator("#weight").fill("550")
            page.locator("#italic").check()
            page.locator("#tabular").check()
            page.locator("#alternate").check()
            page.locator("#disambiguated").check()
            page.locator("#preview-text").fill(
                "Beacon ₹1,234.56 ≈ €1,234.56"
            )
            state = page.locator("#live-preview").evaluate(
                """node => ({
                  text: node.textContent,
                  weight: getComputedStyle(node).fontWeight,
                  style: getComputedStyle(node).fontStyle,
                  numeric: getComputedStyle(node).fontVariantNumeric,
                  features: getComputedStyle(node).fontFeatureSettings,
                  family: getComputedStyle(node).fontFamily
                })"""
            )
            assert state["text"] == "Beacon ₹1,234.56 ≈ €1,234.56"
            assert state["weight"] == "550"
            assert state["style"] == "italic"
            assert "tabular-nums" in state["numeric"]
            assert "ss01" in state["features"]
            assert "ss02" in state["features"]
            assert "Pivot Sans" in state["family"]

            screenshot.parent.mkdir(parents=True, exist_ok=True)
            page.screenshot(path=str(screenshot), full_page=True)
            context.close()
            browser.close()
    finally:
        server.shutdown()
        server.server_close()

    print(f"unicode-range cases: {len(selective)} passed")
    print(f"browser money rows: {len(measurements)} aligned")
    print(f"browser ss02 rows: {len(ss02_measurements)} width-stable")
    print(f"wrote {screenshot}")


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "--screenshot",
        type=Path,
        default=Path("/tmp/pivot-sans-specimen-browser.png"),
    )
    args = parser.parse_args()
    audit(args.screenshot.resolve())


if __name__ == "__main__":
    main()
