//! Tests for materialized view fast-path and staleness behavior.

use std::sync::Arc;
use std::time::{SystemTime, UNIX_EPOCH};
use stemedb_core::types::LifecycleStage;
use stemedb_storage::HybridStore;

use super::{
    create_test_assertion, store_assertion, store_materialized_view,
    store_materialized_view_with_time, QueryEngine,
};
use crate::query::Query;

#[tokio::test]
async fn test_fast_path_returns_materialized_view() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    let assertion = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    store_assertion(&store, &assertion).await;
    store_materialized_view(&store, "Tesla", "revenue", &assertion).await;

    let engine = QueryEngine::new(Arc::new(store));

    let query = Query::builder().subject("Tesla").predicate("revenue").build();
    let result = engine.execute(&query).await.expect("execute");

    // Should get exactly the MV winner
    assert_eq!(result.assertions.len(), 1);
    assert_eq!(result.assertions[0].subject, "Tesla");
    assert_eq!(result.assertions[0].predicate, "revenue");
}

#[tokio::test]
async fn test_fast_path_falls_back_when_no_mv() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // Store assertion but NO materialized view
    let assertion = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    store_assertion(&store, &assertion).await;

    let engine = QueryEngine::new(Arc::new(store));

    let query = Query::builder().subject("Tesla").predicate("revenue").build();
    let result = engine.execute(&query).await.expect("execute");

    // Should still find assertion via slow path (SP index)
    assert_eq!(result.assertions.len(), 1);
    assert_eq!(result.assertions[0].subject, "Tesla");
}

#[tokio::test]
async fn test_fast_path_respects_lifecycle_filter() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // MV winner is Approved
    let approved = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    store_assertion(&store, &approved).await;
    store_materialized_view(&store, "Tesla", "revenue", &approved).await;

    // Also store a Proposed assertion via SP index
    let proposed = create_test_assertion("Tesla", "revenue", LifecycleStage::Proposed);
    store_assertion(&store, &proposed).await;

    let engine = QueryEngine::new(Arc::new(store));

    // Query for Proposed — MV winner is Approved, should fall through to slow path
    let query = Query::builder()
        .subject("Tesla")
        .predicate("revenue")
        .lifecycle(LifecycleStage::Proposed)
        .build();

    let result = engine.execute(&query).await.expect("execute");

    assert_eq!(result.assertions.len(), 1);
    assert_eq!(result.assertions[0].lifecycle, LifecycleStage::Proposed);
}

#[tokio::test]
async fn test_fast_path_not_used_for_subject_only() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    let assertion = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    store_assertion(&store, &assertion).await;
    store_materialized_view(&store, "Tesla", "revenue", &assertion).await;

    let engine = QueryEngine::new(Arc::new(store));

    // Subject-only query should use S: index, not MV:
    let query = Query::builder().subject("Tesla").build();
    let result = engine.execute(&query).await.expect("execute");

    assert_eq!(result.assertions.len(), 1);
}

#[tokio::test]
async fn test_query_strategy_selection() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    let tesla_revenue = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    let apple_profit = create_test_assertion("Apple", "profit", LifecycleStage::Approved);

    store_assertion(&store, &tesla_revenue).await;
    store_assertion(&store, &apple_profit).await;

    let engine = QueryEngine::new(Arc::new(store));

    // Test 1: Both subject and predicate -> compound index (O(1))
    let query1 = Query::builder().subject("Tesla").predicate("revenue").build();
    let result1 = engine.execute(&query1).await.expect("execute");
    assert_eq!(result1.assertions.len(), 1);

    // Test 2: Only subject -> subject index (O(1))
    let query2 = Query::builder().subject("Tesla").build();
    let result2 = engine.execute(&query2).await.expect("execute");
    assert_eq!(result2.assertions.len(), 1);

    // Test 3: Neither -> full scan (O(n))
    let query3 = Query::builder().lifecycle(LifecycleStage::Approved).build();
    let result3 = engine.execute(&query3).await.expect("execute");
    assert_eq!(result3.assertions.len(), 2);
}

// ========================================================================
// MV STALENESS TESTS
// ========================================================================

