# This file was auto-generated by Fern from our API Definition.

from __future__ import annotations

import base64
import logging
import typing

import httpx

from .client import Agora as BaseAgora
from .client import AsyncAgora as BaseAsyncAgora
from .core.api_error import ApiError
from .core.domain import Area, Pool
from .agentkit.token import generate_convo_ai_token

_AUTH_MODE = typing.Literal["app-credentials", "basic", "token"]
_DEBUG_LOGGER = logging.getLogger("agora_agent")


def _redact_headers(headers: typing.Mapping[str, str]) -> typing.Dict[str, str]:
    """Redact sensitive header values for debug logging."""
    out: typing.Dict[str, str] = {}
    for k, v in headers.items():
        kl = k.lower()
        if kl == "authorization":
            if v.startswith("Basic "):
                out[k] = "Basic ***"
            elif v.startswith("agora token="):
                out[k] = "agora token=***"
            else:
                out[k] = "***"
        else:
            out[k] = v
    return out


def _debug_request(request: httpx.Request) -> None:
    """Log HTTP request when debug is enabled."""
    headers = _redact_headers(request.headers)
    body_preview = ""
    if request.content:
        try:
            preview = request.content[:500] if len(request.content) > 500 else request.content
            body_preview = f" body={preview!r}..."
        except Exception:
            body_preview = " body=<unable to read>"
    _DEBUG_LOGGER.debug(
        "HTTP request: %s %s headers=%s%s",
        request.method,
        request.url,
        headers,
        body_preview,
    )


def _debug_response(response: httpx.Response) -> None:
    """Log HTTP response when debug is enabled."""
    headers = _redact_headers(dict(response.headers))
    _DEBUG_LOGGER.debug(
        "HTTP response: %s %s status=%d headers=%s",
        response.request.method,
        response.request.url,
        response.status_code,
        headers,
    )


async def _debug_request_async(request: httpx.Request) -> None:
    _debug_request(request)


async def _debug_response_async(response: httpx.Response) -> None:
    _debug_response(response)


def _create_debug_client(
    *,
    timeout: typing.Optional[float],
    follow_redirects: typing.Optional[bool],
) -> httpx.Client:
    """Create an httpx.Client with request/response logging hooks."""
    return httpx.Client(
        timeout=timeout or 60.0,
        follow_redirects=follow_redirects if follow_redirects is not None else False,
        event_hooks={"request": [_debug_request], "response": [_debug_response]},
    )


def _create_debug_async_client(
    *,
    timeout: typing.Optional[float],
    follow_redirects: typing.Optional[bool],
) -> httpx.AsyncClient:
    """Create an httpx.AsyncClient with request/response logging hooks."""
    return httpx.AsyncClient(
        timeout=timeout or 60.0,
        follow_redirects=follow_redirects if follow_redirects is not None else False,
        event_hooks={
            "request": [_debug_request_async],
            "response": [_debug_response_async],
        },
    )


def _basic_auth_header(customer_id: str, customer_secret: str) -> str:
    encoded = base64.b64encode(f"{customer_id}:{customer_secret}".encode()).decode()
    return f"Basic {encoded}"


