#!/usr/bin/env python3
"""
Unit tests for RegenCache (thread-safe LRU cache)

Tests:
1. Basic LRU eviction
2. Thread safety (concurrent reads/writes)
3. Cache key composition (includes code_version)
4. Hit/miss statistics
"""
import threading
import sys
from pathlib import Path

# Add helix-cdc to path
sys.path.insert(0, str(Path(__file__).parent.parent))

from helix_cdc.regen_cache import RegenCache


def test_basic_lru_and_thread_safety():
    """Test basic LRU eviction and thread safety"""
    c = RegenCache(max_items=3, code_version="v1")

    # Fill cache
    c.set("k1", b"v1")
    c.set("k2", b"v2")
    c.set("k3", b"v3")

    assert c.get("k1") == b"v1", "k1 should exist"

    # Add k4 - should evict k2 (oldest, since k1 was just accessed)
    c.set("k4", b"v4")

    assert c.get("k2") is None, "k2 should be evicted (oldest)"
    assert c.get("k1") == b"v1", "k1 should still exist"
    assert c.get("k4") == b"v4", "k4 should exist"

    print("✅ LRU eviction works")

    # Thread safety smoke test
    def writer(i):
        for j in range(10):
            c.set(f"k{i}-{j}", bytes([j]))

    threads = [threading.Thread(target=writer, args=(i,)) for i in range(4)]
    for t in threads:
        t.start()
    for t in threads:
        t.join()

    # Should not raise and size should be <= max_items
    stats = c.stats()
    assert stats["items"] <= 3, f"Cache size {stats['items']} exceeds max_items=3"

    print(f"✅ Thread safety verified (final size: {stats['items']})")


def test_make_key_with_version():
    """Test cache key composition includes code_version"""
    c1 = RegenCache(max_items=10, code_version="abc123")
    c2 = RegenCache(max_items=10, code_version="def456")

    # Same tensor but different code versions
    key1 = c1.make_key("output.weight", 107520000, backend="helix_real")
    key2 = c2.make_key("output.weight", 107520000, backend="helix_real")

    assert key1 != key2, "Keys with different code_version should differ"
    assert "abc123" in key1, "key1 should contain code_version"
    assert "def456" in key2, "key2 should contain code_version"

    print(f"✅ Cache keys include code_version:")
    print(f"   key1: {key1[:50]}...")
    print(f"   key2: {key2[:50]}...")


def test_hit_miss_stats():
    """Test cache hit/miss statistics"""
    c = RegenCache(max_items=10, code_version="test")

    c.set("k1", b"v1")

    # Hit
    assert c.get("k1") == b"v1"
    # Miss
    assert c.get("k2") is None

    stats = c.stats()

    assert stats["hits"] == 1, f"Expected 1 hit, got {stats['hits']}"
    assert stats["misses"] == 1, f"Expected 1 miss, got {stats['misses']}"
    assert stats["items"] == 1, f"Expected 1 item, got {stats['items']}"

    print(f"✅ Stats tracking works: {stats}")


def test_clear():
    """Test cache clear"""
    c = RegenCache(max_items=10, code_version="test")

    c.set("k1", b"v1")
    c.set("k2", b"v2")
    assert c.get("k1") is not None

    c.clear()

    assert c.get("k1") is None, "Cache should be empty after clear"
    stats = c.stats()
    assert stats["items"] == 0, "Stats should show 0 items after clear"
    assert stats["hits"] == 0, "Stats should reset after clear"

    print(f"✅ Clear works: {stats}")


if __name__ == "__main__":
    print("=" * 80)
    print("RegenCache Unit Tests")
    print("=" * 80)
    print()

    test_basic_lru_and_thread_safety()
    test_make_key_with_version()
    test_hit_miss_stats()
    test_clear()

    print()
    print("=" * 80)
    print("✅ All tests passed")
    print("=" * 80)
