"""
Simple Performance Testing for PrecipGen Workflows.

This is a simplified version to test basic performance functionality.
"""

import pytest
import tempfile
import shutil
import time
import psutil
import threading
from pathlib import Path
from typing import List, Dict, Optional, Any
import pandas as pd
import numpy as np
from datetime import datetime
from dataclasses import dataclass, field
from hypothesis import given, strategies as st, assume, settings, HealthCheck

from precipgen.utils.directory_manager import DirectoryManager


@dataclass
class PerformanceMetrics:
    """Performance metrics for a single test execution."""
    test_name: str
    execution_time: float
    peak_memory_mb: float
    avg_cpu_percent: float
    data_size: int
    success: bool
    error_message: Optional[str] = None
    additional_metrics: Dict[str, Any] = field(default_factory=dict)


@dataclass
class ResourceMonitoringResult:
    """Result from resource monitoring during test execution."""
    memory_samples: List[float]
    cpu_samples: List[float]
    peak_memory_mb: float
    avg_memory_mb: float
    avg_cpu_percent: float
    max_cpu_percent: float
    monitoring_duration: float


class ResourceMonitor:
    """Monitor system resource usage during test execution."""
    
    def __init__(self, sampling_interval: float = 0.1):
        """Initialize resource monitor with sampling interval in seconds."""
        self.sampling_interval = sampling_interval
        self.memory_samples: List[float] = []
        self.cpu_samples: List[float] = []
        self.monitoring = False
        self.monitor_thread: Optional[threading.Thread] = None
        self.start_time: Optional[float] = None
    
    def start_monitoring(self) -> None:
        """Start resource monitoring in background thread."""
        if self.monitoring:
            return
        
        self.monitoring = True
        self.memory_samples.clear()
        self.cpu_samples.clear()
        self.start_time = time.time()
        
        self.monitor_thread = threading.Thread(target=self._monitor_resources)
        self.monitor_thread.daemon = True
        self.monitor_thread.start()
    
    def stop_monitoring(self) -> ResourceMonitoringResult:
        """Stop monitoring and return results."""
        if not self.monitoring:
            raise ValueError("Monitoring not started")
        
        self.monitoring = False
        if self.monitor_thread:
            self.monitor_thread.join(timeout=1.0)
        
        duration = time.time() - self.start_time if self.start_time else 0.0
        
        return ResourceMonitoringResult(
            memory_samples=self.memory_samples.copy(),
            cpu_samples=self.cpu_samples.copy(),
            peak_memory_mb=max(self.memory_samples) if self.memory_samples else 0.0,
            avg_memory_mb=sum(self.memory_samples) / len(self.memory_samples) if self.memory_samples else 0.0,
            avg_cpu_percent=sum(self.cpu_samples) / len(self.cpu_samples) if self.cpu_samples else 0.0,
            max_cpu_percent=max(self.cpu_samples) if self.cpu_samples else 0.0,
            monitoring_duration=duration
        )
    
    def _monitor_resources(self) -> None:
        """Background thread function to monitor resources."""
        process = psutil.Process()
        
        while self.monitoring:
            try:
                # Memory usage in MB
                memory_mb = process.memory_info().rss / (1024 * 1024)
                self.memory_samples.append(memory_mb)
                
                # CPU usage percentage
                cpu_percent = process.cpu_percent()
                self.cpu_samples.append(cpu_percent)
                
                time.sleep(self.sampling_interval)
            except (psutil.NoSuchProcess, psutil.AccessDenied):
                break


