//! Multi-run pass^k tool-selection floor gate.
//!
//! Drives `mcptest run` against a deterministic stdio fixture server
//! plus a `tool_selection:` floor. With no provider API key in the env,
//! the agent runner falls back to the stub model, which replies with
//! text and never calls a tool. A floor demanding 100% selection of
//! `get_weather` therefore cannot be met, so the run must exit non-zero
//! and print the cost-vs-accuracy pair. The companion happy-path test
//! shows a `min_selection_rate: 0.0` floor passing on the same stub.
//!
//! The fixture server is POSIX shell so the test does not depend on
//! Python or Node, matching the agent crate's stub e2e test.

#![cfg(unix)]

use std::path::Path;

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

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

/// Write a `mcptest mock` manifest exposing a `get_weather` tool, and return
/// the JSON-array server command to splice into a suite's `command:`. The
/// native mock answers the handshake instantly, unlike a fork-per-line shell
/// fixture (which timed out the handshake under parallel load).
fn weather_server_command(dir: &Path) -> String {
    let manifest = dir.join("get_weather.yaml");
    std::fs::write(
        &manifest,
        r#"mock_server:
  name: weather-fixture
  tools:
    - name: get_weather
      description: "Get the weather for a city"
      input_schema:
        type: object
        properties:
          city: { type: string }
        required: [city]
      response:
        content:
          - type: text
            text: "It is 72 F and sunny."
"#,
    )
    .expect("write mock manifest");
    let bin = env!("CARGO_BIN_EXE_mcptest");
    format!(
        r#"['{bin}', 'mock', '--tools-from', '{}']"#,
        manifest.display()
    )
}

/// Strip provider credentials so the agent runner is forced down the
/// deterministic stub-model path regardless of the developer's shell.
fn strip_provider_keys(cmd: &mut Command) {
    for var in [
        "ANTHROPIC_API_KEY",
        "OPENAI_API_KEY",
        "GEMINI_API_KEY",
        "GOOGLE_API_KEY",
        "MISTRAL_API_KEY",
    ] {
        cmd.env_remove(var);
    }
}

fn write_suite(dir: &Path, server_cmd: &str, floor_rate: &str) -> std::path::PathBuf {
    let yaml = format!(
        r#"servers:
  weather:
    command: {server_cmd}
agents:
  - name: weather selection
    model: claude-sonnet-4-5
    servers: [weather]
    prompt: What is the weather in Sacramento?
    runs: 3
    tool_selection:
      expected_tool: get_weather
      min_selection_rate: {floor_rate}
"#,
    );
    let path = dir.join("mcptest.yaml");
    std::fs::write(&path, yaml).expect("write suite");
    path
}

#[test]
fn unmet_tool_selection_floor_exits_nonzero_with_cost_vs_accuracy_pair() {
    let dir = tempfile::tempdir().unwrap();
    let server_cmd = weather_server_command(dir.path());
    // A floor demanding 100% selection can never be met by the stub
    // model, which only emits text.
    let config = write_suite(dir.path(), &server_cmd, "1.0");

    let mut cmd = mcptest();
    strip_provider_keys(&mut cmd);
    // WOR-1312: with no key the stub provider must be requested explicitly.
    cmd.current_dir(dir.path())
        .args(["run", "--allow-stub-provider", "--config"])
        .arg(&config)
        .assert()
        // Exit 1: a floor breach is a failing test outcome, reusing the
        // test-failure code rather than minting a new one.
        .code(1)
        // The cost-vs-accuracy pair is printed so the operator sees
        // selection rate and pass^k together.
        .stdout(
            predicate::str::contains("tool-selection floor")
                .and(predicate::str::contains("selection 0/3 (0%)"))
                .and(predicate::str::contains("pass^k 0%")),
        );
}

#[test]
fn met_tool_selection_floor_exits_zero() {
    let dir = tempfile::tempdir().unwrap();
    let server_cmd = weather_server_command(dir.path());
    // A floor of 0.0 is vacuously met even when the stub never selects
    // the tool, proving the gate only fails when the floor is breached.
    let config = write_suite(dir.path(), &server_cmd, "0.0");

    let mut cmd = mcptest();
    strip_provider_keys(&mut cmd);
    // WOR-1312: with no key the stub provider must be requested explicitly.
    cmd.current_dir(dir.path())
        .args(["run", "--allow-stub-provider", "--config"])
        .arg(&config)
        .assert()
        .code(0)
        .stdout(predicate::str::contains("tool-selection floor [PASS]"));
}
