"""
T218-T099: Performance Watchdog - Monitoreo de 3-8 horas
Sistema de watchdog para monitoreo continuo de performance
"""

import time
import threading
import psutil
import json
import os
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Callable
from dataclasses import dataclass, asdict
from collections import deque


@dataclass
class PerformanceSnapshot:
    """Snapshot de performance en un momento dado."""
    timestamp: str
    cpu_percent: float
    memory_percent: float
    memory_mb: float
    disk_io_read_mb: float
    disk_io_write_mb: float
    network_io_sent_mb: float
    network_io_recv_mb: float
    ableton_cpu: float  # Estimado desde logs
    ableton_memory: float  # Estimado desde logs
    generation_queue_size: int
    active_clips: int
    audio_latency_ms: float
    

class PerformanceWatchdog:
    """
    Watchdog de performance para sesiones extendidas (3-8 horas).
    
    T099-T100: Start 3-hour autonomous performance monitoring
    """
    
    DEFAULT_CHECK_INTERVAL = 30  # segundos
    DEFAULT_HISTORY_SIZE = 960  # 8 horas de datos (30s interval)
    
    def __init__(self, session_duration_hours: float = 3.0,
                 check_interval: int = DEFAULT_CHECK_INTERVAL):
        self.session_duration = timedelta(hours=session_duration_hours)
        self.check_interval = check_interval
        self.history_size = int((session_duration_hours * 3600) / check_interval)
        
        self.snapshots: deque = deque(maxlen=self.history_size)
        self.running = False
        self.monitor_thread: Optional[threading.Thread] = None
        self.callbacks: List[Callable] = []
        self.start_time: Optional[datetime] = None
        self.alert_thresholds = {
            'cpu_warning': 80.0,
            'cpu_critical': 95.0,
            'memory_warning': 85.0,
            'memory_critical': 95.0,
            'latency_warning': 50.0,  # ms
            'latency_critical': 100.0  # ms
        }
        self.alerts_triggered: List[Dict[str, Any]] = []
        
    def start(self) -> Dict[str, Any]:
        """
        Inicia el monitoreo de performance.
        
        Returns:
            Estado inicial del monitoreo
        """
        if self.running:
            return {'status': 'already_running', 'start_time': self.start_time.isoformat()}
        
        self.running = True
        self.start_time = datetime.now()
        self.monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
        self.monitor_thread.start()
        
        self._log_event('watchdog_started', f'Started {self.session_duration} monitoring')
        
        return {
            'status': 'started',
            'start_time': self.start_time.isoformat(),
            'expected_end': (self.start_time + self.session_duration).isoformat(),
            'check_interval': self.check_interval,
            'history_capacity': self.history_size
        }
    
    def stop(self) -> Dict[str, Any]:
        """Detiene el monitoreo."""
        if not self.running:
            return {'status': 'not_running'}
        
        self.running = False
        if self.monitor_thread:
            self.monitor_thread.join(timeout=5)
        
        end_time = datetime.now()
        uptime = end_time - self.start_time if self.start_time else timedelta(0)
        
        self._log_event('watchdog_stopped', f'Stopped after {uptime}')
        
        return {
            'status': 'stopped',
            'start_time': self.start_time.isoformat() if self.start_time else None,
            'end_time': end_time.isoformat(),
            'uptime_seconds': uptime.total_seconds(),
            'total_snapshots': len(self.snapshots),
            'alerts_triggered': len(self.alerts_triggered)
        }
    
    def _monitor_loop(self):
        """Bucle principal de monitoreo."""
        while self.running:
            try:
                snapshot = self._collect_snapshot()
                self.snapshots.append(snapshot)
                
                # Verificar alertas
                self._check_alerts(snapshot)
                
                # Notificar callbacks
                for callback in self.callbacks:
                    try:
                        callback(snapshot)
                    except Exception as e:
                        self._log_event('callback_error', str(e), 'ERROR')
                
                # Verificar si se alcanzó la duración máxima
                if self.start_time and (datetime.now() - self.start_time) > self.session_duration:
                    self._log_event('session_complete', 'Session duration reached')
                    self.stop()
                    break
                
                time.sleep(self.check_interval)
            except Exception as e:
                self._log_event('monitor_error', str(e), 'ERROR')
                time.sleep(self.check_interval)
    
    def _collect_snapshot(self) -> PerformanceSnapshot:
        """Recolecta métricas de performance actuales."""
        cpu = psutil.cpu_percent(interval=1)
        memory = psutil.virtual_memory()
        disk_io = psutil.disk_io_counters()
        net_io = psutil.net_io_counters()
        
        # Estimaciones de Ableton (simuladas - en producción leerían de Ableton)
        ableton_cpu = cpu * 0.6  # Estimación
        ableton_memory = (memory.used / 1024 / 1024) * 0.4  # Estimación
        
        # Medir latencia de audio (estimada)
        audio_latency = self._measure_audio_latency()
        
        return PerformanceSnapshot(
            timestamp=datetime.now().isoformat(),
            cpu_percent=cpu,
            memory_percent=memory.percent,
            memory_mb=memory.used / 1024 / 1024,
            disk_io_read_mb=disk_io.read_bytes / 1024 / 1024 if disk_io else 0,
            disk_io_write_mb=disk_io.write_bytes / 1024 / 1024 if disk_io else 0,
            network_io_sent_mb=net_io.bytes_sent / 1024 / 1024 if net_io else 0,
            network_io_recv_mb=net_io.bytes_recv / 1024 / 1024 if net_io else 0,
            ableton_cpu=ableton_cpu,
            ableton_memory=ableton_memory,
            generation_queue_size=0,  # Placeholder
            active_clips=0,  # Placeholder
            audio_latency_ms=audio_latency
        )
    
    def _measure_audio_latency(self) -> float:
        """Mide latencia de audio (implementación simulada)."""
        # En producción, esto leería de Ableton vía MCP
        import random
        return 10.0 + random.uniform(0, 20)  # 10-30ms simulado
    
    def _check_alerts(self, snapshot: PerformanceSnapshot):
        """Verifica y dispara alertas si es necesario."""
        alerts = []
        
        if snapshot.cpu_percent > self.alert_thresholds['cpu_critical']:
            alerts.append({'level': 'CRITICAL', 'metric': 'cpu', 'value': snapshot.cpu_percent})
        elif snapshot.cpu_percent > self.alert_thresholds['cpu_warning']:
            alerts.append({'level': 'WARNING', 'metric': 'cpu', 'value': snapshot.cpu_percent})
        
        if snapshot.memory_percent > self.alert_thresholds['memory_critical']:
            alerts.append({'level': 'CRITICAL', 'metric': 'memory', 'value': snapshot.memory_percent})
        elif snapshot.memory_percent > self.alert_thresholds['memory_warning']:
            alerts.append({'level': 'WARNING', 'metric': 'memory', 'value': snapshot.memory_percent})
        
        if snapshot.audio_latency_ms > self.alert_thresholds['latency_critical']:
            alerts.append({'level': 'CRITICAL', 'metric': 'latency', 'value': snapshot.audio_latency_ms})
        elif snapshot.audio_latency_ms > self.alert_thresholds['latency_warning']:
            alerts.append({'level': 'WARNING', 'metric': 'latency', 'value': snapshot.audio_latency_ms})
        
        for alert in alerts:
            self.alerts_triggered.append({
                'timestamp': snapshot.timestamp,
                **alert
            })
            self._log_event('alert', f"{alert['level']}: {alert['metric']} = {alert['value']}", alert['level'])
    
    def _log_event(self, event_type: str, message: str, level: str = 'INFO'):
        """Registra evento en logs."""
        try:
            from .persistent_logs import log_event
            log_event('performance', f'[{event_type}] {message}', level)
        except:
            pass  # Silenciar si logging no disponible
    
    def get_status(self) -> Dict[str, Any]:
        """Obtiene estado actual del monitoreo."""
        if not self.running:
            return {'status': 'stopped'}
        
        uptime = datetime.now() - self.start_time if self.start_time else timedelta(0)
        remaining = self.session_duration - uptime
        
        # Calcular promedios
        if self.snapshots:
            avg_cpu = sum(s.cpu_percent for s in self.snapshots) / len(self.snapshots)
            avg_mem = sum(s.memory_percent for s in self.snapshots) / len(self.snapshots)
            avg_lat = sum(s.audio_latency_ms for s in self.snapshots) / len(self.snapshots)
        else:
            avg_cpu = avg_mem = avg_lat = 0
        
        return {
            'status': 'running',
            'start_time': self.start_time.isoformat(),
            'uptime_seconds': uptime.total_seconds(),
            'remaining_seconds': max(0, remaining.total_seconds()),
            'progress_percent': min(100, (uptime.total_seconds() / self.session_duration.total_seconds()) * 100),
            'total_snapshots': len(self.snapshots),
            'alerts_count': len(self.alerts_triggered),
            'recent_alerts': self.alerts_triggered[-5:] if self.alerts_triggered else [],
            'averages': {
                'cpu_percent': round(avg_cpu, 2),
                'memory_percent': round(avg_mem, 2),
                'latency_ms': round(avg_lat, 2)
            }
        }
    
    def get_performance_report(self) -> Dict[str, Any]:
        """Genera reporte completo de performance."""
        if not self.snapshots:
            return {'error': 'No data collected'}
        
        cpu_values = [s.cpu_percent for s in self.snapshots]
        mem_values = [s.memory_percent for s in self.snapshots]
        lat_values = [s.audio_latency_ms for s in self.snapshots]
        
        return {
            'duration_seconds': len(self.snapshots) * self.check_interval,
            'snapshots_count': len(self.snapshots),
            'cpu': {
                'min': min(cpu_values),
                'max': max(cpu_values),
                'avg': sum(cpu_values) / len(cpu_values),
                'p95': sorted(cpu_values)[int(len(cpu_values) * 0.95)] if len(cpu_values) > 1 else cpu_values[0]
            },
            'memory': {
                'min': min(mem_values),
                'max': max(mem_values),
                'avg': sum(mem_values) / len(mem_values),
                'p95': sorted(mem_values)[int(len(mem_values) * 0.95)] if len(mem_values) > 1 else mem_values[0]
            },
            'latency': {
                'min': min(lat_values),
                'max': max(lat_values),
                'avg': sum(lat_values) / len(lat_values),
                'p95': sorted(lat_values)[int(len(lat_values) * 0.95)] if len(lat_values) > 1 else lat_values[0]
            },
            'alerts_summary': {
                'total': len(self.alerts_triggered),
                'critical': len([a for a in self.alerts_triggered if a['level'] == 'CRITICAL']),
                'warning': len([a for a in self.alerts_triggered if a['level'] == 'WARNING'])
            },
            'timestamps': {
                'start': self.snapshots[0].timestamp if self.snapshots else None,
                'end': self.snapshots[-1].timestamp if self.snapshots else None
            }
        }
    
    def export_snapshots(self, filepath: str):
        """Exporta snapshots a archivo."""
        data = [asdict(s) for s in self.snapshots]
        with open(filepath, 'w') as f:
            json.dump(data, f, indent=2)


