//! Integration tests for `mcptest run --update-snapshots`.
//!
//! Exercised through the compiled binary so the env-var refusal logic and the
//! record/replay/update behavior stay covered. The CI-refusal gate (exit 5)
//! still fires; outside CI the flag drives the live snapshot matcher, which
//! records only under the flag, diffs on plain runs, and fails a missing
//! snapshot with a record hint instead of minting a baseline (WOR-1292).

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

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

#[test]
fn refuses_to_update_snapshots_when_ci_env_is_set() {
    mcptest()
        .env("CI", "true")
        .args(["run", "--update-snapshots"])
        .assert()
        .code(5)
        .stderr(predicate::str::contains("--allow-update-in-ci"));
}

#[test]
fn allow_update_in_ci_bypasses_the_refusal() {
    let dir = tempfile::tempdir().unwrap();
    mcptest()
        .current_dir(dir.path())
        .env("CI", "true")
        .args(["run", "--update-snapshots", "--allow-update-in-ci"])
        .assert()
        .success()
        .stdout(predicate::str::contains("no config at"));
}

#[test]
fn update_snapshots_outside_ci_runs_normally() {
    let dir = tempfile::tempdir().unwrap();
    mcptest()
        .current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "--update-snapshots"])
        .assert()
        .success()
        .stdout(predicate::str::contains("no config at"));
}

#[test]
fn short_form_dash_u_is_accepted() {
    let dir = tempfile::tempdir().unwrap();
    mcptest()
        .current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "-u"])
        .assert()
        .success()
        .stdout(predicate::str::contains("no config at"));
}

#[test]
fn ci_env_set_to_other_value_does_not_block_update() {
    // We only block when CI is literally "true"; a developer who set
    // CI=local-dev for an experiment should not be locked out.
    let dir = tempfile::tempdir().unwrap();
    mcptest()
        .current_dir(dir.path())
        .env("CI", "local-dev")
        .args(["run", "--update-snapshots"])
        .assert()
        .success()
        .stdout(predicate::str::contains("no config at"));
}

/// Write a `mcptest mock` manifest for an `echo` tool that returns a fixed
/// result `text`, and return the manifest path. Re-calling with new text
/// overwrites the manifest in place (same path), which is how the drift tests
/// change the server's output between runs. The native mock answers the
/// handshake instantly, unlike a fork-per-line shell fixture.
#[cfg(unix)]
fn write_echo_server(dir: &std::path::Path, text: &str) -> std::path::PathBuf {
    let manifest = format!(
        r#"mock_server:
  name: echo
  tools:
    - name: echo
      description: "Echo"
      input_schema:
        type: object
      response:
        content:
          - type: text
            text: "{text}"
"#
    );
    let path = dir.join("echo.yaml");
    std::fs::write(&path, manifest).expect("write mock manifest");
    path
}

#[cfg(unix)]
fn snapshot_suite(server: &std::path::Path) -> String {
    let bin = env!("CARGO_BIN_EXE_mcptest");
    format!(
        r#"servers:
  fixture:
    command: ['{bin}', 'mock', '--tools-from', '{server}']
tools:
  - name: snapshot the echo output
    server: fixture
    tool: echo
    expect:
      - target: result
        matcher:
          snapshot: echo/baseline
"#,
        server = server.display(),
    )
}

#[cfg(unix)]
#[test]
fn snapshot_records_under_update_flag_then_replays_and_detects_drift() {
    let dir = tempfile::tempdir().unwrap();
    let server = write_echo_server(dir.path(), "hello");
    let config = dir.path().join("mcptest.yaml");
    std::fs::write(&config, snapshot_suite(&server)).expect("write suite");

    // A plain run with no recorded snapshot fails with the record hint
    // instead of silently minting its own baseline (WOR-1292).
    mcptest()
        .current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "--config"])
        .arg(&config)
        .assert()
        .code(1)
        .stdout(predicate::str::contains("--update-snapshots"));
    assert!(
        !dir.path().join("snapshots/echo/baseline.json").exists(),
        "a failing first run must not write a snapshot"
    );

    // Recording requires the explicit update flag.
    mcptest()
        .current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "--update-snapshots", "--config"])
        .arg(&config)
        .assert()
        .code(0)
        .stdout(predicate::str::contains("1 passed"));
    assert!(
        dir.path().join("snapshots/echo/baseline.json").exists(),
        "snapshot file should have been recorded"
    );

    // Second run replays against the recorded snapshot and passes.
    mcptest()
        .current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "--config"])
        .arg(&config)
        .assert()
        .code(0)
        .stdout(predicate::str::contains("1 passed"));

    // The server now returns different text, so the snapshot drifts and the
    // run fails.
    let drifted_server = write_echo_server(dir.path(), "goodbye");
    let _ = drifted_server; // same path (echo.sh), rewritten in place
    mcptest()
        .current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "--config"])
        .arg(&config)
        .assert()
        .code(1)
        .stdout(predicate::str::contains("1 failed"));
}

#[cfg(unix)]
#[test]
fn update_snapshots_re_records_a_drifted_snapshot() {
    let dir = tempfile::tempdir().unwrap();
    let server = write_echo_server(dir.path(), "first");
    let config = dir.path().join("mcptest.yaml");
    std::fs::write(&config, snapshot_suite(&server)).expect("write suite");

    // Record the original snapshot (recording needs the explicit flag
    // since WOR-1292).
    mcptest()
        .current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "--update-snapshots", "--config"])
        .arg(&config)
        .assert()
        .code(0);

    // Change the server output, then re-record with --update-snapshots so the
    // run passes against the new value.
    write_echo_server(dir.path(), "second");
    mcptest()
        .current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "--update-snapshots", "--config"])
        .arg(&config)
        .assert()
        .code(0)
        .stdout(predicate::str::contains("1 passed"));

    // The recorded snapshot now holds the new value, so a plain run passes.
    let recorded =
        std::fs::read_to_string(dir.path().join("snapshots/echo/baseline.json")).unwrap();
    assert!(
        recorded.contains("second"),
        "snapshot should have been re-recorded to the new value, got: {recorded}"
    );
}
