use super::*;
use stemedb_core::testing::{self, AssertionBuilder};
use stemedb_core::types::{EscalationLevel, EscalationPolicy, ObjectValue, Vote};
use stemedb_lens::VoteAwareConsensusLens;
use stemedb_storage::{
    key_codec, EscalationStore, GenericEscalationStore, GenericVoteStore, HybridStore, VoteStore,
};
use tokio::sync::Notify;

fn create_assertion(subject: &str, predicate: &str, value: f64, timestamp: u64) -> Assertion {
    AssertionBuilder::new()
        .subject(subject)
        .predicate(predicate)
        .object_number(value)
        .timestamp(timestamp)
        .build()
}

/// Store an assertion at {subject}\x00H:{hash} and update indexes.
async fn store_assertion(store: &Arc<HybridStore>, assertion: &Assertion) -> [u8; 32] {
    use stemedb_storage::IndexStore;

    let bytes = stemedb_core::serde::serialize(assertion).expect("serialize");
    let hash = blake3::hash(&bytes);
    let assertion_hash: [u8; 32] = *hash.as_bytes();
    let key = key_codec::assertion_key(&assertion.subject, &hash.to_hex());
    store.put(&key, &bytes).await.expect("put");

    let index_store = GenericIndexStore::new(store.clone());
    index_store
        .add_to_indexes(&assertion.subject, &assertion.predicate, &assertion_hash)
        .await
        .expect("index");

    assertion_hash
}

fn create_vote(assertion_hash: [u8; 32], agent_id: [u8; 32], weight: f32, timestamp: u64) -> Vote {
    testing::test_vote(assertion_hash, agent_id, weight, timestamp)
}

#[tokio::test]
async fn test_empty_store() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(vote_store);
    let materializer = Materializer::new(store, Box::new(lens));

    let report = materializer.step().await.expect("step");

    assert_eq!(report.pairs_scanned, 0);
    assert_eq!(report.views_updated, 0);
    assert_eq!(report.errors, 0);
}

#[tokio::test]
async fn test_single_assertion_materialized() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(vote_store);
    let materializer = Materializer::new(store.clone(), Box::new(lens));

    // Store one assertion
    let assertion = create_assertion("Tesla", "revenue", 96.7, 1000);
    store_assertion(&store, &assertion).await;

    // Materialize
    let report = materializer.step().await.expect("step");

    assert_eq!(report.pairs_scanned, 1);
    assert_eq!(report.views_updated, 1);

    // Read back the materialized view
    let view = materializer
        .get_materialized_view("Tesla", "revenue")
        .await
        .expect("get_mv")
        .expect("should exist");

    assert_eq!(view.winner.subject, "Tesla");
    assert_eq!(view.winner.predicate, "revenue");
    assert_eq!(view.candidates_count, 1);
    assert!((view.resolution_confidence - 1.0).abs() < f32::EPSILON);
    assert_eq!(view.lens_name, "VoteAwareConsensus");
}

#[tokio::test]
async fn test_vote_weighted_winner() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(Arc::clone(&vote_store));
    let materializer = Materializer::new(store.clone(), Box::new(lens));

    // Store two competing assertions
    let a1 = create_assertion("Tesla", "revenue", 96.7, 1000);
    let a2 = create_assertion("Tesla", "revenue", 100.0, 1100);
    let hash1 = store_assertion(&store, &a1).await;
    let hash2 = store_assertion(&store, &a2).await;

    // Give a2 more votes
    vote_store.put_vote(&create_vote(hash1, [10u8; 32], 0.3, 2000), "Tesla").await.expect("put");
    vote_store.put_vote(&create_vote(hash2, [20u8; 32], 0.8, 2001), "Tesla").await.expect("put");
    vote_store.put_vote(&create_vote(hash2, [30u8; 32], 0.7, 2002), "Tesla").await.expect("put");

    // Materialize
    let report = materializer.step().await.expect("step");
    assert_eq!(report.views_updated, 1);

    // Winner should be a2 (higher aggregate weight)
    let view = materializer
        .get_materialized_view("Tesla", "revenue")
        .await
        .expect("get_mv")
        .expect("should exist");

    assert_eq!(view.winner.object, ObjectValue::Number(100.0));
    assert_eq!(view.candidates_count, 2);
}

