//! Router Unit Tests
//!
//! Comprehensive tests for GraphShardRouter functionality including:
//! - Hash routing consistency
//! - Multi-shard load balancing
//! - Cross-shard relation queries
//! - Global index accuracy

use std::collections::{HashMap, HashSet};
use tempfile::tempdir;

use crate::types::graph::{Entity, Relation, entity_types, relation_types};

use super::config::{ConcurrentGraphConfig, ShardStrategy};
use super::router::GraphShardRouter;

// ============================================================================
// Test Helpers
// ============================================================================

/// Create a test router with specified shard count (no auto-start for sync tests)
fn create_test_router(shard_count: usize) -> GraphShardRouter {
    let dir = tempdir().unwrap();
    let config = ConcurrentGraphConfig::new(dir.path())
        .with_shard_count(shard_count)
        .with_auto_start(false)
        .with_wal_enabled(false);
    GraphShardRouter::new(config)
}

/// Create a test router with auto-start for async tests
fn create_async_router(shard_count: usize) -> GraphShardRouter {
    let dir = tempdir().unwrap();
    let config = ConcurrentGraphConfig::new(dir.path())
        .with_shard_count(shard_count)
        .with_auto_start(true)
        .with_wal_enabled(false);
    GraphShardRouter::new(config)
}

/// Create a test router with specific strategy
fn create_router_with_strategy(shard_count: usize, strategy: ShardStrategy) -> GraphShardRouter {
    let dir = tempdir().unwrap();
    let config = ConcurrentGraphConfig::new(dir.path())
        .with_shard_count(shard_count)
        .with_shard_strategy(strategy)
        .with_auto_start(false)
        .with_wal_enabled(false);
    GraphShardRouter::new(config)
}

/// Create a test router with auto-start and specific strategy
fn create_async_router_with_strategy(shard_count: usize, strategy: ShardStrategy) -> GraphShardRouter {
    let dir = tempdir().unwrap();
    let config = ConcurrentGraphConfig::new(dir.path())
        .with_shard_count(shard_count)
        .with_shard_strategy(strategy)
        .with_auto_start(true)
        .with_wal_enabled(false);
    GraphShardRouter::new(config)
}

/// Create a test entity with given name
fn create_entity(name: &str) -> Entity {
    Entity::new(name, entity_types::PERSON)
}

/// Create a test entity with custom type
fn create_entity_with_type(name: &str, entity_type: &str) -> Entity {
    Entity::new(name, entity_type)
}

/// Create a test relation
fn create_relation(source_id: &str, target_id: &str) -> Relation {
    Relation::new(source_id, target_id, relation_types::FRIEND)
}

/// Create a test relation with custom type
fn create_relation_with_type(source_id: &str, target_id: &str, rel_type: &str) -> Relation {
    Relation::new(source_id, target_id, rel_type)
}

// ============================================================================
// 5.1.2.1 Hash Routing Consistency Tests
// ============================================================================

#[test]
fn test_hash_routing_same_id_consistency() {
    let router = create_test_router(8);
    
    // Same ID should always route to the same shard
    let test_id = "test-entity-12345";
    
    let first_shard = router.route_entity(test_id);
    
    // Route the same ID multiple times
    for _ in 0..100 {
        let shard = router.route_entity(test_id);
        assert_eq!(shard, first_shard, "Same ID should always route to same shard");
    }
}

#[test]
fn test_hash_routing_different_ids_consistency() {
    let router = create_test_router(8);
    
    // Store routing results for 100 different IDs
    let mut routing_map: HashMap<String, usize> = HashMap::new();
    
    for i in 0..100 {
        let id = format!("entity-{}", i);
        let shard = router.route_entity(&id);
        routing_map.insert(id, shard);
    }
    
    // Verify all IDs route to the same shard on repeated calls
    for (id, expected_shard) in &routing_map {
        let actual_shard = router.route_entity(id);
        assert_eq!(actual_shard, *expected_shard, "ID {} should consistently route to shard {}", id, expected_shard);
    }
}

#[test]
fn test_hash_routing_deterministic_across_routers() {
    // Create two separate routers with same configuration
    let router1 = create_test_router(8);
    let router2 = create_test_router(8);
    
    // Same ID should route to same shard across different router instances
    for i in 0..50 {
        let id = format!("test-entity-{}", i);
        let shard1 = router1.route_entity(&id);
        let shard2 = router2.route_entity(&id);
        
        assert_eq!(shard1, shard2, "Same ID should route to same shard across router instances");
    }
}

