//! Battery 9: AliasStore Resolution and Cross-Scheme Queries.
//!
//! Tests alias storage, resolution, and transitive expansion for cross-scheme queries.
//!
//! # Test Coverage
//!
//! | Test | Feature | Validates |
//! |------|---------|-----------|
//! | `test_alias_direct_resolution` | Basic alias | Query alias, get canonical |
//! | `test_alias_transitive_resolution` | A → B → C chain | Transitive resolution |
//! | `test_alias_cycle_detection` | A → B → A | Safe termination |
//! | `test_alias_bidirectional` | Reverse lookup | get_aliases for canonical |
//! | `test_alias_delete` | Delete alias | Clean removal |
//! | `test_alias_suggest` | Suggest aliases | Similarity-based suggestions |

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

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

/// 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 9.1: Direct alias resolution.
///
/// Store alias: code://rust/auth/jwt/aud → rfc://7519/jwt/audience
/// Query: get_canonical("code://rust/auth/jwt/aud")
/// Expect: rfc://7519/jwt/audience
#[tokio::test]
async fn test_alias_direct_resolution() {
    let store = Arc::new(HybridStore::open_temp().expect("create store"));
    let alias_store = GenericAliasStore::new(store);

    // Store alias
    let alias = create_alias("code://rust/auth/jwt/aud", "rfc://7519/jwt/audience");
    alias_store.set_alias(&alias).await.expect("set alias");

    // Resolve alias
    let canonical =
        alias_store.get_canonical("code://rust/auth/jwt/aud").await.expect("get canonical");

    assert!(canonical.is_some(), "should find canonical path");
    let canonical_path = canonical.unwrap();
    assert_eq!(canonical_path.scheme, "rfc");
    assert_eq!(canonical_path.segments, vec!["7519", "jwt", "audience"]);
}

/// Test 9.2: Transitive alias resolution.
///
/// Store chain: internal://jwt → code://rust/auth/jwt → rfc://7519/jwt
/// Query: resolve_all("internal://jwt")
/// Expect: all three paths returned
#[tokio::test]
async fn test_alias_transitive_resolution() {
    let store = Arc::new(HybridStore::open_temp().expect("create store"));
    let alias_store = GenericAliasStore::new(store);

    // Store alias chain: internal → code → rfc
    let alias1 = create_alias("internal://jwt/impl", "code://rust/auth/jwt");
    let alias2 = create_alias("code://rust/auth/jwt", "rfc://7519/jwt");

    alias_store.set_alias(&alias1).await.expect("set alias1");
    alias_store.set_alias(&alias2).await.expect("set alias2");

    // Resolve all from internal (start of chain)
    let all_paths = alias_store.resolve_all("internal://jwt/impl").await.expect("resolve all");

    assert!(all_paths.contains(&"internal://jwt/impl".to_string()), "should include starting path");
    assert!(
        all_paths.contains(&"code://rust/auth/jwt".to_string()),
        "should include intermediate alias"
    );
    assert!(all_paths.contains(&"rfc://7519/jwt".to_string()), "should include final canonical");

    // Should have exactly 3 paths
    assert_eq!(all_paths.len(), 3, "should resolve to 3 paths in chain");
}

/// Test 9.3: Cycle detection.
///
/// Store cycle: A → B → A
/// Query: resolve_all("A")
/// Expect: Safe termination, returns [A, B] without infinite loop
#[tokio::test]
async fn test_alias_cycle_detection() {
    let store = Arc::new(HybridStore::open_temp().expect("create store"));
    let alias_store = GenericAliasStore::new(store);

    // Create a cycle: code → rfc → code
    let alias1 = create_alias("code://cycle/a", "rfc://cycle/b");
    let alias2 = create_alias("rfc://cycle/b", "code://cycle/a");

    alias_store.set_alias(&alias1).await.expect("set alias1");
    alias_store.set_alias(&alias2).await.expect("set alias2");

    // Resolve all - should not hang
    let all_paths = alias_store.resolve_all("code://cycle/a").await.expect("resolve all (cycle)");

    // Should have exactly 2 paths
    assert_eq!(all_paths.len(), 2, "cycle should resolve to 2 paths");
    assert!(all_paths.contains(&"code://cycle/a".to_string()), "should include A");
    assert!(all_paths.contains(&"rfc://cycle/b".to_string()), "should include B");
}

/// Test 9.4: Bidirectional lookup (reverse index).
///
/// Store alias: code://rust/auth/jwt → rfc://7519/jwt
/// Query: get_aliases("rfc://7519/jwt")
/// Expect: [code://rust/auth/jwt]
#[tokio::test]
async fn test_alias_bidirectional() {
    let store = Arc::new(HybridStore::open_temp().expect("create store"));
    let alias_store = GenericAliasStore::new(store);

    // Store multiple aliases pointing to same canonical
    let alias1 = create_alias("code://rust/auth/jwt", "rfc://7519/jwt");
    let alias2 = create_alias("internal://jwt/impl", "rfc://7519/jwt");

    alias_store.set_alias(&alias1).await.expect("set alias1");
    alias_store.set_alias(&alias2).await.expect("set alias2");

    // Reverse lookup: get all aliases for canonical
    let aliases = alias_store.get_aliases("rfc://7519/jwt").await.expect("get aliases");

    assert_eq!(aliases.len(), 2, "should have 2 aliases for canonical");

    let alias_strings: Vec<String> = aliases.iter().map(|p| p.to_wire_format()).collect();
    assert!(
        alias_strings.contains(&"code://rust/auth/jwt".to_string()),
        "should include code alias"
    );
    assert!(
        alias_strings.contains(&"internal://jwt/impl".to_string()),
        "should include internal alias"
    );
}

