#!/usr/bin/env python3
#exonware/xwsystem/tests/1.unit/caching_tests/test_lfu_optimized.py
"""
Unit tests for optimized O(1) LFU cache.
Company: eXonware.com
Author: eXonware Backend Team
Email: connect@exonware.com
Version: 0.0.1.388
Generation Date: 01-Nov-2025
"""

import pytest
import time
from exonware.xwsystem.caching.lfu_optimized import OptimizedLFUCache
@pytest.mark.xwsystem_unit
@pytest.mark.xwsystem_performance


class TestOptimizedLFUCache:
    """Test optimized O(1) LFU cache."""

    def test_basic_operations(self):
        """Test basic get/put/delete operations."""
        cache = OptimizedLFUCache(capacity=3)
        cache.put("k1", "v1")
        cache.put("k2", "v2")
        cache.put("k3", "v3")
        assert cache.get("k1") == "v1"
        assert cache.get("k2") == "v2"
        assert cache.get("k3") == "v3"
        assert cache.size() == 3

    def test_o1_eviction_performance(self):
        """Test that eviction is O(1), not O(n)."""
        # Measure eviction time - run multiple times and take average for stability
        times = []
        for iteration in range(3):  # Run 3 times and average
            # Create fresh cache for each iteration to get clean stats
            cache = OptimizedLFUCache(capacity=1000)
            # Fill cache
            for i in range(1000):
                cache.put(f"key_{i}", f"value_{i}")
            start = time.perf_counter()
            # Force 100 evictions
            for i in range(1000, 1100):
                cache.put(f"key_{i}", f"value_{i}")
            end = time.perf_counter()
            times.append((end - start) * 1000)
            # Verify evictions happened on this iteration
            stats = cache.get_stats()
            assert stats['evictions'] == 100, f"Iteration {iteration}: Expected 100 evictions, got {stats['evictions']}"
        avg_elapsed_ms = sum(times) / len(times)
        # O(1) eviction: threshold stable on CI/loaded machines while still catching O(n) regression.
        assert avg_elapsed_ms < 75, f"Evictions too slow: {avg_elapsed_ms:.3f}ms (expected < 75ms for 100 O(1) evictions)"

    def test_lfu_eviction_policy(self):
        """Test that least frequently used items are evicted."""
        cache = OptimizedLFUCache(capacity=3)
        # Add items
        cache.put("k1", "v1")
        cache.put("k2", "v2")
        cache.put("k3", "v3")
        # Access k1 twice, k2 once, k3 not at all
        cache.get("k1")
        cache.get("k1")
        cache.get("k2")
        # Add new item - should evict k3 (frequency=0 from insertion only)
        cache.put("k4", "v4")
        # k3 should be gone
        assert cache.get("k3") is None
        # Others should remain
        assert cache.get("k1") == "v1"
        assert cache.get("k2") == "v2"
        assert cache.get("k4") == "v4"

    def test_frequency_tracking(self):
        """Test that frequencies are tracked correctly."""
        cache = OptimizedLFUCache(capacity=5)
        cache.put("k1", "v1")
        # Access multiple times
        for _ in range(5):
            cache.get("k1")
        stats = cache.get_stats()
        # k1 should have frequency of 6 (1 from put, 5 from gets)
        assert stats['hits'] == 5

    def test_statistics(self):
        """Test cache statistics."""
        cache = OptimizedLFUCache(capacity=100)
        # Add items
        for i in range(50):
            cache.put(f"key_{i}", f"value_{i}")
        # Access some items
        for i in range(25):
            cache.get(f"key_{i}")
        # Get non-existent items
        for i in range(50, 60):
            cache.get(f"key_{i}")
        stats = cache.get_stats()
        assert stats['size'] == 50
        assert stats['hits'] == 25
        assert stats['misses'] == 10
        assert stats['type'] == 'OptimizedLFU'

    def test_context_manager(self):
        """Test context manager support."""
        with OptimizedLFUCache(capacity=10) as cache:
            cache.put("key", "value")
            assert cache.get("key") == "value"
@pytest.mark.xwsystem_unit
@pytest.mark.xwsystem_performance


class TestOptimizedLFUPerformance:
    """Performance-specific tests for optimized LFU."""

    def test_large_cache_performance(self):
        """Test performance with large cache."""
        cache = OptimizedLFUCache(capacity=10000)
        # Fill cache
        start = time.perf_counter()
        for i in range(10000):
            cache.put(f"key_{i}", f"value_{i}")
        end = time.perf_counter()
        fill_time = end - start
        # Threshold stable on CI/loaded machines.
        assert fill_time < 5.0, f"Filling cache too slow: {fill_time:.2f}s"
        # Test get performance
        start = time.perf_counter()
        for i in range(1000):
            cache.get(f"key_{i % 10000}")
        end = time.perf_counter()
        get_time = end - start
        assert get_time < 0.5, f"Get operations too slow: {get_time:.3f}s"

    def test_eviction_scales_linearly(self):
        """Test that eviction time doesn't grow with cache size."""
        # Small cache
        small_cache = OptimizedLFUCache(capacity=100)
        for i in range(100):
            small_cache.put(f"key_{i}", f"value_{i}")
        start = time.perf_counter()
        for i in range(100, 200):
            small_cache.put(f"key_{i}", f"value_{i}")
        small_time = time.perf_counter() - start
        # Large cache
        large_cache = OptimizedLFUCache(capacity=10000)
        for i in range(10000):
            large_cache.put(f"key_{i}", f"value_{i}")
        start = time.perf_counter()
        for i in range(10000, 10100):
            large_cache.put(f"key_{i}", f"value_{i}")
        large_time = time.perf_counter() - start
        # Large cache evictions should not be 100x slower
        # Allow 3x slowdown for overhead, but not O(n) behavior
        assert large_time < small_time * 3, (
            f"Eviction time grows too much with size: "
            f"small={small_time:.4f}s, large={large_time:.4f}s"
        )
