//! Jury inter-juror agreement corpus tests.
//!
//! Loads `tests/fixtures/jury-agreement/corpus.yaml` and asserts that
//! the pure-math alpha helper lands in the expected band for each
//! hand-crafted fixture.

use std::path::PathBuf;

use serde::Deserialize;

use mcptest_core::eval::jury::agreement::{
    compute_agreement, interpret_alpha, krippendorff_alpha, score_range, score_variance,
    AgreementInterpretation,
};

#[derive(Debug, Deserialize)]
struct Entry {
    id: String,
    #[allow(dead_code)]
    pattern: String,
    scores: Vec<f64>,
    expected_alpha: Option<f64>,
    alpha_tolerance: f64,
    interpretation: String,
}

#[derive(Debug, Deserialize)]
struct Corpus {
    entries: Vec<Entry>,
}

fn corpus_path() -> PathBuf {
    let manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    manifest
        .join("..")
        .join("..")
        .join("tests")
        .join("fixtures")
        .join("jury-agreement")
        .join("corpus.yaml")
}

fn load() -> Corpus {
    let path = corpus_path();
    let raw =
        std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {}: {e}", path.display()));
    serde_yaml::from_str(&raw).expect("corpus.yaml parses as Corpus")
}

fn interpretation_from_string(s: &str) -> AgreementInterpretation {
    match s {
        "high_agreement" => AgreementInterpretation::HighAgreement,
        "tentative_agreement" => AgreementInterpretation::TentativeAgreement,
        "low_agreement" => AgreementInterpretation::LowAgreement,
        other => panic!("unknown interpretation '{other}'"),
    }
}

#[test]
fn corpus_has_exactly_ten_entries() {
    let corpus = load();
    assert_eq!(corpus.entries.len(), 10);
}

#[test]
fn corpus_covers_each_pattern_family() {
    let corpus = load();
    let mut seen: Vec<String> = corpus.entries.iter().map(|e| e.pattern.clone()).collect();
    seen.sort();
    seen.dedup();
    for required in [
        "unanimous",
        "narrow",
        "wide",
        "polarized",
        "single",
        "identical",
    ] {
        assert!(seen.contains(&required.to_string()), "missing {required}");
    }
}

#[test]
fn alpha_matches_expected_for_every_fixture() {
    let corpus = load();
    for entry in &corpus.entries {
        let alpha = krippendorff_alpha(&entry.scores);
        match entry.expected_alpha {
            None => assert!(
                alpha.is_none(),
                "{} expected None alpha, got {:?}",
                entry.id,
                alpha
            ),
            Some(expected) => {
                let actual = alpha.unwrap_or_else(|| panic!("{} expected Some alpha", entry.id));
                assert!(
                    (actual - expected).abs() <= entry.alpha_tolerance,
                    "{} expected alpha {} +/- {}, got {}",
                    entry.id,
                    expected,
                    entry.alpha_tolerance,
                    actual
                );
            }
        }
    }
}

#[test]
fn interpretation_matches_expected_for_every_fixture() {
    let corpus = load();
    for entry in &corpus.entries {
        let agreement = compute_agreement(&entry.scores);
        let expected = interpretation_from_string(&entry.interpretation);
        assert_eq!(
            agreement.interpretation, expected,
            "{} expected {:?} interpretation, got {:?}",
            entry.id, expected, agreement.interpretation
        );
    }
}

#[test]
fn variance_and_range_are_non_negative_for_every_fixture() {
    let corpus = load();
    for entry in &corpus.entries {
        let var = score_variance(&entry.scores);
        let range = score_range(&entry.scores);
        assert!(var >= 0.0, "{} variance was negative", entry.id);
        assert!(range >= 0.0, "{} range was negative", entry.id);
    }
}

#[test]
fn single_juror_fixture_returns_low_agreement_with_no_alpha() {
    let corpus = load();
    let single = corpus
        .entries
        .iter()
        .find(|e| e.pattern == "single")
        .expect("corpus has a single-juror fixture");
    let agreement = compute_agreement(&single.scores);
    assert!(agreement.krippendorff_alpha.is_none());
    assert_eq!(
        agreement.interpretation,
        AgreementInterpretation::LowAgreement
    );
}

#[test]
fn identical_scores_return_one_point_zero_alpha() {
    let corpus = load();
    let identical = corpus
        .entries
        .iter()
        .find(|e| e.pattern == "identical")
        .expect("corpus has an identical-scores fixture");
    let alpha = krippendorff_alpha(&identical.scores).expect("alpha defined");
    assert!((alpha - 1.0).abs() < 1e-9);
}

#[test]
fn interpret_alpha_thresholds_match_corpus_buckets() {
    assert_eq!(
        interpret_alpha(0.85),
        AgreementInterpretation::HighAgreement
    );
    assert_eq!(
        interpret_alpha(0.70),
        AgreementInterpretation::TentativeAgreement
    );
    assert_eq!(interpret_alpha(0.50), AgreementInterpretation::LowAgreement);
}
