//! Tests the `runtime::session::prefix_state_cache` module in `sipp`.
//!
//! Covers runtime support modules with deterministic in-memory fixtures and no native model execution.

use super::*;
use crate::runtime::session::PrefixCachePolicy;

fn entry(snapshot_scope: &str, tokens: Vec<i32>, priority: u64) -> PrefixCacheEntry {
    let policy = PrefixCachePolicy::new(2);
    let prefix_hash = policy.hash_prefix(&tokens, tokens.len());
    PrefixCacheEntry {
        model_fingerprint: 7,
        snapshot_scope: snapshot_scope.to_string(),
        token_count: tokens.len(),
        prefix_hash,
        retention_priority: priority,
        hit_count: 0,
        approx_bytes: 0,
        prefix_tokens: tokens,
        state_bytes: vec![1, 2, 3],
        last_used: Instant::now(),
    }
}

#[test]
fn finds_longest_matching_prefix_and_records_hit() {
    let mut cache = PrefixStateCache::default();
    cache.insert_test_entry(entry("a", vec![1, 2], 0));
    cache.insert_test_entry(entry("a", vec![1, 2, 3, 4], 0));
    let mut policy = PrefixCachePolicy::new(2);

    let found = cache
        .find_best_prefix(7, "a", &[1, 2, 3, 4, 9], &mut policy)
        .expect("prefix");

    assert_eq!(found.token_count, 4);
    assert_eq!(policy.stats.hit_count, 1);
    assert_eq!(policy.stats.restored_token_count, 4);
}

#[test]
fn matching_prefix_hit_count_saturates() {
    let mut cache = PrefixStateCache::default();
    let mut entry = entry("a", vec![1, 2], 0);
    entry.hit_count = u64::MAX;
    cache.insert_test_entry(entry);
    let mut policy = PrefixCachePolicy::new(2);

    let found = cache
        .find_best_prefix(7, "a", &[1, 2, 3], &mut policy)
        .expect("prefix");

    assert_eq!(found.hit_count, u64::MAX);
}

#[test]
fn isolates_by_snapshot_scope_then_prefers_priority() {
    let mut cache = PrefixStateCache::default();
    cache.insert_test_entry(entry("other", vec![1, 2], 99));
    cache.insert_test_entry(entry("target", vec![1, 2], 1));
    cache.insert_test_entry(entry("target", vec![1, 2], 2));
    let mut policy = PrefixCachePolicy::new(2);

    let found = cache
        .find_best_prefix(7, "target", &[1, 2, 3], &mut policy)
        .expect("prefix");

    assert_eq!(found.snapshot_scope, "target");
    assert_eq!(found.retention_priority, 2);
}

#[test]
fn finds_prompt_seed_prefix_below_interval_minimum() {
    let mut cache = PrefixStateCache::default();
    let tokens: Vec<_> = (0..18).collect();
    cache.insert_test_entry(entry("ctx", tokens, 0));
    let prompt: Vec<_> = (0..19).collect();
    let mut policy = PrefixCachePolicy::new(128);

    let found = cache
        .find_best_prefix(7, "ctx", &prompt, &mut policy)
        .expect("prompt seed prefix");

    assert_eq!(found.token_count, 18);
    assert_eq!(policy.stats.hit_count, 1);
    assert_eq!(policy.stats.restored_token_count, 18);
}

#[test]
fn prefix_state_cache_presizes_bounded_collections() {
    let cache = PrefixStateCache::new(5, 100);

    assert!(cache.entries.capacity() >= 5);
    assert!(cache.lookup_buckets.capacity() >= 5);
}