class Agora(BaseAgora):
    """
    Agora extends the base client with domain pool support for
    regional URL cycling and automatic domain selection.

    This client automatically:
    - Selects the best domain based on DNS resolution
    - Cycles through region prefixes on request failures
    - Supports US, EU, AP, and CN areas

    Parameters
    ----------
    area : Area
        The area to use for regional URL selection.

    app_id : str
        Your Agora App ID.

    app_certificate : str
        Your Agora App Certificate.

    customer_id : typing.Optional[str]
        Customer ID for Basic auth. When provided together with
        ``customer_secret``, the client uses
        ``Authorization: Basic base64(customer_id:customer_secret)``.
        When omitted the client operates in *app-credentials* mode and
        auto-generates a ConvoAI token per request.

    customer_secret : typing.Optional[str]
        Customer secret for Basic auth (required when ``customer_id`` is set).

    auth_token : typing.Optional[str]
        Pre-built raw token for REST API authentication. Pass the raw token
        string — the SDK automatically sets the header as
        ``Authorization: agora token=<auth_token>``.
        Mutually exclusive with ``customer_id``/``customer_secret``.

    headers : typing.Optional[typing.Dict[str, str]]
        Additional headers to send with every request.

    timeout : typing.Optional[float]
        The timeout to be used, in seconds, for requests. By default the
        timeout is 60 seconds, unless a custom httpx client is used, in which
        case this default is not enforced.

    follow_redirects : typing.Optional[bool]
        Whether the default httpx client follows redirects or not, this is
        irrelevant if a custom httpx client is passed in.

    httpx_client : typing.Optional[httpx.Client]
        The httpx client to use for making requests, a preconfigured client is
        used by default, however this is useful should you want to pass in any
        custom httpx configuration.

    debug : bool
        If True, log HTTP requests and responses (method, URL, headers, status).
        Uses the ``agora_agent`` logger; enable with
        ``logging.getLogger("agora_agent").setLevel(logging.DEBUG)`` or pass
        ``debug=True`` which configures the logger automatically. Ignored when
        ``httpx_client`` is provided.

    Examples
    --------
    # App-credentials mode (auto token generation per request)
    from agora_agent import Agora, Area

    client = Agora(
        area=Area.US,
        app_id="YOUR_APP_ID",
        app_certificate="YOUR_APP_CERTIFICATE",
    )

    # Basic auth mode
    from agora_agent import Agora, Area

    client = Agora(
        area=Area.US,
        app_id="YOUR_APP_ID",
        app_certificate="YOUR_APP_CERTIFICATE",
        customer_id="YOUR_CUSTOMER_ID",
        customer_secret="YOUR_CUSTOMER_SECRET",
    )

    # Pre-built token mode (for debugging or custom token lifecycles)
    from agora_agent import Agora, Area
    from agora_agent.agentkit.token import generate_convo_ai_token

    raw_token = generate_convo_ai_token(app_id="...", app_certificate="...", channel_name="...", account="1")
    client = Agora(
        area=Area.US,
        app_id="YOUR_APP_ID",
        app_certificate="YOUR_APP_CERTIFICATE",
        auth_token=raw_token,  # SDK adds "agora token=" prefix automatically
    )
    """

    def __init__(
        self,
        *,
        area: Area,
        app_id: str,
        app_certificate: str,
        customer_id: typing.Optional[str] = None,
        customer_secret: typing.Optional[str] = None,
        auth_token: typing.Optional[str] = None,
        headers: typing.Optional[typing.Dict[str, str]] = None,
        timeout: typing.Optional[float] = None,
        follow_redirects: typing.Optional[bool] = True,
        httpx_client: typing.Optional[httpx.Client] = None,
        debug: bool = False,
    ):
        self._pool = Pool(area)
        self.app_id = app_id
        self.app_certificate = app_certificate

        if customer_id is not None:
            if customer_secret is None:
                raise ValueError("customer_secret is required when customer_id is provided")
            if auth_token is not None:
                raise ValueError("auth_token and customer_id/customer_secret are mutually exclusive")
            self.auth_mode: _AUTH_MODE = "basic"
            authorization = _basic_auth_header(customer_id, customer_secret)
        elif auth_token is not None:
            self.auth_mode = "token"
            authorization = f"agora token={auth_token}"
        else:
            self.auth_mode = "app-credentials"
            authorization = ""

        if debug and httpx_client is None:
            _DEBUG_LOGGER.setLevel(logging.DEBUG)
            if not _DEBUG_LOGGER.handlers:
                _h = logging.StreamHandler()
                _h.setFormatter(logging.Formatter("%(levelname)s [%(name)s] %(message)s"))
                _DEBUG_LOGGER.addHandler(_h)
            httpx_client = _create_debug_client(
                timeout=timeout,
                follow_redirects=follow_redirects,
            )

        super().__init__(
            base_url=self._pool.get_current_url(),
            authorization=authorization,
            username="",
            password="",
            headers=headers,
            timeout=timeout,
            follow_redirects=follow_redirects,
            httpx_client=httpx_client,
        )

    @property
    def pool(self) -> Pool:
        """
        Get the underlying domain pool for advanced usage.
        You can use this to manually cycle regions or select the best domain.
        """
        return self._pool

    def next_region(self) -> None:
        """
        Cycle to the next region prefix in the pool.
        Call this when a request fails to try a different region.
        """
        self._pool.next_region()
        self._update_base_url()

    def select_best_domain(self) -> None:
        """
        Select the best domain using DNS resolution.
        This is automatically called periodically, but you can call it manually.
        """
        self._pool.select_best_domain()
        self._update_base_url()

    def get_current_url(self) -> str:
        """
        Get the current URL being used for requests.
        """
        return self._pool.get_current_url()

    def _update_base_url(self) -> None:
        """
        Update the base URL in the client wrapper to match the pool's current URL.
        """
        self._client_wrapper._base_url = self._pool.get_current_url()

    def stop_agent(self, agent_id: str) -> None:
        """Stop an agent by its ID.

        Use this when handling a stop request from your client app (e.g., an
        end-call button) without holding an ``AgentSession`` reference. Create a
        client with the same credentials used to start the agent and call this
        method.

        If the agent has already stopped (e.g., timed out or crashed), this
        method returns successfully rather than raising.

        Parameters
        ----------
        agent_id : str
            The agent instance ID returned by ``AgentSession.start()``.

        Example
        -------
        # End-call handler — no session reference needed
        client = Agora(area=Area.US, app_id="...", app_certificate="...")
        client.stop_agent(agent_id)
        """
        request_options: typing.Optional[typing.Dict[str, typing.Any]] = None
        if self.auth_mode == "app-credentials":
            token = generate_convo_ai_token(
                app_id=self.app_id,
                app_certificate=self.app_certificate,
                channel_name="stop",
                account=agent_id,
            )
            request_options = {"additional_headers": {"Authorization": f"agora token={token}"}}

        try:
            self.agents.stop(self.app_id, agent_id, request_options=request_options)
        except ApiError as e:
            if e.status_code == 404:
                return  # Agent already stopped — treat as success
            raise


