#!/usr/bin/env python3
"""Site integrity: every internal link resolves, every fragment target exists, JSON valid, manifest regenerated."""
import hashlib
import json
import re
import sys
from pathlib import Path
from urllib.parse import urlparse, unquote

SITE = Path(__file__).resolve().parent.parent / "site"

HREF_RE = re.compile(r'(?:href|src)="([^"]+)"')
ID_RE = re.compile(r'id="([^"]+)"')

ids_by_file = {}
links = []  # (source_file, target_path, fragment)
html_files = sorted(SITE.rglob("*.html"))
for f in html_files:
    text = f.read_text(encoding="utf-8", errors="replace")
    ids_by_file[f] = set(ID_RE.findall(text))
    for raw in HREF_RE.findall(text):
        if raw.startswith(("http://", "https://", "mailto:", "data:", "javascript:")):
            continue
        parsed = urlparse(raw)
        target = unquote(parsed.path)
        frag = parsed.fragment
        if not target and frag:
            links.append((f, f, frag))
            continue
        resolved = (f.parent / target).resolve()
        links.append((f, resolved, frag))

broken_files, broken_frags = [], []
for src, target, frag in links:
    if isinstance(target, Path) and target != src:
        if not target.exists():
            broken_files.append((str(src.relative_to(SITE)), str(target)))
            continue
    if frag:
        tf = target if isinstance(target, Path) and target.suffix == ".html" else src
        if tf.suffix == ".html" and tf.exists():
            if frag not in ids_by_file.get(tf, set()) and f'name="{frag}"' not in tf.read_text(encoding="utf-8", errors="replace"):
                # allow slide anchors etc. only if truly present
                broken_frags.append((str(src.relative_to(SITE)), str(tf.relative_to(SITE)), frag))

print(f"html files: {len(html_files)}; internal links checked: {len(links)}")
print(f"broken file links: {len(broken_files)}")
for b in broken_files[:20]:
    print("   ", b)
print(f"broken fragments: {len(broken_frags)}")
for b in broken_frags[:30]:
    print("   ", b)

# JSON validity
for j in list(SITE.rglob("*.json")):
    try:
        json.load(open(j))
    except Exception as e:
        print(f"INVALID JSON: {j}: {e}")
        sys.exit(1)
print("all JSON files parse")

# claims.json invariants
data = json.load(open(SITE / "assets" / "claims.json"))
n_corpus = sum(1 for c in data["claims"] if c["domain"] != "durable-anchors")
n_da = sum(1 for c in data["claims"] if c["domain"] == "durable-anchors")
assert n_corpus == 132, n_corpus
assert n_da == data["summary"].get("n_da_admitted", 0), (n_da, data["summary"].get("n_da_admitted"))
assert all(c.get("decision_use", {}).get("grade") in ("A", "B", "C") for c in data["claims"])
assert all(c.get("external_verification") for c in data["claims"])
print(f"claims.json invariants hold (132 corpus + {n_da} admitted durable anchors, all graded, all externally checked)")

# regenerate manifest over every shipped file
files = []
for f in sorted(SITE.rglob("*")):
    if f.is_dir() or f.name == ".DS_Store" or f.name == "site-manifest.json":
        continue
    rel = str(f.relative_to(SITE))
    files.append({"path": rel, "bytes": f.stat().st_size,
                  "sha256": hashlib.sha256(f.read_bytes()).hexdigest()})
manifest = {"generatedAt": "2026-07-15", "foundationVersion": "0.1.0",
            "note": "Regenerated 2026-07-15 after the evidence-ledger/decision-register build; covers every shipped file. The previously shipped manifest recorded stale hashes for assets/site.css, assets/site.js, and README.md and omitted fonts and favicon.",
            "files": files}
(SITE / "site-manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
print(f"manifest regenerated: {len(files)} files")

if broken_files or broken_frags:
    sys.exit(2)
print("SITE INTEGRITY: PASS")
