"""Run real Verifiers v1 orchestration/MCP/scoring with scripted CPU fixtures.

No model inference, model tokens, logprobs, optimizer, or GPU is involved.
Run from the repository root with .venv-verifiers/bin/python
examples/agent_gyms/verifiers_smoke.py --output runs/agent-gyms/verifiers-native-smoke
"""

from __future__ import annotations

import argparse
import asyncio
import hashlib
import importlib.metadata
import json
import os
from pathlib import Path
import platform
import sys

ROOT = Path(__file__).resolve().parents[2]
EXAMPLES = Path(__file__).resolve().parent
sys.path.insert(0, str(EXAMPLES))
# Native MCP launches this importable example module in a separate local process.
os.environ["PYTHONPATH"] = os.pathsep.join([str(EXAMPLES), os.environ.get("PYTHONPATH", "")])

import verifiers.v1 as vf  # noqa: E402
from verifiers.v1.clients import ModelContext  # noqa: E402
from verifiers.v1.runtimes import SubprocessConfig  # noqa: E402

from verifiers_environment import (  # noqa: E402
    ENVIRONMENT_ID, UPSTREAM_COMMIT, UPSTREAM_VERSION, GermanTasksetConfig,
    ScriptedFixtureHarness,
)

__all__ = ["ScriptedFixtureHarness"]


def sha256(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest()


def runtime_provenance() -> dict:
    distribution = importlib.metadata.distribution("verifiers")
    direct_url = json.loads(distribution.read_text("direct_url.json") or "{}")
    runtime_commit = direct_url.get("vcs_info", {}).get("commit_id")
    if runtime_commit != UPSTREAM_COMMIT:
        raise RuntimeError("install requirements/verifiers.txt: runtime must be the pinned Git source")
    module_root = Path(vf.__file__).resolve().parents[1]
    digest = hashlib.sha256()
    source_files = {}
    for path in sorted(module_root.rglob("*.py")):
        identity = str(path.relative_to(module_root))
        value = sha256(path)
        source_files[identity] = value
        digest.update(f"{identity}\0{value}\n".encode())
    return {"distribution_version": distribution.version, "runtime_git_commit": runtime_commit,
            "direct_url": direct_url, "installed_python_source_sha256": digest.hexdigest(),
            "installed_python_files": source_files}


async def smoke(output: Path) -> dict:
    if importlib.metadata.version("verifiers") != UPSTREAM_VERSION:
        raise RuntimeError(f"requires verifiers=={UPSTREAM_VERSION}")
    provenance = runtime_provenance()
    output.mkdir(parents=True, exist_ok=False)
    config = vf.SingleAgentEnvConfig(
        taskset=GermanTasksetConfig(id=ENVIRONMENT_ID, split="train", count=2, seed=7331),
        agent=vf.AgentConfig(harness=vf.HarnessConfig(id="verifiers_smoke"),
                             runtime=SubprocessConfig()),
    )
    env = vf.SingleAgentEnv(config)
    ctx = ModelContext(model="scripted-fixture/no-model", client=vf.EvalClientConfig(
        base_url="http://127.0.0.1:1/v1", api_key_var="HESSIAN_FIXTURE_NO_KEY"))
    tasks = list(env.taskset)
    cases = [("isolated_a", tasks[0], 1.0), ("isolated_b", tasks[1], 1.0)]
    cases.extend((name, tasks[0].with_data(fixture_scenario=name), 0.0)
                 for name in ["fake_tools", "unknown_tool", "invalid_record", "calculator_first", "wrong_answer"])
    results = []

    async def execute(case):
        name, task, expected = case
        episode = await env.run_episode(task, ctx)
        (output / f"{name}.json").write_text(
            json.dumps(episode.to_record(), indent=2, ensure_ascii=False) + "\n")
        if not episode.ok or len(episode.traces) != 1:
            raise AssertionError(f"{name}: native framework failed: {episode.model_dump_json()}")
        trace = episode.traces[0]
        assert trace.reward == expected, (name, trace.reward, expected, trace.info)
        assert not trace.calls and all(not node.sampled and not node.token_ids and not node.mask
                                       for node in trace.nodes), "fixture must not fabricate sampled model evidence"
        assert trace.info["native_lifecycle"] == ["task.setup", "fixture.harness.launch",
                                                  "task.finalize", "task.reward"]
        commands = trace.info["trusted_mcp_commands"]
        if expected:
            assert len(commands) == 2
            assert commands[0]["arguments"]["record_id"] == task.data.metadata["record_id"]
            assert trace.info["strict_score"]["success"]
        if name == "fake_tools":
            assert commands == [] and not trace.info.get("mcp_transport_calls")
        if name == "unknown_tool":
            assert commands == [] and trace.info["mcp_transport_calls"][0]["is_error"]
        return {"case": name, "task_id": task.data.id, "native_episode_id": episode.id,
                "native_trace_id": trace.id, "reward": trace.reward,
                "strict_success": trace.info["strict_score"]["success"],
                "reason": trace.info["strict_score"]["reason"],
                "trusted_tool_calls": len(commands), "episode_sha256": sha256(output / f"{name}.json")}

    # Deliberately interleave two different tasks, then reuse one task for
    # adversarial episodes: native authenticated state must remain per rollout.
    results.extend(await asyncio.gather(*(execute(case) for case in cases[:2])))
    for case in cases[2:]:
        results.append(await execute(case))
    assert tasks[0].data.metadata["record_id"] != tasks[1].data.metadata["record_id"]
    source_paths = [Path(__file__).resolve(), Path(__file__).with_name("verifiers_environment.py"),
                    ROOT / "src/hessian_recipes/agent_rl_tasks.py", ROOT / "requirements/verifiers.txt"]
    report = {
        "schema_version": 1, "status": "passed", "framework": "Prime Verifiers v1",
        "verifiers_version": UPSTREAM_VERSION, "upstream_tag_commit": UPSTREAM_COMMIT,
        "runtime_distribution": provenance,
        "policy_kind": "scripted_fixture", "model_inference": False, "training": False,
        "training_compatible": False, "gpu_used": False,
        "scope": "native SingleAgentEnv/Agent/Rollout/Harness + actual local MCP tools + native reward lifecycle",
        "token_evidence": "none: fixture graph nodes are unsampled with empty token IDs/masks; no logprobs fabricated",
        "client_endpoint": ctx.client.base_url, "external_model_requests": 0,
        "state_isolation": "two different tasks interleaved, then independent adversarial episodes",
        "python": platform.python_version(),
        "dependencies": {d.metadata["Name"]: d.version for d in importlib.metadata.distributions()},
        "source_sha256": {str(path.relative_to(ROOT)): sha256(path) for path in source_paths},
        "cases": results,
    }
    (output / "report.json").write_text(json.dumps(report, indent=2, ensure_ascii=False) + "\n")
    return report


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--output", type=Path, default=ROOT / "runs/agent-gyms/verifiers-native-smoke")
    args = parser.parse_args()
    report = asyncio.run(smoke(args.output.resolve()))
    print(json.dumps({"status": report["status"], "cases": len(report["cases"]),
                      "policy_kind": report["policy_kind"], "output": str(args.output.resolve())}))


if __name__ == "__main__":
    main()
