//! WOR-1319: no silent stub-provider fallback for `llm-jury` verdicts.
//!
//! The live drive used to install a canned passing stub as the jury
//! provider whenever no real provider resolved during the run, so a
//! jury-gated assertion could pass on a fabricated verdict with no
//! credential present. The default is now a hard, actionable error
//! naming the jury tests and the escape hatch; `--allow-stub-provider`
//! restores the fallback for offline smoke runs and marks every jury
//! test with a `stub:`-leading report row. Mirrors
//! `cli_stub_provider_gate.rs` (the WOR-1312 agent-cell gate).

#![cfg(unix)]

use std::path::Path;

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

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

/// Strip provider credentials so the run cannot resolve a real provider
/// 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);
    }
}

/// Suite with one mock server and one tool test gated by an `llm-jury`
/// assertion. With no agent in the suite, no real provider can ever
/// resolve, so the jury can only run on the canned stub.
fn write_jury_suite(dir: &Path) -> std::path::PathBuf {
    let manifest = dir.join("echo.yaml");
    std::fs::write(
        &manifest,
        r#"mock_server:
  name: echo
  tools:
    - name: echo
      description: "Echo"
      input_schema:
        type: object
      response:
        content:
          - type: text
            text: "It is 72 F and sunny."
"#,
    )
    .expect("write mock manifest");
    let bin = env!("CARGO_BIN_EXE_mcptest");
    let yaml = format!(
        r#"servers:
  echo:
    command: ['{bin}', 'mock', '--tools-from', '{manifest}']
tools:
  - name: jury gated echo
    server: echo
    tool: echo
    args: {{ msg: hi }}
    expect:
      - target: result.content[0].text
        matcher:
          llm-jury:
            rubric: Is this a plausible weather report?
            jurors:
              - {{ model: claude-sonnet-4-5 }}
"#,
        manifest = manifest.display(),
    );
    let path = dir.join("mcptest.yaml");
    std::fs::write(&path, yaml).expect("write suite");
    path
}

#[test]
fn jury_without_provider_fails_the_run_naming_the_test_and_flag() {
    let dir = tempfile::tempdir().unwrap();
    let config = write_jury_suite(dir.path());

    let mut cmd = mcptest();
    strip_provider_keys(&mut cmd);
    cmd.current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "--config"])
        .arg(&config)
        .assert()
        .code(1)
        .stderr(
            predicate::str::contains("llm-jury")
                .and(predicate::str::contains("jury gated echo"))
                .and(predicate::str::contains("--allow-stub-provider")),
        );
}

#[test]
fn allow_stub_provider_runs_the_jury_offline_and_marks_the_stub_row() {
    let dir = tempfile::tempdir().unwrap();
    let config = write_jury_suite(dir.path());

    let mut cmd = mcptest();
    strip_provider_keys(&mut cmd);
    cmd.current_dir(dir.path())
        .env_remove("CI")
        .args(["run", "--allow-stub-provider", "--config"])
        .arg(&config)
        .assert()
        .code(0)
        // The `(jury provider)` report row leads with `stub:` so the
        // fabricated verdict cannot be read as a real jury run.
        .stdout(
            predicate::str::contains("jury gated echo (jury provider)")
                .and(predicate::str::contains("stub:")),
        );
}