/// Test 9.5: Delete alias.
///
/// Store alias, verify exists, delete, verify gone.
/// Also verify reverse index is updated.
#[tokio::test]
async fn test_alias_delete() {
    let store = Arc::new(HybridStore::open_temp().expect("create store"));
    let alias_store = GenericAliasStore::new(store);

    // Store alias
    let alias = create_alias("code://rust/auth/jwt", "rfc://7519/jwt");
    alias_store.set_alias(&alias).await.expect("set alias");

    // Verify it exists
    let canonical = alias_store
        .get_canonical("code://rust/auth/jwt")
        .await
        .expect("get canonical before delete");
    assert!(canonical.is_some(), "alias should exist before delete");

    // Delete
    let deleted = alias_store.delete_alias("code://rust/auth/jwt").await.expect("delete alias");
    assert!(deleted, "delete should return true");

    // Verify forward lookup is gone
    let canonical_after = alias_store
        .get_canonical("code://rust/auth/jwt")
        .await
        .expect("get canonical after delete");
    assert!(canonical_after.is_none(), "alias should not exist after delete");

    // Verify reverse lookup is updated
    let aliases =
        alias_store.get_aliases("rfc://7519/jwt").await.expect("get aliases after delete");
    assert!(aliases.is_empty(), "reverse index should be empty after delete");
}

/// Test 9.6: Delete non-existent alias returns false.
#[tokio::test]
async fn test_alias_delete_nonexistent() {
    let store = Arc::new(HybridStore::open_temp().expect("create store"));
    let alias_store = GenericAliasStore::new(store);

    // Try to delete non-existent alias
    let deleted = alias_store.delete_alias("nonexistent://path").await.expect("delete nonexistent");

    assert!(!deleted, "delete should return false for non-existent alias");
}

/// Test 9.7: Alias suggestions based on leaf similarity.
///
/// Given existing subjects with similar leaf names across DIFFERENT schemes,
/// suggest potential aliases.
///
/// Note: Suggestions only work across different schemes (cross-scheme aliasing).
/// Same-scheme paths are not suggested as aliases.
#[tokio::test]
async fn test_alias_suggest() {
    let store = Arc::new(HybridStore::open_temp().expect("create store"));
    let alias_store = GenericAliasStore::new(store);

    // Existing subjects in the system
    let existing_subjects = vec![
        "rfc://7519/jwt/audience".to_string(),
        "internal://jwt/aud".to_string(), // different scheme, similar leaf
        "owasp://top10/injection".to_string(),
        "code://rust/citadeldb/net/tls".to_string(),
    ];

    // Ask for suggestions for a new code path with "audience" leaf
    let suggestions = alias_store
        .suggest_aliases("code://new/jwt/audience", &existing_subjects)
        .await
        .expect("suggest aliases");

    // Should suggest the RFC path with same leaf name (different scheme)
    let suggested_paths: Vec<&str> = suggestions.iter().map(|(p, _)| p.as_str()).collect();
    assert!(
        suggested_paths.contains(&"rfc://7519/jwt/audience"),
        "should suggest rfc://7519/jwt/audience (same leaf 'audience', different scheme)"
    );

    // Should suggest the internal path with similar leaf 'aud' (different scheme)
    // ('aud' is substring of 'audience')
    assert!(
        suggested_paths.contains(&"internal://jwt/aud"),
        "should suggest internal://jwt/aud (similar leaf 'aud', different scheme)"
    );

    // Should NOT suggest unrelated paths
    assert!(
        !suggested_paths.contains(&"owasp://top10/injection"),
        "should NOT suggest unrelated path"
    );

    // Should NOT suggest same-scheme paths even with similar leaf
    assert!(
        !suggested_paths.contains(&"code://rust/citadeldb/net/tls"),
        "should NOT suggest same-scheme path"
    );
}

/// Test 9.8: List all aliases.
///
/// Store multiple aliases, list all, verify count and content.
#[tokio::test]
async fn test_alias_list_all() {
    let store = Arc::new(HybridStore::open_temp().expect("create store"));
    let alias_store = GenericAliasStore::new(store);

    // Store multiple aliases
    let alias1 = create_alias("code://a", "rfc://1");
    let alias2 = create_alias("code://b", "rfc://2");
    let alias3 = create_alias("internal://c", "rfc://3");

    alias_store.set_alias(&alias1).await.expect("set alias1");
    alias_store.set_alias(&alias2).await.expect("set alias2");
    alias_store.set_alias(&alias3).await.expect("set alias3");

    // List all
    let all_aliases = alias_store.list_all_aliases().await.expect("list all");

    assert_eq!(all_aliases.len(), 3, "should have 3 aliases");

    // Verify content
    let alias_map: std::collections::HashMap<String, String> = all_aliases.into_iter().collect();

    assert_eq!(
        alias_map.get("code://a"),
        Some(&"rfc://1".to_string()),
        "code://a should map to rfc://1"
    );
    assert_eq!(
        alias_map.get("code://b"),
        Some(&"rfc://2".to_string()),
        "code://b should map to rfc://2"
    );
    assert_eq!(
        alias_map.get("internal://c"),
        Some(&"rfc://3".to_string()),
        "internal://c should map to rfc://3"
    );
}
