//! Runtime Feature Wiring Verification: Adaptive Cache
//!
//! This test module verifies the wiring status and integration points for the
//! AdaptiveCache feature in the memory-storage-redb crate.
//!
//! ## Analysis Summary (Updated 2026-03-12)
//!
//! **Current State**: AdaptiveCache is NOW WIRED into RedbStorage via AdaptiveCacheAdapter.
//!
//! **Architecture**:

// Integration tests are separate crate roots and don't inherit .clippy.toml settings
#![allow(clippy::unwrap_used)]
#![allow(clippy::expect_used)]
#![allow(clippy::float_cmp)]
#![allow(clippy::doc_markdown)]
#![allow(clippy::uninlined_format_args)]
//! - `LRUCache` is a metadata-only cache that tracks access patterns
//! - `AdaptiveCache<V>` is a value-storing cache that would duplicate data
//! - `AdaptiveCacheAdapter` wraps `AdaptiveCache<()>` for metadata-only caching with adaptive TTL
//!
//! **Solution Implemented**:
//! 1. Created `Cache` trait that abstracts common cache operations
//! 2. Created `AdaptiveCacheAdapter` that wraps `AdaptiveCache<()>` for metadata-only use
//! 3. Updated `RedbStorage` to use `Box<dyn Cache>` with `AdaptiveCacheAdapter` as default
//! 4. Added `new_with_adaptive_config()` constructor for custom adaptive cache settings
//!
//! **Benefits**:
//! - Adaptive TTL: Frequently accessed items get longer TTL
//! - Cold item detection: Rarely accessed items get shorter TTL
//! - No value duplication: Uses unit type `()` as stored value
//! - Single source of truth: redb remains the data store

use do_memory_core::{Episode, TaskContext, TaskType};
use do_memory_storage_redb::{
    AdaptiveCache, AdaptiveCacheAdapter, AdaptiveCacheConfig, Cache, CacheConfig, CacheMetrics,
    LRUCache, RedbStorage,
};
use std::time::Duration;
use tempfile::tempdir;
use uuid::Uuid;

// ============================================================================
// Verification Test 1: AdaptiveCache Works in Isolation
// ============================================================================

/// Verify that AdaptiveCache works correctly when used standalone.
/// This proves the implementation is sound, just not integrated.
#[tokio::test]
async fn test_adaptive_cache_works_in_isolation() {
    let config = AdaptiveCacheConfig {
        max_size: 100,
        default_ttl: Duration::from_secs(60),
        min_ttl: Duration::from_secs(10),
        max_ttl: Duration::from_secs(300),
        hot_threshold: 5,
        cold_threshold: 2,
        adaptation_rate: 0.25,
        window_size: 10,
        cleanup_interval_secs: 0, // Disable background cleanup for test
        enable_background_cleanup: false,
    };

    let cache: AdaptiveCache<String> = AdaptiveCache::new(config);

    let id = Uuid::new_v4();
    let value = "test_value".to_string();

    // Test miss (no value stored yet)
    let found = cache.record_access(id, false, Some(value.clone())).await;
    assert!(!found, "First access should be a miss");

    // Test hit
    let found = cache.record_access(id, true, None).await;
    assert!(found, "Second access should be a hit");

    // Test get
    let retrieved = cache.get(id).await;
    assert_eq!(retrieved, Some(value));

    // Test metrics
    let metrics = cache.get_metrics().await;
    assert_eq!(metrics.base.hits, 1);
    assert_eq!(metrics.base.misses, 1);
}

// ============================================================================
// Verification Test 2: Adaptive TTL Adjustment
// ============================================================================

