"""Reasoners service wiring (composition root).

This module wires a root platform client (with configured transports) to reasoners
gateways, and returns user-facing service clients.
"""

from __future__ import annotations

from typing import Any, Protocol, TYPE_CHECKING, cast
from relationalai.services._shared.da_wiring import build_da_base_url_provider
from relationalai.util.adapters import SyncToAsyncGatewayAdapter
from relationalai.util.noclose import NoClose
from relationalai.services._shared.lazy_async_gateway import LazyAsyncGateway
from relationalai.services._shared.sql_wiring import build_sql_client, require_app_name, sync_gateway_to_async

from .client import ReasonersClient
from .client_sync import ReasonersClientSync
from .constants import DEFAULT_REASONERS_SCHEMA
from .errors import ErrorHandlerChain
from .gateways import AsyncHttpGateway, HttpGateway, SqlGateway
from .protocol import ReasonersGateway

if TYPE_CHECKING:
    from relationalai.client.core import ClientCore
    from relationalai.config.config import Config

class _AsyncPlatform(Protocol):
    core: "ClientCore"


class _SyncPlatform(Protocol):
    core: "ClientCore"


def _build_sql_fallback(platform: Any, cfg: "Config") -> tuple[SqlGateway | None, Any]:
    """Best-effort: build a SQL gateway to use as DA fallback.

    Returns ``(sql_gateway, sql_async_gateway)`` when Snowflake SQL is available,
    or ``(None, None)`` when it is not (e.g. no Snowflake connection configured).
    The async variant is a ``SyncToAsyncGatewayAdapter`` wrapping the sync gateway.
    """
    try:
        base = platform.core.sql_executor
    except Exception:
        return None, None
    if base is None:
        return None, None
    try:
        app = require_app_name(platform.core, service="SQL fallback")
    except Exception:
        return None, None

    sql = build_sql_client(platform.core, error_handlers=ErrorHandlerChain())
    gw_sync = SqlGateway(executor=sql, config=cfg, app=app, schema=DEFAULT_REASONERS_SCHEMA)
    gw_async = sync_gateway_to_async(gw_sync)
    return gw_sync, gw_async


def build_reasoners_client(platform: _AsyncPlatform) -> ReasonersClient:
    """Build an async-first `ReasonersClient` bound to a root platform client."""
    cfg = platform.core.config

    if cfg.direct_access:
        base_url_provider = build_da_base_url_provider(platform.core)

        # Best-effort SQL fallback for operations the DA API doesn't support yet.
        _sql_fb_sync, sql_fb_async = _build_sql_fallback(platform, cfg)

        async def _build_gateway() -> ReasonersGateway:
            http_async = await platform.core.ensure_http_async()
            if http_async is not None:
                return AsyncHttpGateway(
                    config=cfg, base_url_provider=base_url_provider, http=http_async, sql_fallback=sql_fb_async,
                )

            http = platform.core.http_executor
            if http is None:
                raise RuntimeError("Direct Access backend requires an HTTP executor on Client.")
            gw_sync = HttpGateway(
                config=cfg, base_url_provider=base_url_provider, http=http, sql_fallback=_sql_fb_sync,
            )
            return cast(ReasonersGateway, SyncToAsyncGatewayAdapter(gw_sync))

        gw_lazy = LazyAsyncGateway(_build_gateway)
        return ReasonersClient(cast(ReasonersGateway, NoClose(gw_lazy)), config=cfg)

    base = platform.core.sql_executor
    if base is None:
        raise RuntimeError("SQL backend requires a SQL executor on Client.")
    app = require_app_name(platform.core, service="SQL backend")

    sql = build_sql_client(platform.core, error_handlers=ErrorHandlerChain())
    gw_sync = SqlGateway(
        executor=sql,
        config=cfg,
        app=app,
        schema=DEFAULT_REASONERS_SCHEMA,
    )
    gw = cast(ReasonersGateway, sync_gateway_to_async(gw_sync))
    return ReasonersClient(gw, config=cfg)


def build_reasoners_client_sync(platform: _SyncPlatform) -> ReasonersClientSync:
    """Build a sync `ReasonersClientSync` bound to a root platform client."""
    cfg = platform.core.config

    if cfg.direct_access:
        base_url_provider = build_da_base_url_provider(platform.core)
        http = platform.core.http_executor
        if http is None:
            raise RuntimeError("Direct Access backend requires an HTTP executor on ClientSync.")

        # Best-effort SQL fallback for operations the DA API doesn't support yet.
        sql_fb_sync, _sql_fb_async = _build_sql_fallback(platform, cfg)

        gw = HttpGateway(config=cfg, base_url_provider=base_url_provider, http=http, sql_fallback=sql_fb_sync)
        return ReasonersClientSync(gw, config=cfg)

    base = platform.core.sql_executor
    if base is None:
        raise RuntimeError("SQL backend requires a SQL executor on ClientSync.")
    app = require_app_name(platform.core, service="SQL backend")

    sql = build_sql_client(platform.core, error_handlers=ErrorHandlerChain())
    gw = SqlGateway(
        executor=sql,
        config=cfg,
        app=app,
        schema=DEFAULT_REASONERS_SCHEMA,
    )
    return ReasonersClientSync(gw, config=cfg)

