"""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/token exchange is intentionally out of scope here.
Callers must provide a token and a base URL in config.
"""

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"


def default_config() -> Config:
    """Single config initialization point for the client module."""
    from relationalai.config import create_config

    return create_config()


def require_direct_access_config(*, cfg: Config, token: str | None) -> tuple[str, str]:
    base_url = cfg.reasoners.direct_access_base_url
    if not base_url:
        raise NotImplementedError(
            "Direct Access (HTTP) requires reasoners.direct_access_base_url. Auth is not implemented yet in this repo."
        )
    if token is None:
        raise NotImplementedError(
            "Direct Access (HTTP) requires a pre-issued token (pass token=... to connect/from_session). "
            "Auth/token exchange is not implemented yet."
        )
    return str(base_url), str(token)


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 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) -> tuple[HttpClient | None, str | None]:
    """Build the sync HTTP transport (requests), returning (http_client, base_url)."""
    if cfg.reasoners.backend != "direct_access":
        return None, None

    base_url, token = require_direct_access_config(cfg=cfg, token=token)
    exec_cfg = effective_execution_cfg(cfg)

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

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


async def build_http_async(cfg: Config, *, token: str | None) -> tuple[AsyncHttpClient | None, str | None]:
    """Build the async HTTP transport (aiohttp), returning (http_client, base_url)."""
    if cfg.reasoners.backend != "direct_access":
        return None, None

    base_url, token = require_direct_access_config(cfg=cfg, token=token)
    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, None

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