# Instancia global
_watchdog_instance: Optional[PerformanceWatchdog] = None

def start_performance_monitoring(duration_hours: float = 3.0) -> Dict[str, Any]:
    """
    T099: Inicia monitoreo de performance de 3 horas (o configurable 3-8 horas).
    
    Args:
        duration_hours: Duración del monitoreo (0.5 - 8.0 horas)
        
    Returns:
        Estado inicial del monitoreo
    """
    global _watchdog_instance
    
    # Limitar rango 0.5 - 8 horas
    duration = max(0.5, min(8.0, duration_hours))
    
    if _watchdog_instance is None or not _watchdog_instance.running:
        _watchdog_instance = PerformanceWatchdog(session_duration_hours=duration)
    
    return _watchdog_instance.start()


def get_performance_status() -> Dict[str, Any]:
    """
    T099: Obtiene estado actual del monitoreo de performance.
    
    Returns:
        Estado actual con uptime, estadísticas y alertas
    """
    global _watchdog_instance
    
    if _watchdog_instance is None:
        return {'status': 'not_initialized'}
    
    return _watchdog_instance.get_status()


def stop_performance_monitoring() -> Dict[str, Any]:
    """Detiene el monitoreo de performance."""
    global _watchdog_instance
    
    if _watchdog_instance is None:
        return {'status': 'not_initialized'}
    
    return _watchdog_instance.stop()


if __name__ == '__main__':
    # Test del watchdog
    result = start_performance_monitoring(duration_hours=0.1)  # 6 minutos para test
    print("Started:", result)
    
    # Simular monitoreo
    time.sleep(5)
    
    status = get_performance_status()
    print("Status:", status)
    
    # Detener
    stop_result = stop_performance_monitoring()
    print("Stopped:", stop_result)
