#!/usr/bin/env python3
"""verify-img-reader-both-ways.py — prove the reader's own rule both ways
(mirror of verify-svg-xml-both-ways.py).

The wall's whole-site img gate once used IMG_TAG_RE = <img\\b[^>]*>, which
stopped at the FIRST '>' it saw — even when that '>' sat INSIDE a quoted
attribute value. A perfectly legal tag whose value contained the boundary
character (alt="a > b") was unreadable to the wall: the rule that decided
what a tag IS was still the hand's.

The reader (iter_img_tags in verify-descs.py) now scans quote-aware: a '>'
inside a " or ' quoted attribute value is part of the value, not the end of
the tag. Proof, both ways:

1. honest estate            -> verify-descs exits 0 (every tag read)
2. reader unit: a tag whose alt contains '>' AND whose title contains '>'
                            -> iter_img_tags yields the WHOLE tag (the old
                               [^>]* would stop at the first '>' inside)
3. planted tag with '>' in alt, value != desc
                            -> READ, then ALT MISMATCH (the lie is caught —
                               proving the reading went THROUGH the '>')
4. planted tag with '>' in a LATER attribute (title after alt, alt == desc)
                            -> READ (boundary respects quotes anywhere)
5. planted dropped-alt tag  -> still unreadable, fails (reverse gate holds)
6. reader unit: an img-shaped word inside an HTML comment
                            -> NO tag yielded (the comment is words)
7. planted img-shaped word in a comment on a page
                            -> exit 0 (a comment's img words are not served)
8. planted REAL img (dropped alt) INSIDE a comment
                            -> exit 0 (commented-out markup is never rendered:
                               words, not markup)
"""
import os
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path

TOOLS = Path("/home/exedev/hq/members/vigo/site/tools")
SRC = (TOOLS / "verify-descs.py").read_text()

# extract the reader function from the wall's source — the harness tests the
# REAL function, not a re-implementation, without importing the whole script
# (verify-descs.py is a script, not a module: importing it runs the whole
# wall and exits before the harness can act).
_m = re.search(r"(def iter_img_tags\(.*?)\n\ndef collapse", SRC, re.S)
if not _m:
    print("FAIL harness: could not extract iter_img_tags from verify-descs.py")
    sys.exit(1)
_ns = {}
exec(_m.group(1), _ns)
iter_img_tags = _ns["iter_img_tags"]

ok = True

def run_wall(site_dir):
    # run the COPIED verify-descs.py inside the tmp tree, so __file__-derived
    # site paths point at the planted copy, not the real estate
    script = Path(site_dir) / "tools" / "verify-descs.py"
    return subprocess.run(
        [sys.executable, str(script)],
        capture_output=True, text=True, cwd=Path(site_dir) / "tools",
        env={**os.environ, 'PYTHONPATH': str(Path(site_dir) / "tools")})

# 1. honest estate — the wall passes with the new reader (with the sitemap
# regenerated in the copy: the ring's desc grew this wake, so a stale sitemap
# would fail the sitemap-entry check, not the img reader)
tmp = Path(tempfile.mkdtemp(prefix="imgreader-test-"))
shutil.copytree(TOOLS.parent, tmp / "site", ignore=shutil.ignore_patterns("*.png"))
site2 = tmp / "site"
r0 = subprocess.run([sys.executable, str(site2 / "tools" / "sitemap.py")],
                    capture_output=True, text=True, cwd=str(site2 / "tools"))
r1 = run_wall(site2)
print("== 1. honest estate")
if r1.returncode == 0:
    print("PASS honest: verify-descs exits 0")
else:
    print(f"FAIL honest: exit {r1.returncode}")
    print(r1.stdout[-600:].strip())
    ok = False

# 2. reader unit — the boundary rule reads quotes in ANY attribute
sample = '<img src="/assets/x.svg" alt="a > b" title="c > d">'
got = list(iter_img_tags(sample))
print("\n== 2. reader unit: '>' inside alt AND title")
if len(got) == 1 and got[0] == sample:
    print("PASS reader-unit: the whole tag is yielded — quoted '>' does not end it")
else:
    print(f"FAIL reader-unit: got {got!r}")
    ok = False

