"""Base retry configuration and strategies for the Trax platform.

This module provides the foundation for retry logic with exponential backoff,
jitter, and integration with the error classification system.
"""

import asyncio
import random
import time
from dataclasses import dataclass
from enum import Enum
from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union

from ..errors import TraxError, is_retryable_error
from ..logging import get_logger

T = TypeVar('T')


class RetryStrategy(Enum):
    """Retry strategies for different scenarios."""
    EXPONENTIAL = "exponential"
    LINEAR = "linear"
    CONSTANT = "constant"
    FIBONACCI = "fibonacci"


@dataclass
class RetryConfig:
    """Configuration for retry behavior."""
    
    # Basic retry settings
    max_retries: int = 3
    initial_delay: float = 1.0
    max_delay: float = 60.0
    
    # Exponential backoff settings
    exponential_multiplier: float = 2.0
    exponential_base: float = 1.0
    
    # Linear retry settings
    linear_increment: float = 1.0
    
    # Jitter settings
    jitter_factor: float = 0.1  # 10% jitter by default
    jitter_min: float = 0.0
    jitter_max: float = 1.0
    
    # Strategy
    strategy: RetryStrategy = RetryStrategy.EXPONENTIAL
    
    # Error handling
    retryable_exceptions: Optional[List[Type[Exception]]] = None
    non_retryable_exceptions: Optional[List[Type[Exception]]] = None
    
    # Logging
    log_retries: bool = True
    log_success: bool = True
    
    # Circuit breaker settings
    circuit_breaker_enabled: bool = True
    circuit_breaker_threshold: int = 5
    circuit_breaker_timeout: float = 60.0
    
    def __post_init__(self):
        """Post-initialization validation."""
        if self.max_retries < 0:
            raise ValueError("max_retries must be non-negative")
        if self.initial_delay < 0:
            raise ValueError("initial_delay must be non-negative")
        if self.max_delay < self.initial_delay:
            raise ValueError("max_delay must be greater than or equal to initial_delay")
        if self.exponential_multiplier <= 0:
            raise ValueError("exponential_multiplier must be positive")
        if not 0 <= self.jitter_factor <= 1:
            raise ValueError("jitter_factor must be between 0 and 1")


class RetryState:
    """State tracking for retry operations."""
    
    def __init__(self, config: RetryConfig):
        self.config = config
        self.attempt = 0
        self.last_error: Optional[Exception] = None
        self.last_delay: float = 0.0
        self.start_time: float = time.time()
        self.total_delay: float = 0.0
        self.logger = get_logger(__name__)
    
    def increment_attempt(self) -> None:
        """Increment the attempt counter."""
        self.attempt += 1
    
    def set_last_error(self, error: Exception) -> None:
        """Set the last error encountered."""
        self.last_error = error
    
    def set_last_delay(self, delay: float) -> None:
        """Set the last delay used."""
        self.last_delay = delay
        self.total_delay += delay
    
    def get_elapsed_time(self) -> float:
        """Get the total elapsed time since start."""
        return time.time() - self.start_time
    
    def should_retry(self, error: Exception) -> bool:
        """Determine if the operation should be retried."""
        if self.attempt >= self.config.max_retries:
            return False
        
        # Check if error is explicitly non-retryable
        if self.config.non_retryable_exceptions:
            for exc_type in self.config.non_retryable_exceptions:
                if isinstance(error, exc_type):
                    return False
        
        # Check if error is explicitly retryable
        if self.config.retryable_exceptions:
            for exc_type in self.config.retryable_exceptions:
                if isinstance(error, exc_type):
                    return True
        
        # Use error classification system
        return is_retryable_error(error)
    
    def calculate_delay(self) -> float:
        """Calculate the delay for the next retry attempt."""
        if self.attempt == 0:
            return 0.0
        
        if self.config.strategy == RetryStrategy.EXPONENTIAL:
            delay = self.config.initial_delay * (self.config.exponential_multiplier ** (self.attempt - 1))
        elif self.config.strategy == RetryStrategy.LINEAR:
            delay = self.config.initial_delay + (self.config.linear_increment * (self.attempt - 1))
        elif self.config.strategy == RetryStrategy.CONSTANT:
            delay = self.config.initial_delay
        elif self.config.strategy == RetryStrategy.FIBONACCI:
            delay = self.config.initial_delay * self._fibonacci(self.attempt)
        else:
            delay = self.config.initial_delay
        
        # Apply jitter
        if self.config.jitter_factor > 0:
            jitter = random.uniform(
                self.config.jitter_min,
                self.config.jitter_max
            ) * self.config.jitter_factor * delay
            delay += jitter
        
        # Cap at maximum delay
        return min(delay, self.config.max_delay)
    
    def _fibonacci(self, n: int) -> int:
        """Calculate the nth Fibonacci number."""
        if n <= 1:
            return n
        a, b = 0, 1
        for _ in range(2, n + 1):
            a, b = b, a + b
        return b
    
    def log_retry_attempt(self, error: Exception, delay: float) -> None:
        """Log retry attempt information."""
        if not self.config.log_retries:
            return
        
        self.logger.warning(
            f"Retry attempt {self.attempt}/{self.config.max_retries} failed",
            extra={
                "retry_attempt": self.attempt,
                "max_retries": self.config.max_retries,
                "error_type": type(error).__name__,
                "error_message": str(error),
                "delay_seconds": delay,
                "total_elapsed_time": self.get_elapsed_time(),
                "strategy": self.config.strategy.value
            }
        )
    
    def log_retry_success(self, result: Any) -> None:
        """Log successful retry."""
        if not self.config.log_success:
            return
        
        self.logger.info(
            f"Operation succeeded after {self.attempt} retry attempts",
            extra={
                "retry_attempts": self.attempt,
                "total_elapsed_time": self.get_elapsed_time(),
                "total_delay": self.total_delay
            }
        )
    
    def log_max_retries_exceeded(self, error: Exception) -> None:
        """Log when maximum retries are exceeded."""
        self.logger.error(
            f"Operation failed after {self.attempt} retry attempts",
            extra={
                "retry_attempts": self.attempt,
                "max_retries": self.config.max_retries,
                "total_elapsed_time": self.get_elapsed_time(),
                "total_delay": self.total_delay,
                "final_error_type": type(error).__name__,
                "final_error_message": str(error)
            }
        )


