"""
aiel_sdk.client
===============

Root SDK client for calling AIEL APIs.

This module defines :class:`~aiel_sdk.client.AielClient`, an "AWS-SDK style" entry point that
centralizes configuration (base URL, auth, headers, timeouts) and exposes service clients
(e.g., IntegrationsService, MemoryService).

Auth resolution
---------------
`AielClient` resolves an API token in the following order:

1) Explicit `api_key` passed to the constructor
2) Environment variable `AIEL_TOKEN`
3) OS keyring entry (via `keyring.get_password("aiel", "default")`) when available

If no token is found, requests are sent without Authorization headers (and may fail
depending on server policy).

Services
--------
After initialization, the client exposes:
- ``client.integrations``: :class:`~aiel_sdk.services.integrations.IntegrationsService`
- ``client.memory``: :class:`~aiel_sdk.services.memory.MemoryService`

These service objects use the same underlying transport, retry policy, and headers.

Transport, retries, and timeouts
--------------------------------
Requests are sent through a configurable :class:`~aiel_sdk.transport.Transport`.
By default, the SDK uses :class:`~aiel_sdk.transport.UrllibTransport` with a default
:class:`~aiel_sdk.transport.RetryPolicy`.

The `timeout` parameter controls request timeouts (seconds) unless overridden per-call.

Request correlation
-------------------
If a `request_id` is provided to `_request`, the transport may attach it for correlation
(implementation depends on the transport).
"""

from __future__ import annotations

import os
from typing import Any, Dict, Mapping, Optional

from ..services.integrations import IntegrationsService
from ..services.memory import MemoryService
from ..transport import RetryPolicy, Transport, UrllibTransport


def _try_get_keyring_token(service: str = "aiel", username: str = "default") -> str | None:
    """
    Attempt to read an API token from the OS keyring.

    This helper is best-effort:
    - If the `keyring` package is not installed or fails to load, returns None.
    - If the keyring backend is unavailable or lookup fails, returns None.

    Args:
        service:
            Keyring service name. Defaults to "aiel".
        username:
            Keyring username entry. Defaults to "default".

    Returns:
        str | None:
            Token string if found, otherwise None.
    """
    try:
        import keyring  # type: ignore
    except Exception:
        return None
    try:
        return keyring.get_password(service, username)
    except Exception:
        return None


def _resolve_token(explicit: str | None) -> str | None:
    """
    Resolve an API token using the SDK's standard precedence order.

    Precedence:
    1) `explicit` argument (if provided and truthy)
    2) Environment variable `AIEL_TOKEN`
    3) OS keyring token (see `_try_get_keyring_token`)

    Args:
        explicit:
            Token string passed directly by the caller (e.g., constructor `api_key`).

    Returns:
        str | None:
            The resolved token, or None if no token source was available.
    """
    if explicit:
        return explicit
    env_token = os.getenv("AIEL_TOKEN")
    if env_token:
        return env_token
    kr = _try_get_keyring_token()
    if kr:
        return kr
    return None


