//! Battery 4: Conflict Score Calibration.
//!
//! Tests variance-based and entropy-based conflict score calculations.
//!
//! # Test Coverage
//!
//! | Test | Method | Validates |
//! |------|--------|-----------|
//! | `test_variance_conflict_score_unanimous` | Variance | Same confidence = 0.0 |
//! | `test_variance_conflict_score_maximum` | Variance | 0.0 vs 1.0 = 1.0 |
//! | `test_variance_conflict_score_moderate` | Variance | Range check |
//! | `test_variance_conflict_score_single` | Variance | Single assertion = 0.0 |
//! | `test_variance_conflict_score_empty` | Variance | Empty input = 0.0 |
//! | `test_skeptic_entropy_same_confidence_different_objects` | Entropy | Detects object disagreement |
//! | `test_skeptic_entropy_unanimous_different_confidence` | Entropy | Same object = 0.0 |
//! | `test_variance_score_nan_defensive` | Variance | NaN handling |

#![allow(clippy::expect_used)] // Test code uses expect() for clear failure messages

use super::helpers::*;

/// Test 4.1: Variance conflict score is zero for unanimous confidence.
///
/// 5 assertions, all with confidence 0.8.
/// `compute_conflict_score()` returns 0.0 (no variance).
#[test]
fn test_variance_conflict_score_unanimous() {
    use stemedb_lens::compute_conflict_score;

    let assertions: Vec<Assertion> = (0..5)
        .map(|i| {
            AssertionBuilder::new()
                .subject("test")
                .predicate("conflict")
                .object_number(i as f64)
                .confidence(0.8)
                .build()
        })
        .collect();

    let score = compute_conflict_score(&assertions);
    assert_eq!(score, 0.0, "All same confidence = no variance");
}

/// Test 4.2: Variance conflict score is maximum for extreme disagreement.
///
/// 2 assertions with confidence 0.0 and 1.0.
/// `compute_conflict_score()` returns 1.0 (maximum variance).
#[test]
fn test_variance_conflict_score_maximum() {
    use stemedb_lens::compute_conflict_score;

    let assertions = vec![
        AssertionBuilder::new()
            .subject("test")
            .predicate("conflict")
            .object_number(1.0)
            .confidence(0.0)
            .build(),
        AssertionBuilder::new()
            .subject("test")
            .predicate("conflict")
            .object_number(2.0)
            .confidence(1.0)
            .build(),
    ];

    let score = compute_conflict_score(&assertions);
    assert!(
        (score - 1.0).abs() < 0.01,
        "Maximum variance (0.0 vs 1.0) should return ~1.0, got {}",
        score
    );
}

/// Test 4.3: Variance conflict score is moderate for moderate disagreement.
///
/// 3 assertions with confidence 0.2, 0.5, 0.8.
/// `compute_conflict_score()` returns a value between 0.2 and 0.8.
#[test]
fn test_variance_conflict_score_moderate() {
    use stemedb_lens::compute_conflict_score;

    let assertions = vec![
        AssertionBuilder::new()
            .subject("test")
            .predicate("conflict")
            .object_number(1.0)
            .confidence(0.2)
            .build(),
        AssertionBuilder::new()
            .subject("test")
            .predicate("conflict")
            .object_number(2.0)
            .confidence(0.5)
            .build(),
        AssertionBuilder::new()
            .subject("test")
            .predicate("conflict")
            .object_number(3.0)
            .confidence(0.8)
            .build(),
    ];

    let score = compute_conflict_score(&assertions);
    assert!(
        score > 0.2 && score < 0.8,
        "Moderate variance should return value between 0.2 and 0.8, got {}",
        score
    );
}

/// Test 4.4: Variance conflict score is zero for single assertion.
///
/// 1 assertion. Returns 0.0 (no conflict possible).
#[test]
fn test_variance_conflict_score_single() {
    use stemedb_lens::compute_conflict_score;

    let assertion = AssertionBuilder::new()
        .subject("test")
        .predicate("conflict")
        .object_number(1.0)
        .confidence(0.7)
        .build();

    let score = compute_conflict_score(&[assertion]);
    assert_eq!(score, 0.0, "Single assertion = no conflict");
}

