#!/usr/bin/env python3
"""Release structure, integrity, metadata, and clipping-bound tests."""

from __future__ import annotations

import hashlib
import json
import struct
from pathlib import Path

from fontTools.ttLib import TTFont

ROOT = Path(__file__).resolve().parent.parent
DIST = ROOT / "dist"
UPSTREAM = ROOT / "upstream"

STATIC_SUFFIXES = {
    "Light",
    "Regular",
    "Medium",
    "SemiBold",
    "Bold",
    "LightItalic",
    "Italic",
    "MediumItalic",
    "SemiBoldItalic",
    "BoldItalic",
}
PIN_FILES = {
    "PivotSans-Wght450.ttf",
    "PivotSans-Wght450.woff2",
    "PivotSans-Wght550.ttf",
    "PivotSans-Wght550.woff2",
    "PivotSans-Wght450Italic.ttf",
    "PivotSans-Wght450Italic.woff2",
    "PivotSans-Wght550Italic.ttf",
    "PivotSans-Wght550Italic.woff2",
}
SUBSET_FILES = {
    f"PivotSans{infix}-{subset}[wght].woff2"
    for infix in ("", "-Italic")
    for subset in ("latin-core", "latin-ext", "vietnamese")
}
VARIABLE_FILES = (
    DIST / "PivotSans[wght].ttf",
    DIST / "PivotSans-Italic[wght].ttf",
    DIST / "PivotSans[wght].woff2",
    DIST / "PivotSans-Italic[wght].woff2",
)
TABULAR_SOURCES = {
    "zero",
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine",
    "dollar",
    "sterling",
    "yen",
    "uni20B9",
    "Euro",
    "dollar.BRACKET.varAlt01",
}
SS02_L_SOURCES = {
    "l": "U+006C",
    "lacute": "U+013A",
    "uni013C": "U+013C",
    "uni013C.loclMAH": "U+013C (locl MAH)",
    "lcaron": "U+013E",
    "ldot": "U+0140",
    "lslash": "U+0142",
    "uni1E37": "U+1E37",
    "uni1E3B": "U+1E3B",
}


