//! Integration tests for the `calibration:` check block.
//!
//! Drives `mcptest run` over a calibration suite and asserts the exit code and
//! the reporter line, with and without an explicit `expect:`, plus the
//! load-error path for a missing labels file.

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

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

fn write(path: &std::path::Path, body: &str) {
    if let Some(parent) = path.parent() {
        std::fs::create_dir_all(parent).unwrap();
    }
    std::fs::write(path, body).unwrap();
}

/// A perfectly calibrated labels set: confident-and-right plus
/// unconfident-and-wrong, so ECE and Brier are both 0.
const GOOD_LABELS: &str =
    "{\"confidence\": 1.0, \"correct\": true}\n{\"confidence\": 0.0, \"correct\": false}\n";

/// An overconfident judge: always claims full confidence but is right half the
/// time, so ECE is 0.5, well over the 0.10 default ceiling.
const OVERCONFIDENT_LABELS: &str =
    "{\"confidence\": 1.0, \"correct\": true}\n{\"confidence\": 1.0, \"correct\": false}\n";

#[test]
fn calibration_defaults_pass_for_a_calibrated_judge() {
    let dir = tempdir().unwrap();
    write(&dir.path().join("labels.jsonl"), GOOD_LABELS);
    let cfg = dir.path().join("suite.yml");
    write(
        &cfg,
        "calibration:\n  - name: judge calibration\n    labels: ./labels.jsonl\n",
    );
    mcptest()
        .args(["run", "--config", cfg.to_str().unwrap()])
        .assert()
        .success()
        .stdout(predicate::str::contains("1 passed"));
}

#[test]
fn calibration_defaults_fail_for_an_overconfident_judge() {
    let dir = tempdir().unwrap();
    write(&dir.path().join("labels.jsonl"), OVERCONFIDENT_LABELS);
    let cfg = dir.path().join("suite.yml");
    write(
        &cfg,
        "calibration:\n  - name: judge calibration\n    labels: ./labels.jsonl\n",
    );
    mcptest()
        .args(["run", "--config", cfg.to_str().unwrap()])
        .assert()
        .failure()
        // The default pretty reporter prints the per-test failure detail
        // inline on stdout (the `minimal` reporter is what puts FAIL lines on
        // stderr).
        .stdout(predicate::str::contains("judge calibration"))
        .stdout(predicate::str::contains("ece"));
}

#[test]
fn explicit_expect_overrides_the_default_ceiling() {
    // The overconfident judge has ECE 0.5; a loose explicit ceiling lets it
    // pass, proving the explicit expect: replaces the default.
    let dir = tempdir().unwrap();
    write(&dir.path().join("labels.jsonl"), OVERCONFIDENT_LABELS);
    let cfg = dir.path().join("suite.yml");
    write(
        &cfg,
        "calibration:\n  - name: loose\n    labels: ./labels.jsonl\n    expect:\n      - target: ece\n        matcher: { schema: { maximum: 0.9 } }\n",
    );
    mcptest()
        .args(["run", "--config", cfg.to_str().unwrap()])
        .assert()
        .success()
        .stdout(predicate::str::contains("1 passed"));
}

#[test]
fn missing_labels_file_is_a_load_error() {
    let dir = tempdir().unwrap();
    let cfg = dir.path().join("suite.yml");
    write(
        &cfg,
        "calibration:\n  - name: judge\n    labels: ./does-not-exist.jsonl\n",
    );
    mcptest()
        .args(["run", "--config", cfg.to_str().unwrap()])
        .assert()
        .failure();
}
