"""Retry strategies for fault tolerance."""

import random
from abc import ABC, abstractmethod


class RetryStrategy(ABC):
    """Base class for retry strategies."""
    
    @abstractmethod
    def get_delay(self, attempt: int, max_attempts: int) -> float:
        """Calculate delay (seconds) before next retry attempt."""
        pass
    
    @abstractmethod
    def should_retry(self, exception: Exception, attempt: int, max_attempts: int) -> bool:
        """Determine if retry should be attempted."""
        pass


class ExponentialBackoffStrategy(RetryStrategy):
    """Exponential backoff with optional jitter."""
    
    def __init__(self, base_delay: float = 1.0, max_delay: float = 60.0, jitter: bool = True):
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.jitter = jitter
    
    def get_delay(self, attempt: int, max_attempts: int) -> float:
        delay = min(self.base_delay * (2 ** attempt), self.max_delay)
        if self.jitter:
            delay = delay * (0.5 + random.random())
        return delay
    
    def should_retry(self, exception: Exception, attempt: int, max_attempts: int) -> bool:
        return attempt < max_attempts


class LinearBackoffStrategy(RetryStrategy):
    """Linear backoff strategy."""
    
    def __init__(self, increment: float = 1.0, max_delay: float = 60.0):
        self.increment = increment
        self.max_delay = max_delay
    
    def get_delay(self, attempt: int, max_attempts: int) -> float:
        return min(self.increment * (attempt + 1), self.max_delay)
    
    def should_retry(self, exception: Exception, attempt: int, max_attempts: int) -> bool:
        return attempt < max_attempts


class FixedDelayStrategy(RetryStrategy):
    """Fixed delay between retries."""
    
    def __init__(self, delay: float = 2.0):
        self.delay = delay
    
    def get_delay(self, attempt: int, max_attempts: int) -> float:
        return self.delay
    
    def should_retry(self, exception: Exception, attempt: int, max_attempts: int) -> bool:
        return attempt < max_attempts
