"""
Smoke test for Task 3.2: Routing Cache

Basic validation that the routing cache implementation works.
Run with: python backend/tests/test_routing_cache_smoke.py
"""

import sys
import os
import time

# Add parent directory to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))

from api.services.routing_cache_service import RoutingCacheService, CachedRoutingResult


def test_cache_initialization():
    """Test cache initializes correctly."""
    print("✓ Testing cache initialization...")
    
    cache = RoutingCacheService(ttl_seconds=60, max_size=1000)
    assert cache.ttl_seconds == 60
    assert cache.max_size == 1000
    assert len(cache._cache) == 0
    
    print("  Cache initialized with TTL=60s, max_size=1000")
    print("✓ Cache initialization works\n")


def test_cache_set_get():
    """Test basic set/get operations."""
    print("✓ Testing cache set/get...")
    
    cache = RoutingCacheService(ttl_seconds=60)
    
    result = CachedRoutingResult(
        selected_provider="openai",
        selected_model="gpt-4",
        selected_model_id="openai/gpt-4",
        canonical_model_id="gpt-4",
        model_hle=0.95,
        model_price=30.0,
        input_price_per_million=30.0,
        output_price_per_million=60.0,
        effective_hle=0.95,
        hle_source="model",
        hle_clamped=False,
        timestamp=time.time(),
    )
    
    # Set
    cache.set(
        subscription_id=123,
        provider="openai",
        desired_hle=0.9,
        tags={"project": "test"},
        exclude_model_ids=None,
        result=result,
    )
    
    # Get (cache hit)
    cached = cache.get(
        subscription_id=123,
        provider="openai",
        desired_hle=0.9,
        tags={"project": "test"},
        exclude_model_ids=None,
    )
    
    assert cached is not None, "❌ Cache should return result"
    assert cached.selected_provider == "openai", "❌ Wrong provider"
    assert cached.selected_model == "gpt-4", "❌ Wrong model"
    
    print("  Cached: openai/gpt-4 (HLE=0.95)")
    print("  Retrieved: {}/{}".format(cached.selected_provider, cached.selected_model))
    print("✓ Cache set/get works\n")


def test_cache_miss():
    """Test cache miss returns None."""
    print("✓ Testing cache miss...")
    
    cache = RoutingCacheService(ttl_seconds=60)
    
    # Try to get non-existent entry
    cached = cache.get(
        subscription_id=999,
        provider="anthropic",
        desired_hle=0.8,
        tags={},
        exclude_model_ids=None,
    )
    
    assert cached is None, "❌ Cache miss should return None"
    
    print("  Cache miss correctly returned None")
    print("✓ Cache miss works\n")


def test_cache_ttl():
    """Test cache TTL expiration."""
    print("✓ Testing cache TTL expiration...")
    
    cache = RoutingCacheService(ttl_seconds=2)  # 2 second TTL for fast test
    
    result = CachedRoutingResult(
        selected_provider="anthropic",
        selected_model="claude-3-opus",
        selected_model_id="anthropic/claude-3-opus",
        canonical_model_id="claude-3-opus-20240229",
        model_hle=0.98,
        model_price=15.0,
        input_price_per_million=15.0,
        output_price_per_million=75.0,
        effective_hle=0.98,
        hle_source="model",
        hle_clamped=False,
        timestamp=time.time(),
    )
    
    # Set
    cache.set(
        subscription_id=456,
        provider="anthropic",
        desired_hle=0.95,
        tags={},
        exclude_model_ids=None,
        result=result,
    )
    
    # Should be available immediately
    cached = cache.get(
        subscription_id=456,
        provider="anthropic",
        desired_hle=0.95,
        tags={},
        exclude_model_ids=None,
    )
    assert cached is not None, "❌ Should be cached immediately"
    print("  Entry cached (expires in 2s)")
    
    # Wait for expiration
    print("  Waiting 2.5s for expiration...")
    time.sleep(2.5)
    
    # Should be expired
    cached = cache.get(
        subscription_id=456,
        provider="anthropic",
        desired_hle=0.95,
        tags={},
        exclude_model_ids=None,
    )
    assert cached is None, "❌ Cache should expire after TTL"
    
    print("  Entry expired after TTL ✓")
    print("✓ Cache TTL works\n")


