"""Lifecycle hooks and schemas."""

import json
import logging
import threading
from abc import ABC
from typing import Any, Callable, Dict, Optional


class EventoSchema:
    """Schema para eventos versionados con validación."""
    
    SCHEMA_VERSION = "1.0"
    
    TIPOS_VALIDOS = {
        "EXITO", "ERROR", "TIMEOUT", "REINTENTO", "INCIDENTE",
        "TTL_EXPIRADO", "SATURACION", "CUSTOM"
    }
    
    @staticmethod
    def validar(tipo: str, datos: Dict[str, Any], max_kb: int = 100) -> bool:
        """Valida evento contra schema."""
        if tipo not in EventoSchema.TIPOS_VALIDOS:
            raise ValueError(f"Tipo inválido: {tipo}")
        
        size_bytes = len(json.dumps(datos, default=str).encode('utf-8'))
        if size_bytes > max_kb * 1024:
            raise ValueError(f"Evento > {max_kb}KB")
        
        return True


class LifecycleHooks:
    """Gestiona hooks del ciclo de vida del agente."""
    
    def __init__(self):
        self._hooks = {
            "on_start": [],
            "on_stop": [],
            "on_error": [],
            "on_task_received": [],
            "on_task_completed": [],
            "on_task_failed": [],
            "on_retry": [],
            "on_metrics_snapshot": [],
        }
        self.lock = threading.Lock()
    
    def registrar(self, evento: str, callback: Callable):
        """Registra callback para evento del ciclo de vida."""
        if evento not in self._hooks:
            raise ValueError(f"Evento inválido: {evento}")
        with self.lock:
            self._hooks[evento].append(callback)
    
    def invocar(self, evento: str, *args, **kwargs):
        """Invoca callbacks registrados para evento."""
        with self.lock:
            callbacks = self._hooks.get(evento, []).copy()
        for cb in callbacks:
            try:
                if callable(cb):
                    cb(*args, **kwargs)
            except Exception as e:
                logging.error(f"Error en hook {evento}: {e}")


class MiddlewarePlugin(ABC):
    """Base middleware plugin for task processing pipeline."""

    def before(self, task: Any, context: Dict[str, Any]) -> Any:
        """Runs before task processing. May return modified task."""
        return task

    def after(self, result: Any, context: Dict[str, Any]) -> Any:
        """Runs after task processing. May return modified result."""
        return result

    def on_error(self, error: Exception, context: Dict[str, Any]) -> Any:
        """Runs when task processing fails. May return modified error."""
        return error


class MiddlewareStack:
    """Middleware stack for task processing pipeline."""

    def __init__(self):
        self._middlewares = []
        self.lock = threading.Lock()

    def add(self, middleware: MiddlewarePlugin) -> None:
        """Adds a middleware to the stack."""
        if middleware is None:
            raise ValueError("Middleware cannot be None")
        with self.lock:
            self._middlewares.append(middleware)

    def apply_before(self, task: Any, context: Dict[str, Any]) -> Any:
        """Applies before() in registration order."""
        with self.lock:
            middlewares = list(self._middlewares)
        current = task
        for mw in middlewares:
            try:
                new_task = mw.before(current, context)
                if new_task is not None:
                    current = new_task
            except Exception as e:
                logging.error(f"Error in middleware before(): {e}")
        return current

    def apply_after(self, result: Any, context: Dict[str, Any]) -> Any:
        """Applies after() in reverse order."""
        with self.lock:
            middlewares = list(self._middlewares)
        current = result
        for mw in reversed(middlewares):
            try:
                new_result = mw.after(current, context)
                if new_result is not None:
                    current = new_result
            except Exception as e:
                logging.error(f"Error in middleware after(): {e}")
        return current

    def apply_error(self, error: Exception, context: Dict[str, Any]) -> Any:
        """Applies on_error() in reverse order."""
        with self.lock:
            middlewares = list(self._middlewares)
        current = error
        for mw in reversed(middlewares):
            try:
                new_error = mw.on_error(current, context)
                if new_error is not None:
                    current = new_error
            except Exception as e:
                logging.error(f"Error in middleware on_error(): {e}")
        return current

    def list_middlewares(self) -> list:
        """Returns registered middlewares."""
        with self.lock:
            return list(self._middlewares)


class GestorAntiInanicion:
    """Previene inanición (starvation) de tareas de baja prioridad.
    
    Las tareas de baja prioridad pueden quedar bloqueadas indefinidamente
    si el sistema recibe continuamente tareas de alta prioridad. Este gestor
    asegura que cada N tareas de alta prioridad, se procese al menos una
    de baja prioridad.
    """
    
    def __init__(self, ratio_minimo_baja_prioridad: float = 0.2):
        """
        Inicializa gestor anti-inanición.
        
        Args:
            ratio_minimo_baja_prioridad: Proporción mínima de tareas de baja 
                                        prioridad a ejecutar (0.0 a 1.0)
        """
        self.ratio_minimo = max(0.0, min(1.0, ratio_minimo_baja_prioridad))
        self.contador_tareas = 0
        self.tareas_baja_prioridad_ejecutadas = 0
        self.lock = threading.Lock()
    
    def puede_saltarse_baja_prioridad(self) -> bool:
        """Determina si se puede saltarse una tarea de baja prioridad."""
        with self.lock:
            if self.contador_tareas == 0:
                return False
            
            # Si no hemos alcanzado el ratio mínimo, NO saltamos tareas de baja prioridad
            ratio_actual = self.tareas_baja_prioridad_ejecutadas / self.contador_tareas
            puede_saltarse = ratio_actual >= self.ratio_minimo
            
            return puede_saltarse
    
    def registrar_tarea_ejecutada(self, es_baja_prioridad: bool):
        """Registra una tarea ejecutada."""
        with self.lock:
            self.contador_tareas += 1
            if es_baja_prioridad:
                self.tareas_baja_prioridad_ejecutadas += 1
    
    def obtener_estadisticas(self) -> Dict[str, Any]:
        """Obtiene estadísticas del gestor anti-inanición."""
        with self.lock:
            if self.contador_tareas == 0:
                ratio = 0.0
            else:
                ratio = self.tareas_baja_prioridad_ejecutadas / self.contador_tareas
            
            return {
                "total_tareas": self.contador_tareas,
                "tareas_baja_prioridad": self.tareas_baja_prioridad_ejecutadas,
                "ratio_actual": ratio,
                "ratio_minimo_requerido": self.ratio_minimo,
            }
    
    def limpiar(self):
        """Limpia estadísticas."""
        with self.lock:
            self.contador_tareas = 0
            self.tareas_baja_prioridad_ejecutadas = 0
