"""
Main client classes for the Kronical SDK.

KronicalClient — synchronous (default)
AsyncKronicalClient — async/await
"""
import logging
import time
from typing import Any, Dict, Optional

import httpx

from kronical.exceptions import raise_for_status, RateLimitError
from kronical.feed import FeedResource, AsyncFeedResource
from kronical.pulse import PulseResource, AsyncPulseResource
from kronical.enrichment import EnrichmentResource, AsyncEnrichmentResource
from kronical.smart_columns import SmartColumnsResource, AsyncSmartColumnsResource
from kronical.watchlist import WatchlistResource, AsyncWatchlistResource
from kronical.usage import UsageResource, AsyncUsageResource

logger = logging.getLogger("kronical")

_DEFAULT_TIMEOUT = 30.0
_MAX_RETRIES = 3
_RETRY_BACKOFF_BASE = 1.0


def _build_headers(api_key: str) -> Dict[str, str]:
    return {
        "Authorization": f"Bearer {api_key}",
        "Content-Type": "application/json",
        "User-Agent": "kronical-python/0.1.2",
    }


class KronicalClient:
    """
    Synchronous Kronical API client.

    Usage:
        client = KronicalClient(api_key="kron_hf_...", base_url="https://api.kronical.ai")
        feed = client.feed.get(limit=10)
    """

    def __init__(
        self,
        api_key: str,
        base_url: str,
        timeout: float = _DEFAULT_TIMEOUT,
        max_retries: int = _MAX_RETRIES,
    ):
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._max_retries = max_retries
        self._http = httpx.Client(
            base_url=self._base_url,
            headers=_build_headers(api_key),
            timeout=timeout,
        )

        # Resource endpoints
        self.feed = FeedResource(self)
        self.pulse = PulseResource(self)
        self.enrichment = EnrichmentResource(self)
        self.smart_columns = SmartColumnsResource(self)
        self.watchlist = WatchlistResource(self)
        self.usage = UsageResource(self)

    def request(
        self,
        method: str,
        path: str,
        *,
        json: Optional[Dict[str, Any]] = None,
        params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> Dict[str, Any]:
        """Make an HTTP request with automatic retry on 429."""
        for attempt in range(self._max_retries + 1):
            try:
                response = self._http.request(
                    method,
                    path,
                    json=json,
                    params=params,
                    headers=headers,
                )
            except httpx.HTTPError as exc:
                if attempt < self._max_retries:
                    time.sleep(_RETRY_BACKOFF_BASE * (2 ** attempt))
                    continue
                raise

            if response.status_code == 429 and attempt < self._max_retries:
                retry_after = _parse_retry_after(response)
                logger.info(f"Rate limited, retrying in {retry_after}s (attempt {attempt + 1})")
                time.sleep(retry_after)
                continue

            if response.status_code >= 400:
                raise_for_status(response.status_code, response.json())

            return response.json()

        # Should not reach here, but just in case
        raise_for_status(response.status_code, response.json())

    def health(self) -> Dict[str, Any]:
        """GET /health — check service status (no auth required)."""
        response = self._http.get("/health")
        if response.status_code >= 400:
            raise_for_status(response.status_code, response.json())
        return response.json()

    def close(self) -> None:
        """Close the underlying HTTP client."""
        self._http.close()

    def __enter__(self):
        return self

    def __exit__(self, *args):
        self.close()


class AsyncKronicalClient:
    """
    Asynchronous Kronical API client.

    Usage:
        async with AsyncKronicalClient(api_key="kron_hf_...", base_url="https://api.kronical.ai") as client:
            feed = await client.feed.get(limit=10)
    """

    def __init__(
        self,
        api_key: str,
        base_url: str,
        timeout: float = _DEFAULT_TIMEOUT,
        max_retries: int = _MAX_RETRIES,
    ):
        self._api_key = api_key
        self._base_url = base_url.rstrip("/")
        self._max_retries = max_retries
        self._http = httpx.AsyncClient(
            base_url=self._base_url,
            headers=_build_headers(api_key),
            timeout=timeout,
        )

        # Resource endpoints
        self.feed = AsyncFeedResource(self)
        self.pulse = AsyncPulseResource(self)
        self.enrichment = AsyncEnrichmentResource(self)
        self.smart_columns = AsyncSmartColumnsResource(self)
        self.watchlist = AsyncWatchlistResource(self)
        self.usage = AsyncUsageResource(self)

    async def request(
        self,
        method: str,
        path: str,
        *,
        json: Optional[Dict[str, Any]] = None,
        params: Optional[Dict[str, Any]] = None,
        headers: Optional[Dict[str, str]] = None,
    ) -> Dict[str, Any]:
        """Make an HTTP request with automatic retry on 429."""
        import asyncio

        for attempt in range(self._max_retries + 1):
            try:
                response = await self._http.request(
                    method,
                    path,
                    json=json,
                    params=params,
                    headers=headers,
                )
            except httpx.HTTPError:
                if attempt < self._max_retries:
                    await asyncio.sleep(_RETRY_BACKOFF_BASE * (2 ** attempt))
                    continue
                raise

            if response.status_code == 429 and attempt < self._max_retries:
                retry_after = _parse_retry_after(response)
                logger.info(f"Rate limited, retrying in {retry_after}s (attempt {attempt + 1})")
                await asyncio.sleep(retry_after)
                continue

            if response.status_code >= 400:
                raise_for_status(response.status_code, response.json())

            return response.json()

        raise_for_status(response.status_code, response.json())

    async def health(self) -> Dict[str, Any]:
        """GET /health — check service status (no auth required)."""
        response = await self._http.get("/health")
        if response.status_code >= 400:
            raise_for_status(response.status_code, response.json())
        return response.json()

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

    async def __aenter__(self):
        return self

    async def __aexit__(self, *args):
        await self.close()


def _parse_retry_after(response: httpx.Response) -> float:
    """Parse Retry-After header, defaulting to exponential backoff."""
    retry_after = response.headers.get("Retry-After")
    if retry_after:
        try:
            return float(retry_after)
        except ValueError:
            pass
    return _RETRY_BACKOFF_BASE
