"""Retry logic handler"""

import time
from typing import Callable, TypeVar, Set
from .exceptions import OctenAPIError, OctenTimeoutError, OctenConnectionError

T = TypeVar("T")


class RetryHandler:
    """Retry handler with exponential backoff strategy

    Attributes:
        max_retries: Maximum number of retries
        base_delay: Base delay time (seconds)
        max_delay: Maximum delay time (seconds)
    """

    RETRYABLE_STATUS_CODES: Set[int] = {
        408,  # Request Timeout
        429,  # Too Many Requests
        500,  # Internal Server Error
        502,  # Bad Gateway
        503,  # Service Unavailable
        504,  # Gateway Timeout
    }

    def __init__(
        self,
        max_retries: int = 3,
        base_delay: float = 1.0,
        max_delay: float = 60.0,
    ):
        self.max_retries = max_retries
        self.base_delay = base_delay
        self.max_delay = max_delay

    def execute(self, func: Callable[[], T]) -> T:
        """Execute function with auto retry on failure

        Args:
            func: Function to execute

        Returns:
            Function return value

        Raises:
            Exception from last attempt
        """
        last_exception = None

        for attempt in range(self.max_retries + 1):
            try:
                return func()
            except (OctenAPIError, OctenTimeoutError, OctenConnectionError) as e:
                last_exception = e

                if not self._should_retry(e, attempt):
                    raise

                if attempt < self.max_retries:
                    delay = self._calculate_delay(attempt, e)
                    time.sleep(delay)

        if last_exception:
            raise last_exception

    def _should_retry(self, error: Exception, attempt: int) -> bool:
        """Determine if should retry

        Args:
            error: Occurred error
            attempt: Current attempt number (starts from 0)

        Returns:
            Whether should retry
        """
        if attempt >= self.max_retries:
            return False

        if isinstance(error, OctenTimeoutError):
            return True

        if isinstance(error, OctenConnectionError):
            return True

        if isinstance(error, OctenAPIError):
            if error.status_code in self.RETRYABLE_STATUS_CODES:
                return True

        return False

    def _calculate_delay(self, attempt: int, error: Exception) -> float:
        """Calculate delay time (exponential backoff)

        Args:
            attempt: Current attempt number (starts from 0)
            error: Occurred error

        Returns:
            Delay time (seconds)
        """
        if isinstance(error, OctenAPIError) and error.status_code == 429:
            if hasattr(error, "retry_after") and error.retry_after:
                return min(error.retry_after, self.max_delay)

        delay = self.base_delay * (2**attempt)

        return min(delay, self.max_delay)


class AsyncRetryHandler(RetryHandler):
    """Async retry handler"""

    async def execute_async(self, func: Callable[[], T]) -> T:
        """Execute function asynchronously with auto retry on failure

        Args:
            func: Async function to execute

        Returns:
            Function return value

        Raises:
            Exception from last attempt
        """
        import asyncio

        last_exception = None

        for attempt in range(self.max_retries + 1):
            try:
                return await func()
            except (OctenAPIError, OctenTimeoutError, OctenConnectionError) as e:
                last_exception = e

                if not self._should_retry(e, attempt):
                    raise

                if attempt < self.max_retries:
                    delay = self._calculate_delay(attempt, e)
                    await asyncio.sleep(delay)

        if last_exception:
            raise last_exception
