"""
engine.retry — Retry policy + Circuit breaker.

Prevents LLM API failures from cascading.
"""
from __future__ import annotations
import time
from typing import List


class RetryPolicy:
    def __init__(self, max_attempts: int = 3, base_delay: float = 1.0,
                 max_delay: float = 60.0, exponential_base: float = 2.0,
                 retryable_errors: List[str] = None):
        self.max_attempts = max_attempts
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.exponential_base = exponential_base
        self.retryable_errors = retryable_errors or ["RateLimitError", "TimeoutError", "ServiceUnavailable"]

    def should_retry(self, error: Exception, attempt: int) -> bool:
        if attempt >= self.max_attempts:
            return False
        error_type = type(error).__name__
        return any(rt in error_type or rt in str(error) for rt in self.retryable_errors)

    def get_delay(self, attempt: int) -> float:
        delay = self.base_delay * (self.exponential_base ** attempt)
        return min(delay, self.max_delay)


class CircuitBreaker:
    """
    CLOSED  → normal
    OPEN    → fast fail (don't call LLM)
    HALF    → tentative, allow 1 request
    """
    CLOSED = "closed"
    OPEN = "open"
    HALF = "half_open"

    def __init__(self, failure_threshold: int = 5, recovery_timeout: float = 30.0):
        self.failure_threshold = failure_threshold
        self.recovery_timeout = recovery_timeout
        self.state = self.CLOSED
        self.failure_count = 0
        self.last_failure_time = 0.0

    def allow_request(self) -> bool:
        if self.state == self.CLOSED:
            return True
        if self.state == self.OPEN:
            if time.time() - self.last_failure_time > self.recovery_timeout:
                self.state = self.HALF
                return True
            return False
        if self.state == self.HALF:
            return True
        return False

    def record_success(self):
        self.failure_count = 0
        self.state = self.CLOSED

    def record_failure(self):
        self.failure_count += 1
        self.last_failure_time = time.time()
        if self.failure_count >= self.failure_threshold:
            self.state = self.OPEN
        elif self.state == self.HALF:
            self.state = self.OPEN
