#!/usr/bin/env python3
"""Offline verifier for the static source-only audit package.

It validates only package contents and recorded public metadata.  It performs
no network access and does not run, import, or assess any paper artefact.
"""

from __future__ import annotations

import hashlib
import json
from pathlib import Path
import re
import sys


ROOT = Path(__file__).resolve().parent
CLAIMS = (
    (
        "C1",
        "Algorithm outputs ε-stationary point using Õ(n²/ε^1.5) queries to comparison oracle",
        "pages/claim-c1/page.md",
        ("main_2.tex:195–203", "main_2.tex:1307–1355", "PDF pp. 2–3", "PDF pp. 22–23"),
    ),
    (
        "C2",
        "Quantum comparison oracle variant achieves Õ(n/ε^1.5) queries for finding stationary points",
        "pages/claim-c2/page.md",
        ("main_2.tex:205–209", "main_2.tex:211–213", "main_2.tex:1688–1691", "PDF p. 29"),
    ),
    (
        "C3",
        "Develops subroutine estimating normalized Hessian to accuracy δ using Õ(n² log(1/δ)) queries",
        "pages/claim-c3/page.md",
        ("main_2.tex:427–438", "main_2.tex:508–515", "PDF pp. 8–9", "PDF p. 8"),
    ),
)
REQUIRED_FILES = {
    "README.md",
    "index.html",
    "PROVENANCE.md",
    "MANIFEST.md",
    "MANIFEST.sha256",
    "manifest.json",
    "LICENSE",
    "NOTICE",
    "verify.py",
    "evidence/README.md",
    "evidence/claims-snapshot.json",
    *(page for _, _, page, _ in CLAIMS),
}
REQUIRED_TAGS = ("icml2026-repro", "paper-VFpMpVXDJS")
FEED_REVISION = "7b5b56aebf3abe590eab9f2c241a796125cab928"
FEED_ETAG = "f8b74517cd33712f80399eea913efac0bb41078f"
FEED_URL = f"https://huggingface.co/spaces/ICML-2026-agent-repro/challenge/resolve/{FEED_REVISION}/claims.json"
OUTCOME = "not assessed"
STALE_REVIEW_WORDING = "review" + " pending"
UNSUPPORTED_DISPOSITION_TERMS = ("review" + "ed", "appro" + "ved")
CONCLUSION_TERMS = (
    "proof audit",
    "implementation",
    "execution",
    "result",
    "reproduction",
    "score",
    "claim verification",
)
MARKDOWN_LINK = re.compile(r"\[[^\]]+\]\(([^)]+)\)")
HTML_HREF = re.compile(r'href="([^"]+)"')


class VerificationError(RuntimeError):
    """Raised when the package contract is not met."""


def require(condition: bool, message: str) -> None:
    if not condition:
        raise VerificationError(message)


def read_text(relative_path: str) -> str:
    return (ROOT / relative_path).read_text(encoding="utf-8")


def read_json(relative_path: str) -> dict:
    try:
        value = json.loads(read_text(relative_path))
    except json.JSONDecodeError as error:
        raise VerificationError(f"invalid JSON in {relative_path}: {error}") from error
    require(isinstance(value, dict), f"{relative_path} must contain a JSON object")
    return value


def package_files() -> list[Path]:
    return sorted(
        (
            path
            for path in ROOT.rglob("*")
            if path.is_file()
            and ".git" not in path.parts
            and "__pycache__" not in path.parts
            and path.name != "MANIFEST.sha256"
        ),
        key=lambda path: path.relative_to(ROOT).as_posix(),
    )


def relative_paths() -> set[str]:
    return {path.relative_to(ROOT).as_posix() for path in package_files()}


def check_required_files() -> None:
    for relative_path in REQUIRED_FILES:
        require((ROOT / relative_path).is_file(), f"missing required file: {relative_path}")


