#!/usr/bin/env python3
"""Reject disagreement with the canonical adapter manifest version."""

from __future__ import annotations

import argparse
import json
from pathlib import Path
import re
import sys

try:
    import tomllib as toml
except ModuleNotFoundError:  # pragma: no cover - Python <= 3.10
    try:
        import tomli as toml  # type: ignore[no-redef]
    except ModuleNotFoundError:
        print("MISSING-DEP: Python 3.11+ or tomli is required", file=sys.stderr)
        raise SystemExit(2)

SEMVER = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$")


def load_toml(path: Path, errors: list[str]) -> dict | None:
    try:
        with path.open("rb") as stream:
            return toml.load(stream)
    except (OSError, toml.TOMLDecodeError) as exc:
        errors.append(f"{path}: cannot read TOML: {exc}")
        return None


def text_version(path: Path, pattern: re.Pattern[str], label: str, errors: list[str]) -> str | None:
    try:
        text = path.read_text(encoding="utf-8")
    except OSError as exc:
        errors.append(f"{path}: cannot read {label}: {exc}")
        return None
    match = pattern.search(text)
    if not match:
        errors.append(f"{path}: cannot find {label}")
        return None
    return match.group(1)


# [impl->REQ-DIST-VERSION-CONSISTENCY]
# [impl->REQ-PARITY-BASELINE]
def check_versions(root: Path) -> list[str]:
    """Return every disagreement with adapter/omp-spt.toml's canonical version."""
    errors: list[str] = []
    manifest_path = root / "adapter" / "omp-spt.toml"
    manifest = load_toml(manifest_path, errors)
    canonical = None
    if manifest is not None:
        canonical = manifest.get("adapter", {}).get("version")
        if not isinstance(canonical, str) or not SEMVER.fullmatch(canonical):
            errors.append(f"{manifest_path}: adapter.version is not canonical SemVer: {canonical!r}")
            canonical = None

    observed: list[tuple[str, Path, str | None]] = []

    cargo_path = root / "tools" / "omp-spt" / "Cargo.toml"
    cargo = load_toml(cargo_path, errors)
    cargo_version = cargo.get("package", {}).get("version") if cargo is not None else None
    if cargo is not None and not isinstance(cargo_version, str):
        errors.append(f"{cargo_path}: package.version is missing or not a string")
        cargo_version = None
    observed.append(("helper crate", cargo_path, cargo_version))

    lock_path = root / "tools" / "omp-spt" / "Cargo.lock"
    lock = load_toml(lock_path, errors)
    lock_version = None
    if lock is not None:
        packages = [item for item in lock.get("package", []) if item.get("name") == "omp-spt"]
        if len(packages) != 1 or not isinstance(packages[0].get("version"), str):
            errors.append(f"{lock_path}: expected exactly one versioned omp-spt package")
        else:
            lock_version = packages[0]["version"]
    observed.append(("helper lockfile", lock_path, lock_version))

    plugin_path = root / "adapter" / "strings" / "package.json"
    try:
        plugin = json.loads(plugin_path.read_text(encoding="utf-8"))
        plugin_version = plugin.get("version")
        if not isinstance(plugin_version, str):
            errors.append(f"{plugin_path}: version is missing or not a string")
            plugin_version = None
    except (OSError, json.JSONDecodeError) as exc:
        errors.append(f"{plugin_path}: cannot read package metadata: {exc}")
        plugin_version = None
    observed.append(("packaged OMP plugin", plugin_path, plugin_version))

    observed.extend(
        [
            (
                "latest changelog heading",
                root / "CHANGELOG.md",
                text_version(
                    root / "CHANGELOG.md",
                    re.compile(r"^## \[([0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?)\]", re.MULTILINE),
                    "release heading",
                    errors,
                ),
            ),
            (
                "README release claim",
                root / "README.md",
                text_version(
                    root / "README.md",
                    re.compile(r"\bThe v([0-9]+\.[0-9]+\.[0-9]+) release asset supports\b"),
                    "versioned release claim",
                    errors,
                ),
            ),
            (
                "quickstart release claim",
                root / "docs-site" / "src" / "quickstart.md",
                text_version(
                    root / "docs-site" / "src" / "quickstart.md",
                    re.compile(r"\bThe v([0-9]+\.[0-9]+\.[0-9]+) `omp-spt` release supports\b"),
                    "versioned release claim",
                    errors,
                ),
            ),
            (
                "parity baseline omp-spt release",
                root / "docs" / "PARITY.md",
                text_version(
                    root / "docs" / "PARITY.md",
                    re.compile(
                        r"^Current baseline: `omp-spt v([0-9]+\.[0-9]+\.[0-9]+)` → "
                        r"`(?:BigscreenVR/claude-spt-bs|SaberMage/claude-spt) "
                        r"v[0-9]+\.[0-9]+\.[0-9]+`\.$",
                        re.MULTILINE,
                    ),
                    "versioned claude-spt parity baseline",
                    errors,
                ),
            ),
        ]
    )

    if canonical is not None:
        for label, path, version in observed:
            if version is not None and version != canonical:
                errors.append(
                    f"{path}: {label} version {version!r} does not match canonical manifest version {canonical!r}"
                )
    return errors


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument(
        "--root",
        type=Path,
        default=Path(__file__).resolve().parents[2],
        help="repository root (defaults to the script's repository)",
    )
    args = parser.parse_args(argv)
    root = args.root.resolve()
    errors = check_versions(root)
    if errors:
        for error in errors:
            print(f"FAIL: {error}")
        return 1

    with (root / "adapter" / "omp-spt.toml").open("rb") as stream:
        version = toml.load(stream)["adapter"]["version"]
    print(f"VERSION-CONSISTENCY OK: {version}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
