//! Integration tests for `mcptest ledger`.
//!
//! `emit` turns a saved agent envelope into a session-ledger NDJSON file;
//! `diff` gates a run's ledger against a baseline trajectory and exits
//! non-zero when divergences exceed `--max-diff`.

use assert_cmd::Command;
use predicates::prelude::*;
use tempfile::tempdir;

fn mcptest() -> Command {
    Command::cargo_bin("mcptest").expect("binary builds")
}

const ENVELOPE: &str = r#"{"tool_calls":[
    {"name":"search","server":"web","args":{"q":"rust"},"hop_index":0},
    {"name":"fetch","server":"web","args":{"id":1},"hop_index":1}
],"tool_results":[{"is_error":false,"result":{}},{"is_error":false,"result":{}}]}"#;

#[test]
fn emit_writes_header_plus_one_record_per_call() {
    let dir = tempdir().unwrap();
    let env = dir.path().join("env.json");
    let out = dir.path().join("baseline.ndjson");
    std::fs::write(&env, ENVELOPE).unwrap();
    mcptest()
        .args(["ledger", "emit", env.to_str().unwrap()])
        .args(["--session-id", "s1", "--output", out.to_str().unwrap()])
        .assert()
        .success();
    let written = std::fs::read_to_string(&out).unwrap();
    // Header + two tool calls.
    assert_eq!(written.lines().count(), 3);
    assert!(written.contains("\"type\":\"header\""));
    assert!(written.contains("\"tool_name\":\"search\""));
}

#[test]
fn diff_against_self_is_clean_and_exits_zero() {
    let dir = tempdir().unwrap();
    let env = dir.path().join("env.json");
    let ledger = dir.path().join("ledger.ndjson");
    std::fs::write(&env, ENVELOPE).unwrap();
    mcptest()
        .args(["ledger", "emit", env.to_str().unwrap()])
        .args(["--output", ledger.to_str().unwrap()])
        .assert()
        .success();
    mcptest()
        .args([
            "ledger",
            "diff",
            ledger.to_str().unwrap(),
            ledger.to_str().unwrap(),
        ])
        .assert()
        .success()
        .stdout(predicate::str::contains("clean"));
}

#[test]
fn diff_fails_when_trajectory_diverges_beyond_threshold() {
    let dir = tempdir().unwrap();
    let base_env = dir.path().join("base.json");
    let act_env = dir.path().join("act.json");
    let base = dir.path().join("base.ndjson");
    let act = dir.path().join("act.ndjson");
    std::fs::write(&base_env, ENVELOPE).unwrap();
    std::fs::write(&act_env, ENVELOPE.replace("fetch", "delete")).unwrap();
    for (env, out) in [(&base_env, &base), (&act_env, &act)] {
        mcptest()
            .args(["ledger", "emit", env.to_str().unwrap()])
            .args(["--output", out.to_str().unwrap()])
            .assert()
            .success();
    }
    // Default --max-diff 0: a changed tool at a hop is a remove + add (2), so
    // the gate fails.
    mcptest()
        .args([
            "ledger",
            "diff",
            base.to_str().unwrap(),
            act.to_str().unwrap(),
        ])
        .assert()
        .failure()
        .stdout(predicate::str::contains("delete"));
    // The same divergence is tolerated under a higher threshold.
    mcptest()
        .args([
            "ledger",
            "diff",
            base.to_str().unwrap(),
            act.to_str().unwrap(),
        ])
        .args(["--max-diff", "5"])
        .assert()
        .success();
}