/// Verify that AdaptiveCache adjusts TTLs based on access patterns.
#[tokio::test]
async fn test_adaptive_cache_ttl_adjustment() {
    let config = AdaptiveCacheConfig {
        max_size: 100,
        default_ttl: Duration::from_secs(60),
        min_ttl: Duration::from_secs(10),
        max_ttl: Duration::from_secs(300),
        hot_threshold: 3,
        cold_threshold: 1,
        adaptation_rate: 0.5,
        window_size: 10,
        cleanup_interval_secs: 0,
        enable_background_cleanup: false,
    };

    let cache: AdaptiveCache<String> = AdaptiveCache::new(config);

    let id = Uuid::new_v4();

    // Insert with default TTL
    cache
        .record_access(id, false, Some("value".to_string()))
        .await;
    let initial_ttl = cache.ttl(id).await.expect("Should have TTL");
    assert_eq!(
        initial_ttl,
        Duration::from_secs(60),
        "Should start with default TTL"
    );

    // Access multiple times to become "hot" (access_count >= hot_threshold)
    // Each hit increments access_count and may adjust TTL
    for _ in 0..5 {
        cache.record_access(id, true, None).await;
    }

    let hot_ttl = cache.ttl(id).await.expect("Should have TTL after accesses");

    // Hot items should have increased TTL (access_count >= hot_threshold of 3)
    // With adaptation_rate 0.5, TTL increases by 50% each access above threshold
    assert!(
        hot_ttl > initial_ttl,
        "Hot item TTL ({:?}) should be > initial TTL ({:?})",
        hot_ttl,
        initial_ttl
    );

    // The TTL should be bounded by max_ttl
    assert!(
        hot_ttl <= Duration::from_secs(300),
        "TTL should not exceed max_ttl"
    );
}

// ============================================================================
// Verification Test 3: Hot/Cold Detection
// ============================================================================

/// Verify that AdaptiveCache correctly identifies hot and cold items.
#[tokio::test]
async fn test_adaptive_cache_hot_cold_detection() {
    let config = AdaptiveCacheConfig {
        max_size: 100,
        default_ttl: Duration::from_secs(60),
        min_ttl: Duration::from_secs(10),
        max_ttl: Duration::from_secs(300),
        hot_threshold: 5,
        cold_threshold: 2,
        adaptation_rate: 0.25,
        window_size: 10,
        cleanup_interval_secs: 0,
        enable_background_cleanup: false,
    };

    let cache: AdaptiveCache<String> = AdaptiveCache::new(config);

    // Create a hot item (access many times)
    let hot_id = Uuid::new_v4();
    cache
        .record_access(hot_id, false, Some("hot".to_string()))
        .await;
    // record_access with hit=false creates entry with access_count=0
    // Each subsequent hit=true call increments access_count
    for _ in 0..10 {
        cache.record_access(hot_id, true, None).await;
    }

    // Create a cold item (rarely accessed)
    let cold_id = Uuid::new_v4();
    cache
        .record_access(cold_id, false, Some("cold".to_string()))
        .await;
    // Only 1 hit after the miss
    cache.record_access(cold_id, true, None).await;

    let metrics = cache.get_metrics().await;

    // Hot item should have high access count (0 initial + 10 hits = 10)
    let hot_count = cache.access_count(hot_id).await;
    assert_eq!(hot_count, Some(10), "Hot item should have 10 accesses");

    // Cold item should have low access count (0 initial + 1 hit = 1)
    let cold_count = cache.access_count(cold_id).await;
    assert_eq!(cold_count, Some(1), "Cold item should have 1 access");

    // Check hot/cold counts in metrics
    // Hot: access_count >= hot_threshold (5), so 10 >= 5 -> hot
    // Cold: access_count <= cold_threshold (2), so 1 <= 2 -> cold
    assert!(
        metrics.hot_item_count >= 1,
        "Should have at least 1 hot item"
    );
    assert!(
        metrics.cold_item_count >= 1,
        "Should have at least 1 cold item"
    );
}

// ============================================================================
// Verification Test 4: RedbStorage Uses AdaptiveCacheAdapter by Default
// ============================================================================

/// Verify that RedbStorage uses AdaptiveCacheAdapter by default.
/// This test documents the new wiring state where adaptive TTL is enabled.
#[tokio::test]
async fn test_redb_storage_uses_adaptive_cache_by_default() {
    let dir = tempdir().expect("Failed to create temp dir");
    let db_path = dir.path().join("test.redb");

    let storage = RedbStorage::new(&db_path)
        .await
        .expect("Failed to create storage");

    // RedbStorage exposes cache metrics via get_cache_metrics()
    // With AdaptiveCacheAdapter, this returns CacheMetrics from adaptive cache
    let metrics: CacheMetrics = storage.get_cache_metrics().await;

    // Initial state
    assert_eq!(metrics.hits, 0);
    assert_eq!(metrics.misses, 0);
    assert_eq!(metrics.item_count, 0);

    // Store an episode to trigger cache activity
    let episode = Episode::new(
        "Test task".to_string(),
        TaskContext::default(),
        TaskType::Testing,
    );
    storage
        .store_episode(&episode)
        .await
        .expect("Failed to store");

    // Get metrics after activity
    let metrics = storage.get_cache_metrics().await;
    assert_eq!(
        metrics.misses, 1,
        "Should have recorded a miss for the new episode"
    );
}

