"""
Performance tests for GRASS RAG pipeline
Validates accuracy, speed, and size requirements
"""

import pytest
import time
import statistics
from typing import List, Dict, Any

from grass_rag import GrassRAG
from grass_rag.core.models import RAGResponse


class TestPerformanceRequirements:
    """Test suite for validating performance requirements"""
    
    @pytest.fixture(scope="class")
    def rag_pipeline(self):
        """Shared RAG pipeline for performance tests"""
        config = {
            "cache_size": 1000,
            "max_response_time": 5.0,
            "template_threshold": 0.8
        }
        return GrassRAG(config)
    
    def test_accuracy_requirement(self, rag_pipeline):
        """Test that accuracy requirement (>90%) is met"""
        
        # Comprehensive test queries covering all major GRASS GIS operations
        test_queries = [
            # Terrain analysis
            "calculate slope from DEM",
            "generate aspect from elevation",
            "terrain analysis with slope and aspect",
            
            # Data management
            "import raster file into GRASS",
            "export vector data to shapefile", 
            "load GeoTIFF into GRASS GIS",
            
            # Vector operations
            "create buffer zones around points",
            "perform vector overlay analysis",
            "intersect two vector layers",
            
            # Raster operations
            "raster calculator map algebra",
            "classify raster into categories",
            "resample raster to different resolution",
            
            # Hydrological analysis
            "watershed analysis and flow direction",
            "calculate flow accumulation",
            "delineate drainage basins",
            
            # Interpolation
            "interpolate surface from point data",
            "IDW interpolation method",
            "spline surface interpolation",
            
            # Visualization
            "create contour lines from elevation",
            "generate topographic contours",
            
            # Advanced operations
            "viewshed analysis from point",
            "cost surface analysis",
            "network analysis shortest path"
        ]
        
        quality_scores = []
        response_details = []
        
        for query in test_queries:
            response = rag_pipeline.ask(query)
            quality_scores.append(response.confidence)
            
            response_details.append({
                "query": query,
                "confidence": response.confidence,
                "source_type": response.source_type,
                "response_time": response.response_time_ms
            })
        
        # Calculate accuracy metrics
        avg_quality = statistics.mean(quality_scores)
        min_quality = min(quality_scores)
        max_quality = max(quality_scores)
        
        # Count high-quality responses (≥0.9)
        high_quality_count = sum(1 for score in quality_scores if score >= 0.9)
        high_quality_percentage = (high_quality_count / len(quality_scores)) * 100
        
        # Print detailed results for analysis
        print(f"\n📊 ACCURACY TEST RESULTS:")
        print(f"   Total queries: {len(test_queries)}")
        print(f"   Average quality: {avg_quality:.3f}")
        print(f"   Quality range: {min_quality:.3f} - {max_quality:.3f}")
        print(f"   High quality (≥0.9): {high_quality_count}/{len(test_queries)} ({high_quality_percentage:.1f}%)")
        
        # Show queries that didn't meet quality threshold
        low_quality_queries = [
            detail for detail in response_details 
            if detail["confidence"] < 0.9
        ]
        
        if low_quality_queries:
            print(f"\n⚠️  Queries below 0.9 quality:")
            for detail in low_quality_queries:
                print(f"   - {detail['query'][:50]}... (quality: {detail['confidence']:.3f})")
        
        # Validate requirements
        assert avg_quality >= 0.9, f"Average quality {avg_quality:.3f} below 0.9 requirement"
        assert high_quality_percentage >= 90, f"Only {high_quality_percentage:.1f}% of queries meet quality requirement"
    
    def test_speed_requirement(self, rag_pipeline):
        """Test that speed requirement (<5 seconds) is met"""
        
        # Test queries with different complexity levels
        speed_test_queries = [
            # Simple template matches (should be very fast)
            "calculate slope",
            "import raster",
            "export data",
            "create buffer",
            "vector overlay",
            
            # Medium complexity
            "watershed analysis with flow accumulation",
            "interpolate surface using IDW method",
            "classify raster data into categories",
            "create contour lines from elevation",
            "perform spatial overlay operations",
            
            # Complex queries (fallback scenarios)
            "advanced geostatistical analysis with kriging",
            "complex multi-criteria decision analysis",
            "sophisticated terrain modeling workflow",
            "comprehensive hydrological modeling setup",
            "integrated remote sensing classification pipeline"
        ]
        
        response_times = []
        response_details = []
        
        for query in speed_test_queries:
            start_time = time.time()
            response = rag_pipeline.ask(query)
            end_time = time.time()
            
            query_time = end_time - start_time
            response_times.append(query_time)
            
            response_details.append({
                "query": query,
                "response_time": query_time,
                "reported_time": response.response_time_ms / 1000,
                "source_type": response.source_type
            })
        
        # Calculate speed metrics
        avg_response_time = statistics.mean(response_times)
        min_response_time = min(response_times)
        max_response_time = max(response_times)
        
        # Count fast responses (<5s)
        fast_responses = sum(1 for time_val in response_times if time_val < 5.0)
        fast_percentage = (fast_responses / len(response_times)) * 100
        
        # Count very fast responses (<1s)
        very_fast_responses = sum(1 for time_val in response_times if time_val < 1.0)
        very_fast_percentage = (very_fast_responses / len(response_times)) * 100
        
        # Print detailed results
        print(f"\n⚡ SPEED TEST RESULTS:")
        print(f"   Total queries: {len(speed_test_queries)}")
        print(f"   Average response time: {avg_response_time:.3f}s")
        print(f"   Response time range: {min_response_time:.3f}s - {max_response_time:.3f}s")
        print(f"   Fast responses (<5s): {fast_responses}/{len(response_times)} ({fast_percentage:.1f}%)")
        print(f"   Very fast responses (<1s): {very_fast_responses}/{len(response_times)} ({very_fast_percentage:.1f}%)")
        
        # Show slow queries
        slow_queries = [
            detail for detail in response_details 
            if detail["response_time"] >= 5.0
        ]
        
        if slow_queries:
            print(f"\n🐌 Slow queries (≥5s):")
            for detail in slow_queries:
                print(f"   - {detail['query'][:50]}... ({detail['response_time']:.3f}s, {detail['source_type']})")
        
        # Validate requirements
        assert avg_response_time < 5.0, f"Average response time {avg_response_time:.3f}s above 5s requirement"
        assert fast_percentage >= 95, f"Only {fast_percentage:.1f}% of queries under 5s requirement"
    
    def test_template_performance(self, rag_pipeline):
        """Test template system performance specifically"""
        
        # Queries that should definitely hit templates
        template_queries = [
            "slope calculation",
            "import raster data", 
            "export vector",
            "buffer analysis",
            "contour generation",
            "watershed analysis",
            "vector overlay",
            "raster calculator",
            "data classification",
            "surface interpolation"
        ]
        
        template_hits = 0
        template_times = []
        
        for query in template_queries:
            response = rag_pipeline.ask(query)
            
            if response.source_type == "template":
                template_hits += 1
                template_times.append(response.response_time_ms / 1000)
        
        template_hit_rate = (template_hits / len(template_queries)) * 100
        avg_template_time = statistics.mean(template_times) if template_times else 0
        
        print(f"\n📋 TEMPLATE PERFORMANCE:")
        print(f"   Template hit rate: {template_hit_rate:.1f}%")
        print(f"   Average template response time: {avg_template_time:.3f}s")
        
        # Template system should have high hit rate and be very fast
        assert template_hit_rate >= 80, f"Template hit rate {template_hit_rate:.1f}% too low"
        if template_times:
            assert avg_template_time < 0.1, f"Template responses too slow: {avg_template_time:.3f}s"
    
    def test_cache_performance(self, rag_pipeline):
        """Test cache system performance"""
        
        # Test cache effectiveness with repeated queries
        test_query = "calculate slope from DEM"
        
        # First query (cache miss)
        start_time = time.time()
        response1 = rag_pipeline.ask(test_query)
        first_time = time.time() - start_time
        
        # Second query (should hit cache)
        start_time = time.time()
        response2 = rag_pipeline.ask(test_query)
        second_time = time.time() - start_time
        
        # Third query (should also hit cache)
        start_time = time.time()
        response3 = rag_pipeline.ask(test_query)
        third_time = time.time() - start_time
        
        print(f"\n💾 CACHE PERFORMANCE:")
        print(f"   First query: {first_time:.3f}s")
        print(f"   Second query: {second_time:.3f}s")
        print(f"   Third query: {third_time:.3f}s")
        
        # Responses should be identical
        assert response1.answer == response2.answer == response3.answer
        
        # Cache hits should be faster (or at least not slower)
        assert second_time <= first_time * 1.1, "Cache hit not faster than miss"
        assert third_time <= first_time * 1.1, "Cache hit not faster than miss"
    
    def test_concurrent_performance(self, rag_pipeline):
        """Test performance under concurrent load"""
        import threading
        import queue
        
        # Test concurrent queries
        test_queries = [
            "calculate slope",
            "import raster", 
            "export vector",
            "create buffer",
            "vector overlay"
        ] * 4  # 20 total queries
        
        results_queue = queue.Queue()
        
        def worker(query):
            start_time = time.time()
            response = rag_pipeline.ask(query)
            end_time = time.time()
            
            results_queue.put({
                "query": query,
                "response_time": end_time - start_time,
                "confidence": response.confidence
            })
        
        # Start all threads
        threads = []
        overall_start = time.time()
        
        for query in test_queries:
            thread = threading.Thread(target=worker, args=(query,))
            threads.append(thread)
            thread.start()
        
        # Wait for completion
        for thread in threads:
            thread.join(timeout=30)  # 30 second timeout
        
        overall_time = time.time() - overall_start
        
        # Collect results
        results = []
        while not results_queue.empty():
            results.append(results_queue.get())
        
        if results:
            avg_concurrent_time = statistics.mean([r["response_time"] for r in results])
            avg_concurrent_quality = statistics.mean([r["confidence"] for r in results])
            
            print(f"\n🔄 CONCURRENT PERFORMANCE:")
            print(f"   Total queries: {len(test_queries)}")
            print(f"   Completed queries: {len(results)}")
            print(f"   Overall time: {overall_time:.3f}s")
            print(f"   Average query time: {avg_concurrent_time:.3f}s")
            print(f"   Average quality: {avg_concurrent_quality:.3f}")
            
            # All queries should complete successfully
            assert len(results) == len(test_queries), f"Only {len(results)}/{len(test_queries)} queries completed"
            
            # Performance should remain good under load
            assert avg_concurrent_time < 5.0, f"Concurrent performance degraded: {avg_concurrent_time:.3f}s"
            assert avg_concurrent_quality >= 0.9, f"Concurrent quality degraded: {avg_concurrent_quality:.3f}"
    
    def test_memory_efficiency(self, rag_pipeline):
        """Test memory usage efficiency"""
        import psutil
        import os
        
        process = psutil.Process(os.getpid())
        initial_memory = process.memory_info().rss / 1024 / 1024  # MB
        
        # Process many queries to test memory growth
        for i in range(100):
            query = f"test query {i % 10}"  # Cycle through 10 different queries
            response = rag_pipeline.ask(query)
            assert response is not None
        
        final_memory = process.memory_info().rss / 1024 / 1024  # MB
        memory_growth = final_memory - initial_memory
        
        print(f"\n💾 MEMORY EFFICIENCY:")
        print(f"   Initial memory: {initial_memory:.1f}MB")
        print(f"   Final memory: {final_memory:.1f}MB")
        print(f"   Memory growth: {memory_growth:.1f}MB")
        
        # Memory growth should be reasonable
        assert memory_growth < 50, f"Memory growth too high: {memory_growth:.1f}MB"
    
    def test_size_requirement_estimation(self, rag_pipeline):
        """Test package size requirement estimation"""
        
        # Get component information
        template_stats = rag_pipeline._pipeline.get_template_stats()
        cache_stats = rag_pipeline._pipeline.get_cache_stats()
        
        # Estimate sizes (rough calculation)
        template_count = template_stats["total_templates"]
        cache_capacity = cache_stats["l2_max_size"]
        
        # Rough size estimates
        estimated_template_size = template_count * 2000  # ~2KB per template
        estimated_cache_overhead = cache_capacity * 500   # ~500B per cache slot
        estimated_code_size = 25 * 1024 * 1024          # ~25MB for Python code
        
        total_package_size = estimated_template_size + estimated_cache_overhead + estimated_code_size
        
        print(f"\n📦 PACKAGE SIZE ESTIMATION:")
        print(f"   Templates: {template_count} (~{estimated_template_size/1024:.0f}KB)")
        print(f"   Cache overhead: ~{estimated_cache_overhead/1024:.0f}KB")
        print(f"   Code size: ~{estimated_code_size/1024/1024:.0f}MB")
        print(f"   Total estimated: ~{total_package_size/1024/1024:.1f}MB")
        print(f"   (Excluding AI models: ~700MB)")
        
        # Package should be well under 1GB
        # Note: This excludes AI models which are downloaded separately
        assert total_package_size < 100 * 1024 * 1024, f"Package too large: {total_package_size/1024/1024:.1f}MB"


if __name__ == "__main__":
    pytest.main([__file__, "-v", "-s"])