"""Rulebook / AsyncRulebook — main SDK client classes."""

from __future__ import annotations

import os
from typing import Any, Mapping, Union

import httpx
from typing_extensions import Self

from rulebook import resources
from rulebook._base_client import AsyncAPIClient, SyncAPIClient
from rulebook._constants import BASE_URL, API_KEY_ENV_VAR, DEFAULT_MAX_RETRIES
from rulebook._exceptions import RulebookError
from rulebook._types import NOT_GIVEN, NotGiven, is_given

__all__ = ["Rulebook", "AsyncRulebook"]


class Rulebook(SyncAPIClient):
    """Synchronous client for the Rulebook Company API.

    Usage::

        from rulebook import Rulebook

        client = Rulebook(api_key="rb-...")
        exchanges = client.exchanges.list()
        detail = client.exchanges.retrieve("NYSE")

    Or as a context manager::

        with Rulebook(api_key="rb-...") as client:
            exchanges = client.exchanges.list()
    """

    exchanges: resources.Exchanges
    fee_schedule_results: resources.FeeScheduleResults
    with_raw_response: RulebookWithRawResponse

    # client options
    api_key: str

    def __init__(
        self,
        *,
        api_key: str | None = None,
        base_url: str | None = None,
        timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.Client | None = None,
    ) -> None:
        """Construct a new synchronous Rulebook client instance.

        This automatically infers the API key from the ``RULEBOOK_API_KEY``
        environment variable if not provided.

        Args:
            api_key: Your Rulebook API key. Falls back to ``RULEBOOK_API_KEY`` env var.
            base_url: Override the default base URL.
            timeout: Request timeout in seconds (default: 30).
            max_retries: Maximum number of retries for failed requests (default: 2).
            default_headers: Headers to include in every request.
            default_query: Query parameters to include in every request.
            http_client: Custom ``httpx.Client`` instance.
        """
        if api_key is None:
            api_key = os.environ.get(API_KEY_ENV_VAR)
        if api_key is None:
            raise RulebookError(
                "The api_key client option must be set either by passing api_key to the client "
                f"or by setting the {API_KEY_ENV_VAR} environment variable"
            )
        self.api_key = api_key

        if base_url is None:
            base_url = os.environ.get("RULEBOOK_BASE_URL", BASE_URL)

        super().__init__(
            api_key=api_key,
            base_url=base_url,
            max_retries=max_retries,
            timeout=timeout,
            custom_headers=default_headers,
            custom_query=default_query,
            http_client=http_client,
        )

        self.exchanges = resources.Exchanges(self)
        self.fee_schedule_results = resources.FeeScheduleResults(self)
        self.with_raw_response = RulebookWithRawResponse(self)

    def copy(
        self,
        *,
        api_key: str | None = None,
        base_url: str | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int | NotGiven = NOT_GIVEN,  # type: ignore[assignment]
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
    ) -> Self:
        """Create a new client instance re-using the same options with optional overrides."""
        return self.__class__(
            api_key=api_key or self.api_key,
            base_url=base_url or self._base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers={**self._custom_headers, **(default_headers or {})},
            default_query={**self._custom_query, **(default_query or {})},
        )

    with_options = copy
    """Alias for :meth:`copy` — create a new client with overridden settings."""

    def __enter__(self) -> Rulebook:
        return self

    def __exit__(self, *args: Any) -> None:
        self.close()


class AsyncRulebook(AsyncAPIClient):
    """Asynchronous client for the Rulebook Company API.

    Usage::

        from rulebook import AsyncRulebook

        client = AsyncRulebook(api_key="rb-...")
        exchanges = await client.exchanges.list()
        detail = await client.exchanges.retrieve("NYSE")

    Or as an async context manager::

        async with AsyncRulebook(api_key="rb-...") as client:
            exchanges = await client.exchanges.list()
    """

    exchanges: resources.AsyncExchanges
    fee_schedule_results: resources.AsyncFeeScheduleResults
    with_raw_response: AsyncRulebookWithRawResponse

    # client options
    api_key: str

    def __init__(
        self,
        *,
        api_key: str | None = None,
        base_url: str | None = None,
        timeout: Union[float, httpx.Timeout, None, NotGiven] = NOT_GIVEN,
        max_retries: int = DEFAULT_MAX_RETRIES,
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
        http_client: httpx.AsyncClient | None = None,
    ) -> None:
        """Construct a new async Rulebook client instance.

        Args:
            api_key: Your Rulebook API key. Falls back to ``RULEBOOK_API_KEY`` env var.
            base_url: Override the default base URL.
            timeout: Request timeout in seconds (default: 30).
            max_retries: Maximum number of retries for failed requests (default: 2).
            default_headers: Headers to include in every request.
            default_query: Query parameters to include in every request.
            http_client: Custom ``httpx.AsyncClient`` instance.
        """
        if api_key is None:
            api_key = os.environ.get(API_KEY_ENV_VAR)
        if api_key is None:
            raise RulebookError(
                "The api_key client option must be set either by passing api_key to the client "
                f"or by setting the {API_KEY_ENV_VAR} environment variable"
            )
        self.api_key = api_key

        if base_url is None:
            base_url = os.environ.get("RULEBOOK_BASE_URL", BASE_URL)

        super().__init__(
            api_key=api_key,
            base_url=base_url,
            max_retries=max_retries,
            timeout=timeout,
            custom_headers=default_headers,
            custom_query=default_query,
            http_client=http_client,
        )

        self.exchanges = resources.AsyncExchanges(self)
        self.fee_schedule_results = resources.AsyncFeeScheduleResults(self)
        self.with_raw_response = AsyncRulebookWithRawResponse(self)

    def copy(
        self,
        *,
        api_key: str | None = None,
        base_url: str | None = None,
        timeout: float | httpx.Timeout | None | NotGiven = NOT_GIVEN,
        max_retries: int | NotGiven = NOT_GIVEN,  # type: ignore[assignment]
        default_headers: Mapping[str, str] | None = None,
        default_query: Mapping[str, object] | None = None,
    ) -> Self:
        """Create a new client instance re-using the same options with optional overrides."""
        return self.__class__(
            api_key=api_key or self.api_key,
            base_url=base_url or self._base_url,
            timeout=self.timeout if isinstance(timeout, NotGiven) else timeout,
            max_retries=max_retries if is_given(max_retries) else self.max_retries,
            default_headers={**self._custom_headers, **(default_headers or {})},
            default_query={**self._custom_query, **(default_query or {})},
        )

    with_options = copy

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

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


class RulebookWithRawResponse:
    """Provides raw response access for all resources."""

    def __init__(self, client: Rulebook) -> None:
        self.exchanges = resources.ExchangesWithRawResponse(client.exchanges)
        self.fee_schedule_results = resources.FeeScheduleResultsWithRawResponse(
            client.fee_schedule_results
        )


class AsyncRulebookWithRawResponse:
    """Provides async raw response access for all resources."""

    def __init__(self, client: AsyncRulebook) -> None:
        self.exchanges = resources.AsyncExchangesWithRawResponse(client.exchanges)
        self.fee_schedule_results = resources.AsyncFeeScheduleResultsWithRawResponse(
            client.fee_schedule_results
        )