class SimplePerformanceTester:
    """Simple performance tester for basic functionality."""
    
    def __init__(self, base_output_dir: Optional[Path] = None):
        """Initialize performance tester with output directory."""
        self.base_output_dir = base_output_dir or Path("./precipgen_output")
        self.directory_manager = DirectoryManager(self.base_output_dir)
    
    def test_workflow_execution_times(self, data_sizes: List[int]) -> List[PerformanceMetrics]:
        """Test workflow execution times with different data sizes."""
        results = []
        
        for data_size in data_sizes:
            try:
                metrics = self._test_simple_workflow_performance(data_size)
                results.append(metrics)
            except Exception as e:
                error_metrics = PerformanceMetrics(
                    test_name=f"simple_workflow_{data_size}days",
                    execution_time=0.0,
                    peak_memory_mb=0.0,
                    avg_cpu_percent=0.0,
                    data_size=data_size,
                    success=False,
                    error_message=str(e)
                )
                results.append(error_metrics)
        
        return results
    
    def _test_simple_workflow_performance(self, data_size: int) -> PerformanceMetrics:
        """Test performance of a simple workflow."""
        test_name = f"simple_workflow_{data_size}days"
        station_id = f"SIMPLE{data_size:06d}"
        
        monitor = ResourceMonitor()
        
        try:
            monitor.start_monitoring()
            start_time = time.time()
            
            # Simulate simple workflow
            self._simulate_simple_workflow(station_id, data_size)
            
            execution_time = time.time() - start_time
            monitoring_result = monitor.stop_monitoring()
            
            return PerformanceMetrics(
                test_name=test_name,
                execution_time=execution_time,
                peak_memory_mb=monitoring_result.peak_memory_mb,
                avg_cpu_percent=monitoring_result.avg_cpu_percent,
                data_size=data_size,
                success=True
            )
            
        except Exception as e:
            if monitor.monitoring:
                monitor.stop_monitoring()
            
            return PerformanceMetrics(
                test_name=test_name,
                execution_time=0.0,
                peak_memory_mb=0.0,
                avg_cpu_percent=0.0,
                data_size=data_size,
                success=False,
                error_message=str(e)
            )
    
    def _simulate_simple_workflow(self, station_id: str, data_size: int) -> None:
        """Simulate a simple workflow for performance testing."""
        # Create directory structure
        station_dir = self.directory_manager.create_station_directory(station_id)
        
        # Generate test data
        test_data = self._generate_test_data(data_size)
        
        # Save test data to simulate data acquisition
        data_dir = station_dir / "data"
        data_file = data_dir / "precipitation.csv"
        test_data.to_csv(data_file)
        
        # Simulate some processing
        time.sleep(0.01)  # Small delay to simulate work
        
        # Create analysis output
        analysis_dir = station_dir / "analysis"
        analysis_file = analysis_dir / "params.json"
        with open(analysis_file, 'w') as f:
            import json
            json.dump({'station_id': station_id, 'data_size': data_size}, f)
    
    def _generate_test_data(self, n_days: int) -> pd.Series:
        """Generate simple test precipitation data."""
        start_date = datetime(2000, 1, 1)
        dates = pd.date_range(start_date, periods=n_days, freq='D')
        
        # Simple random data
        np.random.seed(42)
        precip_data = np.random.exponential(2.0, n_days) * np.random.binomial(1, 0.2, n_days)
        
        return pd.Series(precip_data, index=dates, name='precipitation')