#[tokio::test]
async fn test_multiple_pairs_materialized() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(vote_store);
    let materializer = Materializer::new(store.clone(), Box::new(lens));

    // Store assertions for different subject+predicate pairs
    store_assertion(&store, &create_assertion("Tesla", "revenue", 96.7, 1000)).await;
    store_assertion(&store, &create_assertion("Tesla", "profit", 12.0, 1001)).await;
    store_assertion(&store, &create_assertion("Apple", "revenue", 380.0, 1002)).await;

    let report = materializer.step().await.expect("step");

    assert_eq!(report.pairs_scanned, 3);
    assert_eq!(report.views_updated, 3);

    // Verify all three MVs exist
    assert!(materializer.get_materialized_view("Tesla", "revenue").await.expect("get").is_some());
    assert!(materializer.get_materialized_view("Tesla", "profit").await.expect("get").is_some());
    assert!(materializer.get_materialized_view("Apple", "revenue").await.expect("get").is_some());

    // And non-existent pair returns None
    assert!(materializer.get_materialized_view("Apple", "profit").await.expect("get").is_none());
}

#[tokio::test]
async fn test_idempotent_materialization() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(vote_store);
    let materializer = Materializer::new(store.clone(), Box::new(lens));

    store_assertion(&store, &create_assertion("Tesla", "revenue", 96.7, 1000)).await;

    // Run two passes
    let report1 = materializer.step().await.expect("step1");
    let report2 = materializer.step().await.expect("step2");

    // Both should succeed and update (we always overwrite for freshness)
    assert_eq!(report1.views_updated, 1);
    assert_eq!(report2.views_updated, 1);
}

#[tokio::test]
async fn test_no_mv_for_nonexistent_pair() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(vote_store);
    let materializer = Materializer::new(store, Box::new(lens));

    let result = materializer.get_materialized_view("Nonexistent", "field").await.expect("get");
    assert!(result.is_none());
}

#[tokio::test]
async fn test_parse_sp_key() {
    // Valid key (key_codec format: {subject}\x00SP:{predicate})
    let key = key_codec::subject_predicate_key("Tesla", "revenue");
    let result = key_codec::extract_sp_key(&key);
    assert_eq!(result, Some(("Tesla".to_string(), "revenue".to_string())));

    // Wrong prefix (subject index key, not SP: key)
    let key = key_codec::subject_index_key("Tesla");
    assert!(key_codec::extract_sp_key(&key).is_none());

    // ConceptPath subject with :// in scheme
    let key = key_codec::subject_predicate_key(
        "code://rust/citadeldb/auth/jwt/audience_validation",
        "config_value",
    );
    let result = key_codec::extract_sp_key(&key);
    assert_eq!(
        result,
        Some((
            "code://rust/citadeldb/auth/jwt/audience_validation".to_string(),
            "config_value".to_string(),
        ))
    );

    // ConceptPath with multiple scheme-like colons
    let key =
        key_codec::subject_predicate_key("rfc://7519/jwt/audience_validation", "must_validate");
    let result = key_codec::extract_sp_key(&key);
    assert_eq!(
        result,
        Some(("rfc://7519/jwt/audience_validation".to_string(), "must_validate".to_string(),))
    );
}

#[tokio::test]
async fn test_materialize_pair_directly() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(vote_store);
    let materializer = Materializer::new(store.clone(), Box::new(lens));

    let assertion = create_assertion("Tesla", "revenue", 96.7, 1000);
    store_assertion(&store, &assertion).await;

    // Materialize a specific pair
    let view = materializer
        .materialize_pair("Tesla", "revenue")
        .await
        .expect("materialize")
        .expect("should have winner");

    assert_eq!(view.winner.subject, "Tesla");
    assert!(view.materialized_at > 0);

    // Empty pair returns None
    let no_view = materializer.materialize_pair("Nonexistent", "field").await.expect("materialize");
    assert!(no_view.is_none());
}

#[tokio::test]
async fn test_mv_key_construction() {
    let key = key_codec::mv_key("Tesla", "revenue");
    // key_codec format: {subject}\x00MV:{predicate}
    let mut expected = b"Tesla".to_vec();
    expected.push(0x00);
    expected.extend_from_slice(b"MV:revenue");
    assert_eq!(key, expected);
}

