//! Tests for alias resolution in QueryEngine.
//!
//! Tests the `resolve_aliases` query flag and `alias_store` integration.

use std::sync::Arc;
use stemedb_core::testing::AssertionBuilder;
use stemedb_core::types::{AliasOrigin, ConceptAlias, ConceptPath, LifecycleStage};
use stemedb_storage::{AliasStore, GenericAliasStore, HybridStore};

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

/// Helper to create a test ConceptAlias.
fn create_alias(alias: &str, canonical: &str) -> ConceptAlias {
    ConceptAlias::new(
        ConceptPath::parse(alias).expect("valid alias path"),
        ConceptPath::parse(canonical).expect("valid canonical path"),
        [1u8; 32], // agent_id
        1000,      // timestamp
        AliasOrigin::Manual,
    )
}

/// Test that resolve_aliases: true expands subject to aliased paths.
#[tokio::test]
async fn test_resolve_aliases_expands_subjects() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // Create assertions for two different subjects (aliased paths)
    let code_assertion = AssertionBuilder::new()
        .subject("code://rust/myapp/tls")
        .predicate("cert_verification")
        .object_text("enabled")
        .confidence(0.8)
        .lifecycle(LifecycleStage::Approved)
        .build();

    let rfc_assertion = AssertionBuilder::new()
        .subject("rfc://5246/tls")
        .predicate("cert_verification")
        .object_text("required")
        .confidence(0.95)
        .lifecycle(LifecycleStage::Approved)
        .build();

    store_assertion(&store, &code_assertion).await;
    store_assertion(&store, &rfc_assertion).await;

    // Set up alias store with alias: code://rust/myapp/tls -> rfc://5246/tls
    let alias_store = GenericAliasStore::new(store.clone());
    let alias = create_alias("code://rust/myapp/tls", "rfc://5246/tls");
    alias_store.set_alias(&alias).await.expect("set alias");

    // Create query engine with alias store
    let engine = QueryEngine::new(Arc::new(store.clone()))
        .with_alias_store(Arc::new(alias_store) as Arc<dyn AliasStore>);

    // Query with resolve_aliases: true should find BOTH assertions
    let query = Query::builder()
        .subject("code://rust/myapp/tls")
        .predicate("cert_verification")
        .resolve_aliases(true)
        .build();

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

    assert_eq!(result.assertions.len(), 2, "Should find assertions from both aliased subjects");

    let subjects: Vec<&str> = result.assertions.iter().map(|a| a.subject.as_str()).collect();
    assert!(subjects.contains(&"code://rust/myapp/tls"), "Should include code assertion");
    assert!(subjects.contains(&"rfc://5246/tls"), "Should include rfc assertion");
}

/// Test that resolve_aliases: false queries exact subject only.
#[tokio::test]
async fn test_resolve_aliases_false_is_exact() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // Create assertions for two different subjects
    let code_assertion = AssertionBuilder::new()
        .subject("code://rust/myapp/tls")
        .predicate("cert_verification")
        .object_text("enabled")
        .confidence(0.8)
        .lifecycle(LifecycleStage::Approved)
        .build();

    let rfc_assertion = AssertionBuilder::new()
        .subject("rfc://5246/tls")
        .predicate("cert_verification")
        .object_text("required")
        .confidence(0.95)
        .lifecycle(LifecycleStage::Approved)
        .build();

    store_assertion(&store, &code_assertion).await;
    store_assertion(&store, &rfc_assertion).await;

    // Set up alias store with alias
    let alias_store = GenericAliasStore::new(store.clone());
    let alias = create_alias("code://rust/myapp/tls", "rfc://5246/tls");
    alias_store.set_alias(&alias).await.expect("set alias");

    let engine = QueryEngine::new(Arc::new(store.clone()))
        .with_alias_store(Arc::new(alias_store) as Arc<dyn AliasStore>);

    // Query with resolve_aliases: false (default) should find only the exact subject
    let query = Query::builder()
        .subject("code://rust/myapp/tls")
        .predicate("cert_verification")
        .resolve_aliases(false)
        .build();

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

    assert_eq!(result.assertions.len(), 1, "Should find only exact subject assertion");
    assert_eq!(result.assertions[0].subject, "code://rust/myapp/tls");
}

