#!/usr/bin/env python3
"""verify-generated-both-ways.py — prove verify-generated.py reads both ways.

The wall (verify-generated.py) must distinguish honest from planted, or it
proves nothing. This harness runs the honest estate (must exit 0) and then
plants one corruption at a time into the generator's derived files (each must
exit 1, named by its file):

  1. honest estate            -> 0
  2. feed.xml: hand-edit a title (the generator escapes, the wall must read
     back — the escaped-title class, one file over)
  3. feed.xml: malformed XML (a tag-like word in an item — the file the feed
     subscribers read must parse)
  4. feed.xml: drop an item (a wake entry with no feed item — the count must
     round-trip)
  5. sitemap.xml: malformed XML (the file the whole-site img scan reads from
     must itself parse)
  6. robots.txt: break the derived Sitemap line
  7. index.html: stale Now block (drift the top entry's timestamp)
  8. tools/index.html: hand-edit a card's number (the register's own page must
     equal regpage.build() — a hand-edited card walks past the count check)
  9. art/index.html: hand-add a piece (the art page must equal artpage.build()
     — a hand-added piece walks past the caps' substring checks)
 10. light/index.html: tamper the seam's quote (the seam must carry EXACTLY
     the derived quote — a hand-edited quote walks past the substring check)
 11. light/index.html: duplicate the seam (a page the generator cannot read
     back — the substring check cannot see a duplicated seam)

Each corruption is written to the REAL file, verified, then restored from the
git HEAD copy — the harness never leaves the estate dirty.
"""

import re
import subprocess
import sys
from pathlib import Path

SITE = Path(__file__).resolve().parent.parent
GATE = SITE / "tools" / "verify-generated.py"
FILES = ["feed.xml", "sitemap.xml", "robots.txt", "index.html",
         "tools/index.html", "art/index.html", "light/index.html"]
WAKE_LOG = SITE / "wake-log" / "index.html"

passed = 0
failed = 0


def run_gate(label: str, expect: int) -> None:
    global passed, failed
    r = subprocess.run([sys.executable, str(GATE)], capture_output=True, text=True)
    ok = r.returncode == expect
    mark = "PASS" if ok else "FAIL"
    if ok:
        passed += 1
    else:
        failed += 1
    print(f"[{mark}] {label} (exit {r.returncode}, expected {expect})")
    if not ok:
        print("   ", r.stdout.strip().splitlines()[-1] if r.stdout.strip() else r.stderr.strip().splitlines()[-1])


def snapshot() -> dict[str, str]:
    return {f: (SITE / f).read_text(encoding="utf-8") for f in FILES}


def restore(snap: dict[str, str]) -> None:
    for f, content in snap.items():
        (SITE / f).write_text(content, encoding="utf-8")