#[tokio::test]
async fn test_run_notified_triggers_on_notify() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(vote_store);
    let materializer = Arc::new(Materializer::new(store.clone(), Box::new(lens)));

    // Store an assertion so materialization has work to do
    let assertion = create_assertion("Tesla", "revenue", 96.7, 1000);
    store_assertion(&store, &assertion).await;

    let notify = Arc::new(Notify::new());
    let notify_clone = Arc::clone(&notify);
    let materializer_clone = Arc::clone(&materializer);

    // Spawn the event-driven loop
    let handle = tokio::spawn(async move {
        // Use a very long max_interval so it only triggers via notification
        materializer_clone.run_notified(notify_clone, std::time::Duration::from_secs(60)).await;
    });

    // Give the loop time to start waiting
    tokio::time::sleep(std::time::Duration::from_millis(50)).await;

    // Signal the notification
    notify.notify_one();

    // Wait for the materializer to process
    tokio::time::sleep(std::time::Duration::from_millis(100)).await;

    // Verify the MV was created
    let view = materializer.get_materialized_view("Tesla", "revenue").await.expect("get_mv");
    assert!(view.is_some(), "Materialized view should exist after notification");

    handle.abort();
}

#[tokio::test]
async fn test_escalation_triggers_on_high_conflict() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(Arc::clone(&vote_store));

    // Configure escalation
    let esc_store = Arc::new(GenericEscalationStore::new(store.clone()));
    let policy = EscalationPolicy {
        name: "HighConflict".to_string(),
        min_conflict_score: 0.4, // Lower threshold to account for floating point precision
        level: EscalationLevel::High,
        predicate_pattern: None,
    };

    let materializer = Materializer::new(store.clone(), Box::new(lens))
        .with_escalation(esc_store.clone(), vec![policy]);

    // Store two competing assertions with DIFFERENT confidence values to create conflict
    // Conflict score is based on variance in confidence, not votes
    let mut a1 = create_assertion("Tesla", "revenue", 96.7, 1000);
    a1.confidence = 0.3; // Low confidence
    let mut a2 = create_assertion("Tesla", "revenue", 100.0, 1100);
    a2.confidence = 1.0; // High confidence
    let hash1 = store_assertion(&store, &a1).await;
    let hash2 = store_assertion(&store, &a2).await;

    // Give both some votes (not relevant for conflict, but for resolution)
    vote_store.put_vote(&create_vote(hash1, [10u8; 32], 0.5, 2000), "Tesla").await.expect("put");
    vote_store.put_vote(&create_vote(hash2, [20u8; 32], 0.5, 2001), "Tesla").await.expect("put");

    // Materialize
    let report = materializer.step().await.expect("step");

    // Check what was materialized
    let _view = materializer
        .get_materialized_view("Tesla", "revenue")
        .await
        .expect("get_mv")
        .expect("view");

    assert_eq!(report.views_updated, 1);
    assert_eq!(report.escalations_triggered, 1);

    // Verify escalation was recorded
    let pending = esc_store.get_pending_escalations().await.expect("get_pending");
    assert_eq!(pending.len(), 1);

    let event = &pending[0];
    assert_eq!(event.subject, "Tesla");
    assert_eq!(event.predicate, "revenue");
    assert_eq!(event.level, EscalationLevel::High);
    assert!(!event.resolved);
}

#[tokio::test]
async fn test_escalation_does_not_trigger_on_low_conflict() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(Arc::clone(&vote_store));

    // Configure escalation with high threshold
    let esc_store = Arc::new(GenericEscalationStore::new(store.clone()));
    let policy = EscalationPolicy {
        name: "VeryHighConflict".to_string(),
        min_conflict_score: 0.9,
        level: EscalationLevel::Critical,
        predicate_pattern: None,
    };

    let materializer = Materializer::new(store.clone(), Box::new(lens))
        .with_escalation(esc_store.clone(), vec![policy]);

    // Store two assertions but give one strong votes (low conflict)
    let a1 = create_assertion("Tesla", "revenue", 96.7, 1000);
    let a2 = create_assertion("Tesla", "revenue", 100.0, 1100);
    let hash1 = store_assertion(&store, &a1).await;
    let hash2 = store_assertion(&store, &a2).await;

    // Skewed votes create low conflict
    vote_store.put_vote(&create_vote(hash1, [10u8; 32], 0.2, 2000), "Tesla").await.expect("put");
    vote_store.put_vote(&create_vote(hash2, [20u8; 32], 0.9, 2001), "Tesla").await.expect("put");

    // Materialize
    let report = materializer.step().await.expect("step");

    assert_eq!(report.views_updated, 1);
    assert_eq!(report.escalations_triggered, 0);

    // No escalations should be recorded
    let pending = esc_store.get_pending_escalations().await.expect("get_pending");
    assert_eq!(pending.len(), 0);
}

