"""RunBlob async client."""
from __future__ import annotations

import json
import logging
import uuid
from typing import Any, TypeVar, Type, overload

import httpx
from pydantic import BaseModel

from ._config import ClientConfig, resolve_api_key
from ._errors import (
    APIConnectionError,
    APITimeoutError,
    CircuitBreakerOpenError,
    raise_for_status,
)
from ._internal.retry import execute_with_retry
from ._internal.circuit_breaker import CircuitBreaker
from ._internal.rate_limiter import TokenBucketRateLimiter
from ._internal.cache import AsyncTTLCache

logger = logging.getLogger("runblob")

T = TypeVar("T", bound=BaseModel)


class RunBlob:
    """Async RunBlob API client.

    Args:
        api_key: Your RunBlob API key. Falls back to RUNBLOB_API_KEY env var.
        base_url: API base URL override (useful for testing/staging).
        timeout: Request timeout in seconds (default 120).
        max_retries: Max automatic retries on transient errors (default 3).
        max_connections: Connection pool size (default 100).
        rate_limit_rps: Client-side rate limit in requests/sec (default 50).
        circuit_breaker_threshold: Consecutive failures to trip breaker (default 5).
        circuit_breaker_timeout: Seconds before breaker tries half-open (default 60).
        cache_ttl: Cache TTL for GET responses in seconds (default 30).
    """

    _config: ClientConfig
    _http: httpx.AsyncClient
    _circuit_breaker: CircuitBreaker
    _rate_limiter: TokenBucketRateLimiter
    _cache: AsyncTTLCache

    def __init__(
        self,
        *,
        api_key: str | None = None,
        base_url: str | None = None,
        timeout: float = 120.0,
        max_retries: int = 3,
        max_connections: int = 100,
        max_keepalive_connections: int = 20,
        keepalive_expiry: float = 30.0,
        rate_limit_rps: float = 50.0,
        circuit_breaker_threshold: int = 5,
        circuit_breaker_timeout: float = 60.0,
        cache_ttl: float = 30.0,
        cache_max_size: int = 256,
    ) -> None:
        resolved_key = resolve_api_key(api_key)

        self._config = ClientConfig(
            api_key=resolved_key,
            base_url=base_url or "https://platform.runblob.io",
            timeout=timeout,
            max_retries=max_retries,
            max_connections=max_connections,
            max_keepalive_connections=max_keepalive_connections,
            keepalive_expiry=keepalive_expiry,
            rate_limit_rps=rate_limit_rps,
            circuit_breaker_threshold=circuit_breaker_threshold,
            circuit_breaker_timeout=circuit_breaker_timeout,
            cache_ttl=cache_ttl,
            cache_max_size=cache_max_size,
        )

        self._http = httpx.AsyncClient(
            limits=httpx.Limits(
                max_connections=max_connections,
                max_keepalive_connections=max_keepalive_connections,
                keepalive_expiry=keepalive_expiry,
            ),
            timeout=httpx.Timeout(timeout, connect=10.0),
            http2=True,
        )

        self._circuit_breaker = CircuitBreaker(
            threshold=circuit_breaker_threshold,
            timeout=circuit_breaker_timeout,
        )
        self._rate_limiter = TokenBucketRateLimiter(rps=rate_limit_rps)
        self._cache = AsyncTTLCache(ttl=cache_ttl, max_size=cache_max_size)

        from .resources.nanobanana import NanoBanana
        from .resources.kling import Kling
        from .resources.veo import Veo
        from .resources.chatgpt import ChatGPT
        self.nanobanana = NanoBanana(self)
        self.kling = Kling(self)
        self.veo = Veo(self)
        self.chatgpt = ChatGPT(self)

    def _build_headers(self, *, idempotency_key: str | None = None) -> dict[str, str]:
        """Build request headers with auth, SDK version, and request tracing."""
        request_id = uuid.uuid4().hex[:16]
        headers = {
            "Authorization": f"Bearer {self._config.api_key}",
            "Content-Type": "application/json",
            "User-Agent": f"runblob-python/{self._config.version}",
            "X-SDK-Version": self._config.version,
            "X-Request-ID": request_id,
        }
        if idempotency_key:
            headers["X-Idempotency-Key"] = idempotency_key
        return headers

    async def request(
        self,
        method: str,
        path: str,
        *,
        response_model: type[T],
        body: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None,
        idempotency_key: str | None = None,
        use_cache: bool = False,
    ) -> T:
        """Execute an API request with retry, rate limiting, and caching."""
        url = f"{self._config.base_url.rstrip('/')}/{path.lstrip('/')}"

        await self._rate_limiter.acquire()

        if not self._circuit_breaker.is_available:
            raise CircuitBreakerOpenError(
                f"Circuit breaker is OPEN after {self._circuit_breaker.total_trips} trips. "
                f"Requests are being rejected to protect the system."
            )

        cache_key: str | None = None
        if use_cache and method.upper() == "GET":
            cache_key = f"{method}:{url}:{json.dumps(params, sort_keys=True) if params else ''}"
            cached = await self._cache.get(cache_key)
            if cached is not None:
                logger.debug("Cache HIT: %s %s", method, path)
                return cached  # type: ignore[return-value]

        headers = self._build_headers(idempotency_key=idempotency_key)
        request_kwargs: dict[str, Any] = {
            "method": method,
            "url": url,
            "headers": headers,
        }
        if body is not None:
            request_kwargs["json"] = body
        if params is not None:
            request_kwargs["params"] = {k: v for k, v in params.items() if v is not None}

        try:
            response = await execute_with_retry(
                self._http,
                request_kwargs,
                max_retries=self._config.max_retries,
            )
        except httpx.TimeoutException as exc:
            await self._circuit_breaker.on_failure()
            raise APITimeoutError(f"Request timed out: {method} {path}") from exc
        except httpx.HTTPError as exc:
            await self._circuit_breaker.on_failure()
            raise APIConnectionError(f"Connection error: {method} {path}: {exc}") from exc

        if response.is_success:
            await self._circuit_breaker.on_success()
        else:
            if response.status_code >= 500:
                await self._circuit_breaker.on_failure()
            raise_for_status(response)

        result = response_model.model_validate(response.json())

        if cache_key is not None:
            await self._cache.set(cache_key, result)

        return result

    async def request_raw(
        self,
        method: str,
        path: str,
        *,
        body: dict[str, Any] | None = None,
        params: dict[str, Any] | None = None,
    ) -> httpx.Response:
        """Execute a request and return raw httpx.Response (for list endpoints etc.)."""
        url = f"{self._config.base_url.rstrip('/')}/{path.lstrip('/')}"

        await self._rate_limiter.acquire()

        if not self._circuit_breaker.is_available:
            raise CircuitBreakerOpenError(
                "Circuit breaker is OPEN. Requests rejected."
            )

        headers = self._build_headers()
        request_kwargs: dict[str, Any] = {
            "method": method,
            "url": url,
            "headers": headers,
        }
        if body is not None:
            request_kwargs["json"] = body
        if params is not None:
            request_kwargs["params"] = {k: v for k, v in params.items() if v is not None}

        try:
            response = await execute_with_retry(
                self._http,
                request_kwargs,
                max_retries=self._config.max_retries,
            )
        except httpx.TimeoutException as exc:
            await self._circuit_breaker.on_failure()
            raise APITimeoutError(f"Request timed out: {method} {path}") from exc
        except httpx.HTTPError as exc:
            await self._circuit_breaker.on_failure()
            raise APIConnectionError(f"Connection error: {method} {path}: {exc}") from exc

        if response.is_success:
            await self._circuit_breaker.on_success()
        else:
            if response.status_code >= 500:
                await self._circuit_breaker.on_failure()
            raise_for_status(response)

        return response

    async def close(self) -> None:
        """Close the underlying HTTP client and release connections."""
        await self._http.aclose()
        await self._cache.clear()

    async def __aenter__(self) -> RunBlob:
        return self

    async def __aexit__(self, *args: Any) -> None:
        await self.close()

    @property
    def config(self) -> ClientConfig:
        """Read-only access to client configuration."""
        return self._config

    @property
    def circuit_breaker(self) -> CircuitBreaker:
        """Access circuit breaker for monitoring."""
        return self._circuit_breaker

    @property
    def cache(self) -> AsyncTTLCache:
        """Access cache for manual invalidation."""
        return self._cache
