"""
Unit tests for performance optimization services.

Tests core functionality of resource monitoring, M3 optimization,
and performance benchmarking.
"""

import asyncio
import pytest
from unittest.mock import MagicMock, patch
from typing import Dict, List

from src.services.performance import (
    ResourceMonitor,
    SystemResources,
    PerformanceMetrics,
    M3OptimizationConfig
)
from src.services.performance_optimizer import PerformanceOptimizer
from src.services.ffmpeg_optimizer import FFmpegOptimizer
from src.services.performance_benchmarker import PerformanceBenchmarker


class TestResourceMonitor:
    """Test resource monitoring functionality."""
    
    @pytest.fixture
    def resource_monitor(self):
        """Create a resource monitor instance."""
        return ResourceMonitor(
            memory_threshold=80.0,
            cpu_threshold=90.0,
            disk_threshold=85.0
        )
    
    def test_resource_monitor_initialization(self, resource_monitor):
        """Test resource monitor initialization."""
        assert resource_monitor.memory_threshold == 80.0
        assert resource_monitor.cpu_threshold == 90.0
        assert resource_monitor.disk_threshold == 85.0
        assert not resource_monitor.is_monitoring
    
    @patch('psutil.virtual_memory')
    @patch('psutil.cpu_percent')
    @patch('psutil.disk_usage')
    def test_get_system_resources(self, mock_disk, mock_cpu, mock_vm, resource_monitor):
        """Test getting current system resource usage."""
        # Mock system resources
        mock_vm.return_value.percent = 75.0
        mock_vm.return_value.available = 1024 * 1024 * 1024  # 1GB
        mock_cpu.return_value = 65.0
        mock_disk.return_value.percent = 60.0
        
        resources = resource_monitor.get_system_resources()
        
        assert resources.memory_percent == 75.0
        assert resources.cpu_percent == 65.0
        assert resources.disk_percent == 60.0
        assert resources.memory_available_mb > 0
    
    def test_resource_threshold_checking(self, resource_monitor):
        """Test resource threshold checking logic."""
        # Test normal usage
        assert not resource_monitor.is_memory_critical(75.0)
        assert not resource_monitor.is_cpu_critical(65.0)
        assert not resource_monitor.is_disk_critical(60.0)
        
        # Test critical usage
        assert resource_monitor.is_memory_critical(85.0)
        assert resource_monitor.is_cpu_critical(95.0)
        assert resource_monitor.is_disk_critical(90.0)
    
    @pytest.mark.asyncio
    async def test_start_stop_monitoring(self, resource_monitor):
        """Test starting and stopping resource monitoring."""
        # Start monitoring
        await resource_monitor.start_monitoring()
        assert resource_monitor.is_monitoring
        assert resource_monitor.monitor_task is not None
        
        # Stop monitoring
        await resource_monitor.stop_monitoring()
        assert not resource_monitor.is_monitoring
        assert resource_monitor.monitor_task is None


class TestPerformanceOptimizer:
    """Test performance optimization functionality."""
    
    @pytest.fixture
    def optimizer(self):
        """Create a performance optimizer instance."""
        return PerformanceOptimizer(max_workers=8)
    
    def test_optimizer_initialization(self, optimizer):
        """Test optimizer initialization."""
        assert optimizer.max_workers == 8
        assert optimizer.resource_monitor is not None
        assert optimizer.m3_config is not None
    
    def test_get_m3_optimization_config(self, optimizer):
        """Test M3 optimization configuration."""
        config = optimizer.get_m3_optimization_config()
        
        assert config.use_metal_acceleration
        assert config.optimize_memory_layout
        assert config.parallel_processing_enabled
        assert config.chunk_size_mb > 0
    
    @pytest.mark.asyncio
    async def test_optimize_processing_pipeline(self, optimizer):
        """Test processing pipeline optimization."""
        # Mock a simple processing function
        async def process_item(item):
            await asyncio.sleep(0.01)  # Simulate work
            return f"processed_{item}"
        
        items = list(range(5))
        
        # Test optimization
        result = await optimizer.optimize_processing_pipeline(
            items, process_item, batch_size=2
        )
        
        assert len(result) == 5
        assert all(r.startswith("processed_") for r in result)


class TestFFmpegOptimizer:
    """Test FFmpeg optimization functionality."""
    
    @pytest.fixture
    def ffmpeg_optimizer(self):
        """Create an FFmpeg optimizer instance."""
        return FFmpegOptimizer()
    
    def test_ffmpeg_optimizer_initialization(self, ffmpeg_optimizer):
        """Test FFmpeg optimizer initialization."""
        assert ffmpeg_optimizer.m3_optimized
        assert ffmpeg_optimizer.hardware_acceleration_enabled
        assert ffmpeg_optimizer.audio_quality_preserved
    
    def test_get_m3_optimized_params(self, ffmpeg_optimizer):
        """Test M3-optimized FFmpeg parameters."""
        # Test video input
        params = ffmpeg_optimizer.get_optimized_params(
            input_format="mp4",
            output_format="wav",
            target_sample_rate=16000
        )
        
        assert "-hwaccel" in params
        assert "videotoolbox" in params
        assert "-ar" in params
        assert "16000" in params
        assert "-ac" in params
        assert "1" in params
    
    def test_get_transcription_optimized_params(self, ffmpeg_optimizer):
        """Test transcription-optimized FFmpeg parameters."""
        params = ffmpeg_optimizer.get_transcription_optimized_params(
            input_format="mp4"
        )
        
        assert "-ar" in params
        assert "16000" in params
        assert "-ac" in params
        assert "1" in params
        assert "-f" in params
        assert "wav" in params