#[tokio::test]
async fn test_escalation_predicate_pattern_matching() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(Arc::clone(&vote_store));

    // Configure escalation only for clinical predicates
    let esc_store = Arc::new(GenericEscalationStore::new(store.clone()));
    let policy = EscalationPolicy {
        name: "ClinicalConflict".to_string(),
        min_conflict_score: 0.4, // Lower threshold to account for floating point precision
        level: EscalationLevel::High,
        predicate_pattern: Some("treats".to_string()),
    };

    let materializer = Materializer::new(store.clone(), Box::new(lens))
        .with_escalation(esc_store.clone(), vec![policy]);

    // Store two pairs: one clinical, one financial
    // Both need DIFFERENT confidence values to create conflict
    let mut a1 = create_assertion("Semaglutide", "treats_diabetes", 1.0, 1000);
    a1.confidence = 0.2; // Low confidence
    let mut a2 = create_assertion("Semaglutide", "treats_diabetes", 0.0, 1100);
    a2.confidence = 0.9; // High confidence
    let hash1 = store_assertion(&store, &a1).await;
    let hash2 = store_assertion(&store, &a2).await;
    vote_store
        .put_vote(&create_vote(hash1, [10u8; 32], 0.5, 2000), "Semaglutide")
        .await
        .expect("put");
    vote_store
        .put_vote(&create_vote(hash2, [20u8; 32], 0.5, 2001), "Semaglutide")
        .await
        .expect("put");

    let mut a3 = create_assertion("Tesla", "revenue", 96.7, 1000);
    a3.confidence = 0.3; // Low confidence
    let mut a4 = create_assertion("Tesla", "revenue", 100.0, 1100);
    a4.confidence = 1.0; // High confidence
    let hash3 = store_assertion(&store, &a3).await;
    let hash4 = store_assertion(&store, &a4).await;
    vote_store.put_vote(&create_vote(hash3, [30u8; 32], 0.5, 2002), "Tesla").await.expect("put");
    vote_store.put_vote(&create_vote(hash4, [40u8; 32], 0.5, 2003), "Tesla").await.expect("put");

    // Materialize
    let report = materializer.step().await.expect("step");

    assert_eq!(report.views_updated, 2);
    assert_eq!(report.escalations_triggered, 1); // Only clinical

    // Only the clinical escalation should be recorded
    let pending = esc_store.get_pending_escalations().await.expect("get_pending");
    assert_eq!(pending.len(), 1);
    assert_eq!(pending[0].predicate, "treats_diabetes");
}

#[tokio::test]
async fn test_no_escalation_without_store() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));
    let vote_store = Arc::new(GenericVoteStore::new(store.clone()));
    let lens = VoteAwareConsensusLens::new(Arc::clone(&vote_store));

    // No escalation store configured
    let materializer = Materializer::new(store.clone(), Box::new(lens));

    // Store competing assertions
    let a1 = create_assertion("Tesla", "revenue", 96.7, 1000);
    let a2 = create_assertion("Tesla", "revenue", 100.0, 1100);
    let hash1 = store_assertion(&store, &a1).await;
    let hash2 = store_assertion(&store, &a2).await;
    vote_store.put_vote(&create_vote(hash1, [10u8; 32], 0.5, 2000), "Tesla").await.expect("put");
    vote_store.put_vote(&create_vote(hash2, [20u8; 32], 0.5, 2001), "Tesla").await.expect("put");

    // Materialize
    let report = materializer.step().await.expect("step");

    assert_eq!(report.views_updated, 1);
    assert_eq!(report.escalations_triggered, 0); // No escalation store = no triggers
}
