//! Battery 2: JWT Conflict Scenario.
//!
//! Tests escalation mechanisms and layered consensus with cross-tier disagreement.
//!
//! # Test Coverage
//!
//! | Test | Feature | Validates |
//! |------|---------|-----------|
//! | `test_jwt_conflict_escalation_fires` | Escalation | Conflict score threshold triggers event |
//! | `test_jwt_escalation_predicate_filter` | Escalation | Predicate pattern filtering |
//! | `test_jwt_layered_lens_tier_agreement` | Layered lens | Tier-by-tier resolution |

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

use super::helpers::*;

/// Test 2.1: Escalation event fires when conflict score exceeds threshold.
///
/// Setup:
/// - RFC 7519 (Tier 0, confidence 1.0): aud_validation = Boolean(true)
/// - Approved runbook (Tier 2, confidence 0.95): aud_validation = Boolean(true)
/// - Internal wiki (Tier 3, confidence 0.8): aud_validation = Boolean(false)
/// - Stack Overflow (Tier 5, confidence 0.6): aud_validation = Boolean(false)
///
/// Escalation policy: min_conflict_score=0.5, level=High, predicate_pattern=None
///
/// Proves the escalation mechanism correctly detects cross-tier disagreement
/// and records an event for external review.
#[tokio::test]
async fn test_jwt_conflict_escalation_fires() {
    let dir = tempdir().expect("create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let base_ts: u64 = 1_000_000;

    // === Setup: Create 4 conflicting assertions ===

    // RFC 7519 (Tier 0, confidence 1.0): aud_validation = Boolean(true)
    let rfc_7519 = create_signed_assertion_with_source(
        "JWT_aud_validation",
        "aud_validation",
        ObjectValue::Boolean(true),
        SourceClass::Regulatory,
        1.0,
        base_ts,
    );

    // Approved runbook (Tier 2, confidence 0.95): aud_validation = Boolean(true)
    let approved_runbook = create_signed_assertion_with_source(
        "JWT_aud_validation",
        "aud_validation",
        ObjectValue::Boolean(true),
        SourceClass::Observational,
        0.95,
        base_ts + 1,
    );

    // Internal wiki (Tier 3, confidence 0.8): aud_validation = Boolean(false)
    let internal_wiki = create_signed_assertion_with_source(
        "JWT_aud_validation",
        "aud_validation",
        ObjectValue::Boolean(false),
        SourceClass::Expert,
        0.8,
        base_ts + 2,
    );

    // Stack Overflow (Tier 5, confidence 0.6): aud_validation = Boolean(false)
    let stack_overflow = create_signed_assertion_with_source(
        "JWT_aud_validation",
        "aud_validation",
        ObjectValue::Boolean(false),
        SourceClass::Anecdotal,
        0.6,
        base_ts + 3,
    );

    // === Step 1: Write all 4 to WAL ===
    let mut journal = Journal::open(&wal_dir).expect("open journal");
    journal.append(serialize_assertion(&rfc_7519).expect("ser")).expect("append rfc");
    journal.append(serialize_assertion(&approved_runbook).expect("ser")).expect("append runbook");
    journal.append(serialize_assertion(&internal_wiki).expect("ser")).expect("append wiki");
    journal
        .append(serialize_assertion(&stack_overflow).expect("ser"))
        .expect("append stackoverflow");

    // === Step 2: Ingest all 4 via IngestWorker ===
    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(HybridStore::open(&db_dir).expect("open store"));

    let mut worker =
        IngestWorker::new(journal.clone(), store.clone()).await.expect("create worker");

    for _ in 0..4 {
        let bytes = worker.step().await.expect("ingest step");
        assert!(bytes > 0, "should process data from WAL");
    }

    // === Step 3: Configure escalation policy and materialize ===
    let policy = EscalationPolicy {
        name: "security-config".to_string(),
        min_conflict_score: 0.5,
        level: EscalationLevel::High,
        predicate_pattern: None,
    };

    let escalation_store = Arc::new(GenericEscalationStore::new(store.clone()));
    let lens = SyncLensWrapper(LayeredConsensusLens::new());
    let materializer = Materializer::new(store.clone(), Box::new(lens))
        .with_escalation(escalation_store.clone() as Arc<dyn EscalationStore>, vec![policy]);

    let report = materializer.step().await.expect("materialize");
    assert_eq!(report.views_updated, 1, "should update one view");
    assert_eq!(report.escalations_triggered, 1, "should trigger one escalation");

    // === Step 4: Verify escalation event ===
    let pending = escalation_store.get_pending_escalations().await.expect("get pending");
    assert_eq!(pending.len(), 1, "should have one pending escalation");

    let event = &pending[0];
    assert_eq!(event.subject, "JWT_aud_validation");
    assert_eq!(event.predicate, "aud_validation");
    assert_eq!(event.level, EscalationLevel::High);
    assert!(
        event.conflict_score >= 0.5,
        "conflict_score should be >= 0.5, got {}",
        event.conflict_score
    );
    assert!(!event.resolved, "escalation should not be resolved");
}

/// Test 2.2: Escalation predicate pattern filtering works correctly.
///
/// Two policies:
/// - Policy A: predicate_pattern=Some("aud"), triggers on "aud_validation"
/// - Policy B: predicate_pattern=Some("revenue"), does NOT trigger on "aud_validation"
///
/// Only Policy A should fire, creating a Critical-level escalation.
#[tokio::test]
async fn test_jwt_escalation_predicate_filter() {
    let dir = tempdir().expect("create temp dir");
    let wal_dir = dir.path().join("wal");
    let db_dir = dir.path().join("db");

    let base_ts: u64 = 1_000_000;

    // Same four assertions as 2.1
    let rfc_7519 = create_signed_assertion_with_source(
        "JWT_aud_validation",
        "aud_validation",
        ObjectValue::Boolean(true),
        SourceClass::Regulatory,
        1.0,
        base_ts,
    );

    let approved_runbook = create_signed_assertion_with_source(
        "JWT_aud_validation",
        "aud_validation",
        ObjectValue::Boolean(true),
        SourceClass::Observational,
        0.95,
        base_ts + 1,
    );

    let internal_wiki = create_signed_assertion_with_source(
        "JWT_aud_validation",
        "aud_validation",
        ObjectValue::Boolean(false),
        SourceClass::Expert,
        0.8,
        base_ts + 2,
    );

    let stack_overflow = create_signed_assertion_with_source(
        "JWT_aud_validation",
        "aud_validation",
        ObjectValue::Boolean(false),
        SourceClass::Anecdotal,
        0.6,
        base_ts + 3,
    );

    // Write to WAL and ingest
    let mut journal = Journal::open(&wal_dir).expect("open journal");
    journal.append(serialize_assertion(&rfc_7519).expect("ser")).expect("append");
    journal.append(serialize_assertion(&approved_runbook).expect("ser")).expect("append");
    journal.append(serialize_assertion(&internal_wiki).expect("ser")).expect("append");
    journal.append(serialize_assertion(&stack_overflow).expect("ser")).expect("append");

    let journal = Arc::new(Mutex::new(journal));
    let store = Arc::new(HybridStore::open(&db_dir).expect("open store"));

    let mut worker =
        IngestWorker::new(journal.clone(), store.clone()).await.expect("create worker");

    for _ in 0..4 {
        worker.step().await.expect("ingest step");
    }

    // === Configure two policies ===
    let policy_a = EscalationPolicy {
        name: "policy-aud".to_string(),
        min_conflict_score: 0.3,
        level: EscalationLevel::Critical,
        predicate_pattern: Some("aud".to_string()),
    };

    let policy_b = EscalationPolicy {
        name: "policy-revenue".to_string(),
        min_conflict_score: 0.3,
        level: EscalationLevel::Medium,
        predicate_pattern: Some("revenue".to_string()),
    };

    let escalation_store = Arc::new(GenericEscalationStore::new(store.clone()));
    let lens = SyncLensWrapper(LayeredConsensusLens::new());
    let materializer = Materializer::new(store.clone(), Box::new(lens)).with_escalation(
        escalation_store.clone() as Arc<dyn EscalationStore>,
        vec![policy_a, policy_b],
    );

    let report = materializer.step().await.expect("materialize");
    assert_eq!(report.escalations_triggered, 1, "should trigger exactly one escalation");

    // === Verify only Policy A fired ===
    let pending = escalation_store.get_pending_escalations().await.expect("get pending");
    assert_eq!(pending.len(), 1, "should have exactly one pending escalation");

    let event = &pending[0];
    assert_eq!(event.level, EscalationLevel::Critical, "should be Critical (Policy A)");
    assert_eq!(event.predicate, "aud_validation");
    assert!(event.reason.contains("policy-aud"), "reason should reference Policy A");
}

/// Test 2.3: Layered Consensus Lens shows tier-by-tier resolution.
///
/// With the JWT assertions:
/// - Tier 0 (Regulatory): Boolean(true) wins
/// - Tier 2 (Observational): Boolean(true) wins
/// - Tier 3 (Expert): Boolean(false) wins
/// - Tier 5 (Anecdotal): Boolean(false) wins
///
/// Cross-tier conflict should be high (tiers 0/2 vs 3/5 disagree).
/// Overall winner should come from Tier 0 (highest authority).
#[tokio::test]
async fn test_jwt_layered_lens_tier_agreement() {
    use stemedb_lens::{LayeredConsensusLens, LayeredLens};

    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let index_store = GenericIndexStore::new(store.clone());

    let base_ts: u64 = 1_000_000;

    // RFC 7519 (Tier 0): Boolean(true)
    let rfc_7519 = AssertionBuilder::new()
        .subject("JWT_aud_validation")
        .predicate("aud_validation")
        .object(ObjectValue::Boolean(true))
        .source_class(SourceClass::Regulatory)
        .confidence(1.0)
        .agent_id([1u8; 32])
        .timestamp(base_ts)
        .build();

    // Approved runbook (Tier 2): Boolean(true)
    let approved_runbook = AssertionBuilder::new()
        .subject("JWT_aud_validation")
        .predicate("aud_validation")
        .object(ObjectValue::Boolean(true))
        .source_class(SourceClass::Observational)
        .confidence(0.95)
        .agent_id([2u8; 32])
        .timestamp(base_ts + 1)
        .build();

    // Internal wiki (Tier 3): Boolean(false)
    let internal_wiki = AssertionBuilder::new()
        .subject("JWT_aud_validation")
        .predicate("aud_validation")
        .object(ObjectValue::Boolean(false))
        .source_class(SourceClass::Expert)
        .confidence(0.8)
        .agent_id([3u8; 32])
        .timestamp(base_ts + 2)
        .build();

    // Stack Overflow (Tier 5): Boolean(false)
    let stack_overflow = AssertionBuilder::new()
        .subject("JWT_aud_validation")
        .predicate("aud_validation")
        .object(ObjectValue::Boolean(false))
        .source_class(SourceClass::Anecdotal)
        .confidence(0.6)
        .agent_id([4u8; 32])
        .timestamp(base_ts + 3)
        .build();

    store_assertion_direct(&store, &index_store, &rfc_7519).await;
    store_assertion_direct(&store, &index_store, &approved_runbook).await;
    store_assertion_direct(&store, &index_store, &internal_wiki).await;
    store_assertion_direct(&store, &index_store, &stack_overflow).await;

    // === Resolve with LayeredConsensusLens ===
    let lens = LayeredConsensusLens::new();
    let assertions = vec![rfc_7519, approved_runbook, internal_wiki, stack_overflow];
    let result = lens.resolve_layered(&assertions);

    // === Assert tier-specific results ===

    // Should have 4 tiers (0, 2, 3, 5)
    assert_eq!(result.tiers.len(), 4, "should have 4 tiers");

    // Tier 0 (Regulatory): Boolean(true)
    let tier_0 = result.tiers.iter().find(|t| t.tier == 0).expect("tier 0 should exist");
    assert_eq!(tier_0.candidates_count, 1);
    assert!(tier_0.winner.is_some(), "tier 0 should have a winner");
    assert_eq!(
        tier_0.winner.as_ref().expect("tier 0 winner").object,
        ObjectValue::Boolean(true),
        "Tier 0 should say Boolean(true)"
    );

    // Tier 2 (Observational): Boolean(true)
    let tier_2 = result.tiers.iter().find(|t| t.tier == 2).expect("tier 2 should exist");
    assert_eq!(tier_2.candidates_count, 1);
    assert!(tier_2.winner.is_some(), "tier 2 should have a winner");
    assert_eq!(
        tier_2.winner.as_ref().expect("tier 2 winner").object,
        ObjectValue::Boolean(true),
        "Tier 2 should say Boolean(true)"
    );

    // Tier 3 (Expert): Boolean(false)
    let tier_3 = result.tiers.iter().find(|t| t.tier == 3).expect("tier 3 should exist");
    assert_eq!(tier_3.candidates_count, 1);
    assert!(tier_3.winner.is_some(), "tier 3 should have a winner");
    assert_eq!(
        tier_3.winner.as_ref().expect("tier 3 winner").object,
        ObjectValue::Boolean(false),
        "Tier 3 should say Boolean(false)"
    );

    // Tier 5 (Anecdotal): Boolean(false)
    let tier_5 = result.tiers.iter().find(|t| t.tier == 5).expect("tier 5 should exist");
    assert_eq!(tier_5.candidates_count, 1);
    assert!(tier_5.winner.is_some(), "tier 5 should have a winner");
    assert_eq!(
        tier_5.winner.as_ref().expect("tier 5 winner").object,
        ObjectValue::Boolean(false),
        "Tier 5 should say Boolean(false)"
    );

    // === Assert overall results ===

    // Overall winner should be from Tier 0 (highest authority)
    assert!(result.overall_winner.is_some(), "should have overall winner");
    assert_eq!(
        result.overall_winner.as_ref().expect("overall winner").object,
        ObjectValue::Boolean(true),
        "Overall winner should be Boolean(true) from Tier 0"
    );
    assert_eq!(
        result.overall_winner.as_ref().expect("overall winner").source_class,
        SourceClass::Regulatory,
        "Overall winner should be from Tier 0 (Regulatory)"
    );

    // Cross-tier conflict should be high (tiers 0/2 vs 3/5 disagree)
    assert!(
        result.overall_conflict_score > 0.5,
        "overall_conflict_score should be > 0.5 (cross-tier disagreement), got {}",
        result.overall_conflict_score
    );
}