/// Test that resolve_aliases: true without alias_store gracefully returns exact subject.
#[tokio::test]
async fn test_no_alias_store_graceful() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    let assertion = AssertionBuilder::new()
        .subject("code://rust/myapp/tls")
        .predicate("cert_verification")
        .object_text("enabled")
        .confidence(0.8)
        .lifecycle(LifecycleStage::Approved)
        .build();

    store_assertion(&store, &assertion).await;

    // Query engine WITHOUT alias store
    let engine = QueryEngine::new(Arc::new(store));

    // Query with resolve_aliases: true should still work (graceful degradation)
    let query = Query::builder()
        .subject("code://rust/myapp/tls")
        .predicate("cert_verification")
        .resolve_aliases(true)
        .build();

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

    assert_eq!(result.assertions.len(), 1, "Should find exact subject assertion");
    assert_eq!(result.assertions[0].subject, "code://rust/myapp/tls");
}

/// Test that alias resolution deduplicates by assertion hash.
#[tokio::test]
async fn test_resolve_aliases_deduplicates() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // Create a single assertion
    let assertion = AssertionBuilder::new()
        .subject("code://rust/myapp/tls")
        .predicate("cert_verification")
        .object_text("enabled")
        .confidence(0.8)
        .lifecycle(LifecycleStage::Approved)
        .build();

    store_assertion(&store, &assertion).await;

    // Set up alias store where both paths lead to the same subject
    // (In real use, this wouldn't happen, but tests the dedup logic)
    let alias_store = GenericAliasStore::new(store.clone());
    // No alias set - both code:// and the query subject are the same

    let engine = QueryEngine::new(Arc::new(store.clone()))
        .with_alias_store(Arc::new(alias_store) as Arc<dyn AliasStore>);

    // Query with resolve_aliases: true
    let query = Query::builder().subject("code://rust/myapp/tls").resolve_aliases(true).build();

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

    // Should have exactly 1 result (no duplicates)
    assert_eq!(result.assertions.len(), 1);
}

/// Test subject-only query with alias resolution.
#[tokio::test]
async fn test_resolve_aliases_subject_only() {
    let store = Arc::new(HybridStore::open_temp().expect("store"));

    // Create assertions for two aliased subjects with different predicates
    let code_assertion1 = AssertionBuilder::new()
        .subject("code://rust/myapp/tls")
        .predicate("cert_verification")
        .object_text("enabled")
        .confidence(0.8)
        .lifecycle(LifecycleStage::Approved)
        .build();

    let code_assertion2 = AssertionBuilder::new()
        .subject("code://rust/myapp/tls")
        .predicate("timeout")
        .object_text("30s")
        .confidence(0.9)
        .lifecycle(LifecycleStage::Approved)
        .build();

    let rfc_assertion = AssertionBuilder::new()
        .subject("rfc://5246/tls")
        .predicate("cert_verification")
        .object_text("required")
        .confidence(0.95)
        .lifecycle(LifecycleStage::Approved)
        .build();

    store_assertion(&store, &code_assertion1).await;
    store_assertion(&store, &code_assertion2).await;
    store_assertion(&store, &rfc_assertion).await;

    // Set up alias store
    let alias_store = GenericAliasStore::new(store.clone());
    let alias = create_alias("code://rust/myapp/tls", "rfc://5246/tls");
    alias_store.set_alias(&alias).await.expect("set alias");

    let engine = QueryEngine::new(Arc::new(store.clone()))
        .with_alias_store(Arc::new(alias_store) as Arc<dyn AliasStore>);

    // Query by subject only (no predicate filter) with resolve_aliases: true
    let query = Query::builder().subject("code://rust/myapp/tls").resolve_aliases(true).build();

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

    // Should find all 3 assertions (2 from code://, 1 from rfc://)
    assert_eq!(result.assertions.len(), 3, "Should find all assertions from aliased subjects");
}

/// Test default query (resolve_aliases not set) behaves like exact match.
#[tokio::test]
async fn test_resolve_aliases_default_is_false() {
    let query = Query::builder().subject("test").predicate("pred").build();

    assert!(!query.resolve_aliases, "Default should be false");
}

/// Test query builder sets resolve_aliases correctly.
#[tokio::test]
async fn test_query_builder_resolve_aliases() {
    let query_true =
        Query::builder().subject("test").predicate("pred").resolve_aliases(true).build();

    let query_false =
        Query::builder().subject("test").predicate("pred").resolve_aliases(false).build();

    assert!(query_true.resolve_aliases, "Should be true when set to true");
    assert!(!query_false.resolve_aliases, "Should be false when set to false");
}