// ============================================================================
// Verification Test 4b: RedbStorage Can Use Legacy LRUCache
// ============================================================================

/// Verify that RedbStorage can still use legacy LRUCache via new_with_cache_config.
#[tokio::test]
async fn test_redb_storage_can_use_legacy_lru_cache() {
    let dir = tempdir().expect("Failed to create temp dir");
    let db_path = dir.path().join("test_lru.redb");

    // Use legacy constructor for LRUCache
    let config = CacheConfig {
        max_size: 100,
        default_ttl_secs: 1800,
        cleanup_interval_secs: 60,
        enable_background_cleanup: false,
    };
    let storage = RedbStorage::new_with_cache_config(&db_path, config)
        .await
        .expect("Failed to create storage with LRU cache");

    // Verify it works
    let metrics = storage.get_cache_metrics().await;
    assert_eq!(metrics.hits, 0);
    assert_eq!(metrics.misses, 0);
}

// ============================================================================
// Verification Test 4c: RedbStorage with Custom Adaptive Config
// ============================================================================

/// Verify that RedbStorage can use custom AdaptiveCacheConfig.
#[tokio::test]
async fn test_redb_storage_with_custom_adaptive_config() {
    let dir = tempdir().expect("Failed to create temp dir");
    let db_path = dir.path().join("test_adaptive_custom.redb");

    let config = AdaptiveCacheConfig {
        max_size: 500,
        default_ttl: Duration::from_secs(900),
        min_ttl: Duration::from_secs(60),
        max_ttl: Duration::from_secs(3600),
        hot_threshold: 5,
        cold_threshold: 1,
        adaptation_rate: 0.3,
        window_size: 15,
        cleanup_interval_secs: 120,
        enable_background_cleanup: false,
    };

    let storage = RedbStorage::new_with_adaptive_config(&db_path, config)
        .await
        .expect("Failed to create storage with custom adaptive config");

    let metrics = storage.get_cache_metrics().await;
    assert_eq!(metrics.hits, 0);
    assert_eq!(metrics.misses, 0);
}

// ============================================================================
// Verification Test 5: CacheConfig vs AdaptiveCacheConfig
// ============================================================================

/// Verify the configuration types are separate and have different capabilities.
#[test]
fn test_cache_config_types_are_different() {
    // LRUCache uses CacheConfig
    let lru_config = CacheConfig {
        max_size: 100,
        default_ttl_secs: 1800,
        cleanup_interval_secs: 60,
        enable_background_cleanup: true,
    };

    // AdaptiveCache uses AdaptiveCacheConfig
    let adaptive_config = AdaptiveCacheConfig {
        max_size: 100,
        default_ttl: Duration::from_secs(1800),
        min_ttl: Duration::from_secs(300),
        max_ttl: Duration::from_secs(7200),
        hot_threshold: 10,
        cold_threshold: 2,
        adaptation_rate: 0.25,
        window_size: 20,
        cleanup_interval_secs: 60,
        enable_background_cleanup: true,
    };

    // CacheConfig has NO adaptive TTL settings
    // AdaptiveCacheConfig has:
    // - min_ttl, max_ttl (TTL bounds)
    // - hot_threshold, cold_threshold (access pattern detection)
    // - adaptation_rate (how fast TTL changes)
    // - window_size (access tracking)

    // Verify they are different types
    assert_ne!(
        std::any::type_name_of_val(&lru_config),
        std::any::type_name_of_val(&adaptive_config)
    );
}

// ============================================================================
// Verification Test 6: API Difference - Value Storage
// ============================================================================

