"""
Performance Tests

Benchmark the speed and performance characteristics of each model variant.
Establishes baselines for capacity planning.
"""

import pytest
import time
from unittest.mock import Mock
from kernel_service.registry import get_registry


@pytest.fixture
def sample_claim():
    """Sample claim for performance testing"""
    return {
        "claimId": "PERF-TEST-001",
        "patientId": "PAT-001",
        "providerId": "PROV-001",
        "payerId": "PYR-001",
        "serviceDate": "2024-01-15",
        "procedureCode": "99213",
        "billedAmount": 500.0,
        "metadata": {"reviewThreshold": 0.8},
    }


@pytest.fixture
def mock_dependencies():
    """Create mocked dependencies for performance testing"""
    mock_fusion = Mock()
    mock_fusion.get_structured_evidence = Mock(return_value=0.7)
    mock_fusion.get_unstructured_evidence = Mock(return_value=0.6)
    mock_fusion.get_contextual_evidence = Mock(return_value=0.5)
    mock_fusion.get_patient_evidence = Mock(return_value=0.4)
    mock_fusion.get_provider_evidence = Mock(return_value=0.3)
    mock_fusion.get_payer_evidence = Mock(return_value=0.2)
    mock_fusion.get_temporal_evidence = Mock(return_value=0.1)
    mock_fusion.fuse = Mock(return_value=(0.6, {
        "structured": {"score": 0.7, "weight": 0.25, "contribution": 0.175},
        "unstructured": {"score": 0.6, "weight": 0.15, "contribution": 0.09},
        "contextual": {"score": 0.5, "weight": 0.10, "contribution": 0.05},
        "patient": {"score": 0.4, "weight": 0.15, "contribution": 0.06},
        "provider": {"score": 0.3, "weight": 0.15, "contribution": 0.045},
        "payer": {"score": 0.2, "weight": 0.10, "contribution": 0.02},
        "temporal": {"score": 0.1, "weight": 0.10, "contribution": 0.01},
    }))
    
    mock_context = Mock()
    mock_context.get_patient_context = Mock(return_value={"patient_denial_rate": 0.2})
    mock_context.get_provider_context = Mock(return_value={"provider_denial_rate": 0.15})
    mock_context.get_payer_context = Mock(return_value={"payer_denial_rate": 0.25})
    mock_context.get_temporal_context = Mock(return_value={"claim_age_days": 30})
    mock_context.get_financial_context = Mock(return_value={})
    mock_context.get_relationship_context = Mock(return_value={})
    mock_context.get_policy_context = Mock(return_value={})
    
    return mock_fusion, mock_context


