"""Replay synthetic semantic-cache migrations against pinned, unchanged source.

Usage: python cache-probe.py /path/to/graphify-source
Only __init__.py, cache.py, and paths.py are needed; no model is contacted.
"""
from pathlib import Path
import hashlib
import os
import platform
import sys
import tempfile
import warnings

PIN = "c3dad319ee42f42dfbe3f88b71f0a29303fcb832"
DIGESTS = {
    "graphify/__init__.py": "5afcd670aa65337ae49735582a9bc482a721abdfbfc063fcad1d0ccc90b0838a",
    "graphify/cache.py": "f73e216e6ac263a060664589cf9cb0f2d33af4b2d3d012ebca19f621014ee194",
    "graphify/paths.py": "529cf874f7a990d8f318c8ca343bffced5378a5cff74f31c36404dc609ee5855",
}
source = Path(sys.argv[1]).resolve()
for name, digest in DIGESTS.items():
    assert hashlib.sha256((source / name).read_bytes()).hexdigest() == digest, name
os.environ["GRAPHIFY_OUT"] = "graphify-out"
sys.path.insert(0, str(source))
from graphify import cache as c


def ids(result):
    return sorted(n["id"] for n in result["nodes"])


def put(root, file, node, **kwargs):
    assert c.save_semantic_cache(
        [{"id": node, "source_file": file}], [], root=root, **kwargs
    ) == 1


def check(root, files, **kwargs):
    with warnings.catch_warnings(record=True) as caught:
        warnings.simplefilter("always")
        nodes, edges, hyperedges, misses = c.check_semantic_cache(
            files, root=root, **kwargs
        )
    return sorted(n["id"] for n in nodes), misses, [str(w.message) for w in caught]


print(f"Python {platform.python_version()}; source {PIN}")
with tempfile.TemporaryDirectory(prefix="graphify-prompt-probe-") as tmp:
    root = Path(tmp)
    for name in ("a.md", "b.md", "merge.md", "mode.md", "fresh.md"):
        (root / name).write_text(f"# Synthetic {name}\n", encoding="utf-8")

    # Flat entries model the documented pre-fingerprint storage layout.
    put(root, "a.md", "legacy_a")
    put(root, "b.md", "legacy_b")
    put(root, "a.md", "current_a", prompt="PROMPT V2")
    got, misses, messages = check(root, ["a.md", "b.md"], prompt="PROMPT V2")
    assert got == ["current_a", "legacy_b"] and not misses
    assert len(messages) == 1 and "1 semantic cache entry predate" in messages[0]
    print(f"mixed corpus: nodes={got}; misses={len(misses)}; warnings={len(messages)}")
    print("warning:", messages[0])

    strict = c.load_cached(root / "b.md", root, kind="semantic",
                           prompt="PROMPT V2", allow_legacy=False)
    assert strict is None
    print("strict legacy read: MISS")

    put(root, "b.md", "current_b", prompt="PROMPT V2")
    got, misses, messages = check(root, ["a.md", "b.md"], prompt="PROMPT V2")
    assert got == ["current_a", "current_b"] and not misses and not messages
    print(f"current entries win: nodes={got}; warnings={len(messages)}")

    put(root, "fresh.md", "v1_only", prompt="PROMPT V1")
    got, misses, messages = check(root, ["fresh.md"], prompt="PROMPT V2")
    assert not got and misses == ["fresh.md"] and not messages
    print("different prompt without legacy: MISS")

    put(root, "merge.md", "unknown_chunk")
    put(root, "merge.md", "v2_chunk1", prompt="PROMPT V2", merge_existing=True)
    entry = c.load_cached(root / "merge.md", root, kind="semantic", prompt="PROMPT V2")
    assert ids(entry) == ["v2_chunk1"]
    put(root, "merge.md", "v2_chunk2", prompt="PROMPT V2", merge_existing=True)
    entry = c.load_cached(root / "merge.md", root, kind="semantic", prompt="PROMPT V2")
    assert ids(entry) == ["v2_chunk1", "v2_chunk2"]
    print(f"checkpoint merge: nodes={ids(entry)}; legacy excluded")

    put(root, "mode.md", "deep_node", prompt="PROMPT V2", mode="deep")
    got, misses, _ = check(root, ["mode.md"], prompt="PROMPT V2")
    assert not got and misses == ["mode.md"]
    got, misses, _ = check(root, ["mode.md"], prompt="PROMPT V2", mode="deep")
    assert got == ["deep_node"] and not misses
    print("mode isolation: standard=MISS; deep=HIT")

    assert c.prompt_fingerprint(" a  \r\nb\r\n") == c.prompt_fingerprint("a\nb\n")
    assert c.prompt_fingerprint("a\nb") != c.prompt_fingerprint("a\nc")
    print("prompt normalization: line endings/trailing spaces=equal; edited text=different")

    # An unreadable specification explicitly falls back to the flat layout.
    got, misses, messages = check(root, ["a.md"], prompt_file=root / "missing-spec.md")
    assert got == ["legacy_a"] and not misses
    assert len(messages) == 1 and "could not read extraction prompt" in messages[0]
    print(f"unreadable prompt file: nodes={got}; warnings={len(messages)}")

    # Flush while the temporary output tree still exists.
    c._flush_stat_index()
print("PASS: 8 cache scenarios; no model calls")