def check_readme() -> None:
    readme = read_text("README.md")
    require(readme.startswith("---\n"), "README.md must begin with Space front matter")
    front_matter, _, _ = readme[4:].partition("\n---\n")
    require(front_matter, "README.md front matter must close")
    require("sdk: static" in front_matter, "README.md must declare sdk: static")
    require("pinned: false" in front_matter, "README.md must declare pinned: false")
    for tag in REQUIRED_TAGS:
        require(f"  - {tag}" in front_matter, f"README.md missing required tag: {tag}")
    require("not assessed" in readme.lower(), "README.md must state the fixed outcome")
    require(STALE_REVIEW_WORDING not in readme.lower(), "README.md carries stale review-pending wording")


def check_claim_records(manifest: dict, snapshot: dict) -> None:
    require(manifest.get("fixed_outcome") == OUTCOME, "manifest fixed outcome is wrong")
    require(snapshot.get("fixed_outcome") == OUTCOME, "snapshot fixed outcome is wrong")
    require("review_status" not in manifest, "manifest must not assert a review status")
    require("review_status" not in snapshot, "snapshot must not assert a review status")

    manifest_feed = manifest.get("claims_feed", {})
    snapshot_feed = snapshot.get("claims_feed", {})
    for feed in (manifest_feed, snapshot_feed):
        require(feed.get("url") == FEED_URL, "claims-feed URL is not immutable")
        require(feed.get("space_revision") == FEED_REVISION, "claims-feed revision is wrong")
        require(feed.get("asset_etag") == FEED_ETAG, "claims-feed ETag is wrong")

    space_config = manifest.get("space_configuration", {})
    require(space_config.get("sdk") == "static", "manifest Space SDK is wrong")
    require(set(REQUIRED_TAGS).issubset(space_config.get("required_tags", [])), "manifest tags are incomplete")

    manifest_claims = manifest.get("claims")
    snapshot_claims = snapshot.get("claims")
    require(isinstance(manifest_claims, list) and len(manifest_claims) == len(CLAIMS), "manifest claim count is wrong")
    require(isinstance(snapshot_claims, list) and len(snapshot_claims) == len(CLAIMS), "snapshot claim count is wrong")
    # The lengths are checked above, so ordinary zip is exact here and works
    # with the system Python versions commonly available for offline checks.
    for expected, manifest_claim, snapshot_claim in zip(CLAIMS, manifest_claims, snapshot_claims):
        claim_id, claim_text, page, _ = expected
        require(manifest_claim.get("id") == claim_id, f"manifest claim id mismatch: {claim_id}")
        require(snapshot_claim.get("id") == claim_id, f"snapshot claim id mismatch: {claim_id}")
        require(manifest_claim.get("exact_registry_claim") == claim_text, f"manifest claim text mismatch: {claim_id}")
        require(snapshot_claim.get("text") == claim_text, f"snapshot claim text mismatch: {claim_id}")
        require(manifest_claim.get("page") == page, f"manifest claim page mismatch: {claim_id}")
        require(snapshot_claim.get("page") == page, f"snapshot claim page mismatch: {claim_id}")
        require(manifest_claim.get("registry_status") == "unverified", f"manifest registry status mismatch: {claim_id}")
        require(snapshot_claim.get("registry_status") == "unverified", f"snapshot registry status mismatch: {claim_id}")
        require(manifest_claim.get("fixed_outcome") == OUTCOME, f"manifest outcome mismatch: {claim_id}")
        require(snapshot_claim.get("fixed_outcome") == OUTCOME, f"snapshot outcome mismatch: {claim_id}")

    conclusion = str(manifest.get("conclusion", "")).lower()
    for term in CONCLUSION_TERMS:
        require(term in conclusion, f"manifest conclusion omits boundary term: {term}")


