"""Health monitoring and failover management for pricing providers."""

import time
from typing import Dict, List, Optional, Any
from enum import Enum
from dataclasses import dataclass, field
from datetime import datetime, timedelta
from collections import deque
from src.core.logger import get_logger

logger = get_logger(__name__)


class HealthStatus(Enum):
    """Provider health status."""
    HEALTHY = "healthy"
    DEGRADED = "degraded"
    UNHEALTHY = "unhealthy"
    UNKNOWN = "unknown"


@dataclass
class HealthMetrics:
    """Health metrics for a provider."""
    status: HealthStatus = HealthStatus.UNKNOWN
    last_check: Optional[datetime] = None
    last_success: Optional[datetime] = None
    last_failure: Optional[datetime] = None
    success_count: int = 0
    failure_count: int = 0
    response_times: deque = field(default_factory=lambda: deque(maxlen=100))
    consecutive_failures: int = 0
    circuit_breaker_open: bool = False
    circuit_breaker_opened_at: Optional[datetime] = None
    
    def record_success(self, response_time: float):
        """Record a successful operation.
        
        Args:
            response_time: Response time in seconds
        """
        self.status = HealthStatus.HEALTHY
        self.last_check = datetime.utcnow()
        self.last_success = datetime.utcnow()
        self.success_count += 1
        self.response_times.append(response_time)
        self.consecutive_failures = 0
        self.circuit_breaker_open = False
        self.circuit_breaker_opened_at = None
    
    def record_failure(self):
        """Record a failed operation."""
        self.last_check = datetime.utcnow()
        self.last_failure = datetime.utcnow()
        self.failure_count += 1
        self.consecutive_failures += 1
        
        # Open circuit breaker after 5 consecutive failures
        if self.consecutive_failures >= 5:
            if not self.circuit_breaker_open:
                self.circuit_breaker_open = True
                self.circuit_breaker_opened_at = datetime.utcnow()
                logger.warning(f"Circuit breaker opened after {self.consecutive_failures} failures")
        
        # Update status based on failure rate
        total = self.success_count + self.failure_count
        if total > 0:
            failure_rate = self.failure_count / total
            if failure_rate > 0.5:
                self.status = HealthStatus.UNHEALTHY
            elif failure_rate > 0.2:
                self.status = HealthStatus.DEGRADED
    
    def get_avg_response_time(self) -> float:
        """Get average response time.
        
        Returns:
            Average response time in seconds, or 0.0 if no data
        """
        if not self.response_times:
            return 0.0
        return sum(self.response_times) / len(self.response_times)
    
    def should_attempt(self, circuit_breaker_timeout: int = 60) -> bool:
        """Check if we should attempt to use this provider.
        
        Args:
            circuit_breaker_timeout: Seconds to wait before retrying after circuit breaker opens
            
        Returns:
            True if we should attempt, False otherwise
        """
        if not self.circuit_breaker_open:
            return True
        
        # Check if timeout has passed
        if self.circuit_breaker_opened_at:
            elapsed = (datetime.utcnow() - self.circuit_breaker_opened_at).total_seconds()
            if elapsed >= circuit_breaker_timeout:
                # Half-open state: allow one attempt
                logger.info("Circuit breaker half-open, allowing attempt")
                return True
        
        return False
    
    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary for API responses."""
        return {
            'status': self.status.value,
            'last_check': self.last_check.isoformat() if self.last_check else None,
            'last_success': self.last_success.isoformat() if self.last_success else None,
            'last_failure': self.last_failure.isoformat() if self.last_failure else None,
            'success_count': self.success_count,
            'failure_count': self.failure_count,
            'avg_response_time': round(self.get_avg_response_time(), 3),
            'consecutive_failures': self.consecutive_failures,
            'circuit_breaker_open': self.circuit_breaker_open,
            'circuit_breaker_opened_at': (
                self.circuit_breaker_opened_at.isoformat()
                if self.circuit_breaker_opened_at else None
            ),
        }


class HealthMonitor:
    """Monitors health of pricing providers and manages failover."""
    
    def __init__(
        self,
        circuit_breaker_timeout: int = 60,
        min_success_rate: float = 0.8,
        max_avg_response_time: float = 5.0
    ):
        """Initialize health monitor.
        
        Args:
            circuit_breaker_timeout: Seconds to wait before retrying after circuit breaker opens
            min_success_rate: Minimum success rate to be considered healthy (0.0-1.0)
            max_avg_response_time: Maximum average response time in seconds to be considered healthy
        """
        self.circuit_breaker_timeout = circuit_breaker_timeout
        self.min_success_rate = min_success_rate
        self.max_avg_response_time = max_avg_response_time
        
        self._metrics: Dict[str, HealthMetrics] = {}
        self.logger = get_logger(__name__)
    
    def record_success(self, provider_name: str, response_time: float):
        """Record a successful operation for a provider.
        
        Args:
            provider_name: Name of the provider
            response_time: Response time in seconds
        """
        if provider_name not in self._metrics:
            self._metrics[provider_name] = HealthMetrics()
        
        self._metrics[provider_name].record_success(response_time)
        self.logger.debug(f"Recorded success for {provider_name} ({response_time:.3f}s)")
    
    def record_failure(self, provider_name: str):
        """Record a failed operation for a provider.
        
        Args:
            provider_name: Name of the provider
        """
        if provider_name not in self._metrics:
            self._metrics[provider_name] = HealthMetrics()
        
        self._metrics[provider_name].record_failure()
        self.logger.warning(f"Recorded failure for {provider_name} "
                          f"(consecutive: {self._metrics[provider_name].consecutive_failures})")
    
    def is_healthy(self, provider_name: str) -> bool:
        """Check if a provider is healthy.
        
        Args:
            provider_name: Name of the provider
            
        Returns:
            True if provider is healthy
        """
        if provider_name not in self._metrics:
            return True  # Assume healthy if no metrics yet
        
        metrics = self._metrics[provider_name]
        
        # Check circuit breaker
        if not metrics.should_attempt(self.circuit_breaker_timeout):
            return False
        
        # Check status
        if metrics.status == HealthStatus.UNHEALTHY:
            return False
        
        # Check success rate
        total = metrics.success_count + metrics.failure_count
        if total > 10:  # Need minimum data points
            success_rate = metrics.success_count / total
            if success_rate < self.min_success_rate:
                return False
        
        # Check response time
        if metrics.response_times:
            avg_response_time = metrics.get_avg_response_time()
            if avg_response_time > self.max_avg_response_time:
                return False
        
        return True
    
    def get_health_status(self, provider_name: str) -> HealthStatus:
        """Get health status for a provider.
        
        Args:
            provider_name: Name of the provider
            
        Returns:
            Health status
        """
        if provider_name not in self._metrics:
            return HealthStatus.UNKNOWN
        
        return self._metrics[provider_name].status
    
    def get_metrics(self, provider_name: str) -> Optional[HealthMetrics]:
        """Get health metrics for a provider.
        
        Args:
            provider_name: Name of the provider
            
        Returns:
            Health metrics or None if not found
        """
        return self._metrics.get(provider_name)
    
    def get_all_metrics(self) -> Dict[str, Dict[str, Any]]:
        """Get all provider metrics.
        
        Returns:
            Dictionary mapping provider names to their metrics
        """
        return {
            name: metrics.to_dict()
            for name, metrics in self._metrics.items()
        }
    
    def select_healthiest(self, provider_names: List[str]) -> Optional[str]:
        """Select the healthiest provider from a list.
        
        Args:
            provider_names: List of provider names to choose from
            
        Returns:
            Name of healthiest provider, or None if none are healthy
        """
        healthy_providers = [
            name for name in provider_names
            if self.is_healthy(name)
        ]
        
        if not healthy_providers:
            return None
        
        # Sort by health metrics (better providers first)
        def health_score(name: str) -> tuple:
            metrics = self._metrics.get(name)
            if not metrics:
                return (1, 0, 0)  # Unknown providers get lowest priority
            
            # Score: (status_weight, -avg_response_time, success_rate)
            status_weights = {
                HealthStatus.HEALTHY: 3,
                HealthStatus.DEGRADED: 2,
                HealthStatus.UNHEALTHY: 1,
                HealthStatus.UNKNOWN: 0,
            }
            
            success_rate = (
                metrics.success_count / (metrics.success_count + metrics.failure_count)
                if (metrics.success_count + metrics.failure_count) > 0
                else 0.0
            )
            
            return (
                status_weights.get(metrics.status, 0),
                -metrics.get_avg_response_time(),
                success_rate
            )
        
        sorted_providers = sorted(healthy_providers, key=health_score, reverse=True)
        return sorted_providers[0] if sorted_providers else None
    
    def reset_circuit_breaker(self, provider_name: str):
        """Manually reset circuit breaker for a provider.
        
        Args:
            provider_name: Name of the provider
        """
        if provider_name in self._metrics:
            self._metrics[provider_name].circuit_breaker_open = False
            self._metrics[provider_name].circuit_breaker_opened_at = None
            self._metrics[provider_name].consecutive_failures = 0
            self.logger.info(f"Circuit breaker reset for {provider_name}")
    
    def reset_metrics(self, provider_name: Optional[str] = None):
        """Reset metrics for a provider or all providers.
        
        Args:
            provider_name: Name of provider to reset, or None to reset all
        """
        if provider_name:
            if provider_name in self._metrics:
                del self._metrics[provider_name]
                self.logger.info(f"Reset metrics for {provider_name}")
        else:
            self._metrics.clear()
            self.logger.info("Reset all provider metrics")
