#!/usr/bin/env python3
"""Run two pinned upstream functions with synthetic graphs and a Choice mock.

No network, environment/key-file reads, third-party dependencies, or full-module
import. Supply the unmodified graphify/jev_bridge.py from the commit below.
This probes wrapper behavior, not model accuracy or the complete Graphify app.
"""
import ast
import hashlib
import json
import os
import platform
import sys
import types
from datetime import datetime, timezone
from pathlib import Path

COMMIT = "85808c6a12ac48a4aa39d149df5f6d8b99f07d37"
SOURCE_SHA256 = "62c10d01da3dbd3c50f9b631149fd47bcd09efebf349e9022a29267a1414c623"
SOURCE_URL = f"https://raw.githubusercontent.com/54lynnn/graphify-jev/{COMMIT}/graphify/jev_bridge.py"


class GraphFixture:
    """Only the graph interface used by the two source functions."""
    def __init__(self, nodes, edges, commit):
        self.nodes = nodes
        self.edges = edges
        self.graph = {"commit": commit}

    def number_of_nodes(self):
        return len(self.nodes)

    def number_of_edges(self):
        return len(self.edges)

    def degree(self, node):
        return sum(node in edge for edge in self.edges)

    def __contains__(self, node):
        return node in self.nodes


def main():
    if len(sys.argv) != 2:
        raise SystemExit("Usage: python3 graphify_offline.py /path/to/pinned/jev_bridge.py")
    source = Path(sys.argv[1]).read_bytes()
    actual = hashlib.sha256(source).hexdigest()
    if actual != SOURCE_SHA256:
        raise SystemExit(f"Pinned source SHA-256 mismatch: {actual}")
    tree = ast.parse(source)
    names = {"_get_cache_key", "pick_seeds_with_jev"}
    functions = [node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name in names]
    assert {node.name for node in functions} == names
    subset = ast.Module(body=[ast.ImportFrom(module="__future__", names=[ast.alias(name="annotations")], level=0), *functions], type_ignores=[])
    ast.fix_missing_locations(subset)
    calls = []

    def mocked_choice(state, instructions, options):
        calls.append({"instructions": instructions, "options": dict(options)})
        # Synthetic oracle: choose the option explicitly carrying TARGET.
        chosen = next((k for k, v in options.items() if "TARGET" in v), next(iter(options)))
        return chosen, 0.99, {k: float(k == chosen) for k in options}

    cluster_module = types.ModuleType("graphify.cluster")
    cluster_module.cluster = lambda graph: {0: list(graph.nodes)}
    sys.modules["graphify"] = types.ModuleType("graphify")
    sys.modules["graphify.cluster"] = cluster_module
    namespace = {"os": os, "is_available": lambda: True,
                 "_call_jev_choice": mocked_choice, "_SEED_CACHE": {}, "_MAX_CACHE_SIZE": 128}
    exec(compile(subset, "pinned-upstream-functions", "exec"), namespace)
    key = namespace["_get_cache_key"]
    pick = namespace["pick_seeds_with_jev"]
    cache = namespace["_SEED_CACHE"]

    graph = GraphFixture({
        "a": {"label": "a", "source_file": "fixture.py", "docstring": "TARGET handler"},
        "b": {"label": "b", "source_file": "fixture.py", "docstring": "unrelated handler"},
    }, [("a", "b")], "fixture-commit-A")
    before_key = key(graph, "  TARGET  ", "/fixture/graph.json")
    first = pick(graph, "  TARGET  ", graph_path="/fixture/graph.json")
    graph.nodes["a"]["docstring"] = "unrelated handler"
    graph.nodes["b"]["docstring"] = "TARGET handler"
    graph.graph["commit"] = "fixture-commit-B"
    after_key = key(graph, "target", "/fixture/graph.json")
    calls_before = len(calls)
    cached = pick(graph, "target", graph_path="/fixture/graph.json")
    additional_calls = len(calls) - calls_before
    cache.clear()
    fresh = pick(graph, "target", graph_path="/fixture/graph.json")
    assert before_key == after_key
    assert first == cached == ["a"] and fresh == ["b"]
    assert additional_calls == 0
    cache_result = {"nodes": 2, "edges": 1, "beforeKey": before_key, "afterKey": after_key,
                    "firstSeeds": first, "cachedSeedsAfterContentAndCommitChange": cached,
                    "extraChoiceCallsOnCacheHit": additional_calls, "seedsAfterCacheClear": fresh}

    cache.clear()
    calls.clear()
    hubs = [f"hub-{i:02d}" for i in range(40)]
    nodes = {name: {"label": name, "source_file": "one-file.py", "docstring": "unrelated symbol"} for name in hubs}
    nodes["target"] = {"label": "target", "source_file": "one-file.py", "docstring": "TARGET handler"}
    edges = [(a, b) for i, a in enumerate(hubs) for b in hubs[i + 1:]]
    graph = GraphFixture(nodes, edges, "fixture-commit-C")
    seeds_41 = pick(graph, "TARGET", graph_path="/fixture/cap41.json")
    offered_41 = list(calls[-1]["options"])
    calls.clear()
    del nodes["hub-39"]
    graph.edges = [edge for edge in edges if "hub-39" not in edge]
    seeds_40 = pick(graph, "TARGET", graph_path="/fixture/cap40.json")
    offered_40 = list(calls[-1]["options"])
    assert len(offered_41) == len(offered_40) == 40
    assert "target" not in offered_41 and "target" in offered_40
    assert seeds_41 == ["hub-00"] and seeds_40 == ["target"]
    cap_result = {"communityCount": 1, "sourceFileCount": 1,
                  "nodes41": {"offeredCount": len(offered_41), "targetOffered": "target" in offered_41, "seeds": seeds_41,
                              "hubDegree": 39, "targetDegree": 0},
                  "nodes40": {"offeredCount": len(offered_40), "targetOffered": "target" in offered_40, "seeds": seeds_40,
                              "hubDegree": 38, "targetDegree": 0}}
    print(json.dumps({
        "schemaVersion": 1, "runAt": datetime.now(timezone.utc).isoformat(),
        "scope": "Pinned original functions with mocked Choice, mocked clustering and synthetic graph interface; not full integration or model inference",
        "environment": {"python": platform.python_version(), "implementation": platform.python_implementation(),
                        "system": platform.system(), "release": platform.release(), "machine": platform.machine(),
                        "dependencies": "Python standard library only"},
        "source": {"commit": COMMIT, "url": SOURCE_URL, "sha256": actual, "functions": sorted(names)},
        "networkRequests": 0, "keyOrEnvFilesRead": 0, "paidInferenceCalls": 0,
        "cacheSameSizeContentChange": cache_result, "candidateCap": cap_result,
        "assertions": "passed",
        "limitations": ["Does not run real clustering, graph extraction, network handling, NetworkX or Jev.",
                        "Does not measure frequency or accuracy loss on real repositories.",
                        "A cache collision matters only when the same path/object and counts survive while cached node IDs remain valid.",
                        "The 41-node fixture uses one community and one file, bypassing the optional file-selection stage."],
    }, indent=2))


if __name__ == "__main__":
    main()
