"""
T232: Latency Tester
Testing de latencias masivas con 100 clips concurrentes
"""

import time
import threading
import statistics
from datetime import datetime
from typing import Dict, List, Any, Optional
from dataclasses import dataclass, field
from concurrent.futures import ThreadPoolExecutor, as_completed


@dataclass
class LatencyResult:
    """Resultado de prueba de latencia."""
    clip_index: int
    operation: str
    start_time: float
    end_time: float
    success: bool
    error: Optional[str] = None
    
    @property
    def latency_ms(self) -> float:
        return (self.end_time - self.start_time) * 1000


class LatencyTester:
    """
    Tester de latencias para clips concurrentes.
    
    T232: Testing con 100+ clips concurrentes.
    """
    
    def __init__(self, max_concurrent_clips: int = 100):
        self.max_concurrent_clips = max_concurrent_clips
        self.results: List[LatencyResult] = []
        
    def run_concurrent_clips_test(self, 
                                   num_clips: int = 100,
                                   operations: List[str] = None) -> Dict[str, Any]:
        """
        Ejecuta test con múltiples clips concurrentes.
        
        Args:
            num_clips: Número de clips a probar
            operations: Lista de operaciones ('create', 'play', 'stop', 'delete')
            
        Returns:
            Resultados del test de latencia
        """
        operations = operations or ['create', 'play']
        
        print(f"[T232] Starting concurrent clips test: {num_clips} clips")
        
        self.results = []
        start_time = time.time()
        
        # Ejecutar operaciones en paralelo
        with ThreadPoolExecutor(max_workers=20) as executor:
            futures = []
            
            for i in range(num_clips):
                for operation in operations:
                    future = executor.submit(
                        self._execute_clip_operation,
                        i, operation
                    )
                    futures.append((i, operation, future))
            
            # Recolectar resultados
            for clip_idx, operation, future in futures:
                try:
                    result = future.result(timeout=30)
                    self.results.append(result)
                except Exception as e:
                    self.results.append(LatencyResult(
                        clip_index=clip_idx,
                        operation=operation,
                        start_time=0,
                        end_time=0,
                        success=False,
                        error=str(e)
                    ))
        
        total_time = time.time() - start_time
        
        return self._analyze_results(num_clips, operations, total_time)
    
    def _execute_clip_operation(self, clip_index: int, 
                                operation: str) -> LatencyResult:
        """Ejecuta operación en un clip."""
        start = time.time()
        
        try:
            # Simulación de operación - en producción usaría MCP
            if operation == 'create':
                # Simular creación de clip
                time.sleep(0.05)  # 50ms simulado
                success = True
            elif operation == 'play':
                time.sleep(0.02)
                success = True
            elif operation == 'stop':
                time.sleep(0.01)
                success = True
            elif operation == 'delete':
                time.sleep(0.03)
                success = True
            else:
                success = False
            
            return LatencyResult(
                clip_index=clip_index,
                operation=operation,
                start_time=start,
                end_time=time.time(),
                success=success
            )
        except Exception as e:
            return LatencyResult(
                clip_index=clip_index,
                operation=operation,
                start_time=start,
                end_time=time.time(),
                success=False,
                error=str(e)
            )
    
    def _analyze_results(self, num_clips: int, 
                        operations: List[str],
                        total_time: float) -> Dict[str, Any]:
        """Analiza resultados del test."""
        # Agrupar por operación
        by_operation = {}
        for op in operations:
            op_results = [r for r in self.results if r.operation == op]
            
            latencies = [r.latency_ms for r in op_results if r.success]
            errors = [r for r in op_results if not r.success]
            
            by_operation[op] = {
                'count': len(op_results),
                'successful': len(latencies),
                'failed': len(errors),
                'latencies': {
                    'min': min(latencies) if latencies else 0,
                    'max': max(latencies) if latencies else 0,
                    'avg': statistics.mean(latencies) if latencies else 0,
                    'median': statistics.median(latencies) if latencies else 0,
                    'p95': self._percentile(latencies, 95) if latencies else 0,
                    'p99': self._percentile(latencies, 99) if latencies else 0,
                    'std': statistics.stdev(latencies) if len(latencies) > 1 else 0
                } if latencies else None,
                'errors': [e.error for e in errors if e.error]
            }
        
        # Análisis general
        all_latencies = [r.latency_ms for r in self.results if r.success]
        total_operations = len(self.results)
        successful = len(all_latencies)
        
        return {
            'test_id': f'latency_test_{datetime.now().strftime("%Y%m%d_%H%M%S")}',
            'timestamp': datetime.now().isoformat(),
            'configuration': {
                'num_clips': num_clips,
                'operations': operations,
                'max_concurrent': self.max_concurrent_clips
            },
            'results': {
                'total_operations': total_operations,
                'successful': successful,
                'failed': total_operations - successful,
                'success_rate': (successful / total_operations * 100) if total_operations > 0 else 0,
                'total_duration_seconds': total_time,
                'operations_per_second': total_operations / total_time if total_time > 0 else 0
            },
            'by_operation': by_operation,
            'overall_latency': {
                'min': min(all_latencies) if all_latencies else 0,
                'max': max(all_latencies) if all_latencies else 0,
                'avg': statistics.mean(all_latencies) if all_latencies else 0,
                'median': statistics.median(all_latencies) if all_latencies else 0,
                'p95': self._percentile(all_latencies, 95) if all_latencies else 0,
                'p99': self._percentile(all_latencies, 99) if all_latencies else 0
            } if all_latencies else None,
            'grade': self._calculate_grade(all_latencies, total_operations - successful)
        }
    
    def _percentile(self, data: List[float], percentile: int) -> float:
        """Calcula percentil."""
        sorted_data = sorted(data)
        index = int(len(sorted_data) * percentile / 100)
        return sorted_data[min(index, len(sorted_data) - 1)]
    
    def _calculate_grade(self, latencies: List[float], 
                        error_count: int) -> str:
        """Calcula calificación del test."""
        if not latencies:
            return 'F'
        
        avg_latency = statistics.mean(latencies)
        
        if error_count > 10:
            return 'F'
        elif avg_latency < 50 and error_count == 0:
            return 'A+'
        elif avg_latency < 100 and error_count <= 2:
            return 'A'
        elif avg_latency < 200 and error_count <= 5:
            return 'B'
        elif avg_latency < 500:
            return 'C'
        else:
            return 'D'
    
    def run_stress_test(self, duration_seconds: int = 60,
                       ramp_up: bool = True) -> Dict[str, Any]:
        """
        Ejecuta test de estrés durante tiempo especificado.
        
        Args:
            duration_seconds: Duración del test
            ramp_up: Incrementar carga gradualmente
            
        Returns:
            Resultados del test de estrés
        """
        results_by_phase = []
        start_time = time.time()
        
        if ramp_up:
            # Fases de ramp-up
            phases = [
                (10, 10),   # 10 clips por 10s
                (25, 10),   # 25 clips por 10s
                (50, 10),   # 50 clips por 10s
                (100, 10),  # 100 clips por 10s
                (100, duration_seconds - 40)  # 100 clips resto
            ]
        else:
            phases = [(self.max_concurrent_clips, duration_seconds)]
        
        for clip_count, phase_duration in phases:
            phase_start = time.time()
            phase_results = self.run_concurrent_clips_test(
                num_clips=clip_count,
                operations=['create', 'play', 'stop']
            )
            phase_results['phase'] = {
                'clip_count': clip_count,
                'duration': time.time() - phase_start,
                'target_duration': phase_duration
            }
            results_by_phase.append(phase_results)
            
            if time.time() - start_time > duration_seconds:
                break
        
        return {
            'test_type': 'stress_test',
            'total_duration': time.time() - start_time,
            'phases': results_by_phase,
            'summary': self._summarize_stress_results(results_by_phase)
        }
    
    def _summarize_stress_results(self, phases: List[Dict]) -> Dict[str, Any]:
        """Resume resultados de estrés."""
        avg_latencies_by_phase = [
            p['overall_latency']['avg'] if p.get('overall_latency') else 0
            for p in phases
        ]
        
        return {
            'max_sustainable_load': self._find_max_sustainable_load(phases),
            'latency_trend': 'improving' if avg_latencies_by_phase[-1] < avg_latencies_by_phase[0] else 
                           'degrading' if avg_latencies_by_phase[-1] > avg_latencies_by_phase[0] else 'stable',
            'recommended_max_concurrent': self._find_max_sustainable_load(phases),
            'peak_latency': max(
                (p['overall_latency']['max'] for p in phases if p.get('overall_latency')),
                default=0
            )
        }
    
    def _find_max_sustainable_load(self, phases: List[Dict]) -> int:
        """Encuentra carga máxima sostenible."""
        for phase in reversed(phases):
            if phase.get('grade') in ['A', 'A+', 'B']:
                return phase['configuration']['num_clips']
        return 10