/// Verify that AdaptiveCache stores values while LRUCache does not.
/// This is the key architectural difference preventing direct wiring.
#[tokio::test]
async fn test_adaptive_cache_stores_values_lru_does_not() {
    // AdaptiveCache: record_access takes Option<V> to store values
    let adaptive_config = AdaptiveCacheConfig::default();
    let adaptive_cache: AdaptiveCache<String> = AdaptiveCache::new(adaptive_config);

    let id = Uuid::new_v4();
    adaptive_cache
        .record_access(id, false, Some("stored_value".to_string()))
        .await;

    // AdaptiveCache can retrieve the stored value
    let value = adaptive_cache.get(id).await;
    assert_eq!(value, Some("stored_value".to_string()));

    // LRUCache: record_access takes size_bytes, NOT values
    // This proves LRUCache is metadata-only
    let lru_config = CacheConfig::default();
    let lru_cache = LRUCache::new(lru_config);

    // LRUCache::record_access signature:
    // async fn record_access(&self, id: Uuid, hit: bool, size_bytes: Option<usize>) -> bool
    // Note: No value parameter - only size_bytes for tracking
    lru_cache.record_access(id, false, Some(100)).await;

    // LRUCache has no get() method for values - only get_metrics()
    // It tracks metadata (hits, misses, evictions, TTL) but not values
    let metrics = lru_cache.get_metrics().await;
    assert_eq!(metrics.misses, 1);
}

// ============================================================================
// Documentation: Architecture Implementation Complete
// ============================================================================

/// Architecture Implementation Complete
///
/// # Previous State (2026-03-09)
///
/// `RedbStorage` used `LRUCache` for metadata tracking. AdaptiveCache could not be
/// directly wired because it stores values, which would duplicate data stored in redb.
///
/// # Solution Implemented (2026-03-12)
///
/// Created `AdaptiveCacheAdapter` that:
/// - Wraps `AdaptiveCache<()>` (unit type as value)
/// - Implements the `Cache` trait for interchangeability
/// - Provides adaptive TTL without value storage
/// - Maintains single source of truth in redb
///
/// # Architecture
///
/// ```
/// ┌─────────────────────────────────────────────────────────────┐
/// │ RedbStorage                                                  │
/// │  ├─ db: Arc<Database>         // redb database (primary)    │
/// │  └─ cache: Box<dyn Cache>      // trait object              │
/// │       └─ AdaptiveCacheAdapter  // default implementation    │
/// │            └─ AdaptiveCache<()> // unit type = no values    │
/// └─────────────────────────────────────────────────────────────┘
/// ```
///
/// # API Changes
///
/// - `RedbStorage::new()` now uses `AdaptiveCacheAdapter` by default
/// - `RedbStorage::new_with_cache_config()` uses legacy `LRUCache`
/// - `RedbStorage::new_with_adaptive_config()` accepts `AdaptiveCacheConfig`
///
/// # Benefits
///
/// - Adaptive TTL: Hot items live longer, cold items expire faster
/// - No data duplication: Unit type `()` means no stored values
/// - Pluggable: `Box<dyn Cache>` allows any implementation
/// - Backward compatible: Legacy LRU still available via explicit constructor
#[test]
fn test_architecture_implementation_complete() {
    // This test documents the implemented architecture.
    // The AdaptiveCacheAdapter solution is now production-ready.

    println!("Architecture implementation complete - AdaptiveCacheAdapter is the default cache");
}

// ============================================================================
// Integration Test: AdaptiveCacheAdapter Works as Expected
// ============================================================================

/// Test that AdaptiveCacheAdapter provides adaptive TTL features.
#[tokio::test]
async fn test_adaptive_cache_adapter_integration() {
    // AdaptiveCacheAdapter wraps AdaptiveCache<()> for metadata-only caching
    let config = AdaptiveCacheConfig {
        max_size: 100,
        default_ttl: Duration::from_secs(60),
        min_ttl: Duration::from_secs(10),
        max_ttl: Duration::from_secs(300),
        hot_threshold: 3,
        cold_threshold: 1,
        adaptation_rate: 0.5,
        window_size: 10,
        cleanup_interval_secs: 0,
        enable_background_cleanup: false,
    };

    let adapter = AdaptiveCacheAdapter::new(config);
    let id = Uuid::new_v4();

    // Record a miss (new entry) - note: size_bytes is ignored by adapter
    let found = adapter.record_access(id, false, Some(100)).await;
    assert!(!found, "First access should be a miss");

    // Verify contains works
    assert!(adapter.contains(id).await, "Entry should exist after miss");

    // Record multiple hits to make it "hot"
    for _ in 0..5 {
        adapter.record_access(id, true, None).await;
    }

    // Verify hot count increased
    let hot_count = adapter.hot_count().await;
    assert!(hot_count > 0, "Should have at least one hot item");

    // Get metrics - should be standard CacheMetrics
    let metrics = adapter.get_metrics().await;
    assert!(metrics.hits > 0, "Should have recorded hits");
    assert_eq!(
        metrics.misses, 1,
        "Should have one miss from initial insert"
    );
}