#[tokio::test]
async fn test_fast_path_stale_view_falls_back() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // Store an assertion and multiple MVs with different timestamps
    let mv_winner = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    let slow_path_result = create_test_assertion("Tesla", "revenue", LifecycleStage::Proposed);

    store_assertion(&store, &mv_winner).await;
    store_assertion(&store, &slow_path_result).await;

    // Create an old MV (1000 seconds ago)
    let now = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);
    let old_timestamp = now.saturating_sub(1000);
    store_materialized_view_with_time(&store, "Tesla", "revenue", &mv_winner, old_timestamp).await;

    let engine = QueryEngine::new(Arc::new(store));

    // Query with max_stale = 60 seconds (MV is 1000 seconds old, so it's stale)
    let query = Query::builder()
        .subject("Tesla")
        .predicate("revenue")
        .max_stale(60) // Only accept MVs < 60 seconds old
        .build();

    let result = engine.execute(&query).await.expect("execute");

    // Should fall back to slow path and get ALL matching assertions (both)
    // This proves we didn't use the MV (which would return just the winner)
    assert_eq!(result.assertions.len(), 2);
}

#[tokio::test]
async fn test_fast_path_fresh_view_used() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    let assertion = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    store_assertion(&store, &assertion).await;

    // Create a fresh MV (just now)
    let now = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);
    store_materialized_view_with_time(&store, "Tesla", "revenue", &assertion, now).await;

    let engine = QueryEngine::new(Arc::new(store));

    // Query with max_stale = 300 seconds (MV is ~0 seconds old, so it's fresh)
    let query = Query::builder()
        .subject("Tesla")
        .predicate("revenue")
        .max_stale(300) // Accept MVs up to 5 minutes old
        .build();

    let result = engine.execute(&query).await.expect("execute");

    // Should use fast path (MV) and return exactly the winner
    assert_eq!(result.assertions.len(), 1);
    assert_eq!(result.assertions[0].subject, "Tesla");
}

#[tokio::test]
async fn test_fast_path_no_max_stale_always_uses_mv() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    let mv_winner = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    let other = create_test_assertion("Tesla", "revenue", LifecycleStage::Proposed);

    store_assertion(&store, &mv_winner).await;
    store_assertion(&store, &other).await;

    // Store an ANCIENT MV (timestamp = 1, i.e., 1970)
    store_materialized_view_with_time(&store, "Tesla", "revenue", &mv_winner, 1).await;

    let engine = QueryEngine::new(Arc::new(store));

    // Query WITHOUT max_stale (default behavior: accept any age)
    let query = Query::builder().subject("Tesla").predicate("revenue").build();

    let result = engine.execute(&query).await.expect("execute");

    // Should use fast path and return exactly the MV winner (not both assertions)
    // This proves backward compatibility: no max_stale = accept any MV age
    assert_eq!(result.assertions.len(), 1);
    assert_eq!(result.assertions[0].lifecycle, LifecycleStage::Approved);
}

#[tokio::test]
async fn test_fast_path_max_stale_zero_rejects_old_mv() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    let mv_winner = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    let other = create_test_assertion("Tesla", "revenue", LifecycleStage::Proposed);

    store_assertion(&store, &mv_winner).await;
    store_assertion(&store, &other).await;

    // Store an MV that is 1 second old (not brand new)
    let now = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);
    let one_second_ago = now.saturating_sub(1);
    store_materialized_view_with_time(&store, "Tesla", "revenue", &mv_winner, one_second_ago).await;

    let engine = QueryEngine::new(Arc::new(store));

    // Query with max_stale = 0: only accept MVs that are 0 seconds old (brand new)
    // Since our MV is 1 second old, it should be rejected
    let query = Query::builder().subject("Tesla").predicate("revenue").max_stale(0).build();

    let result = engine.execute(&query).await.expect("execute");

    // MV is 1 second old, which is > 0, so should fall back to slow path
    assert_eq!(result.assertions.len(), 2);
}

#[tokio::test]
async fn test_fast_path_max_stale_zero_accepts_brand_new_mv() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    let mv_winner = create_test_assertion("Tesla", "revenue", LifecycleStage::Approved);
    let other = create_test_assertion("Tesla", "revenue", LifecycleStage::Proposed);

    store_assertion(&store, &mv_winner).await;
    store_assertion(&store, &other).await;

    // Store an MV with exact current timestamp (brand new)
    let now = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);
    store_materialized_view_with_time(&store, "Tesla", "revenue", &mv_winner, now).await;

    let engine = QueryEngine::new(Arc::new(store));

    // Query with max_stale = 0: only accept MVs that are 0 seconds old (brand new)
    // Since our MV is brand new (age = 0), it should be accepted
    let query = Query::builder().subject("Tesla").predicate("revenue").max_stale(0).build();

    let result = engine.execute(&query).await.expect("execute");

    // MV age is 0, which is not > 0, so fast path should be used
    assert_eq!(result.assertions.len(), 1);
    assert_eq!(result.assertions[0].lifecycle, LifecycleStage::Approved);
}