def test_cache_statistics():
    """Test cache statistics tracking."""
    print("✓ Testing cache statistics...")
    
    cache = RoutingCacheService(ttl_seconds=60)
    
    result = CachedRoutingResult(
        selected_provider="google",
        selected_model="gemini-pro",
        selected_model_id="google/gemini-pro",
        canonical_model_id="gemini-pro",
        model_hle=0.92,
        model_price=2.0,
        input_price_per_million=0.5,
        output_price_per_million=1.5,
        effective_hle=0.92,
        hle_source="model",
        hle_clamped=False,
        timestamp=time.time(),
    )
    
    # Set
    cache.set(
        subscription_id=789,
        provider="google",
        desired_hle=0.9,
        tags={},
        exclude_model_ids=None,
        result=result,
    )
    
    # 3 cache hits
    for _ in range(3):
        cache.get(
            subscription_id=789,
            provider="google",
            desired_hle=0.9,
            tags={},
            exclude_model_ids=None,
        )
    
    # 2 cache misses
    for i in range(2):
        cache.get(
            subscription_id=999+i,
            provider="nonexistent",
            desired_hle=0.5,
            tags={},
            exclude_model_ids=None,
        )
    
    stats = cache.get_stats()
    
    assert stats["hits"] == 3, f"❌ Expected 3 hits, got {stats['hits']}"
    assert stats["misses"] == 2, f"❌ Expected 2 misses, got {stats['misses']}"
    assert stats["cache_size"] == 1, f"❌ Expected 1 entry, got {stats['cache_size']}"
    
    print(f"  Hits: {stats['hits']}")
    print(f"  Misses: {stats['misses']}")
    print(f"  Hit rate: {stats['hit_rate']}")
    print(f"  Cache size: {stats['cache_size']}")
    print("✓ Cache statistics work\n")


def test_cache_invalidation():
    """Test subscription cache invalidation."""
    print("✓ Testing cache invalidation...")
    
    cache = RoutingCacheService(ttl_seconds=60)
    
    # Add entries for subscription 100
    for provider in ["openai", "anthropic", "google"]:
        result = CachedRoutingResult(
            selected_provider=provider,
            selected_model="test-model",
            selected_model_id=f"{provider}/test-model",
            canonical_model_id="test-model",
            model_hle=0.9,
            model_price=10.0,
            input_price_per_million=5.0,
            output_price_per_million=15.0,
            effective_hle=0.9,
            hle_source="model",
            hle_clamped=False,
            timestamp=time.time(),
        )
        
        cache.set(
            subscription_id=100,
            provider=provider,
            desired_hle=0.9,
            tags={},
            exclude_model_ids=None,
            result=result,
        )
    
    # Add entry for subscription 200
    result = CachedRoutingResult(
        selected_provider="xai",
        selected_model="grok",
        selected_model_id="xai/grok",
        canonical_model_id="grok",
        model_hle=0.85,
        model_price=5.0,
        input_price_per_million=2.5,
        output_price_per_million=7.5,
        effective_hle=0.85,
        hle_source="model",
        hle_clamped=False,
        timestamp=time.time(),
    )
    cache.set(
        subscription_id=200,
        provider="xai",
        desired_hle=0.8,
        tags={},
        exclude_model_ids=None,
        result=result,
    )
    
    print(f"  Cached 3 entries for subscription 100")
    print(f"  Cached 1 entry for subscription 200")
    print(f"  Total cache size: {len(cache._cache)}")
    
    # Invalidate subscription 100
    cache.invalidate_subscription(100)
    
    print(f"  Invalidated subscription 100")
    print(f"  Cache size after invalidation: {len(cache._cache)}")
    
    assert len(cache._cache) == 1, "❌ Should only have 1 entry left"
    
    # Verify subscription 100 entries are gone
    for provider in ["openai", "anthropic", "google"]:
        cached = cache.get(
            subscription_id=100,
            provider=provider,
            desired_hle=0.9,
            tags={},
            exclude_model_ids=None,
        )
        assert cached is None, f"❌ Subscription 100 {provider} should be invalidated"
    
    # Verify subscription 200 still exists
    cached = cache.get(
        subscription_id=200,
        provider="xai",
        desired_hle=0.8,
        tags={},
        exclude_model_ids=None,
    )
    assert cached is not None, "❌ Subscription 200 should still be cached"
    
    print("  Subscription 100 entries invalidated ✓")
    print("  Subscription 200 entries preserved ✓")
    print("✓ Cache invalidation works\n")


