"""Retry logic with tenacity for Hikaflow."""

from __future__ import annotations

import logging
import os
from typing import Optional

# Tenacity is optional - provide fallback if not installed
_tenacity_available = False
try:
    from tenacity import (
        RetryError,
        before_sleep_log,
        retry,
        retry_if_exception_type,
        stop_after_attempt,
        stop_after_delay,
        wait_exponential,
        wait_random_exponential,
    )
    # wait_base is optional (not in all versions)
    try:
        from tenacity import wait_base
    except ImportError:
        wait_base = None
    _tenacity_available = True
except ImportError:
    # Provide no-op decorator if tenacity not installed
    def retry(*args, **kwargs):
        def decorator(func):
            return func
        return decorator

    RetryError = Exception
    wait_base = None


logger = logging.getLogger(__name__)


# --- Custom exceptions ---

class RetryableHTTPError(Exception):
    """HTTP error that should be retried (5xx)."""

    def __init__(self, status_code: int, message: str = ""):
        self.status_code = status_code
        super().__init__(f"HTTP {status_code}: {message}")


class RateLimitError(Exception):
    """Rate limit exceeded."""

    def __init__(self, retry_after: Optional[int] = None):
        self.retry_after = retry_after
        super().__init__(f"Rate limited, retry after {retry_after}s")


# --- Configuration from environment ---

def _get_config(key: str, default: int) -> int:
    """Get retry configuration from environment."""
    return int(os.environ.get(key, default))


HTTP_MAX_RETRIES = _get_config("HIKAFLOW_HTTP_MAX_RETRIES", 3)
HTTP_MAX_DELAY = _get_config("HIKAFLOW_HTTP_MAX_DELAY", 60)
LLM_MAX_RETRIES = _get_config("HIKAFLOW_LLM_MAX_RETRIES", 3)
LLM_MAX_DELAY = _get_config("HIKAFLOW_LLM_MAX_DELAY", 60)
DB_MAX_RETRIES = _get_config("HIKAFLOW_DB_MAX_RETRIES", 5)


# --- Logging callbacks ---

def _log_retry_attempt(retry_state):
    """Log retry attempt with context."""
    exc = retry_state.outcome.exception() if retry_state.outcome else None
    wait_time = retry_state.next_action.sleep if retry_state.next_action else None

    logger.warning(
        "Retry attempt %d for %s: %s (waiting %.1fs)",
        retry_state.attempt_number,
        retry_state.fn.__name__ if retry_state.fn else "unknown",
        str(exc) if exc else "no exception",
        wait_time or 0,
        extra={
            "attempt": retry_state.attempt_number,
            "function": retry_state.fn.__name__ if retry_state.fn else "unknown",
            "exception": str(exc) if exc else None,
            "exception_type": type(exc).__name__ if exc else None,
            "wait_seconds": wait_time,
            "elapsed_seconds": retry_state.seconds_since_start,
        }
    )


def _log_llm_retry(retry_state):
    """Log LLM API retry with cost context."""
    exc = retry_state.outcome.exception() if retry_state.outcome else None
    wait_time = retry_state.next_action.sleep if retry_state.next_action else None

    logger.warning(
        "LLM API retry attempt %d: %s",
        retry_state.attempt_number,
        str(exc) if exc else "no exception",
        extra={
            "event": "llm_api_retry",
            "attempt": retry_state.attempt_number,
            "exception": str(exc) if exc else None,
            "wait_seconds": wait_time,
        }
    )


# --- Pre-configured retry decorators ---