/// Test 4.5: Variance conflict score is zero for empty input.
///
/// 0 assertions. Returns 0.0.
#[test]
fn test_variance_conflict_score_empty() {
    use stemedb_lens::compute_conflict_score;

    let score = compute_conflict_score(&[]);
    assert_eq!(score, 0.0, "Empty input = no conflict");
}

/// Test 4.6: [BUG DETECTOR] Skeptic entropy score detects same-confidence different-object disagreement.
///
/// Three assertions, ALL with confidence 0.9:
/// - Object A: Text("yes"), confidence 0.9
/// - Object B: Text("no"), confidence 0.9
/// - Object C: Text("no"), confidence 0.9
///
/// **Skeptic lens `analyze()`:**
/// - Groups into 2 claims: "yes" (weight 0.9) and "no" (weight 1.8)
/// - Entropy is non-zero because there are two groups with different weights
/// - `conflict_score` > 0.0
/// - `status` is NOT `Unanimous`
///
/// **Note:** The variance-based `compute_conflict_score` would return 0.0 for these
/// candidates (all same confidence). The Skeptic entropy-based score correctly detects
/// the disagreement.
#[tokio::test]
async fn test_skeptic_entropy_same_confidence_different_objects() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let index_store = GenericIndexStore::new(store.clone());

    // Three assertions, all with confidence 0.9, but different objects
    let assertion_a = AssertionBuilder::new()
        .subject("test")
        .predicate("answer")
        .object_text("yes")
        .confidence(0.9)
        .agent_id([1u8; 32])
        .build();

    let assertion_b = AssertionBuilder::new()
        .subject("test")
        .predicate("answer")
        .object_text("no")
        .confidence(0.9)
        .agent_id([2u8; 32])
        .build();

    let assertion_c = AssertionBuilder::new()
        .subject("test")
        .predicate("answer")
        .object_text("no")
        .confidence(0.9)
        .agent_id([3u8; 32])
        .build();

    store_assertion_direct(&store, &index_store, &assertion_a).await;
    store_assertion_direct(&store, &index_store, &assertion_b).await;
    store_assertion_direct(&store, &index_store, &assertion_c).await;

    // Verify variance-based score is 0.0 (all same confidence)
    use stemedb_lens::compute_conflict_score;
    let variance_score =
        compute_conflict_score(&[assertion_a.clone(), assertion_b.clone(), assertion_c.clone()]);
    assert!(
        variance_score < 0.01,
        "Variance-based score should be ~0.0 for same confidence, got {}",
        variance_score
    );

    // Run SkepticResolver to get entropy-based score
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let trust_store = Arc::new(GenericTrustRankStore::new(store.clone()));
    let resolver = SkepticResolver::new(store.clone(), vote_store, trust_store);

    let result = resolver.resolve("test", "answer").await.expect("resolve");
    let view = result.expect("should have view");
    let analysis = &view.analysis;

    // Skeptic should detect the disagreement
    assert_eq!(analysis.candidates_count, 3, "should consider all 3 assertions");
    assert_eq!(analysis.claims.len(), 2, "should have 2 distinct claim groups");

    assert!(
        analysis.conflict_score > 0.0,
        "Skeptic entropy-based score should be > 0.0 for different objects, got {}",
        analysis.conflict_score
    );

    assert_ne!(
        analysis.status,
        ResolutionStatus::Unanimous,
        "Status should NOT be Unanimous when objects disagree"
    );

    // Verify the two groups have expected weights
    // "yes" should have weight_share ~ 0.33 (0.9 / (0.9 + 1.8))
    // "no" should have weight_share ~ 0.67 (1.8 / (0.9 + 1.8))
    let yes_claim =
        analysis.claims.iter().find(|c| matches!(&c.value, ObjectValue::Text(t) if t == "yes"));
    let no_claim =
        analysis.claims.iter().find(|c| matches!(&c.value, ObjectValue::Text(t) if t == "no"));

    assert!(yes_claim.is_some(), "should have 'yes' claim");
    assert!(no_claim.is_some(), "should have 'no' claim");

    let yes_share = yes_claim.expect("yes claim").weight_share;
    let no_share = no_claim.expect("no claim").weight_share;

    assert!(
        (yes_share - 0.33).abs() < 0.05,
        "'yes' should have ~0.33 weight_share, got {}",
        yes_share
    );
    assert!(
        (no_share - 0.67).abs() < 0.05,
        "'no' should have ~0.67 weight_share, got {}",
        no_share
    );
}

