"""Client wiring: build transports from `Config`.

This module contains the low-level constructors used by `ClientCore` to lazily create:
- Snowpark SQL transport (`SqlClient`)
- Direct Access HTTP transports (`HttpClient` / `AsyncHttpClient`)

It also composes the runtime middleware chain based on `config.execution`.

Note: Direct Access authentication is handled via a token handler derived from the active Snowflake
connection, and request-time auth headers are injected by middleware. Service endpoint discovery is handled via the SQLClient
"""

from __future__ import annotations

import importlib
from typing import TYPE_CHECKING
from relationalai.config.config_fields import ExecutionConfig

from relationalai.runtime.retry_classification import (
    _default_retryable_http_error,
    _default_retryable_http_error_async,
    _is_retryable_snowflake_error,
)

from ..transports.http import AsyncHttpClient, HttpClient
from ..middleware.compose import compose_middlewares
from ..transports.sql import SqlClient

if TYPE_CHECKING:
    from relationalai.config.config import Config


_LOGGER_NAME = "relationalai.client.execution"
_DIRECT_ACCESS_SUPPORTED_AUTHENTICATORS = frozenset({"jwt", "programmatic_access_token", "snowflake_jwt"})
def default_config() -> Config:
    """Single config initialization point for the client module."""
    from relationalai.config import create_config

    return create_config()


def effective_execution_cfg(cfg: Config) -> ExecutionConfig:
    """Return the execution config.

    Note: this repo intentionally does not support per-service execution overrides.
    """
    return cfg.execution


def validate_direct_access_auth_config(
    cfg: Config, *, token: str | None, connection_name: str | None = None
) -> None:
    """Raise early when DA backend is configured with an unsupported authenticator."""
    if not cfg.direct_access or token:
        return
    try:
        from relationalai.auth import DirectAccessConfigError
        from relationalai.config.connections import SnowflakeConnection

        conn = cfg.get_connection(SnowflakeConnection, name=connection_name)
    except Exception:
        return

    authenticator = str(getattr(conn, "authenticator", "") or "")
    if authenticator in _DIRECT_ACCESS_SUPPORTED_AUTHENTICATORS:
        return
    supported = ", ".join(sorted(_DIRECT_ACCESS_SUPPORTED_AUTHENTICATORS))
    raise DirectAccessConfigError(
        "Direct Access backend requires a Snowflake connection with one of these authenticators: "
        f"{supported}. Got authenticator={authenticator!r}. "
        "Use `token=...` to bypass connection-based Direct Access auth middleware."
    )


def build_sql_from_config(cfg: Config, *, connection_name: str | None = None) -> tuple[SqlClient | None, str | None]:
    """Build the SQL transport (Snowpark), returning (sql_client, app_name)."""
    try:
        from relationalai.config.connections import SnowflakeConnection

        conn = cfg.get_connection(SnowflakeConnection, name=connection_name)
        session = conn.get_session()
        app_name = conn.rai_app_name
    except Exception as e:
        # Return (None, None) only when SQL is genuinely unavailable:
        # - Snowpark isn't installed, or
        # - no usable Snowflake connection is configured/selected.
        # For everything else (e.g. auth/SSO failures), re-raise so callers see the real error.
        msg = str(e or "").strip().lower()

        # Optional dependency: Snowpark missing.
        if isinstance(e, ModuleNotFoundError):
            missing = (getattr(e, "name", None) or "").strip()
            if missing == "snowflake" or missing.startswith("snowflake.snowpark"):
                return None, None

        # Config-selection: no usable Snowflake connection configured.
        if isinstance(e, ValueError) and (
            ("multiple connections available" in msg)
            or ("connection '" in msg and "not found" in msg)
            or ("default_connection" in msg and "not found" in msg)
            or ("config must have at least one connection" in msg)
            # Selected/default connection exists but is not a SnowflakeConnection
            or ("is not of type snowflakeconnection" in msg)
        ):
            return None, None

        raise

    exec_cfg = effective_execution_cfg(cfg)
    middlewares, metrics = compose_middlewares(
        exec_cfg,
        is_retryable=lambda e, _req: _is_retryable_snowflake_error(e),
        logger_name=_LOGGER_NAME,
    )
    sql = SqlClient(session=session, middlewares=middlewares, execution_metrics=metrics)
    return sql, app_name


def build_http_sync(
    cfg: Config, *, token: str | None, connection_name: str | None = None
) -> HttpClient | None:
    """Build the sync HTTP transport (requests).

    `token` overrides DA auth middleware when provided.
    """
    if not cfg.direct_access:
        return None
    validate_direct_access_auth_config(cfg, token=token, connection_name=connection_name)
    exec_cfg = effective_execution_cfg(cfg)

    sync_middlewares, sync_metrics = compose_middlewares(
        exec_cfg,
        is_retryable=_default_retryable_http_error,
        logger_name=_LOGGER_NAME,
    )

    # Inject Direct Access auth headers per request (upstream style).
    from relationalai.config.connections import SnowflakeConnection
    from relationalai.auth import TokenHandler
    from relationalai.runtime.middleware.da_auth import DirectAccessAuthMiddleware

    if token:
        sync_middlewares = [DirectAccessAuthMiddleware(static_token=token), *list(sync_middlewares or [])]
    else:
        conn = cfg.get_connection(SnowflakeConnection, name=connection_name)
        handler = TokenHandler.from_snowflake_connection(conn)
        sync_middlewares = [DirectAccessAuthMiddleware(token_handler=handler), *list(sync_middlewares or [])]

    requests = importlib.import_module("requests")
    http_session = requests.Session()
    http_session.headers.update({"Accept": "application/json"})
    return HttpClient(session=http_session, middlewares=sync_middlewares, execution_metrics=sync_metrics)


async def build_http_async(
    cfg: Config, *, token: str | None, connection_name: str | None = None
) -> AsyncHttpClient | None:
    """Build the async HTTP transport (aiohttp).

    `token` overrides DA auth middleware when provided.
    """
    if not cfg.direct_access:
        return None
    validate_direct_access_auth_config(cfg, token=token, connection_name=connection_name)
    exec_cfg = effective_execution_cfg(cfg)

    async_middlewares, async_metrics = compose_middlewares(
        exec_cfg,
        is_retryable=_default_retryable_http_error_async,
        logger_name=_LOGGER_NAME,
    )

    try:
        aiohttp = importlib.import_module("aiohttp")
    except ModuleNotFoundError:
        return None

    from relationalai.config.connections import SnowflakeConnection
    from relationalai.auth import TokenHandler
    from relationalai.runtime.middleware.da_auth import DirectAccessAuthMiddleware

    if token:
        async_middlewares = [DirectAccessAuthMiddleware(static_token=token), *list(async_middlewares or [])]
    else:
        conn = cfg.get_connection(SnowflakeConnection, name=connection_name)
        handler = TokenHandler.from_snowflake_connection(conn)
        async_middlewares = [DirectAccessAuthMiddleware(token_handler=handler), *list(async_middlewares or [])]

    session = aiohttp.ClientSession(headers={"Accept": "application/json"})
    return AsyncHttpClient(session=session, middlewares=async_middlewares, execution_metrics=async_metrics)