class AielClient:
    """
    Root client that wires auth + transport + service clients.

    This class stores shared configuration and exposes typed service wrappers
    (e.g., memory and integrations).

    Args:
        base_url:
            Base URL for the AIEL API. Trailing slashes are stripped.
        api_key:
            Optional API token. If omitted, token resolution falls back to
            environment variable `AIEL_TOKEN`, then OS keyring.
        timeout:
            Default request timeout in seconds (applies to all requests unless overridden).
        extra_headers:
            Additional HTTP headers merged into every request.
        use_x_api_token_fallback:
            When True and a token is available, also send `X-API-Token: <token>` in addition
            to `Authorization: Bearer <token>`. Useful for servers that support either header.
        transport:
            Optional custom transport implementing :class:`~aiel_sdk.transport.Transport`.
            Defaults to :class:`~aiel_sdk.transport.UrllibTransport`.
        retry_policy:
            Optional retry policy. Defaults to :class:`~aiel_sdk.transport.RetryPolicy`.
        user_agent:
            Optional User-Agent header value. Defaults to "aiel-sdk/0.1 (urllib)".

    Attributes:
        base_url:
            Normalized base URL (no trailing slash).
        timeout:
            Default timeout (seconds).
        extra_headers:
            Extra headers included with every request.
        use_x_api_token_fallback:
            Whether to include `X-API-Token` when a token is present.
        transport:
            Transport instance used to execute HTTP requests.
        retry_policy:
            Retry policy used by the transport.
        user_agent:
            User-Agent string used for requests.
        integrations:
            Integrations service client.
        memory:
            Memory service client.

    Raises:
        ValueError:
            If `base_url` is empty after normalization.
    """

    def __init__(
        self,
        *,
        base_url: str,
        api_key: str | None = None,
        timeout: float = 30.0,
        extra_headers: Optional[Dict[str, str]] = None,
        use_x_api_token_fallback: bool = False,
        transport: Optional[Transport] = None,
        retry_policy: Optional[RetryPolicy] = None,
        user_agent: Optional[str] = None,
    ) -> None:
        """
        Initialize the client and its service clients.

        This performs:
        - base_url normalization and validation
        - token resolution
        - transport and retry policy configuration
        - service client construction (integrations, memory)

        See class docstring for parameter details.
        """
        self.base_url = base_url.rstrip("/")
        if not self.base_url:
            raise ValueError("base_url is required")

        self.timeout = timeout
        self.extra_headers = extra_headers or {}
        self.use_x_api_token_fallback = use_x_api_token_fallback

        self._token = _resolve_token(api_key)

        self.transport = transport or UrllibTransport()
        self.retry_policy = retry_policy or RetryPolicy()
        self.user_agent = user_agent or "aiel-sdk/0.1 (urllib)"

        # Services
        self.integrations = IntegrationsService(self)
        self.memory = MemoryService(self)

    def _build_headers(self) -> Dict[str, str]:
        """
        Build request headers for an API call.

        Includes:
        - Accept: application/json
        - User-Agent
        - any `extra_headers`
        - Authorization: Bearer <token> (when token is available)
        - X-API-Token: <token> (optional fallback when enabled)

        Returns:
            dict[str, str]:
                Headers to send with the request.
        """
        headers = {
            "Accept": "application/json",
            "User-Agent": self.user_agent,
            **self.extra_headers,
        }
        if self._token:
            headers["Authorization"] = f"Bearer {self._token}"
            if self.use_x_api_token_fallback:
                headers["X-API-Token"] = self._token
        return headers

    def _request(
        self,
        service: str,
        method: str,
        path: str,
        *,
        params: Mapping[str, Any] | None = None,
        json_body: Any | None = None,
        timeout: float | None = None,
        request_id: str | None = None,
    ) -> Any:
        """
        Execute an HTTP request via the configured transport.

        This is the internal low-level request primitive used by service clients.

        Args:
            service:
                Logical service name (used for tracing/logging/routing by the transport).
            method:
                HTTP method (e.g., "GET", "POST", "PATCH").
            path:
                Request path relative to `base_url` (must start with "/").
            params:
                Optional query string parameters.
            json_body:
                Optional JSON payload to send (serialized by the transport).
            timeout:
                Optional per-request timeout override. If None, uses `self.timeout`.
            request_id:
                Optional correlation/request id forwarded to the transport.

        Returns:
            Any:
                Parsed response object as returned by the transport (often a dict or model).

        Raises:
            Exception:
                Transport-specific exceptions (HTTP errors, network errors, retries exhausted).
                Exact exception types depend on the transport implementation.
        """
        return self.transport.request(
            service=service,
            method=method,
            base_url=self.base_url,
            path=path,
            headers=self._build_headers(),
            params=params,
            json_body=json_body,
            timeout=self.timeout if timeout is None else timeout,
            retry=self.retry_policy,
            request_id=request_id,
        )