# Unit tests
class TestSimplePerformance:
    """Unit tests for simple performance functionality."""
    
    def setup_method(self):
        """Set up test environment."""
        self.temp_dir = Path(tempfile.mkdtemp())
        self.performance_tester = SimplePerformanceTester(self.temp_dir)
    
    def teardown_method(self):
        """Clean up test environment."""
        if self.temp_dir.exists():
            shutil.rmtree(self.temp_dir)
    
    def test_resource_monitor_basic_functionality(self):
        """Test basic resource monitoring functionality."""
        monitor = ResourceMonitor(sampling_interval=0.05)
        
        # Start monitoring
        monitor.start_monitoring()
        assert monitor.monitoring is True
        
        # Let it run for a short time
        time.sleep(0.2)
        
        # Stop monitoring
        result = monitor.stop_monitoring()
        assert monitor.monitoring is False
        
        # Verify results
        assert isinstance(result, ResourceMonitoringResult)
        assert len(result.memory_samples) > 0
        assert len(result.cpu_samples) > 0
        assert result.peak_memory_mb > 0
        assert result.avg_memory_mb > 0
        assert result.monitoring_duration > 0
    
    def test_performance_metrics_creation(self):
        """Test creation and validation of performance metrics."""
        metrics = PerformanceMetrics(
            test_name="test_workflow",
            execution_time=1.5,
            peak_memory_mb=128.0,
            avg_cpu_percent=45.0,
            data_size=1000,
            success=True
        )
        
        assert metrics.test_name == "test_workflow"
        assert metrics.execution_time == 1.5
        assert metrics.peak_memory_mb == 128.0
        assert metrics.avg_cpu_percent == 45.0
        assert metrics.data_size == 1000
        assert metrics.success is True
        assert metrics.error_message is None
    
    def test_simple_workflow_execution(self):
        """Test simple workflow execution with performance monitoring."""
        data_sizes = [100, 200]
        results = self.performance_tester.test_workflow_execution_times(data_sizes)
        
        assert len(results) == 2
        
        for result in results:
            assert result.test_name is not None
            assert result.execution_time >= 0
            assert result.peak_memory_mb >= 0
            assert result.avg_cpu_percent >= 0
            assert result.data_size > 0
            assert result.success is True
    
    def test_directory_structure_creation(self):
        """Test that performance testing creates proper directory structure."""
        station_id = "TEST001"
        self.performance_tester._simulate_simple_workflow(station_id, 100)
        
        # Verify directory structure
        station_dir = self.temp_dir / station_id
        assert station_dir.exists()
        
        data_dir = station_dir / "data"
        assert data_dir.exists()
        assert (data_dir / "precipitation.csv").exists()
        
        analysis_dir = station_dir / "analysis"
        assert analysis_dir.exists()
        assert (analysis_dir / "params.json").exists()


# Property-based tests
class TestPerformanceProperties:
    """Property-based tests for performance and scalability."""
    
    def setup_method(self):
        """Set up test environment."""
        self.temp_dir = Path(tempfile.mkdtemp())
        self.performance_tester = SimplePerformanceTester(self.temp_dir)
    
    def teardown_method(self):
        """Clean up test environment."""
        if self.temp_dir.exists():
            shutil.rmtree(self.temp_dir)
    
    @given(
        data_sizes=st.lists(
            st.integers(min_value=50, max_value=500), 
            min_size=1, 
            max_size=3
        )
    )
    @settings(max_examples=5, deadline=30000, suppress_health_check=[HealthCheck.too_slow])
    def test_property_performance_bounds_compliance(self, data_sizes):
        """
        Property 14: Performance Bounds Compliance
        
        For any workflow execution with different data sizes, the system should 
        complete within reasonable time bounds and log performance metrics.
        
        **Validates: Requirements 8.1**
        """
        # Test performance with different data sizes
        results = self.performance_tester.test_workflow_execution_times(data_sizes)
        
        # Verify all tests completed (success or failure recorded)
        assert len(results) > 0, "No performance results generated"
        
        # Verify performance metrics are logged
        for result in results:
            assert result.test_name is not None, "Test name should be recorded"
            assert result.execution_time >= 0, "Execution time should be non-negative"
            assert result.peak_memory_mb >= 0, "Memory usage should be non-negative"
            assert result.avg_cpu_percent >= 0, "CPU usage should be non-negative"
            assert result.data_size > 0, "Data size should be positive"
        
        # Verify reasonable performance bounds for simple workflows
        successful_results = [r for r in results if r.success]
        if successful_results:
            max_execution_time = max(r.execution_time for r in successful_results)
            max_memory_mb = max(r.peak_memory_mb for r in successful_results)
            
            # Simple workflows should complete quickly and use reasonable memory
            assert max_execution_time < 10.0, f"Execution time {max_execution_time}s exceeds reasonable bound"
            assert max_memory_mb < 200.0, f"Memory usage {max_memory_mb}MB exceeds reasonable bound"
        
        # Verify organized directory structure exists for successful tests
        for result in successful_results:
            # Extract station ID from test name
            if "simple_workflow_" in result.test_name:
                data_size = result.data_size
                station_id = f"SIMPLE{data_size:06d}"
                station_dir = self.temp_dir / station_id
                assert station_dir.exists(), f"Station directory should exist for {station_id}"
                
                # Verify basic directory structure
                assert (station_dir / "data").exists(), "Data directory should exist"
                assert (station_dir / "analysis").exists(), "Analysis directory should exist"