if _tenacity_available:
    import httpx

    def http_retry(
        max_attempts: Optional[int] = None,
        max_delay: Optional[int] = None,
    ):
        """Retry decorator for HTTP requests.

        Retries on:
        - Connection errors
        - Timeout errors
        - 5xx server errors (via RetryableHTTPError)

        Uses exponential backoff with jitter.

        Args:
            max_attempts: Maximum retry attempts (default from env or 3).
            max_delay: Maximum delay between retries in seconds.

        Returns:
            Retry decorator.
        """
        max_attempts = max_attempts or HTTP_MAX_RETRIES
        max_delay = max_delay or HTTP_MAX_DELAY

        return retry(
            stop=stop_after_attempt(max_attempts),
            wait=wait_random_exponential(multiplier=1, max=max_delay),
            retry=retry_if_exception_type((
                httpx.ConnectError,
                httpx.TimeoutException,
                httpx.RemoteProtocolError,
                RetryableHTTPError,
            )),
            before_sleep=_log_retry_attempt,
            reraise=True,
        )

    def api_retry(
        max_attempts: Optional[int] = None,
        min_wait: int = 1,
        max_wait: int = 30,
    ):
        """Retry decorator for external API calls.

        Handles rate limits with exponential backoff.

        Args:
            max_attempts: Maximum retry attempts.
            min_wait: Minimum wait between retries in seconds.
            max_wait: Maximum wait between retries in seconds.

        Returns:
            Retry decorator.
        """
        max_attempts = max_attempts or HTTP_MAX_RETRIES

        return retry(
            stop=stop_after_attempt(max_attempts),
            wait=wait_exponential(multiplier=1, min=min_wait, max=max_wait),
            retry=retry_if_exception_type((
                httpx.ConnectError,
                httpx.TimeoutException,
                RateLimitError,
                RetryableHTTPError,
            )),
            before_sleep=_log_retry_attempt,
            reraise=True,
        )

    def database_retry(
        max_attempts: Optional[int] = None,
        max_delay: int = 30,
    ):
        """Retry decorator for database operations.

        Handles transient connection issues.

        Args:
            max_attempts: Maximum retry attempts.
            max_delay: Maximum delay between retries in seconds.

        Returns:
            Retry decorator.
        """
        max_attempts = max_attempts or DB_MAX_RETRIES

        return retry(
            stop=stop_after_attempt(max_attempts),
            wait=wait_exponential(multiplier=0.5, min=0.5, max=max_delay),
            retry=retry_if_exception_type((
                ConnectionError,
                TimeoutError,
            )),
            before_sleep=_log_retry_attempt,
            reraise=True,
        )

    def llm_retry(
        max_attempts: Optional[int] = None,
        max_delay: Optional[int] = None,
    ):
        """Retry decorator for LLM API calls.

        Handles OpenAI/Anthropic rate limits and transient errors.

        Args:
            max_attempts: Maximum retry attempts.
            max_delay: Maximum delay between retries in seconds.

        Returns:
            Retry decorator.
        """
        max_attempts = max_attempts or LLM_MAX_RETRIES
        max_delay = max_delay or LLM_MAX_DELAY

        return retry(
            stop=stop_after_attempt(max_attempts),
            wait=wait_random_exponential(multiplier=2, max=max_delay),
            retry=retry_if_exception_type((
                httpx.TimeoutException,
                RateLimitError,
                RetryableHTTPError,
            )),
            before_sleep=_log_llm_retry,
            reraise=True,
        )

    # Only define WaitFromHeader if wait_base is available
    if wait_base is not None:
        class WaitFromHeader(wait_base):
            """Wait based on Retry-After header from RateLimitError."""

            def __init__(self, fallback: float = 60):
                self.fallback = fallback

            def __call__(self, retry_state):
                exc = retry_state.outcome.exception() if retry_state.outcome else None
                if hasattr(exc, "retry_after") and exc.retry_after:
                    return exc.retry_after
                return self.fallback
    else:
        WaitFromHeader = None

else:
    # Provide no-op decorators when tenacity is not installed
    def http_retry(max_attempts: Optional[int] = None, max_delay: Optional[int] = None):
        def decorator(func):
            return func
        return decorator

    def api_retry(max_attempts: Optional[int] = None, min_wait: int = 1, max_wait: int = 30):
        def decorator(func):
            return func
        return decorator

    def database_retry(max_attempts: Optional[int] = None, max_delay: int = 30):
        def decorator(func):
            return func
        return decorator

    def llm_retry(max_attempts: Optional[int] = None, max_delay: Optional[int] = None):
        def decorator(func):
            return func
        return decorator


def is_available() -> bool:
    """Check if tenacity is available.

    Returns:
        True if tenacity package is installed.
    """
    return _tenacity_available


# --- Response handling utilities ---

def raise_for_status_with_retry(response, max_detail_length: int = 200):
    """Raise appropriate exception for HTTP response status.

    Raises RetryableHTTPError for 5xx errors, RateLimitError for 429,
    and httpx.HTTPStatusError for other errors.

    Args:
        response: httpx.Response object.
        max_detail_length: Maximum length of error detail to include.

    Raises:
        RateLimitError: For 429 responses.
        RetryableHTTPError: For 5xx responses.
        httpx.HTTPStatusError: For other error responses.
    """
    if response.status_code == 429:
        retry_after = response.headers.get("Retry-After")
        raise RateLimitError(
            retry_after=int(retry_after) if retry_after else None
        )

    if 500 <= response.status_code < 600:
        raise RetryableHTTPError(
            status_code=response.status_code,
            message=response.text[:max_detail_length],
        )

    response.raise_for_status()