def benchmark_model(model_id: str, claim: dict, iterations: int = 10) -> dict:
    """Benchmark a model's performance"""
    registry = get_registry()
    model = registry.get_model(model_id)
    
    # Mock dependencies
    # Note: 3-plane fusion (used by lite1) returns Dict with "confidence" key
    # 7-plane fusion (used by k1/k2) returns float
    mock_fusion = Mock()
    
    # Determine if this is a 3-plane model (lite1) or 7-plane model (k1/k2/sim1/audit1)
    is_3plane = model_id == "cxr-k-lite1"
    
    if is_3plane:
        # For 3-plane models (lite1), return dicts
        mock_fusion.get_structured_evidence = Mock(return_value={"confidence": 0.7})
        mock_fusion.get_unstructured_evidence = Mock(return_value={"confidence": 0.6})
        mock_fusion.get_contextual_evidence = Mock(return_value={"confidence": 0.5})
    else:
        # For 7-plane models (k1/k2/sim1/audit1), return floats
        mock_fusion.get_structured_evidence = Mock(return_value=0.7)
        mock_fusion.get_unstructured_evidence = Mock(return_value=0.6)
        mock_fusion.get_contextual_evidence = Mock(return_value=0.5)
        mock_fusion.get_patient_evidence = Mock(return_value=0.4)
        mock_fusion.get_provider_evidence = Mock(return_value=0.3)
        mock_fusion.get_payer_evidence = Mock(return_value=0.2)
        mock_fusion.get_temporal_evidence = Mock(return_value=0.1)
    
    # Fuse method returns tuple (confidence, contributions)
    mock_fusion.fuse = Mock(return_value=(0.6, {
        "structured": {"score": 0.7, "weight": 0.25, "contribution": 0.175},
        "unstructured": {"score": 0.6, "weight": 0.15, "contribution": 0.09},
        "contextual": {"score": 0.5, "weight": 0.10, "contribution": 0.05},
        "patient": {"score": 0.4, "weight": 0.15, "contribution": 0.06},
        "provider": {"score": 0.3, "weight": 0.15, "contribution": 0.045},
        "payer": {"score": 0.2, "weight": 0.10, "contribution": 0.02},
        "temporal": {"score": 0.1, "weight": 0.10, "contribution": 0.01},
    }))
    
    mock_context = Mock()
    mock_context.get_patient_context = Mock(return_value={"patient_denial_rate": 0.2})
    mock_context.get_provider_context = Mock(return_value={"provider_denial_rate": 0.15})
    mock_context.get_payer_context = Mock(return_value={"payer_denial_rate": 0.25})
    mock_context.get_temporal_context = Mock(return_value={"claim_age_days": 30})
    mock_context.get_financial_context = Mock(return_value={})
    mock_context.get_relationship_context = Mock(return_value={})
    mock_context.get_policy_context = Mock(return_value={})
    
    # Set up mocks
    if hasattr(model, '_get_fusion_engine'):
        model._fusion_engine = mock_fusion
    if hasattr(model, '_get_context_collector'):
        model._context_collector = mock_context
    
    # Warm-up
    model.analyze(claim, "test-tenant")
    
    # Benchmark
    times = []
    for _ in range(iterations):
        start = time.perf_counter()
        result = model.analyze(claim, "test-tenant")
        elapsed = (time.perf_counter() - start) * 1000  # Convert to milliseconds
        times.append(elapsed)
        assert "verdict" in result  # Verify it worked
    
    return {
        "model_id": model_id,
        "iterations": iterations,
        "mean_ms": sum(times) / len(times),
        "min_ms": min(times),
        "max_ms": max(times),
        "median_ms": sorted(times)[len(times) // 2],
    }


class TestPerformanceBenchmarks:
    """Performance benchmarks for model variants"""

    def test_lite1_faster_than_k1(self, sample_claim):
        """Test lite1 is faster than k1 (benchmark)"""
        lite1_stats = benchmark_model("cxr-k-lite1", sample_claim, iterations=5)
        k1_stats = benchmark_model("cxr-k1", sample_claim, iterations=5)
        
        # Lite1 should be faster (fewer planes, less context)
        # Note: With mocks, difference may be minimal, but structure should be faster
        print(f"\nLite1: {lite1_stats['mean_ms']:.2f}ms")
        print(f"K1: {k1_stats['mean_ms']:.2f}ms")
        
        # This is informational - actual performance depends on real DB/Qdrant
        assert lite1_stats['mean_ms'] >= 0  # Just verify it runs
        assert k1_stats['mean_ms'] >= 0

    def test_k2_slower_than_k1(self, sample_claim):
        """Test k2 is slower but more accurate (benchmark)"""
        k1_stats = benchmark_model("cxr-k1", sample_claim, iterations=5)
        k2_stats = benchmark_model("cxr-k2", sample_claim, iterations=5)
        
        print(f"\nK1: {k1_stats['mean_ms']:.2f}ms")
        print(f"K2: {k2_stats['mean_ms']:.2f}ms")
        
        # Both should complete
        assert k1_stats['mean_ms'] >= 0
        assert k2_stats['mean_ms'] >= 0

    def test_sim1_overhead(self, sample_claim):
        """Test sim1 has acceptable Monte Carlo overhead"""
        k1_stats = benchmark_model("cxr-k1", sample_claim, iterations=5)
        sim1_stats = benchmark_model("cxr-k-sim1", sample_claim, iterations=5)
        
        print(f"\nK1: {k1_stats['mean_ms']:.2f}ms")
        print(f"Sim1: {sim1_stats['mean_ms']:.2f}ms")
        print(f"Overhead: {sim1_stats['mean_ms'] - k1_stats['mean_ms']:.2f}ms")
        
        # Sim1 should complete (may be slower due to Monte Carlo)
        assert sim1_stats['mean_ms'] >= 0

    def test_7plane_vs_3plane(self, sample_claim):
        """Test 7-plane fusion overhead is acceptable"""
        lite1_stats = benchmark_model("cxr-k-lite1", sample_claim, iterations=5)
        k1_stats = benchmark_model("cxr-k1", sample_claim, iterations=5)
        
        print(f"\n3-plane (Lite1): {lite1_stats['mean_ms']:.2f}ms")
        print(f"7-plane (K1): {k1_stats['mean_ms']:.2f}ms")
        
        # Both should complete
        assert lite1_stats['mean_ms'] >= 0
        assert k1_stats['mean_ms'] >= 0

    def test_model_initialization_time(self):
        """Test model initialization is fast"""
        registry = get_registry()
        
        start = time.perf_counter()
        model = registry.get_model("cxr-k1")
        init_time = (time.perf_counter() - start) * 1000
        
        print(f"\nModel initialization: {init_time:.2f}ms")
        
        # Should be very fast (< 100ms)
        assert init_time < 100

    def test_registry_lookup_time(self):
        """Test registry lookup is O(1) fast"""
        registry = get_registry()
        
        times = []
        for _ in range(100):
            start = time.perf_counter()
            model = registry.get_model("cxr-k1")
            elapsed = (time.perf_counter() - start) * 1000
            times.append(elapsed)
        
        mean_lookup = sum(times) / len(times)
        print(f"\nRegistry lookup (mean over 100): {mean_lookup:.4f}ms")
        
        # Should be very fast (< 1ms per lookup)
        assert mean_lookup < 1.0

    def test_all_models_performance(self, sample_claim):
        """Benchmark all models and report"""
        models = ["cxr-k1", "cxr-k2", "cxr-k-sim1", "cxr-k-lite1", "cxr-k-audit1"]
        results = []
        
        for model_id in models:
            stats = benchmark_model(model_id, sample_claim, iterations=3)
            results.append(stats)
            print(f"\n{model_id}: {stats['mean_ms']:.2f}ms (min: {stats['min_ms']:.2f}ms, max: {stats['max_ms']:.2f}ms)")
        
        # All should complete
        for stats in results:
            assert stats['mean_ms'] >= 0
            assert stats['min_ms'] >= 0
            assert stats['max_ms'] >= 0

