"""Retry wrapper for HubQuantumClient.

Retries API calls for up to 15 minutes when the Hub API returns 503
(Service Unavailable) or is unreachable due to connection errors.
All other errors pass through with Fern's default retry behavior.
"""

import logging
import time
from functools import wraps

import httpx

from qhub.api.quantum import HubQuantumClient
from qhub.api.quantum.core.api_error import ApiError

logger = logging.getLogger(__name__)

RETRY_DURATION_SECONDS = 15 * 60
INITIAL_DELAY_SECONDS = 5.0
MAX_DELAY_SECONDS = 60.0


def _is_service_unavailable(error):
    return isinstance(error, ApiError) and error.status_code == 503


def _is_connection_error(error):
    return isinstance(error, (httpx.ConnectError, httpx.ConnectTimeout))


def _with_service_retry(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        deadline = time.monotonic() + RETRY_DURATION_SECONDS
        delay = INITIAL_DELAY_SECONDS
        attempt = 0

        while True:
            try:
                return func(*args, **kwargs)
            except Exception as e:
                if not (_is_service_unavailable(e) or _is_connection_error(e)):
                    raise

                remaining = deadline - time.monotonic()
                if remaining <= 0:
                    logger.error(
                        "Hub API unavailable for %ds, giving up.",
                        RETRY_DURATION_SECONDS,
                    )
                    raise

                wait = min(delay, remaining)
                attempt += 1
                logger.warning(
                    "Hub API unavailable (attempt %d), retrying in %.0fs "
                    "(%.0fs remaining): %s",
                    attempt, wait, remaining, e,
                )
                time.sleep(wait)
                delay = min(delay * 2, MAX_DELAY_SECONDS)

    return wrapper


class _ServiceRetryProxy:
    def __init__(self, sub_client):
        self._sub_client = sub_client

    def __getattr__(self, name):
        if name.startswith("_"):
            raise AttributeError(name)
        attr = getattr(self._sub_client, name)
        if callable(attr):
            return _with_service_retry(attr)
        return attr


class RetryHubClient:
    """HubQuantumClient wrapper that retries on 503 and connection errors.

    All calls are retried for up to 15 minutes with exponential backoff
    (5s, 10s, 20s, 40s, 60s, 60s, ...) when the Hub API is unavailable.
    All other errors use Fern's default retry behavior (2 attempts).

    This class exposes the same interface as HubQuantumClient (backends,
    jobs, sessions, workloads) so it can be used as a drop-in replacement.
    """

    def __init__(self, client: HubQuantumClient):
        self._client = client
        self._backends = _ServiceRetryProxy(client.backends)
        self._jobs = _ServiceRetryProxy(client.jobs)
        self._sessions = _ServiceRetryProxy(client.sessions)
        self._workloads = _ServiceRetryProxy(client.workloads)

    @property
    def backends(self):
        return self._backends

    @property
    def jobs(self):
        return self._jobs

    @property
    def sessions(self):
        return self._sessions

    @property
    def workloads(self):
        return self._workloads