def sha256(path: Path) -> str:
    digest = hashlib.sha256()
    with path.open("rb") as stream:
        for chunk in iter(lambda: stream.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def read_manifest(path: Path):
    entries = {}
    for line in path.read_text().splitlines():
        if line.strip():
            checksum, relative = line.split(maxsplit=1)
            entries[relative.removeprefix("*")] = checksum
    return entries


def single_substitution_mapping(font: TTFont, feature_tag: str):
    gsub = font["GSUB"].table
    records = [
        record.Feature
        for record in gsub.FeatureList.FeatureRecord
        if record.FeatureTag == feature_tag
    ]
    assert len(records) == 1
    mapping = {}
    for lookup_index in records[0].LookupListIndex:
        lookup = gsub.LookupList.Lookup[lookup_index]
        assert lookup.LookupType == 1
        for subtable in lookup.SubTable:
            mapping.update(subtable.mapping)
    return mapping


def test_expected_release_files_are_present():
    assert {path.name for path in DIST.glob("*.ttf")} == {
        "PivotSans[wght].ttf",
        "PivotSans-Italic[wght].ttf",
    }
    assert {path.name for path in DIST.glob("*.woff2")} == {
        "PivotSans[wght].woff2",
        "PivotSans-Italic[wght].woff2",
    }
    assert {
        path.stem.removeprefix("PivotSans-")
        for path in (DIST / "static").glob("*.ttf")
    } == STATIC_SUFFIXES
    assert {
        path.stem.removeprefix("PivotSans-")
        for path in (DIST / "static").glob("*.woff2")
    } == STATIC_SUFFIXES
    assert {path.name for path in (DIST / "pins").iterdir()} == PIN_FILES
    assert {path.name for path in (DIST / "subsets").iterdir()} == SUBSET_FILES

    for relative in (
        "AUTHORS.txt",
        "OFL.txt",
        "FONTLOG.txt",
        "QA-NOTES.md",
        "README.md",
        "pipeline/build.py",
        "pipeline/build_subsets.py",
        "pipeline/audit_specimen.py",
        "pipeline/measure.py",
        "pipeline/package.py",
        "pipeline/render_qa.py",
        "requirements.txt",
        "requirements-audit.txt",
        "subsets/latin-core.txt",
        "subsets/latin-ext.txt",
        "subsets/vietnamese.txt",
        "dist/pivot-sans.css",
        "dist/pivot-sans-subsets.css",
        "dist/specimen.html",
        "dist/qa-visual.png",
        "dist/SHA256SUMS",
        "upstream/SHA256SUMS",
    ):
        assert (ROOT / relative).is_file(), relative


def test_closed_and_on_hold_scope_did_not_enter_the_1_200_release():
    # The existing installable 450/550 pins remain deliverables. A display
    # pin around 650 and new 450/550 fvar named instances remain out of scope.
    assert {
        path.name for path in (DIST / "pins").iterdir()
    } == PIN_FILES
    assert not [
        path
        for path in DIST.rglob("*")
        if path.is_file() and "Wght650" in path.name
    ]

    for path in VARIABLE_FILES:
        font = TTFont(path)
        axes = font["fvar"].axes
        assert [
            (axis.axisTag, axis.minValue, axis.defaultValue, axis.maxValue)
            for axis in axes
        ] == [("wght", 300, 400, 700)]
        assert "GRAD" not in {axis.axisTag for axis in axes}
        instance_weights = {
            instance.coordinates["wght"]
            for instance in font["fvar"].instances
        }
        assert instance_weights == {300, 400, 500, 600, 700}
        assert not {450, 550} & instance_weights

        # U+00B5 and tabular punctuation were explicitly closed/held. tnum
        # remains the 15 encoded money-grid characters plus the rvrn dollar
        # source that preserves that 15-character contract at heavy weights.
        assert 0x00B5 not in font.getBestCmap()
        tnum = single_substitution_mapping(font, "tnum")
        assert set(tnum) == TABULAR_SOURCES
        assert not {"period", "comma", "colon", "semicolon"} & set(tnum)


def test_upstream_manifest_matches_all_authoritative_inputs():
    entries = read_manifest(UPSTREAM / "SHA256SUMS")
    files = {
        path.relative_to(UPSTREAM).as_posix()
        for path in UPSTREAM.rglob("*")
        if path.is_file() and path.name != "SHA256SUMS"
    }
    assert set(entries) == files
    for relative, expected in entries.items():
        assert sha256(UPSTREAM / relative) == expected, relative


def test_release_manifest_matches_fonts_css_and_specimen():
    entries = read_manifest(DIST / "SHA256SUMS")
    expected_files = {
        path.relative_to(DIST).as_posix()
        for path in DIST.rglob("*")
        if path.is_file()
        and path.suffix in {".ttf", ".woff2", ".css", ".html", ".png"}
    }
    assert set(entries) == expected_files
    for relative, expected in entries.items():
        assert sha256(DIST / relative) == expected, relative


def test_root_ofl_is_verbatim_upstream_license_with_fork_copyright():
    upstream_lines = UPSTREAM.joinpath("OFL.txt").read_text().splitlines()
    root_lines = ROOT.joinpath("OFL.txt").read_text().splitlines()
    assert root_lines[0] == upstream_lines[0]
    assert root_lines[1] == "Copyright 2026 Beacon Bay Technologies, Inc."
    assert [line.rstrip() for line in root_lines[2:]] == [
        line.rstrip() for line in upstream_lines[1:]
    ]


def test_authors_file_preserves_both_fork_and_original_credits():
    authors = (ROOT / "AUTHORS.txt").read_text()
    assert "Beacon Bay Technologies, Inc." in authors
    assert "https://pivotsans.com" in authors
    assert "Olivia King" in authors
    assert "Inclusive Sans Project Authors" in authors


def test_no_macos_metadata_artifacts_in_release_tree():
    forbidden = []
    for path in ROOT.rglob("*"):
        if (
            path.name == ".DS_Store"
            or path.name.startswith("._")
            or path.name == "__MACOSX"
        ):
            forbidden.append(path.relative_to(ROOT).as_posix())
    assert not forbidden


def test_primary_names_do_not_retain_upstream_family_name():
    for path in sorted(DIST.rglob("*.ttf")):
        name = TTFont(path)["name"]
        for name_id in (1, 2, 3, 4, 5, 6, 16, 17, 25):
            value = name.getDebugName(name_id)
            if value is not None:
                assert "Inclusive" not in value, (path.name, name_id, value)


def test_all_distributed_fonts_record_fork_authorship_and_urls():
    paths = sorted(DIST.rglob("*.ttf")) + sorted(DIST.rglob("*.woff2"))
    for path in paths:
        name = TTFont(path)["name"]
        assert name.getDebugName(8) == "Beacon Bay Technologies, Inc.", path
        assert "Olivia King" in name.getDebugName(9), path
        assert "Beacon Bay Technologies, Inc." in name.getDebugName(9), path
        assert name.getDebugName(11) == "https://pivotsans.com", path
        assert name.getDebugName(12) == "https://www.oliviaking.com", path


def test_postscript_names_are_unique_within_each_installable_set():
    installable_sets = (
        sorted(DIST.glob("*.ttf")),
        sorted((DIST / "static").glob("*.ttf")),
        sorted((DIST / "pins").glob("*.ttf")),
    )
    for paths in installable_sets:
        names = [TTFont(path)["name"].getDebugName(6) for path in paths]
        assert len(names) == len(set(names))


def test_static_windows_metrics_cover_actual_family_bounds():
    fonts = [TTFont(path) for path in sorted((DIST / "static").glob("*.ttf"))]
    family_y_min = 0
    family_y_max = 0
    for font in fonts:
        glyf = font["glyf"]
        for glyph_name in font.getGlyphOrder():
            glyph = glyf[glyph_name]
            if glyph.numberOfContours == 0:
                continue
            glyph.recalcBounds(glyf)
            family_y_min = min(family_y_min, glyph.yMin)
            family_y_max = max(family_y_max, glyph.yMax)
    assert (family_y_min, family_y_max) == (-399, 1159)
    for font in fonts:
        assert font["OS/2"].usWinAscent >= family_y_max
        assert font["OS/2"].usWinDescent >= abs(family_y_min)


def test_all_font_build_timestamps_are_deterministic():
    for path in sorted(DIST.rglob("*.ttf")) + sorted(DIST.rglob("*.woff2")):
        assert TTFont(path)["head"].modified == 3_868_128_000, path.name


def test_build_spec_freezes_release_features_and_slots():
    spec = json.loads((ROOT / "pipeline/spec.json").read_text())
    assert spec["fork"] == "Pivot Sans"
    assert spec["fork_version"] == "1.200"
    assert spec["release_date"] == "2026-07-29"
    assert spec["tabular_slot"] == {
        "300": 668,
        "400": 660,
        "500": 666,
        "600": 671,
        "700": 677,
    }
    assert set(spec["currencies"].values()) == {
        "U+0024",
        "U+00A3",
        "U+00A5",
        "U+20B9",
        "U+20AC",
    }
    assert set(spec["math_composites"].values()) == {
        "U+2030",
        "U+2248",
        "U+2260",
        "U+2264",
        "U+2265",
    }
    assert spec["ss02_l_sources"] == SS02_L_SOURCES
    assert spec["static_pins"] == [450, 550]


def test_web_css_exposes_both_full_variable_styles_and_weight_range():
    css = (DIST / "pivot-sans.css").read_text()
    assert css.count('font-family: "Pivot Sans"') == 2
    assert "PivotSans[wght].woff2" in css
    assert "PivotSans-Italic[wght].woff2" in css
    assert css.count("font-weight: 300 700") == 2
    assert "font-style: normal" in css
    assert "font-style: italic" in css


def test_specimen_covers_required_features_and_public_credit():
    specimen = (DIST / "specimen.html").read_text()
    for required in (
        "Pivot Sans 1.200",
        "Beacon Bay Technologies, Inc.",
        "https://pivotsans.com",
        "Variable playground",
        "All weights and styles",
        "tnum",
        "ss01",
        "ss02",
        "disambiguated lowercase l",
        "acct_Il1l-class",
        "l ĺ ļ ľ ŀ ł ḷ ḻ",
        "Money-column alignment",
        "$ £ ¥ ₹ €",
        "≠",
        "≤",
        "≥",
        "≈",
        "‰",
        "Latin extended",
        "Vietnamese",
        "Wght 450",
        "Wght 550",
        "Pivot Sans Pin 450",
        "Pivot Sans Pin 550",
        "PivotSans-Wght450.woff2",
        "PivotSans-Wght450Italic.woff2",
        "PivotSans-Wght550.woff2",
        "PivotSans-Wght550Italic.woff2",
        "not variable-font interpolation",
    ):
        assert required in specimen, required


def test_qa_visual_and_renderer_are_current():
    data = (DIST / "qa-visual.png").read_bytes()
    assert data[:8] == b"\x89PNG\r\n\x1a\n"
    width, height = struct.unpack(">II", data[16:24])
    assert width == 1800
    assert height >= 1300
    renderer = (ROOT / "pipeline/render_qa.py").read_text()
    assert "Pivot Sans 1.200" in renderer
    assert "tabular-money proof" in renderer
    assert "$1,111,111.11" in renderer
    assert "€9,999,999.99" in renderer
    assert "ss02" in renderer
    assert "acct_Il1l-class" in renderer
    assert "l ĺ ļ ľ ŀ ł ḷ ḻ" in renderer
    browser_audit = (ROOT / "pipeline/audit_specimen.py").read_text()
    assert "Pivot Sans 1.200 — specimen and QA" in browser_audit
    assert "unicode-range cases" in browser_audit
    assert "browser money rows" in browser_audit
    assert "Beacon ₹1,234.56 ≈ €1,234.56" in browser_audit
    assert "ss02" in browser_audit
    assert "acct_Il1l-class" in browser_audit
    for pin_file in (
        "PivotSans-Wght450.woff2",
        "PivotSans-Wght450Italic.woff2",
        "PivotSans-Wght550.woff2",
        "PivotSans-Wght550Italic.woff2",
    ):
        assert pin_file in browser_audit


def test_current_release_surfaces_are_1_200_and_fontlog_keeps_history():
    for relative in (
        "README.md",
        "QA-NOTES.md",
        "pipeline/measure.py",
        "pipeline/render_qa.py",
        "dist/specimen.html",
    ):
        text = (ROOT / relative).read_text()
        assert "1.200" in text, relative
        assert "1.100" not in text, relative

    fontlog = (ROOT / "FONTLOG.txt").read_text()
    assert "Version 1.200" in fontlog
    assert "Version 1.100" in fontlog

    for relative in (
        "README.md",
        "FONTLOG.txt",
        "QA-NOTES.md",
        "pipeline/build.py",
        "pipeline/measure.py",
        "pipeline/render_qa.py",
        "dist/specimen.html",
    ):
        assert "1.001" not in (ROOT / relative).read_text(), relative