#[test]
fn test_hash_routing_unicode_ids() {
    let router = create_test_router(8);
    
    // Test routing with Unicode IDs
    let unicode_ids = vec![
        "用户-张三",
        "пользователь-иван",
        "ユーザー-太郎",
        "사용자-철수",
        "المستخدم-أحمد",
        "emoji-user-😀🎉",
    ];
    
    for id in &unicode_ids {
        let shard1 = router.route_entity(id);
        let shard2 = router.route_entity(id);
        assert_eq!(shard1, shard2, "Unicode ID {} should route consistently", id);
        assert!(shard1 < 8, "Shard ID should be within range");
    }
}

#[test]
fn test_hash_routing_empty_and_special_ids() {
    let router = create_test_router(8);
    
    // Pre-create the long string to avoid temporary borrow issues
    let long_id = "x".repeat(1000);
    
    // Test edge cases
    let special_ids = vec![
        "",
        " ",
        "\t\n",
        "a",
        "ab",
        "abc",
        &long_id, // Very long ID
        "id with spaces and special chars!@#$%^&*()",
    ];
    
    for id in special_ids {
        let shard1 = router.route_entity(id);
        let shard2 = router.route_entity(id);
        assert_eq!(shard1, shard2, "Special ID should route consistently");
        assert!(shard1 < 8, "Shard ID should be within range");
    }
}

#[test]
fn test_hash_routing_relation_follows_source() {
    let router = create_test_router(8);
    
    // Relation should be routed based on source entity
    let source_id = "alice-123";
    let target_id = "bob-456";
    
    let relation = create_relation(source_id, target_id);
    let source_shard = router.route_entity(source_id);
    let relation_shard = router.route_relation(&relation);
    
    assert_eq!(source_shard, relation_shard, "Relation should route to same shard as source entity");
}

// ============================================================================
// 5.1.2.2 Multi-Shard Load Balancing Tests
// ============================================================================

#[test]
fn test_hash_distribution_evenness() {
    let shard_count = 8;
    let router = create_test_router(shard_count);
    
    // Generate many random IDs and count distribution
    let mut shard_counts = vec![0usize; shard_count];
    let num_ids = 10000;
    
    for i in 0..num_ids {
        let id = format!("entity-test-{}", i);
        let shard = router.route_entity(&id);
        shard_counts[shard] += 1;
    }
    
    // Calculate distribution statistics
    let expected_per_shard = num_ids / shard_count;
    let tolerance = expected_per_shard / 4; // 25% tolerance
    
    for (shard_id, count) in shard_counts.iter().enumerate() {
        let diff = (*count as i64 - expected_per_shard as i64).abs() as usize;
        assert!(
            diff < tolerance,
            "Shard {} has {} entities, expected ~{} (±{}). Distribution is uneven.",
            shard_id, count, expected_per_shard, tolerance
        );
    }
    
    // Verify all shards received at least some traffic
    for (shard_id, count) in shard_counts.iter().enumerate() {
        assert!(*count > 0, "Shard {} received no entities", shard_id);
    }
}

#[test]
fn test_round_robin_distribution_perfect() {
    let shard_count = 4;
    let router = create_router_with_strategy(shard_count, ShardStrategy::RoundRobin);
    
    // With round-robin, distribution should be perfectly even
    let mut shard_counts = vec![0usize; shard_count];
    let num_ops = 100;
    
    for _ in 0..num_ops {
        // RoundRobin ignores the entity ID, just cycles through shards
        let shard = router.route_entity("any-id");
        shard_counts[shard] += 1;
    }
    
    // Each shard should have exactly 25 entities (100 / 4)
    let expected = num_ops / shard_count;
    for (shard_id, count) in shard_counts.iter().enumerate() {
        assert_eq!(
            *count, expected,
            "Shard {} should have exactly {} entities with round-robin, got {}",
            shard_id, expected, count
        );
    }
}