# 3-5. hostile estate — plant tags on a page in the copy
target = site2 / "writings" / "dead-reckoning.html"
orig = target.read_text()
m = re.search(r'<img src="/assets/([^"]+\.svg)"\s+alt="([^"]*)"', orig)
plant_src = m.group(1) if m else "the-checkbox-trap.svg"
plant_desc = m.group(2) if m else "x"

# 3. '>' inside the alt, value != desc -> READ then MISMATCH (caught)
target.write_text(orig + f'\n<img src="/assets/{plant_src}" alt="a > b">\n')
r3 = run_wall(site2)
print("\n== 3. planted '>' in alt, value != desc")
if r3.returncode == 1 and "ALT MISMATCH" in r3.stdout:
    print("PASS hostile-1: reading went THROUGH the '>' and caught the mismatch")
else:
    print(f"FAIL hostile-1: expected exit 1 with ALT MISMATCH, got {r3.returncode}")
    print(r3.stdout[-800:].strip())
    ok = False

# 4. '>' in a LATER attribute (title after alt), alt still == desc -> READ
target.write_text(orig + f'\n<img src="/assets/{plant_src}" alt="{plant_desc}" title="a > b">\n')
r4 = run_wall(site2)
print("\n== 4. planted '>' in title attr (after alt), alt == desc")
if r4.returncode == 0:
    print("PASS hostile-2: boundary respects quotes in any attribute")
else:
    print(f"FAIL hostile-2: exit {r4.returncode}")
    print(r4.stdout[-800:].strip())
    ok = False

# 5. dropped alt -> still unreadable, reverse gate holds
target.write_text(orig + '\n<img src="/assets/the-checkbox-trap.svg">\n')
r5 = run_wall(site2)
print("\n== 5. planted dropped-alt tag")
if r5.returncode == 1 and "IMG NOT READ" in r5.stdout:
    print("PASS hostile-3: dropped alt still fails — the reverse gate holds")
else:
    print(f"FAIL hostile-3: expected exit 1 with IMG NOT READ, got {r5.returncode}")
    print(r5.stdout[-800:].strip())
    ok = False

# 6. reader unit — the comment is words, not markup: an img-shaped word
#    inside an HTML comment is NOT a tag (the coda ghost the desc wall
#    closed at 04:45, found one surface over in the image wall's own reader)
sample6 = '<!-- the hand\'s words about markup: <img src="/assets/x.svg" alt="y"> -->'
got6 = list(iter_img_tags(sample6))
print("\n== 6. reader unit: img-shaped word inside an HTML comment")
if got6 == []:
    print("PASS comment-unit: an <img> in a comment is words, not a tag")
else:
    print(f"FAIL comment-unit: got {got6!r} — the reader read a comment as a tag")
    ok = False

# 7. hostile estate — plant an img-shaped word in a comment on a real page:
#    the wall must NOT count it as a served tag (exit 0 stays honest), and
#    the real img after the comment must still be read
target.write_text(orig + '\n<!-- the coda\'s own words: <img src="/assets/the-checkbox-trap.svg" alt="stale"> -->\n')
r7 = run_wall(site2)
print("\n== 7. planted img-shaped word inside an HTML comment on a page")
if r7.returncode == 0:
    print("PASS hostile-4: a comment's img words do not become a served tag")
else:
    print(f"FAIL hostile-4: expected exit 0, got {r7.returncode}")
    print(r7.stdout[-800:].strip())
    ok = False

# 8. hostile estate — a REAL img planted inside a comment with a dropped
#    alt must NOT trip the gate either (the browser never renders it): the
#    boundary that decides what a tag IS reads the comments both ways
target.write_text(orig + '\n<!-- <img src="/assets/the-checkbox-trap.svg"> -->\n')
r8 = run_wall(site2)
print("\n== 8. planted real img (dropped alt) INSIDE a comment")
if r8.returncode == 0:
    print("PASS hostile-5: a commented-out img is not a served tag — words, not markup")
else:
    print(f"FAIL hostile-5: expected exit 0, got {r8.returncode}")
    print(r8.stdout[-800:].strip())
    ok = False

shutil.rmtree(tmp, ignore_errors=True)
print("\nRESULT:", "ALL PASS" if ok else "SOME FAILED")
sys.exit(0 if ok else 1)
