"""Sync and async Fibinaci clients."""
from __future__ import annotations

from typing import TYPE_CHECKING, Any

import httpx

from fibinaci._config import FibinaciConfig
from fibinaci._version import __version__
from fibinaci.errors import APIError, AuthError, RateLimitError

if TYPE_CHECKING:
    from fibinaci.resources.annotations import Annotations, AsyncAnnotations
    from fibinaci.resources.api_keys import ApiKeys, AsyncApiKeys
    from fibinaci.resources.benchmarks import AsyncBenchmarks, Benchmarks
    from fibinaci.resources.encryption import AsyncEncryption, Encryption
    from fibinaci.resources.export import AsyncExport, Export
    from fibinaci.resources.integrations import AsyncIntegrations, Integrations
    from fibinaci.resources.outcomes import AsyncOutcomes, Outcomes
    from fibinaci.resources.registry import AsyncRegistry, Registry
    from fibinaci.resources.spans import AsyncSpans, Spans
    from fibinaci.resources.traces import AsyncTraces, Traces
    from fibinaci.resources.usage import AsyncUsage, Usage
    from fibinaci.resources.webhooks import AsyncWebhooks, Webhooks


def _make_headers(config: FibinaciConfig) -> dict[str, str]:
    return {
        "Authorization": f"Bearer {config.api_key}",
        "Content-Type": "application/json",
        "User-Agent": f"fibinaci-python/{__version__}",
    }


def _handle_error(resp: httpx.Response) -> None:
    """Raise typed exception from an error response."""
    try:
        body = resp.json()
        err = body.get("error", {})
    except Exception:
        err = {}

    msg = err.get("message", resp.text)
    kwargs = {
        "error_type": err.get("type"),
        "error_code": err.get("code"),
        "request_id": err.get("request_id"),
    }

    if resp.status_code == 401:
        raise AuthError(msg, status_code=401, **kwargs)
    if resp.status_code == 429:
        retry_after = resp.headers.get("Retry-After")
        raise RateLimitError(
            msg,
            retry_after=int(retry_after) if retry_after else None,
            **kwargs,
        )
    raise APIError(msg, status_code=resp.status_code, **kwargs)


class Fibinaci:
    """Synchronous Fibinaci client."""

    def __init__(
        self,
        api_key: str | None = None,
        base_url: str | None = None,
        **kwargs: Any,
    ) -> None:
        self._config = FibinaciConfig(
            api_key=api_key or "",
            base_url=base_url or "",
            **kwargs,
        )
        self._http = httpx.Client(
            base_url=self._config.base_url,
            headers=_make_headers(self._config),
            timeout=self._config.timeout,
        )
        # Lazy resource init
        self._spans: Spans | None = None
        self._traces: Traces | None = None
        self._outcomes: Outcomes | None = None
        self._annotations: Annotations | None = None
        self._webhooks: Webhooks | None = None
        self._api_keys: ApiKeys | None = None
        self._registry: Registry | None = None
        self._export: Export | None = None
        self._usage: Usage | None = None
        self._benchmarks: Benchmarks | None = None
        self._encryption: Encryption | None = None
        self._integrations: Integrations | None = None

    @property
    def spans(self) -> Spans:
        if self._spans is None:
            from fibinaci.resources.spans import Spans
            self._spans = Spans(self)
        return self._spans

    @property
    def traces(self) -> Traces:
        if self._traces is None:
            from fibinaci.resources.traces import Traces
            self._traces = Traces(self)
        return self._traces

    @property
    def outcomes(self) -> Outcomes:
        if self._outcomes is None:
            from fibinaci.resources.outcomes import Outcomes
            self._outcomes = Outcomes(self)
        return self._outcomes

    @property
    def annotations(self) -> Annotations:
        if self._annotations is None:
            from fibinaci.resources.annotations import Annotations
            self._annotations = Annotations(self)
        return self._annotations

    @property
    def webhooks(self) -> Webhooks:
        if self._webhooks is None:
            from fibinaci.resources.webhooks import Webhooks
            self._webhooks = Webhooks(self)
        return self._webhooks

    @property
    def api_keys(self) -> ApiKeys:
        if self._api_keys is None:
            from fibinaci.resources.api_keys import ApiKeys
            self._api_keys = ApiKeys(self)
        return self._api_keys

    @property
    def registry(self) -> Registry:
        if self._registry is None:
            from fibinaci.resources.registry import Registry
            self._registry = Registry(self)
        return self._registry

    @property
    def export(self) -> Export:
        if self._export is None:
            from fibinaci.resources.export import Export
            self._export = Export(self)
        return self._export

    @property
    def usage(self) -> Usage:
        if self._usage is None:
            from fibinaci.resources.usage import Usage
            self._usage = Usage(self)
        return self._usage

    @property
    def benchmarks(self) -> Benchmarks:
        if self._benchmarks is None:
            from fibinaci.resources.benchmarks import Benchmarks
            self._benchmarks = Benchmarks(self)
        return self._benchmarks

    @property
    def encryption(self) -> Encryption:
        if self._encryption is None:
            from fibinaci.resources.encryption import Encryption
            self._encryption = Encryption(self)
        return self._encryption

    @property
    def integrations(self) -> Integrations:
        if self._integrations is None:
            from fibinaci.resources.integrations import Integrations
            self._integrations = Integrations(self)
        return self._integrations

    def request(self, method: str, path: str, **kwargs: Any) -> Any:
        resp = self._http.request(method, path, **kwargs)
        if resp.status_code >= 400:
            _handle_error(resp)
        return resp.json() if resp.content else None

    def close(self) -> None:
        self._http.close()

    def __enter__(self):
        return self

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