#[test]
fn test_distribution_with_different_shard_counts() {
    let shard_counts_to_test = vec![1, 2, 4, 8, 16, 32];
    let num_ids = 1000;
    
    for shard_count in shard_counts_to_test {
        let router = create_test_router(shard_count);
        let mut distribution = vec![0usize; shard_count];
        
        for i in 0..num_ids {
            let id = format!("entity-{}", i);
            let shard = router.route_entity(&id);
            assert!(shard < shard_count, "Shard ID {} exceeds count {}", shard, shard_count);
            distribution[shard] += 1;
        }
        
        // All shards should receive some traffic (except possibly with very few IDs)
        if num_ids >= shard_count * 10 {
            for (shard_id, count) in distribution.iter().enumerate() {
                assert!(
                    *count > 0,
                    "With {} shards, shard {} received no traffic",
                    shard_count, shard_id
                );
            }
        }
    }
}

#[tokio::test]
async fn test_write_load_balancing() {
    let shard_count = 4;
    let router = create_async_router(shard_count);
    
    // Write many entities and check distribution across shards
    let num_entities = 100;
    
    for i in 0..num_entities {
        let entity = create_entity(&format!("Person-{}", i));
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Check per-shard entity counts
    let mut shard_entity_counts = Vec::new();
    for shard in router.shards() {
        shard_entity_counts.push(shard.entity_count());
    }
    
    // Total should match
    let total: usize = shard_entity_counts.iter().sum();
    assert_eq!(total, num_entities, "Total entity count should match");
    
    // Each shard should have at least some entities
    for (shard_id, count) in shard_entity_counts.iter().enumerate() {
        assert!(
            *count > 0,
            "Shard {} should have at least some entities, got {}",
            shard_id, count
        );
    }
}

#[tokio::test]
async fn test_relation_distribution_follows_source() {
    let shard_count = 4;
    let router = create_async_router(shard_count);
    
    // Create entities
    let mut entity_ids = Vec::new();
    for i in 0..20 {
        let entity = create_entity(&format!("Person-{}", i));
        entity_ids.push(entity.id.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Create relations (chained: 0->1, 1->2, 2->3, ...)
    for i in 0..19 {
        let relation = create_relation(&entity_ids[i], &entity_ids[i + 1]);
        router.put_relation_sync(relation).await.unwrap();
    }
    
    // Verify each relation is stored in the same shard as its source
    for i in 0..19 {
        let source_shard = router.route_entity(&entity_ids[i]);
        let outgoing = router.get_shard(source_shard).unwrap().outgoing_from(&entity_ids[i]);
        
        assert!(
            !outgoing.is_empty(),
            "Source entity {} should have outgoing relations in its shard",
            entity_ids[i]
        );
    }
}

// ============================================================================
// 5.1.2.3 Cross-Shard Relation Query Tests
// ============================================================================

#[tokio::test]
async fn test_cross_shard_relation_creation() {
    let router = create_async_router(4);
    
    // Create entities that will likely be on different shards
    let mut entities = Vec::new();
    for i in 0..100 {
        let entity = create_entity(&format!("Person-{}", i));
        entities.push(entity.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Find pairs of entities on different shards
    let mut cross_shard_pairs = Vec::new();
    for i in 0..entities.len() {
        for j in (i + 1)..entities.len() {
            let shard_i = router.route_entity(&entities[i].id);
            let shard_j = router.route_entity(&entities[j].id);
            if shard_i != shard_j {
                cross_shard_pairs.push((i, j));
                if cross_shard_pairs.len() >= 10 {
                    break;
                }
            }
        }
        if cross_shard_pairs.len() >= 10 {
            break;
        }
    }
    
    assert!(!cross_shard_pairs.is_empty(), "Should find cross-shard entity pairs");
    
    // Create cross-shard relations
    for (i, j) in &cross_shard_pairs {
        let relation = create_relation(&entities[*i].id, &entities[*j].id);
        router.put_relation_sync(relation).await.unwrap();
    }
    
    // Verify global index tracks cross-shard relations
    let stats = router.global_index_stats();
    assert!(stats.cross_shard_count > 0, "Should have cross-shard relations indexed");
}

#[tokio::test]
async fn test_cross_shard_incoming_query() {
    let router = create_async_router(4);
    
    // Create multiple entities
    let mut entity_ids = Vec::new();
    for i in 0..50 {
        let entity = create_entity(&format!("Person-{}", i));
        entity_ids.push(entity.id.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Pick a target entity
    let target_id = &entity_ids[25];
    let target_shard = router.route_entity(target_id);
    
    // Create relations from multiple sources to target
    // Some will be same-shard, some cross-shard
    let mut same_shard_sources = Vec::new();
    let mut cross_shard_sources = Vec::new();
    
    for source_id in entity_ids.iter().take(25) {
        let source_shard = router.route_entity(source_id);
        
        let relation = create_relation(source_id, target_id);
        router.put_relation_sync(relation).await.unwrap();
        
        if source_shard == target_shard {
            same_shard_sources.push(source_id.clone());
        } else {
            cross_shard_sources.push(source_id.clone());
        }
    }
    
    // Query incoming relations
    let incoming = router.incoming_to(target_id);
    
    // Should find all incoming relations (both same-shard and cross-shard)
    assert_eq!(
        incoming.len(),
        same_shard_sources.len() + cross_shard_sources.len(),
        "Should find all incoming relations"
    );
    
    // Verify all sources are present
    let incoming_sources: HashSet<_> = incoming.iter().map(|r| r.source.clone()).collect();
    for source in &same_shard_sources {
        assert!(incoming_sources.contains(source), "Missing same-shard source: {}", source);
    }
    for source in &cross_shard_sources {
        assert!(incoming_sources.contains(source), "Missing cross-shard source: {}", source);
    }
}

#[tokio::test]
async fn test_cross_shard_outgoing_query() {
    let router = create_async_router(4);
    
    // Create entities
    let mut entity_ids = Vec::new();
    for i in 0..30 {
        let entity = create_entity(&format!("Person-{}", i));
        entity_ids.push(entity.id.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Pick a source entity
    let source_id = &entity_ids[0];
    
    // Create relations to multiple targets (some same-shard, some cross-shard)
    for target_id in entity_ids.iter().skip(1).take(20) {
        let relation = create_relation(source_id, target_id);
        router.put_relation_sync(relation).await.unwrap();
    }
    
    // Query outgoing relations (all stored in source's shard)
    let outgoing = router.outgoing_from(source_id);
    
    assert_eq!(outgoing.len(), 20, "Should find all 20 outgoing relations");
    
    // Verify all targets are present
    let outgoing_targets: HashSet<_> = outgoing.iter().map(|r| r.target.clone()).collect();
    for target_id in entity_ids.iter().skip(1).take(20) {
        assert!(outgoing_targets.contains(target_id), "Missing target: {}", target_id);
    }
}

#[tokio::test]
async fn test_relation_lookup_without_source_hint() {
    let router = create_async_router(4);
    
    // Create entities
    let alice = create_entity("Alice");
    let bob = create_entity("Bob");
    let alice_id = alice.id.clone();
    let bob_id = bob.id.clone();
    
    router.put_entity_sync(alice).await.unwrap();
    router.put_entity_sync(bob).await.unwrap();
    
    // Create relation
    let relation = create_relation(&alice_id, &bob_id);
    let relation_id = relation.id.clone();
    router.put_relation_sync(relation).await.unwrap();
    
    // Lookup relation without providing source hint (uses global index)
    let found = router.get_relation(&relation_id, None);
    
    assert!(found.is_some(), "Should find relation without source hint");
    let found = found.unwrap();
    assert_eq!(found.source, alice_id);
    assert_eq!(found.target, bob_id);
}

#[tokio::test]
async fn test_cross_shard_delete_relation() {
    let router = create_async_router(4);
    
    // Create cross-shard entities and relation
    let mut entity_ids = Vec::new();
    for i in 0..100 {
        let entity = create_entity(&format!("Person-{}", i));
        entity_ids.push(entity.id.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Find a cross-shard pair
    let mut source_id = None;
    let mut target_id = None;
    for i in 0..entity_ids.len() {
        for j in (i + 1)..entity_ids.len() {
            if router.route_entity(&entity_ids[i]) != router.route_entity(&entity_ids[j]) {
                source_id = Some(entity_ids[i].clone());
                target_id = Some(entity_ids[j].clone());
                break;
            }
        }
        if source_id.is_some() {
            break;
        }
    }
    
    let source_id = source_id.expect("Should find cross-shard pair");
    let target_id = target_id.expect("Should find cross-shard pair");
    
    // Create cross-shard relation
    let relation = create_relation(&source_id, &target_id);
    let relation_id = relation.id.clone();
    router.put_relation_sync(relation).await.unwrap();
    
    // Verify relation exists
    assert!(router.get_relation(&relation_id, None).is_some());
    
    // Delete relation
    router.delete_relation_sync(&relation_id, Some(&source_id), Some(&target_id)).await.unwrap();
    
    // Verify relation is deleted
    assert!(router.get_relation(&relation_id, None).is_none());
    
    // Verify incoming refs are cleaned up
    let incoming = router.incoming_to(&target_id);
    assert!(
        !incoming.iter().any(|r| r.id == relation_id),
        "Deleted relation should not appear in incoming"
    );
}

// ============================================================================
// 5.1.2.4 Global Index Accuracy Tests
// ============================================================================

#[tokio::test]
async fn test_global_index_entity_tracking() {
    let router = create_async_router(4);
    
    // Add entities
    let mut entity_ids = Vec::new();
    for i in 0..50 {
        let entity = create_entity(&format!("Person-{}", i));
        entity_ids.push(entity.id.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Verify global index stats
    let stats = router.global_index_stats();
    assert_eq!(stats.entity_count, 50, "Global index should track all entities");
}

#[tokio::test]
async fn test_global_index_relation_tracking() {
    let router = create_async_router(4);
    
    // Create entities
    let mut entity_ids = Vec::new();
    for i in 0..20 {
        let entity = create_entity(&format!("Person-{}", i));
        entity_ids.push(entity.id.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Create relations (chain)
    for i in 0..19 {
        let relation = create_relation(&entity_ids[i], &entity_ids[i + 1]);
        router.put_relation_sync(relation).await.unwrap();
    }
    
    // Verify global index stats
    let stats = router.global_index_stats();
    assert_eq!(stats.relation_count, 19, "Global index should track all relations");
}

#[tokio::test]
async fn test_global_index_type_tracking() {
    let router = create_async_router(4);
    
    // Add entities of different types
    for i in 0..10 {
        let entity = create_entity_with_type(&format!("Person-{}", i), entity_types::PERSON);
        router.put_entity_sync(entity).await.unwrap();
    }
    
    for i in 0..5 {
        let entity = create_entity_with_type(&format!("Concept-{}", i), entity_types::CONCEPT);
        router.put_entity_sync(entity).await.unwrap();
    }
    
    for i in 0..3 {
        let entity = create_entity_with_type(&format!("Event-{}", i), entity_types::EVENT);
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Verify type index
    let stats = router.global_index_stats();
    assert_eq!(stats.entity_count, 18, "Should have 18 entities total");
    assert_eq!(stats.type_count, 3, "Should have 3 entity types");
    
    // Query by type
    let persons = router.find_entities_by_type(entity_types::PERSON);
    assert_eq!(persons.len(), 10, "Should find 10 persons");
    
    let concepts = router.find_entities_by_type(entity_types::CONCEPT);
    assert_eq!(concepts.len(), 5, "Should find 5 concepts");
    
    let events = router.find_entities_by_type(entity_types::EVENT);
    assert_eq!(events.len(), 3, "Should find 3 events");
}

#[tokio::test]
async fn test_global_index_cross_shard_refs() {
    let router = create_async_router(4);
    
    // Create entities (enough to spread across shards)
    let mut entity_ids = Vec::new();
    for i in 0..100 {
        let entity = create_entity(&format!("Person-{}", i));
        entity_ids.push(entity.id.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Create cross-shard relations
    let mut cross_shard_count = 0;
    for i in 0..50 {
        let source_shard = router.route_entity(&entity_ids[i]);
        let target_shard = router.route_entity(&entity_ids[99 - i]);
        
        let relation = create_relation(&entity_ids[i], &entity_ids[99 - i]);
        router.put_relation_sync(relation).await.unwrap();
        
        if source_shard != target_shard {
            cross_shard_count += 1;
        }
    }
    
    // Verify cross-shard refs in global index
    let stats = router.global_index_stats();
    assert_eq!(stats.cross_shard_count, cross_shard_count, "Cross-shard count should match");
}

#[tokio::test]
async fn test_global_index_consistency_after_delete() {
    let router = create_async_router(4);
    
    // Create entities
    let alice = create_entity("Alice");
    let bob = create_entity("Bob");
    let alice_id = alice.id.clone();
    let bob_id = bob.id.clone();
    
    router.put_entity_sync(alice).await.unwrap();
    router.put_entity_sync(bob).await.unwrap();
    
    // Create relation
    let relation = create_relation(&alice_id, &bob_id);
    let relation_id = relation.id.clone();
    router.put_relation_sync(relation).await.unwrap();
    
    // Verify initial state
    let stats = router.global_index_stats();
    assert_eq!(stats.entity_count, 2);
    assert_eq!(stats.relation_count, 1);
    
    // Delete relation
    router.delete_relation_sync(&relation_id, Some(&alice_id), Some(&bob_id)).await.unwrap();
    
    // Verify index updated
    let stats = router.global_index_stats();
    assert_eq!(stats.entity_count, 2, "Entities should remain");
    assert_eq!(stats.relation_count, 0, "Relation should be removed from index");
    
    // Delete entity
    router.delete_entity_sync(&alice_id).await.unwrap();
    
    // Verify index updated
    let stats = router.global_index_stats();
    assert_eq!(stats.entity_count, 1, "One entity should remain");
}

#[tokio::test]
async fn test_global_index_rebuild() {
    let router = create_async_router(4);
    
    // Create entities and relations
    let mut entity_ids = Vec::new();
    for i in 0..30 {
        let entity = create_entity(&format!("Person-{}", i));
        entity_ids.push(entity.id.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    for i in 0..29 {
        let relation = create_relation(&entity_ids[i], &entity_ids[i + 1]);
        router.put_relation_sync(relation).await.unwrap();
    }
    
    // Get original stats
    let original_stats = router.global_index_stats();
    
    // Rebuild the global index
    router.rebuild_global_index();
    
    // Verify stats match after rebuild
    let rebuilt_stats = router.global_index_stats();
    assert_eq!(rebuilt_stats.entity_count, original_stats.entity_count);
    assert_eq!(rebuilt_stats.relation_count, original_stats.relation_count);
    assert_eq!(rebuilt_stats.type_count, original_stats.type_count);
    assert_eq!(rebuilt_stats.cross_shard_count, original_stats.cross_shard_count);
}

#[tokio::test]
async fn test_global_index_persistence() {
    let dir = tempdir().unwrap();
    let config = ConcurrentGraphConfig::new(dir.path())
        .with_shard_count(4)
        .with_auto_start(true)
        .with_wal_enabled(false);
    
    let router = GraphShardRouter::new(config);
    
    // Create entities
    let mut entity_ids = Vec::new();
    for i in 0..20 {
        let entity = create_entity(&format!("Person-{}", i));
        entity_ids.push(entity.id.clone());
        router.put_entity_sync(entity).await.unwrap();
    }
    
    // Create relations
    for i in 0..19 {
        let relation = create_relation(&entity_ids[i], &entity_ids[i + 1]);
        router.put_relation_sync(relation).await.unwrap();
    }
    
    // Get stats before persist
    let stats_before = router.global_index_stats();
    
    // Persist the global index
    router.persist_global_index().expect("Should persist global index");
    
    // Load the global index (simulating restart)
    router.load_global_index().expect("Should load global index");
    
    // Verify stats match after load
    let stats_after = router.global_index_stats();
    assert_eq!(stats_after.entity_count, stats_before.entity_count);
    assert_eq!(stats_after.relation_count, stats_before.relation_count);
}

// ============================================================================
// Additional Edge Case Tests
// ============================================================================

#[test]
fn test_single_shard_routing() {
    let router = create_test_router(1);
    
    // With single shard, everything routes to shard 0
    for i in 0..100 {
        let id = format!("entity-{}", i);
        let shard = router.route_entity(&id);
        assert_eq!(shard, 0, "Single shard should always route to shard 0");
    }
}

#[test]
fn test_max_shard_count_routing() {
    let shard_count = 64;
    let router = create_test_router(shard_count);
    
    for i in 0..1000 {
        let id = format!("entity-{}", i);
        let shard = router.route_entity(&id);
        assert!(shard < shard_count, "Shard {} exceeds max {}", shard, shard_count);
    }
}

#[tokio::test]
async fn test_entity_update_preserves_shard() {
    let router = create_async_router(4);
    
    // Create entity
    let mut entity = create_entity("Alice");
    let entity_id = entity.id.clone();
    let original_shard = router.route_entity(&entity_id);
    
    router.put_entity_sync(entity.clone()).await.unwrap();
    
    // Update entity (same ID)
    entity.name = "Alice Updated".to_string();
    router.put_entity_sync(entity).await.unwrap();
    
    // Should still be in same shard
    let retrieved = router.get_entity(&entity_id);
    assert!(retrieved.is_some());
    assert_eq!(retrieved.unwrap().name, "Alice Updated");
    
    // Verify routing hasn't changed
    let new_shard = router.route_entity(&entity_id);
    assert_eq!(original_shard, new_shard);
}

#[tokio::test]
async fn test_bidirectional_relations() {
    let router = create_async_router(4);
    
    // Create entities
    let alice = create_entity("Alice");
    let bob = create_entity("Bob");
    let alice_id = alice.id.clone();
    let bob_id = bob.id.clone();
    
    router.put_entity_sync(alice).await.unwrap();
    router.put_entity_sync(bob).await.unwrap();
    
    // Create bidirectional relations
    let rel_a_to_b = create_relation(&alice_id, &bob_id);
    let rel_b_to_a = create_relation(&bob_id, &alice_id);
    
    router.put_relation_sync(rel_a_to_b).await.unwrap();
    router.put_relation_sync(rel_b_to_a).await.unwrap();
    
    // Verify outgoing from both directions
    let alice_outgoing = router.outgoing_from(&alice_id);
    let bob_outgoing = router.outgoing_from(&bob_id);
    
    assert_eq!(alice_outgoing.len(), 1);
    assert_eq!(alice_outgoing[0].target, bob_id);
    
    assert_eq!(bob_outgoing.len(), 1);
    assert_eq!(bob_outgoing[0].target, alice_id);
    
    // Verify incoming from both directions
    let alice_incoming = router.incoming_to(&alice_id);
    let bob_incoming = router.incoming_to(&bob_id);
    
    assert_eq!(alice_incoming.len(), 1);
    assert_eq!(alice_incoming[0].source, bob_id);
    
    assert_eq!(bob_incoming.len(), 1);
    assert_eq!(bob_incoming[0].source, alice_id);
}

#[tokio::test]
async fn test_multiple_relation_types() {
    let router = create_async_router(4);
    
    // Create entities
    let alice = create_entity("Alice");
    let bob = create_entity("Bob");
    let alice_id = alice.id.clone();
    let bob_id = bob.id.clone();
    
    router.put_entity_sync(alice).await.unwrap();
    router.put_entity_sync(bob).await.unwrap();
    
    // Create relations of different types
    let friend_rel = create_relation_with_type(&alice_id, &bob_id, relation_types::FRIEND);
    let family_rel = create_relation_with_type(&alice_id, &bob_id, relation_types::FAMILY);
    
    router.put_relation_sync(friend_rel).await.unwrap();
    router.put_relation_sync(family_rel).await.unwrap();
    
    // Verify both relations exist
    let outgoing = router.outgoing_from(&alice_id);
    assert_eq!(outgoing.len(), 2);
    
    let rel_types: HashSet<_> = outgoing.iter().map(|r| r.relation_type.as_str()).collect();
    assert!(rel_types.contains(relation_types::FRIEND));
    assert!(rel_types.contains(relation_types::FAMILY));
}

#[tokio::test]
async fn test_self_reference_relation() {
    let router = create_async_router(4);
    
    // Create entity
    let alice = create_entity("Alice");
    let alice_id = alice.id.clone();
    
    router.put_entity_sync(alice).await.unwrap();
    
    // Create self-reference relation
    let self_rel = create_relation(&alice_id, &alice_id);
    router.put_relation_sync(self_rel).await.unwrap();
    
    // Verify outgoing includes self-reference
    let outgoing = router.outgoing_from(&alice_id);
    assert_eq!(outgoing.len(), 1);
    assert_eq!(outgoing[0].source, alice_id);
    assert_eq!(outgoing[0].target, alice_id);
    
    // Verify incoming includes self-reference
    let incoming = router.incoming_to(&alice_id);
    assert_eq!(incoming.len(), 1);
    assert_eq!(incoming[0].source, alice_id);
    assert_eq!(incoming[0].target, alice_id);
}

#[test]
fn test_shard_access() {
    let router = create_test_router(4);
    
    // Verify shard count
    assert_eq!(router.shard_count(), 4);
    
    // Verify can access all shards
    for i in 0..4 {
        let shard = router.get_shard(i);
        assert!(shard.is_some(), "Should be able to access shard {}", i);
        assert_eq!(shard.unwrap().id(), i);
    }
    
    // Verify out-of-bounds returns None
    assert!(router.get_shard(4).is_none());
    assert!(router.get_shard(100).is_none());
}