def test_cache_performance():
    """Test cache performance benefit."""
    print("✓ Testing cache performance benefit...")
    
    cache = RoutingCacheService(ttl_seconds=60)
    
    result = CachedRoutingResult(
        selected_provider="openai",
        selected_model="gpt-4-turbo",
        selected_model_id="openai/gpt-4-turbo",
        canonical_model_id="gpt-4-turbo-preview",
        model_hle=0.96,
        model_price=10.0,
        input_price_per_million=10.0,
        output_price_per_million=30.0,
        effective_hle=0.96,
        hle_source="model",
        hle_clamped=False,
        timestamp=time.time(),
    )
    
    # Simulate "expensive" set operation (with complex cache key)
    start = time.time()
    cache.set(
        subscription_id=12345,
        provider="openai",
        desired_hle=0.95,
        tags={"project": "production", "environment": "prod", "team": "backend"},
        exclude_model_ids={"model-1", "model-2", "model-3"},
        result=result,
    )
    set_time = (time.time() - start) * 1000
    
    # Measure cache get performance (should be very fast)
    start = time.time()
    for _ in range(1000):  # 1000 cache lookups
        cached = cache.get(
            subscription_id=12345,
            provider="openai",
            desired_hle=0.95,
            tags={"project": "production", "environment": "prod", "team": "backend"},
            exclude_model_ids={"model-1", "model-2", "model-3"},
        )
    elapsed = (time.time() - start) * 1000
    avg_per_lookup = elapsed / 1000
    
    print(f"  Cache set time: {set_time:.3f}ms")
    print(f"  1000 cache lookups: {elapsed:.1f}ms")
    print(f"  Average per lookup: {avg_per_lookup:.3f}ms")
    
    assert avg_per_lookup < 0.1, f"❌ Cache lookup too slow: {avg_per_lookup:.3f}ms"
    assert cached is not None, "❌ Cache should return result"
    
    print("✓ Cache is very fast (<0.1ms per lookup)\n")


def main():
    """Run all smoke tests."""
    print("=" * 70)
    print("SMOKE TEST: Routing Cache (Task 3.2)")
    print("=" * 70)
    print()
    
    test_cache_initialization()
    test_cache_set_get()
    test_cache_miss()
    test_cache_ttl()
    test_cache_statistics()
    test_cache_invalidation()
    test_cache_performance()
    
    print("=" * 70)
    print("✅ ALL TESTS PASSED")
    print("=" * 70)
    print()
    print("Summary:")
    print("  ✓ Routing cache initializes correctly")
    print("  ✓ Cache set/get operations work")
    print("  ✓ Cache miss returns None")
    print("  ✓ Cache entries expire after TTL")
    print("  ✓ Cache statistics track hits/misses")
    print("  ✓ Subscription invalidation works")
    print("  ✓ Cache lookups are very fast (<0.1ms)")
    print()
    print("Task 3.2 implementation is working correctly! 🎉")


if __name__ == "__main__":
    main()