def run_latency_test(num_clips: int = 100) -> Dict[str, Any]:
    """
    T232: Ejecuta test de latencia con clips concurrentes.
    
    Args:
        num_clips: Número de clips a probar (default 100)
        
    Returns:
        Resultados del test de latencia
    """
    tester = LatencyTester(max_concurrent_clips=num_clips)
    return tester.run_concurrent_clips_test(num_clips=num_clips)


def run_stress_test(duration_seconds: int = 60) -> Dict[str, Any]:
    """
    Ejecuta test de estrés de larga duración.
    
    Args:
        duration_seconds: Duración del test
        
    Returns:
        Resultados del test de estrés
    """
    tester = LatencyTester()
    return tester.run_stress_test(duration_seconds=duration_seconds)


if __name__ == '__main__':
    # Test de latencia
    print("Running T232: Latency Test with 50 clips...")
    result = run_latency_test(num_clips=50)
    
    print(f"\nTest ID: {result['test_id']}")
    print(f"Success Rate: {result['results']['success_rate']:.1f}%")
    print(f"Grade: {result['grade']}")
    
    if result.get('overall_latency'):
        print(f"Avg Latency: {result['overall_latency']['avg']:.2f}ms")
        print(f"P95 Latency: {result['overall_latency']['p95']:.2f}ms")