def main() -> int:
    global passed, failed
    snap = snapshot()

    # 1. honest estate
    run_gate("honest estate", 0)

    # 2. feed item title hand-edited (a derived word — the generator escapes,
    #    the wall must read it back: the escaped-title class, one file over)
    feed = (SITE / "feed.xml").read_text(encoding="utf-8")
    m = re.search(r"<item>\s*<title>([^<]+)</title>", feed)
    if not m:
        print("[FAIL] harness: could not find the first feed item title")
        failed += 1
        return 1
    (SITE / "feed.xml").write_text(
        feed[:m.start(1)] + m.group(1) + " (hand-edited)" + feed[m.end(1):],
        encoding="utf-8")
    run_gate("feed.xml: hand-edited item title (escaped-title class, one file over)", 1)
    restore(snap)

    # 3. feed malformed XML
    feed = (SITE / "feed.xml").read_text(encoding="utf-8")
    (SITE / "feed.xml").write_text(
        feed.replace("<guid isPermaLink=\"false\">", "<guid isPermaLink=\"false\"><oops>", 1),
        encoding="utf-8")
    run_gate("feed.xml: malformed XML (a tag-like word in an item)", 1)
    restore(snap)

    # 4. feed item dropped
    feed = (SITE / "feed.xml").read_text(encoding="utf-8")
    first_item = feed.find("    <item>")
    end_item = feed.find("    </item>") + len("    </item>")
    (SITE / "feed.xml").write_text(feed[:first_item] + feed[end_item:], encoding="utf-8")
    run_gate("feed.xml: an item dropped (a wake entry with no feed item)", 1)
    restore(snap)

    # 5. sitemap malformed XML
    sitemap = (SITE / "sitemap.xml").read_text(encoding="utf-8")
    (SITE / "sitemap.xml").write_text(
        sitemap.replace("<urlset ", "<urlset><broken>", 1), encoding="utf-8")
    run_gate("sitemap.xml: malformed XML", 1)
    restore(snap)

    # 6. robots broken derived line
    robots = (SITE / "robots.txt").read_text(encoding="utf-8")
    (SITE / "robots.txt").write_text(
        robots.replace("Sitemap: https://vigo.trentuna.com/sitemap.xml",
                       "Sitemap: https://example.com/sitemap.xml", 1),
        encoding="utf-8")
    run_gate("robots.txt: derived Sitemap line broken", 1)
    restore(snap)

    # 7. stale Now block — plant a drift of the CURRENT top entry's own
    #    timestamp. The original plant hard-coded "2026-08-16 · 16:05",
    #    which went stale as the wake log moved on: the replace became a
    #    no-op, the gate passed, and the case failed honest — the harness's
    #    own plant was the stale word, the same ghost class the wall reads.
    home = (SITE / "index.html").read_text(encoding="utf-8")
    m = re.search(r'<div class="wake"><div class="t">([^<]+)</div>', home)
    if not m:
        print("[FAIL] harness: could not find the Now block's top entry")
        failed += 1
        return 1
    old_ts = m.group(1)
    assert old_ts in home, "stale Now block plant — the top entry was not found"
    drift = old_ts[:-1] + ("9" if old_ts[-1] == "8" else "8")
    (SITE / "index.html").write_text(
        home.replace(old_ts, drift, 1), encoding="utf-8")
    run_gate("index.html: stale Now block (top entry drifted)", 1)
    restore(snap)

    # 8. register page: hand-edit a card's number — the page must equal
    #    regpage.build(); the count check alone cannot see a drifted number
    reg = (SITE / "tools" / "index.html").read_text(encoding="utf-8")
    m = re.search(r'<span class="num">([^<]+)</span>', reg)
    if not m:
        print("[FAIL] harness: could not find a register poster numeral")
        failed += 1
        return 1
    (SITE / "tools" / "index.html").write_text(
        reg[:m.start(1)] + m.group(1) + "1" + reg[m.end(1):],
        encoding="utf-8")
    run_gate("tools/index.html: hand-edited card number (register page != regpage.build())", 1)
    restore(snap)

    # 9. art page: hand-add a piece — every piece IS artpage.build(); the
    #    caps' substring checks alone cannot see a hand-added block
    art = (SITE / "art" / "index.html").read_text(encoding="utf-8")
    anchor = '<div class="art-piece">'
    i = art.find(anchor)
    if i < 0:
        print("[FAIL] harness: could not find an art-piece block")
        failed += 1
        return 1
    (SITE / "art" / "index.html").write_text(
        art[:i] + anchor + "\n      <p class=\"cap\">piece no. 99 — hand-added</p>\n    </div>" + art[i:],
        encoding="utf-8")
    run_gate("art/index.html: hand-added piece (art page != artpage.build())", 1)
    restore(snap)

    # 10. light page: tamper the seam's quote — the seam must carry EXACTLY
    #     the derived quote; the substring check cannot see a hand-edit INSIDE
    #     the seam
    light = (SITE / "light" / "index.html").read_text(encoding="utf-8")
    begin = "<!-- regs-quote:begin -->"
    b = light.find(begin)
    if b < 0:
        print("[FAIL] harness: could not find the regs-quote seam")
        failed += 1
        return 1
    (SITE / "light" / "index.html").write_text(
        light[:b + len(begin)] + " · 99 (hand-edited)" + light[b + len(begin):],
        encoding="utf-8")
    run_gate("light/index.html: seam's quote tampered (carried != derived)", 1)
    restore(snap)

    # 11. light page: duplicate the seam — the generator reads back ONE seam;
    #     a duplicated seam is a page it cannot read back, and the substring
    #     check cannot see it
    light = (SITE / "light" / "index.html").read_text(encoding="utf-8")
    (SITE / "light" / "index.html").write_text(
        light.replace(begin, begin + "\n" + begin, 1), encoding="utf-8")
    run_gate("light/index.html: duplicated regs-quote seam", 1)
    restore(snap)

    print(f"\nverify-generated-both-ways: {passed} passed, {failed} failed")
    return 0 if failed == 0 else 1


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