"""
T219: Health Checks Programados
Sistema de health checks periódicos para verificar estado del sistema
"""

import time
import threading
import socket
import json
import os
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional, Callable
from dataclasses import dataclass
from enum import Enum


class HealthStatus(Enum):
    HEALTHY = "healthy"
    WARNING = "warning"
    CRITICAL = "critical"
    UNKNOWN = "unknown"


@dataclass
class HealthCheckResult:
    """Resultado de un health check."""
    name: str
    status: HealthStatus
    timestamp: str
    message: str
    details: Dict[str, Any]
    response_time_ms: float


class HealthCheckSuite:
    """Suite de health checks programados."""
    
    DEFAULT_CHECK_INTERVAL = 60  # segundos
    
    def __init__(self, check_interval: int = DEFAULT_CHECK_INTERVAL):
        self.check_interval = check_interval
        self.checks: Dict[str, Callable] = {}
        self.results: List[HealthCheckResult] = []
        self.running = False
        self.monitor_thread: Optional[threading.Thread] = None
        self.callbacks: List[Callable] = []
        
        # Registrar checks por defecto
        self._register_default_checks()
    
    def _register_default_checks(self):
        """Registra los checks de salud por defecto."""
        self.register_check('ableton_connection', self._check_ableton_connection)
        self.register_check('mcp_wrapper', self._check_mcp_wrapper)
        self.register_check('runtime_socket', self._check_runtime_socket)
        self.register_check('sample_library', self._check_sample_library)
        self.register_check('disk_space', self._check_disk_space)
        self.register_check('memory_usage', self._check_memory_usage)
    
    def register_check(self, name: str, check_func: Callable):
        """Registra un nuevo check de salud."""
        self.checks[name] = check_func
    
    def start(self) -> Dict[str, Any]:
        """Inicia los health checks programados."""
        if self.running:
            return {'status': 'already_running'}
        
        self.running = True
        self.monitor_thread = threading.Thread(target=self._check_loop, daemon=True)
        self.monitor_thread.start()
        
        return {
            'status': 'started',
            'checks_registered': len(self.checks),
            'check_interval': self.check_interval,
            'timestamp': datetime.now().isoformat()
        }
    
    def stop(self) -> Dict[str, Any]:
        """Detiene los health checks."""
        if not self.running:
            return {'status': 'not_running'}
        
        self.running = False
        if self.monitor_thread:
            self.monitor_thread.join(timeout=5)
        
        return {'status': 'stopped', 'timestamp': datetime.now().isoformat()}
    
    def _check_loop(self):
        """Bucle principal de health checks."""
        while self.running:
            try:
                self.run_all_checks()
                time.sleep(self.check_interval)
            except Exception as e:
                self._log_error(f"Health check loop error: {e}")
                time.sleep(self.check_interval)
    
    def run_all_checks(self) -> List[HealthCheckResult]:
        """Ejecuta todos los health checks registrados."""
        results = []
        
        for name, check_func in self.checks.items():
            start_time = time.time()
            try:
                result = check_func()
                response_time = (time.time() - start_time) * 1000
                
                health_result = HealthCheckResult(
                    name=name,
                    status=result.get('status', HealthStatus.UNKNOWN),
                    timestamp=datetime.now().isoformat(),
                    message=result.get('message', ''),
                    details=result.get('details', {}),
                    response_time_ms=response_time
                )
                
            except Exception as e:
                response_time = (time.time() - start_time) * 1000
                health_result = HealthCheckResult(
                    name=name,
                    status=HealthStatus.CRITICAL,
                    timestamp=datetime.now().isoformat(),
                    message=f"Check failed: {str(e)}",
                    details={'error': str(e)},
                    response_time_ms=response_time
                )
            
            results.append(health_result)
            self.results.append(health_result)
        
        # Mantener solo últimos 100 resultados por check
        self.results = self.results[-(len(self.checks) * 100):]
        
        # Notificar callbacks
        for callback in self.callbacks:
            try:
                callback(results)
            except Exception as e:
                self._log_error(f"Callback error: {e}")
        
        return results
    
    def run_single_check(self, name: str) -> Optional[HealthCheckResult]:
        """Ejecuta un check específico."""
        if name not in self.checks:
            return None
        
        start_time = time.time()
        try:
            result = self.checks[name]()
            response_time = (time.time() - start_time) * 1000
            
            return HealthCheckResult(
                name=name,
                status=result.get('status', HealthStatus.UNKNOWN),
                timestamp=datetime.now().isoformat(),
                message=result.get('message', ''),
                details=result.get('details', {}),
                response_time_ms=response_time
            )
        except Exception as e:
            return HealthCheckResult(
                name=name,
                status=HealthStatus.CRITICAL,
                timestamp=datetime.now().isoformat(),
                message=f"Check failed: {str(e)}",
                details={'error': str(e)},
                response_time_ms=(time.time() - start_time) * 1000
            )
    
    def get_health_summary(self) -> Dict[str, Any]:
        """Obtiene resumen de salud del sistema."""
        if not self.results:
            # Ejecutar checks si no hay resultados
            self.run_all_checks()
        
        # Agrupar por nombre y tomar el más reciente
        latest_by_name = {}
        for result in reversed(self.results):
            if result.name not in latest_by_name:
                latest_by_name[result.name] = result
        
        # Contar estados
        status_counts = {'healthy': 0, 'warning': 0, 'critical': 0, 'unknown': 0}
        for result in latest_by_name.values():
            status_counts[result.status.value] += 1
        
        # Determinar estado general
        if status_counts['critical'] > 0:
            overall_status = HealthStatus.CRITICAL
        elif status_counts['warning'] > 0:
            overall_status = HealthStatus.WARNING
        elif status_counts['healthy'] > 0:
            overall_status = HealthStatus.HEALTHY
        else:
            overall_status = HealthStatus.UNKNOWN
        
        return {
            'timestamp': datetime.now().isoformat(),
            'overall_status': overall_status.value,
            'checks_total': len(self.checks),
            'checks_passed': status_counts['healthy'],
            'checks_warning': status_counts['warning'],
            'checks_critical': status_counts['critical'],
            'details': {
                name: {
                    'status': result.status.value,
                    'message': result.message,
                    'response_time_ms': round(result.response_time_ms, 2),
                    'timestamp': result.timestamp
                }
                for name, result in latest_by_name.items()
            }
        }
    
    # === Implementaciones de checks específicos ===
    
    def _check_ableton_connection(self) -> Dict[str, Any]:
        """Verifica conexión con Ableton Live."""
        try:
            # Intentar conexión al runtime
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.settimeout(5)
            result = sock.connect_ex(('127.0.0.1', 9877))
            sock.close()
            
            if result == 0:
                return {
                    'status': HealthStatus.HEALTHY,
                    'message': 'Ableton Live runtime connection OK',
                    'details': {'port': 9877, 'connected': True}
                }
            else:
                return {
                    'status': HealthStatus.CRITICAL,
                    'message': f'Cannot connect to Ableton runtime (error: {result})',
                    'details': {'port': 9877, 'connected': False, 'error_code': result}
                }
        except Exception as e:
            return {
                'status': HealthStatus.CRITICAL,
                'message': f'Connection check failed: {str(e)}',
                'details': {'error': str(e)}
            }
    
    def _check_mcp_wrapper(self) -> Dict[str, Any]:
        """Verifica estado del MCP wrapper."""
        try:
            wrapper_path = os.path.join(
                os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
                'mcp_wrapper.py'
            )
            
            if os.path.exists(wrapper_path):
                return {
                    'status': HealthStatus.HEALTHY,
                    'message': 'MCP wrapper found',
                    'details': {'path': wrapper_path, 'exists': True}
                }
            else:
                return {
                    'status': HealthStatus.WARNING,
                    'message': 'MCP wrapper not found at expected path',
                    'details': {'path': wrapper_path, 'exists': False}
                }
        except Exception as e:
            return {
                'status': HealthStatus.WARNING,
                'message': f'MCP wrapper check failed: {str(e)}',
                'details': {'error': str(e)}
            }
    
    def _check_runtime_socket(self) -> Dict[str, Any]:
        """Verifica socket de runtime."""
        return self._check_ableton_connection()  # Misma implementación
    
    def _check_sample_library(self) -> Dict[str, Any]:
        """Verifica disponibilidad de librería de samples."""
        try:
            library_path = os.path.join(
                os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(__file__)))),
                'librerias', 'all_tracks'
            )
            
            if os.path.exists(library_path):
                # Contar archivos
                file_count = sum(1 for _, _, files in os.walk(library_path) for _ in files)
                
                return {
                    'status': HealthStatus.HEALTHY,
                    'message': f'Sample library accessible ({file_count} files)',
                    'details': {'path': library_path, 'file_count': file_count}
                }
            else:
                return {
                    'status': HealthStatus.WARNING,
                    'message': 'Sample library not found',
                    'details': {'path': library_path, 'exists': False}
                }
        except Exception as e:
            return {
                'status': HealthStatus.WARNING,
                'message': f'Library check failed: {str(e)}',
                'details': {'error': str(e)}
            }
    
    def _check_disk_space(self) -> Dict[str, Any]:
        """Verifica espacio en disco."""
        try:
            import shutil
            stat = shutil.disk_usage('/')
            
            total_gb = stat.total / (1024**3)
            free_gb = stat.free / (1024**3)
            used_percent = (stat.used / stat.total) * 100
            
            if used_percent > 95:
                status = HealthStatus.CRITICAL
            elif used_percent > 85:
                status = HealthStatus.WARNING
            else:
                status = HealthStatus.HEALTHY
            
            return {
                'status': status,
                'message': f'Disk: {free_gb:.1f}GB free of {total_gb:.1f}GB',
                'details': {
                    'total_gb': round(total_gb, 2),
                    'free_gb': round(free_gb, 2),
                    'used_percent': round(used_percent, 2)
                }
            }
        except Exception as e:
            return {
                'status': HealthStatus.UNKNOWN,
                'message': f'Disk check failed: {str(e)}',
                'details': {'error': str(e)}
            }
    
    def _check_memory_usage(self) -> Dict[str, Any]:
        """Verifica uso de memoria."""
        try:
            import psutil
            mem = psutil.virtual_memory()
            
            if mem.percent > 95:
                status = HealthStatus.CRITICAL
            elif mem.percent > 85:
                status = HealthStatus.WARNING
            else:
                status = HealthStatus.HEALTHY
            
            return {
                'status': status,
                'message': f'Memory: {mem.percent}% used ({mem.available/1024**3:.1f}GB free)',
                'details': {
                    'percent': mem.percent,
                    'available_gb': round(mem.available / 1024**3, 2),
                    'total_gb': round(mem.total / 1024**3, 2)
                }
            }
        except Exception as e:
            return {
                'status': HealthStatus.UNKNOWN,
                'message': f'Memory check failed: {str(e)}',
                'details': {'error': str(e)}
            }
    
    def _log_error(self, message: str):
        """Registra error en logs."""
        try:
            from .persistent_logs import log_event
            log_event('health_check', message, 'ERROR')
        except:
            pass