def check_claim_pages() -> None:
    all_claim_texts = [text for _, text, _, _ in CLAIMS]
    for claim_id, claim_text, page, anchors in CLAIMS:
        content = read_text(page)
        require(content.startswith(f"# Claim {claim_id}"), f"page title mismatch: {page}")
        require(content.count(claim_text) == 1, f"page must contain its exact claim once: {page}")
        matches = sum(content.count(other_claim) for other_claim in all_claim_texts)
        require(matches == 1, f"page must contain exactly one registry claim: {page}")
        for anchor in anchors:
            require(anchor in content, f"page missing source anchor {anchor}: {page}")
        require("## Fixed outcome" in content, f"page missing fixed-outcome section: {page}")
        require("**not assessed**" in content, f"page outcome is not fixed: {page}")
        lower_content = content.lower()
        for term in CONCLUSION_TERMS:
            require(term in lower_content, f"page omits boundary term {term}: {page}")
        require(STALE_REVIEW_WORDING not in lower_content, f"page carries stale review-pending wording: {page}")


def check_links() -> None:
    markdown_files = ["README.md", "PROVENANCE.md", "MANIFEST.md", "evidence/README.md", *(page for _, _, page, _ in CLAIMS)]
    for relative_path in markdown_files:
        source_path = ROOT / relative_path
        for target in MARKDOWN_LINK.findall(source_path.read_text(encoding="utf-8")):
            if target.startswith(("https://", "http://", "mailto:")):
                continue
            destination = target.split("#", 1)[0]
            if not destination:
                continue
            require((source_path.parent / destination).is_file(), f"broken local Markdown link in {relative_path}: {target}")

    index = read_text("index.html")
    for target in HTML_HREF.findall(index):
        if target.startswith(("https://", "http://", "mailto:")):
            continue
        require((ROOT / target).is_file(), f"broken local HTML link: {target}")


def check_boundaries() -> None:
    prohibited_suffixes = {".pdf", ".tex", ".tar", ".gz", ".zip", ".ipynb", ".ckpt", ".pt", ".npz"}
    private_prefixes = (
        "/" + "Users" + "/",
        "/" + "home" + "/",
        "C:" + "\\" + "Users" + "\\",
    )
    secret_patterns = (
        re.compile(r"hf_" + r"[A-Za-z0-9]{20,}"),
        re.compile(r"sk-" + r"[A-Za-z0-9]{20,}"),
        re.compile(r"AKIA" + r"[A-Z0-9]{16}"),
    )
    for path in package_files():
        relative_path = path.relative_to(ROOT).as_posix()
        require(path.suffix.lower() not in prohibited_suffixes, f"forbidden redistributed artefact: {relative_path}")
        content = path.read_text(encoding="utf-8")
        require(not any(prefix in content for prefix in private_prefixes), f"private path found in {relative_path}")
        require(not any(pattern.search(content) for pattern in secret_patterns), f"token-shaped content found in {relative_path}")
        require(STALE_REVIEW_WORDING not in content.lower(), f"stale review-pending wording in {relative_path}")
        for term in UNSUPPORTED_DISPOSITION_TERMS:
            require(term not in content.lower(), f"unsupported review disposition in {relative_path}")

    for path in ROOT.rglob("*"):
        if ".git" not in path.parts:
            require("__pycache__" not in path.parts, f"unsupported cached path: {path.relative_to(ROOT)}")


def check_hash_manifest() -> None:
    entries: dict[str, str] = {}
    for line in read_text("MANIFEST.sha256").splitlines():
        digest, separator, relative_path = line.partition("  ")
        require(separator == "  " and len(digest) == 64 and relative_path, f"invalid hash-manifest line: {line}")
        entries[relative_path] = digest

    actual_paths = relative_paths()
    require(set(entries) == actual_paths, "MANIFEST.sha256 file set does not match the package")
    for relative_path in sorted(actual_paths):
        digest = hashlib.sha256((ROOT / relative_path).read_bytes()).hexdigest()
        require(entries[relative_path] == digest, f"SHA-256 mismatch: {relative_path}")


def main() -> int:
    try:
        check_required_files()
        manifest = read_json("manifest.json")
        snapshot = read_json("evidence/claims-snapshot.json")
        check_readme()
        check_claim_records(manifest, snapshot)
        check_claim_pages()
        check_links()
        check_boundaries()
        check_hash_manifest()
    except VerificationError as error:
        print(f"FAIL: {error}", file=sys.stderr)
        return 1
    print("PASS: static source-only audit package is internally consistent and offline-verifiable")
    return 0


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