class TestPerformanceBenchmarker:
    """Test performance benchmarking functionality."""
    
    @pytest.fixture
    def benchmarker(self):
        """Create a performance benchmarker instance."""
        return PerformanceBenchmarker()
    
    @pytest.fixture
    def sample_metrics(self):
        """Create sample performance metrics."""
        return PerformanceMetrics(
            operation="transcription",
            duration_seconds=2.5,
            memory_peak_mb=512.0,
            cpu_peak_percent=75.0,
            throughput_items_per_second=4.0,
            error_count=0,
            success_count=10,
            total_count=10
        )
    
    def test_benchmarker_initialization(self, benchmarker):
        """Test benchmarker initialization."""
        assert benchmarker.benchmarks == {}
        assert benchmarker.report_format == "json"
    
    def test_record_operation(self, benchmarker, sample_metrics):
        """Test recording operation metrics."""
        benchmarker.record_operation("test_op", sample_metrics)
        
        assert "test_op" in benchmarker.benchmarks
        assert len(benchmarker.benchmarks["test_op"]) == 1
        assert benchmarker.benchmarks["test_op"][0] == sample_metrics
    
    def test_calculate_statistics(self, benchmarker):
        """Test calculating performance statistics."""
        # Add multiple metrics
        metrics1 = PerformanceMetrics(
            operation="test",
            duration_seconds=1.0,
            memory_peak_mb=100.0,
            cpu_peak_percent=50.0,
            throughput_items_per_second=10.0,
            error_count=0,
            success_count=5,
            total_count=5
        )
        
        metrics2 = PerformanceMetrics(
            operation="test",
            duration_seconds=2.0,
            memory_peak_mb=200.0,
            cpu_peak_percent=75.0,
            throughput_items_per_second=5.0,
            error_count=1,
            success_count=4,
            total_count=5
        )
        
        benchmarker.record_operation("test", metrics1)
        benchmarker.record_operation("test", metrics2)
        
        stats = benchmarker.calculate_statistics("test")
        
        assert stats.avg_duration == 1.5
        assert stats.avg_memory_peak == 150.0
        assert stats.avg_cpu_peak == 62.5
        assert stats.avg_throughput == 7.5
        assert stats.total_operations == 2
        assert stats.success_rate == 90.0
    
    def test_generate_report(self, benchmarker, sample_metrics):
        """Test report generation."""
        benchmarker.record_operation("test_op", sample_metrics)
        
        report = benchmarker.generate_report()
        
        assert "operations" in report
        assert "test_op" in report["operations"]
        assert "statistics" in report["operations"]["test_op"]
        assert "latest_metrics" in report["operations"]["test_op"]


class TestPerformanceIntegration:
    """Integration tests for performance optimization."""
    
    @pytest.fixture
    def performance_system(self):
        """Create a complete performance optimization system."""
        from src.services.performance import ResourceMonitor
        from src.services.performance_optimizer import PerformanceOptimizer
        from src.services.ffmpeg_optimizer import FFmpegOptimizer
        from src.services.performance_benchmarker import PerformanceBenchmarker
        
        return {
            "resource_monitor": ResourceMonitor(),
            "optimizer": PerformanceOptimizer(),
            "ffmpeg_optimizer": FFmpegOptimizer(),
            "benchmarker": PerformanceBenchmarker()
        }
    
    @pytest.mark.asyncio
    async def test_end_to_end_optimization(self, performance_system):
        """Test end-to-end performance optimization workflow."""
        system = performance_system
        
        # Start resource monitoring
        await system["resource_monitor"].start_monitoring()
        
        # Simulate processing with optimization
        async def process_item(item):
            await asyncio.sleep(0.01)
            return f"processed_{item}"
        
        items = list(range(3))
        
        # Process with optimization
        result = await system["optimizer"].optimize_processing_pipeline(
            items, process_item, batch_size=2
        )
        
        # Create performance metrics
        metrics = PerformanceMetrics(
            operation="test_optimization",
            duration_seconds=0.1,
            memory_peak_mb=50.0,
            cpu_peak_percent=30.0,
            throughput_items_per_second=30.0,
            error_count=0,
            success_count=len(result),
            total_count=len(items)
        )
        
        # Record metrics
        system["benchmarker"].record_operation("test_optimization", metrics)
        
        # Stop monitoring
        await system["resource_monitor"].stop_monitoring()
        
        # Verify results
        assert len(result) == 3
        assert all(r.startswith("processed_") for r in result)
        assert "test_optimization" in system["benchmarker"].benchmarks
    
    def test_m3_optimization_configuration(self, performance_system):
        """Test M3 optimization configuration integration."""
        system = performance_system
        
        # Get M3 config from optimizer
        m3_config = system["optimizer"].get_m3_optimization_config()
        
        # Get FFmpeg params for M3
        ffmpeg_params = system["ffmpeg_optimizer"].get_optimized_params(
            input_format="mp4",
            output_format="wav",
            target_sample_rate=16000
        )
        
        # Verify M3 optimizations are enabled
        assert m3_config.use_metal_acceleration
        assert m3_config.parallel_processing_enabled
        
        # Verify FFmpeg uses M3 optimizations
        assert "-hwaccel" in ffmpeg_params
        assert "videotoolbox" in ffmpeg_params


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