/// Test 4.7: Skeptic entropy score is zero for unanimous object value despite different confidences.
///
/// Three assertions, all same object Text("yes"), but different confidences (0.3, 0.6, 0.9):
///
/// **Skeptic lens `analyze()`:**
/// - Groups into 1 claim (all same object)
/// - `conflict_score` = 0.0 (unanimous — no disagreement on the value)
/// - `status` = `Unanimous`
#[tokio::test]
async fn test_skeptic_entropy_unanimous_different_confidence() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let index_store = GenericIndexStore::new(store.clone());

    // Three assertions, all with object "yes", but different confidences
    let assertion_a = AssertionBuilder::new()
        .subject("test")
        .predicate("answer")
        .object_text("yes")
        .confidence(0.3)
        .agent_id([1u8; 32])
        .build();

    let assertion_b = AssertionBuilder::new()
        .subject("test")
        .predicate("answer")
        .object_text("yes")
        .confidence(0.6)
        .agent_id([2u8; 32])
        .build();

    let assertion_c = AssertionBuilder::new()
        .subject("test")
        .predicate("answer")
        .object_text("yes")
        .confidence(0.9)
        .agent_id([3u8; 32])
        .build();

    store_assertion_direct(&store, &index_store, &assertion_a).await;
    store_assertion_direct(&store, &index_store, &assertion_b).await;
    store_assertion_direct(&store, &index_store, &assertion_c).await;

    // Run SkepticResolver
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let trust_store = Arc::new(GenericTrustRankStore::new(store.clone()));
    let resolver = SkepticResolver::new(store.clone(), vote_store, trust_store);

    let result = resolver.resolve("test", "answer").await.expect("resolve");
    let view = result.expect("should have view");
    let analysis = &view.analysis;

    // Skeptic should detect unanimous agreement (all same object)
    assert_eq!(analysis.candidates_count, 3, "should consider all 3 assertions");
    assert_eq!(analysis.claims.len(), 1, "should have 1 claim group (unanimous)");

    assert_eq!(
        analysis.conflict_score, 0.0,
        "Skeptic entropy-based score should be 0.0 for unanimous object, got {}",
        analysis.conflict_score
    );

    assert_eq!(
        analysis.status,
        ResolutionStatus::Unanimous,
        "Status should be Unanimous when all objects agree"
    );

    // Verify the single claim
    let yes_claim = &analysis.claims[0];
    assert!(matches!(&yes_claim.value, ObjectValue::Text(t) if t == "yes"));
    assert_eq!(yes_claim.assertion_count, 3, "claim should have 3 supporting assertions");
    assert!(
        (yes_claim.weight_share - 1.0).abs() < 0.01,
        "Single claim should have weight_share ~1.0, got {}",
        yes_claim.weight_share
    );
}

/// Test 4.8: Variance score handles NaN confidences defensively.
///
/// 2 assertions with confidence `f32::NAN`.
/// `compute_conflict_score()` returns 0.0 (defensive, not NaN propagation).
#[test]
fn test_variance_score_nan_defensive() {
    use stemedb_lens::compute_conflict_score;

    let mut assertions = vec![
        AssertionBuilder::new()
            .subject("test")
            .predicate("conflict")
            .object_number(1.0)
            .confidence(0.5)
            .build(),
        AssertionBuilder::new()
            .subject("test")
            .predicate("conflict")
            .object_number(2.0)
            .confidence(0.5)
            .build(),
    ];

    // Corrupt confidences to NaN
    assertions[0].confidence = f32::NAN;
    assertions[1].confidence = f32::NAN;

    let score = compute_conflict_score(&assertions);
    assert_eq!(score, 0.0, "NaN confidences should result in defensive 0.0, got {}", score);
}