class AsyncAgora(BaseAsyncAgora):
    """
    AsyncAgora extends the base async client with domain pool support for
    regional URL cycling and automatic domain selection.

    This client automatically:
    - Selects the best domain based on DNS resolution
    - Cycles through region prefixes on request failures
    - Supports US, EU, AP, and CN areas

    Parameters
    ----------
    area : Area
        The area to use for regional URL selection.

    app_id : str
        Your Agora App ID.

    app_certificate : str
        Your Agora App Certificate.

    customer_id : typing.Optional[str]
        Customer ID for Basic auth. When provided together with
        ``customer_secret``, the client uses
        ``Authorization: Basic base64(customer_id:customer_secret)``.
        When omitted the client operates in *app-credentials* mode and
        auto-generates a ConvoAI token per request.

    customer_secret : typing.Optional[str]
        Customer secret for Basic auth (required when ``customer_id`` is set).

    auth_token : typing.Optional[str]
        Pre-built raw token for REST API authentication. Pass the raw token
        string — the SDK automatically sets the header as
        ``Authorization: agora token=<auth_token>``.
        Mutually exclusive with ``customer_id``/``customer_secret``.

    headers : typing.Optional[typing.Dict[str, str]]
        Additional headers to send with every request.

    timeout : typing.Optional[float]
        The timeout to be used, in seconds, for requests. By default the
        timeout is 60 seconds, unless a custom httpx client is used, in which
        case this default is not enforced.

    follow_redirects : typing.Optional[bool]
        Whether the default httpx client follows redirects or not, this is
        irrelevant if a custom httpx client is passed in.

    httpx_client : typing.Optional[httpx.AsyncClient]
        The httpx client to use for making requests, a preconfigured client is
        used by default, however this is useful should you want to pass in any
        custom httpx configuration.

    debug : bool
        If True, log HTTP requests and responses. See ``Agora`` docstring.

    Examples
    --------
    # App-credentials mode (auto token generation per request)
    from agora_agent import AsyncAgora, Area

    client = AsyncAgora(
        area=Area.US,
        app_id="YOUR_APP_ID",
        app_certificate="YOUR_APP_CERTIFICATE",
    )

    # Basic auth mode
    from agora_agent import AsyncAgora, Area

    client = AsyncAgora(
        area=Area.US,
        app_id="YOUR_APP_ID",
        app_certificate="YOUR_APP_CERTIFICATE",
        customer_id="YOUR_CUSTOMER_ID",
        customer_secret="YOUR_CUSTOMER_SECRET",
    )
    """

    def __init__(
        self,
        *,
        area: Area,
        app_id: str,
        app_certificate: str,
        customer_id: typing.Optional[str] = None,
        customer_secret: typing.Optional[str] = None,
        auth_token: typing.Optional[str] = None,
        headers: typing.Optional[typing.Dict[str, str]] = None,
        timeout: typing.Optional[float] = None,
        follow_redirects: typing.Optional[bool] = True,
        httpx_client: typing.Optional[httpx.AsyncClient] = None,
        debug: bool = False,
    ):
        self._pool = Pool(area)
        self.app_id = app_id
        self.app_certificate = app_certificate

        if customer_id is not None:
            if customer_secret is None:
                raise ValueError("customer_secret is required when customer_id is provided")
            if auth_token is not None:
                raise ValueError("auth_token and customer_id/customer_secret are mutually exclusive")
            self.auth_mode: _AUTH_MODE = "basic"
            authorization = _basic_auth_header(customer_id, customer_secret)
        elif auth_token is not None:
            self.auth_mode = "token"
            authorization = f"agora token={auth_token}"
        else:
            self.auth_mode = "app-credentials"
            authorization = ""

        if debug and httpx_client is None:
            _DEBUG_LOGGER.setLevel(logging.DEBUG)
            if not _DEBUG_LOGGER.handlers:
                _h = logging.StreamHandler()
                _h.setFormatter(logging.Formatter("%(levelname)s [%(name)s] %(message)s"))
                _DEBUG_LOGGER.addHandler(_h)
            httpx_client = _create_debug_async_client(
                timeout=timeout,
                follow_redirects=follow_redirects,
            )

        super().__init__(
            base_url=self._pool.get_current_url(),
            authorization=authorization,
            username="",
            password="",
            headers=headers,
            timeout=timeout,
            follow_redirects=follow_redirects,
            httpx_client=httpx_client,
        )

    @property
    def pool(self) -> Pool:
        """
        Get the underlying domain pool for advanced usage.
        You can use this to manually cycle regions or select the best domain.
        """
        return self._pool

    def next_region(self) -> None:
        """
        Cycle to the next region prefix in the pool.
        Call this when a request fails to try a different region.
        """
        self._pool.next_region()
        self._update_base_url()

    async def select_best_domain(self) -> None:
        """
        Select the best domain using DNS resolution (async).
        This is automatically called periodically, but you can call it manually.
        """
        await self._pool.select_best_domain_async()
        self._update_base_url()

    def get_current_url(self) -> str:
        """
        Get the current URL being used for requests.
        """
        return self._pool.get_current_url()

    def _update_base_url(self) -> None:
        """
        Update the base URL in the client wrapper to match the pool's current URL.
        """
        self._client_wrapper._base_url = self._pool.get_current_url()

    async def stop_agent(self, agent_id: str) -> None:
        """Stop an agent by its ID (async).

        Use this when handling a stop request from your client app (e.g., an
        end-call button) without holding an ``AsyncAgentSession`` reference. Create
        a client with the same credentials used to start the agent and call this
        method.

        If the agent has already stopped (e.g., timed out or crashed), this
        method returns successfully rather than raising.

        Parameters
        ----------
        agent_id : str
            The agent instance ID returned by ``AsyncAgentSession.start()``.

        Example
        -------
        # End-call handler — no session reference needed
        client = AsyncAgora(area=Area.US, app_id="...", app_certificate="...")
        await client.stop_agent(agent_id)
        """
        request_options: typing.Optional[typing.Dict[str, typing.Any]] = None
        if self.auth_mode == "app-credentials":
            token = generate_convo_ai_token(
                app_id=self.app_id,
                app_certificate=self.app_certificate,
                channel_name="stop",
                account=agent_id,
            )
            request_options = {"additional_headers": {"Authorization": f"agora token={token}"}}

        try:
            await self.agents.stop(self.app_id, agent_id, request_options=request_options)
        except ApiError as e:
            if e.status_code == 404:
                return  # Agent already stopped — treat as success
            raise
