//! Inline caching validation tests
//!
//! These tests validate that the inline caching improvements
//! work correctly and provide performance benefits.

use jvmrs::interpreter::Interpreter;
use jvmrs::inline_cache::{InlineCacheManager, CallSiteCache, InlineCacheEntry};
use jvmrs::class_file::{ClassFile, MethodInfo};

#[test]
fn test_inline_cache_functionality() {
    let mut interpreter = Interpreter::new();
    let cache_manager = InlineCacheManager::new(true);
    
    // Test that cache manager is properly created
    assert!(cache_manager.is_enabled());
    
    // Test getting cache for non-existent call site
    let cache = cache_manager.get_cache("TestClass", "method", "()V", 100);
    assert!(cache.is_none());
}

#[test]
fn test_call_site_cache_basic_operations() {
    let mut cache = CallSiteCache::new(4);
    
    // Create test method entry
    let entry = InlineCacheEntry {
        receiver_class: "java/lang/String".to_string(),
        method_name: "toString".to_string(),
        descriptor: "()Ljava/lang/String;".to_string(),
        resolved_class: "java/lang/String".to_string(),
        method: std::sync::Arc::new(create_test_method()),
        hit_count: 0,
    };
    
    // Test cache update
    cache.update(entry.clone());
    
    // Test cache lookup
    let found = cache.lookup("java/lang/String", "toString", "()Ljava/lang/String;");
    assert!(found.is_some());
    assert_eq!(found.unwrap().receiver_class, "java/lang/String");
    
    // Test stats
    let stats = cache.stats();
    assert_eq!(stats.total_lookups, 1);
    assert_eq!(stats.cache_hits, 0); // No hits yet, just lookups
    assert_eq!(stats.entry_count, 1);
}

#[test]
fn test_call_site_cache_lru_eviction() {
    let mut cache = CallSiteCache::new(2); // Very small cache
    
    // Create two entries
    let entry1 = create_test_entry("Class1", "method1", "()V");
    let entry2 = create_test_entry("Class2", "method2", "()V");
    
    cache.update(entry1.clone());
    cache.update(entry2.clone());
    
    // Cache should now be full
    assert_eq!(cache.stats().entry_count, 2);
    
    // Add a third entry - should evict the least recently used
    let entry3 = create_test_entry("Class3", "method3", "()V");
    cache.update(entry3.clone());
    
    // Cache should still have 2 entries but different ones
    assert_eq!(cache.stats().entry_count, 2);
    
    // Entry1 and entry2 should have been updated
    let stats1 = cache.lookup("Class1", "method1", "()V");
    let stats2 = cache.lookup("Class2", "method2", "()V");
    let stats3 = cache.lookup("Class3", "method3", "()V");
    
    // One of the original entries should be gone
    let found_original = stats1.is_some() || stats2.is_some();
    let new_entry = stats3.is_some();
    
    assert!(found_original);
    assert!(new_entry);
}

#[test]
fn test_cache_hit_scenarios() {
    let mut cache = CallSiteCache::new(4);
    
    let entry = create_test_entry("TestClass", "testMethod", "()I");
    
    // First lookup - miss
    cache.update(entry.clone());
    let stats = cache.stats();
    assert_eq!(stats.total_lookups, 1);
    assert_eq!(stats.cache_hits, 0);
    
    // Same lookup again - hit
    cache.update(entry.clone());
    let stats = cache.stats();
    assert_eq!(stats.total_lookups, 2);
    assert_eq!(stats.cache_hits, 1);
    assert!(stats.hit_rate > 0.0);
}

#[test]
fn test_cache_disabled_operations() {
    let cache_manager = InlineCacheManager::new(false);
    
    // Cache should be disabled
    assert!(!cache_manager.is_enabled());
    
    // Operations should return None when cache is disabled
    let cache = cache_manager.get_cache("TestClass", "method", "()V", 100);
    assert!(cache.is_none());
    
    // Update should be a no-op when cache is disabled
    let entry = create_test_entry("TestClass", "method", "()V");
    cache_manager.update_cache("TestClass", "method", "()V", 100, entry);
    
    // Stats should be empty when cache is disabled
    let stats = cache_manager.get_stats();
    assert!(stats.is_empty());
}

#[test]
fn test_cache_performance_characteristics() {
    let mut cache = CallSiteCache::new(8);
    let entry = create_test_entry("Test", "method", "()V");
    
    // Test many updates to test performance
    for i in 0..1000 {
        cache.update(entry.clone());
    }
    
    let stats = cache.stats();
    assert_eq!(stats.total_lookups, 1000);
    assert_eq!(stats.cache_hits, 999); // 999 hits after first update
    assert!(stats.hit_rate > 0.9); // Should be very high
    
    // Cache should not grow beyond its limit
    assert_eq!(stats.entry_count, 1); // Still only one entry since we're updating the same method
}

#[test]
fn test_cache_concurrent_access() {
    // Note: This is a basic test, concurrent access to inline cache
    // should be handled by the RwLock in the InlineCacheManager
    let cache_manager = InlineCacheManager::new(true);
    let entry = create_test_entry("ConcurrentTest", "method", "()V");
    
    // Test multiple updates (this would be done by different threads in real usage)
    for i in 0..10 {
        cache_manager.update_cache("ConcurrentTest", "method", "()V", i, entry.clone());
    }
    
    let stats = cache_manager.get_stats();
    assert!(!stats.is_empty());
    assert!(stats.len() > 0);
}

// Helper functions for testing
fn create_test_method() -> MethodInfo {
    MethodInfo {
        access_flags: 0,
        name_index: 0,
        descriptor_index: 0,
        attributes: vec![],
    }
}

fn create_test_entry(class: &str, method: &str, descriptor: &str) -> InlineCacheEntry {
    InlineCacheEntry {
        receiver_class: class.to_string(),
        method_name: method.to_string(),
        descriptor: descriptor.to_string(),
        resolved_class: class.to_string(),
        method: std::sync::Arc::new(create_test_method()),
        hit_count: 0,
    }
}