"""Metrics collection and performance monitoring."""

import threading
import time
from collections import deque
from typing import Dict, Any


class ColectorMetricas:
    """Collects performance metrics with a time-based window."""
    
    def __init__(self, ventana_segundos: int = 60):
        """
        Inicializa colector de métricas.
        
        Args:
            ventana_segundos: Ventana de tiempo para calcular métricas (default 60s)
        """
        self.ventana_segundos = ventana_segundos
        self.latencias: deque = deque(maxlen=10000)  # (timestamp, latency)
        self.eventos: deque = deque(maxlen=20000)    # (timestamp, success)
        self.tareas_completadas = 0
        self.tareas_fallidas = 0
        self.timestamps_inicio: Dict[str, float] = {}
        self.lock = threading.Lock()
    
    def registrar_inicio_tarea(self, tarea_id: str):
        """Registra cuando inicia una tarea."""
        with self.lock:
            self.timestamps_inicio[tarea_id] = time.time()
    
    def registrar_fin_tarea(self, tarea_id: str, exitosa: bool = True):
        """Register task end and compute latency."""
        with self.lock:
            if tarea_id not in self.timestamps_inicio:
                return
            
            inicio = self.timestamps_inicio.pop(tarea_id)
            now = time.time()
            latencia = now - inicio
            self.latencias.append((now, latencia))
            self.eventos.append((now, exitosa))
            
            if exitosa:
                self.tareas_completadas += 1
            else:
                self.tareas_fallidas += 1
    
    def obtener_latencia_promedio(self) -> float:
        """Retorna latencia promedio."""
        with self.lock:
            self._prune(time.time())
            return self._obtener_latencia_promedio_sin_lock()
    
    def _prune(self, now: float) -> None:
        """Remove metrics outside the time window."""
        window_start = now - self.ventana_segundos
        while self.latencias and self.latencias[0][0] < window_start:
            self.latencias.popleft()
        while self.eventos and self.eventos[0][0] < window_start:
            self.eventos.popleft()

    def _obtener_latencia_promedio_sin_lock(self) -> float:
        """Return average latency within the time window."""
        if not self.latencias:
            return 0.0
        return sum(lat for _, lat in self.latencias) / len(self.latencias)
    
    def obtener_latencia_p95(self) -> float:
        """Retorna percentil 95 de latencia."""
        with self.lock:
            self._prune(time.time())
            return self._obtener_latencia_p95_sin_lock()
    
    def _obtener_latencia_p95_sin_lock(self) -> float:
        """Return latency p95 within the time window."""
        if not self.latencias:
            return 0.0
        sorted_latencias = sorted(lat for _, lat in self.latencias)
        idx = int(len(sorted_latencias) * 0.95)
        return sorted_latencias[idx] if idx < len(sorted_latencias) else sorted_latencias[-1]
    
    def obtener_throughput(self) -> float:
        """Retorna throughput: tareas completadas por segundo."""
        with self.lock:
            self._prune(time.time())
            return self._obtener_throughput_sin_lock()
    
    def _obtener_throughput_sin_lock(self) -> float:
        """Return throughput within the time window."""
        total_tareas = len(self.eventos)
        if total_tareas == 0:
            return 0.0
        window_span = self.ventana_segundos
        if self.eventos:
            window_span = max(1.0, min(self.ventana_segundos, time.time() - self.eventos[0][0]))
        return total_tareas / window_span
    
    def obtener_tasa_error(self) -> float:
        """Retorna tasa de error: fallos / total."""
        with self.lock:
            self._prune(time.time())
            return self._obtener_tasa_error_sin_lock()
    
    def _obtener_tasa_error_sin_lock(self) -> float:
        """Return error rate within the time window."""
        total = len(self.eventos)
        if total == 0:
            return 0.0
        fallidas = sum(1 for _, success in self.eventos if not success)
        return fallidas / total
    
    def obtener_estado(self) -> Dict[str, Any]:
        """Return full metrics state within the time window."""
        with self.lock:
            now = time.time()
            self._prune(now)
            total = len(self.eventos)
            tareas_completadas = sum(1 for _, success in self.eventos if success)
            tareas_fallidas = total - tareas_completadas
            return {
                "tareas_completadas": tareas_completadas,
                "tareas_fallidas": tareas_fallidas,
                "total_tareas": total,
                "latencia_promedio": self._obtener_latencia_promedio_sin_lock(),
                "latencia_p95": self._obtener_latencia_p95_sin_lock(),
                "throughput_tareas_por_seg": self._obtener_throughput_sin_lock(),
                "tasa_error": self._obtener_tasa_error_sin_lock(),
                "num_muestras_latencia": len(self.latencias),
                "ventana_segundos": self.ventana_segundos,
            }
    
    def limpiar(self):
        """Clear metrics."""
        with self.lock:
            self.latencias.clear()
            self.eventos.clear()
            self.tareas_completadas = 0
            self.tareas_fallidas = 0
            self.timestamps_inicio.clear()