class CircuitBreaker:
    """Circuit breaker pattern for preventing repeated failures."""
    
    def __init__(self, threshold: int = 5, timeout: float = 60.0):
        self.threshold = threshold
        self.timeout = timeout
        self.failure_count = 0
        self.last_failure_time = 0.0
        self.state = "CLOSED"  # CLOSED, OPEN, HALF_OPEN
        self.logger = get_logger(__name__)
    
    def can_execute(self) -> bool:
        """Check if the operation can be executed."""
        if self.state == "CLOSED":
            return True
        elif self.state == "OPEN":
            if time.time() - self.last_failure_time >= self.timeout:
                self.state = "HALF_OPEN"
                self.logger.info("Circuit breaker transitioning to HALF_OPEN")
                return True
            return False
        else:  # HALF_OPEN
            return True
    
    def on_success(self) -> None:
        """Handle successful operation."""
        if self.state == "HALF_OPEN":
            self.state = "CLOSED"
            self.failure_count = 0
            self.logger.info("Circuit breaker reset to CLOSED")
    
    def on_failure(self) -> None:
        """Handle failed operation."""
        self.failure_count += 1
        self.last_failure_time = time.time()
        
        if self.state == "CLOSED" and self.failure_count >= self.threshold:
            self.state = "OPEN"
            self.logger.warning(
                f"Circuit breaker opened after {self.failure_count} failures",
                extra={
                    "failure_count": self.failure_count,
                    "threshold": self.threshold,
                    "timeout": self.timeout
                }
            )
        elif self.state == "HALF_OPEN":
            self.state = "OPEN"
            self.logger.warning("Circuit breaker reopened after HALF_OPEN failure")
    
    def get_state(self) -> str:
        """Get the current circuit breaker state."""
        return self.state


# Default retry configurations
DEFAULT_RETRY_CONFIG = RetryConfig()

NETWORK_RETRY_CONFIG = RetryConfig(
    max_retries=3,
    initial_delay=1.0,
    max_delay=30.0,
    exponential_multiplier=2.0,
    jitter_factor=0.1,
    strategy=RetryStrategy.EXPONENTIAL
)

API_RETRY_CONFIG = RetryConfig(
    max_retries=5,
    initial_delay=2.0,
    max_delay=60.0,
    exponential_multiplier=2.0,
    jitter_factor=0.2,
    strategy=RetryStrategy.EXPONENTIAL
)

PROCESSING_RETRY_CONFIG = RetryConfig(
    max_retries=2,
    initial_delay=5.0,
    max_delay=30.0,
    exponential_multiplier=1.5,
    jitter_factor=0.05,
    strategy=RetryStrategy.EXPONENTIAL
)