# Instancia global
_health_suite: Optional[HealthCheckSuite] = None

def start_health_checks(interval_seconds: int = 60) -> Dict[str, Any]:
    """
    T219: Inicia health checks programados.
    
    Args:
        interval_seconds: Intervalo entre checks (default 60s)
        
    Returns:
        Estado de inicio
    """
    global _health_suite
    
    if _health_suite is None:
        _health_suite = HealthCheckSuite(check_interval=interval_seconds)
    
    return _health_suite.start()


def get_health_status() -> Dict[str, Any]:
    """
    Obtiene estado de salud actual del sistema.
    
    Returns:
        Resumen de salud con todos los checks
    """
    global _health_suite
    
    if _health_suite is None:
        # Crear y ejecutar checks una vez
        _health_suite = HealthCheckSuite()
        return _health_suite.get_health_summary()
    
    return _health_suite.get_health_summary()


def run_health_check(check_name: str) -> Optional[Dict[str, Any]]:
    """Ejecuta un check específico y retorna resultado."""
    global _health_suite
    
    if _health_suite is None:
        _health_suite = HealthCheckSuite()
    
    result = _health_suite.run_single_check(check_name)
    if result:
        return {
            'name': result.name,
            'status': result.status.value,
            'message': result.message,
            'response_time_ms': round(result.response_time_ms, 2),
            'timestamp': result.timestamp,
            'details': result.details
        }
    return None


if __name__ == '__main__':
    # Test health checks
    print("Starting health checks...")
    result = start_health_checks(interval_seconds=10)
    print("Started:", result)
    
    # Esperar un check
    time.sleep(12)
    
    status = get_health_status()
    print("\nHealth Status:")
    print(json.dumps(status, indent=2))
    
    _health_suite.stop()