class AsyncFibinaci:
    """Asynchronous Fibinaci client."""

    def __init__(
        self,
        api_key: str | None = None,
        base_url: str | None = None,
        **kwargs: Any,
    ) -> None:
        self._config = FibinaciConfig(
            api_key=api_key or "",
            base_url=base_url or "",
            **kwargs,
        )
        self._http = httpx.AsyncClient(
            base_url=self._config.base_url,
            headers=_make_headers(self._config),
            timeout=self._config.timeout,
        )
        self._spans: AsyncSpans | None = None
        self._traces: AsyncTraces | None = None
        self._outcomes: AsyncOutcomes | None = None
        self._annotations: AsyncAnnotations | None = None
        self._webhooks: AsyncWebhooks | None = None
        self._api_keys: AsyncApiKeys | None = None
        self._registry: AsyncRegistry | None = None
        self._export: AsyncExport | None = None
        self._usage: AsyncUsage | None = None
        self._benchmarks: AsyncBenchmarks | None = None
        self._encryption: AsyncEncryption | None = None
        self._integrations: AsyncIntegrations | None = None

    @property
    def spans(self) -> AsyncSpans:
        if self._spans is None:
            from fibinaci.resources.spans import AsyncSpans
            self._spans = AsyncSpans(self)
        return self._spans

    @property
    def traces(self) -> AsyncTraces:
        if self._traces is None:
            from fibinaci.resources.traces import AsyncTraces
            self._traces = AsyncTraces(self)
        return self._traces

    @property
    def outcomes(self) -> AsyncOutcomes:
        if self._outcomes is None:
            from fibinaci.resources.outcomes import AsyncOutcomes
            self._outcomes = AsyncOutcomes(self)
        return self._outcomes

    @property
    def annotations(self) -> AsyncAnnotations:
        if self._annotations is None:
            from fibinaci.resources.annotations import AsyncAnnotations
            self._annotations = AsyncAnnotations(self)
        return self._annotations

    @property
    def webhooks(self) -> AsyncWebhooks:
        if self._webhooks is None:
            from fibinaci.resources.webhooks import AsyncWebhooks
            self._webhooks = AsyncWebhooks(self)
        return self._webhooks

    @property
    def api_keys(self) -> AsyncApiKeys:
        if self._api_keys is None:
            from fibinaci.resources.api_keys import AsyncApiKeys
            self._api_keys = AsyncApiKeys(self)
        return self._api_keys

    @property
    def registry(self) -> AsyncRegistry:
        if self._registry is None:
            from fibinaci.resources.registry import AsyncRegistry
            self._registry = AsyncRegistry(self)
        return self._registry

    @property
    def export(self) -> AsyncExport:
        if self._export is None:
            from fibinaci.resources.export import AsyncExport
            self._export = AsyncExport(self)
        return self._export

    @property
    def usage(self) -> AsyncUsage:
        if self._usage is None:
            from fibinaci.resources.usage import AsyncUsage
            self._usage = AsyncUsage(self)
        return self._usage

    @property
    def benchmarks(self) -> AsyncBenchmarks:
        if self._benchmarks is None:
            from fibinaci.resources.benchmarks import AsyncBenchmarks
            self._benchmarks = AsyncBenchmarks(self)
        return self._benchmarks

    @property
    def encryption(self) -> AsyncEncryption:
        if self._encryption is None:
            from fibinaci.resources.encryption import AsyncEncryption
            self._encryption = AsyncEncryption(self)
        return self._encryption

    @property
    def integrations(self) -> AsyncIntegrations:
        if self._integrations is None:
            from fibinaci.resources.integrations import AsyncIntegrations
            self._integrations = AsyncIntegrations(self)
        return self._integrations

    async def request(self, method: str, path: str, **kwargs: Any) -> Any:
        resp = await self._http.request(method, path, **kwargs)
        if resp.status_code >= 400:
            _handle_error(resp)
        return resp.json() if resp.content else None

    async def close(self) -> None:
        await self._http.aclose()

    async def __aenter__(self):
        return self

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