"""Jobs service wiring (composition root).

This module is the only place that decides **which concrete gateway implementation**
backs `client.jobs`, based on configuration.

Key design points
-----------------
- The *public* API is always `client.jobs.*`.
- Internally, we route by `reasoner_type`:
  - `LOGIC` => transactions-as-jobs (hybrid: DA + SQL)
  - non-LOGIC => jobs
- Transport selection (SQL vs Direct Access) happens here, not in service clients.

Hybrid LOGIC behavior (intentional)
-----------------------------------
When `cfg.reasoners.backend == "direct_access"`:
- LOGIC `create/get/events` use Direct Access HTTP.
- LOGIC `list/cancel` still use Snowflake SQL (`{app}.api.transactions` + cancel procs).

This is why the Direct Access wiring always builds a SQL client for the LOGIC path.
"""

from __future__ import annotations

from typing import Protocol, TYPE_CHECKING, cast

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 JobsClient
from .client_sync import JobsClientSync
from .errors import ErrorHandlerChain
from .gateways import AsyncHttpGateway, HttpGateway, SqlGateway
from .routing import (
    LogicTransactionsGateway,
    LogicTransactionsGatewaySync,
    RouterGateway,
    RouterGatewaySync,
    TxnDaGatewayAsync,
)
from .gateways import JobsGateway
from .routing import TxnAsyncHttpGateway, TxnHttpGateway, TxnSqlGateway

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 _require_da_base_url(cfg: "Config") -> str:
    base_url = cfg.reasoners.direct_access_base_url
    if not base_url:
        raise ValueError("Direct Access requires config.reasoners.direct_access_base_url")
    return str(base_url)


def _build_txn_sql_gateway(*, core: "ClientCore") -> TxnSqlGateway:
    """Build the SQL gateway used for LOGIC list/cancel (required even in DA mode)."""
    sql = build_sql_client(core, error_handlers=ErrorHandlerChain())
    app = require_app_name(core, service="Jobs service")
    return TxnSqlGateway(executor=sql, app=app, config=core.config)


def _build_jobs_da_router_sync(
    *, core: "ClientCore", cfg: "Config", base_url: str
) -> RouterGatewaySync:
    """Build the sync RouterGateway in Direct Access mode (hybrid LOGIC)."""
    http = core.http_executor
    if http is None:
        raise RuntimeError("Direct Access backend requires an HTTP executor on ClientSync.")

    txn_sql = _build_txn_sql_gateway(core=core)
    txn_http = TxnHttpGateway(config=cfg, base_url=str(base_url), http=http)
    logic = LogicTransactionsGatewaySync(txn_sql=txn_sql, txn_http=txn_http)
    jobs = HttpGateway(config=cfg, base_url=str(base_url), http=http)
    return RouterGatewaySync(jobs=jobs, logic=logic)


def build_jobs_client(platform: _AsyncPlatform) -> JobsClient:
    cfg = platform.core.config

    if cfg.reasoners.backend == "direct_access":
        base_url = _require_da_base_url(cfg)

        async def _build_gateway() -> JobsGateway:
            txn_sql = _build_txn_sql_gateway(core=platform.core)

            http_async = await platform.core.ensure_http_async()
            if http_async is not None:
                jobs_gw = cast(JobsGateway, AsyncHttpGateway(config=cfg, base_url=base_url, http=http_async))
                txn_http = TxnAsyncHttpGateway(config=cfg, base_url=base_url, http=http_async)
                logic_gw = cast(JobsGateway, LogicTransactionsGateway(txn_sql=txn_sql, txn_http=txn_http))
                return cast(JobsGateway, RouterGateway(jobs=jobs_gw, logic=logic_gw))

            http = platform.core.http_executor
            if http is None:
                raise RuntimeError("Direct Access backend requires an HTTP executor on Client.")
            jobs_sync = HttpGateway(config=cfg, base_url=base_url, http=http)
            jobs_gw = cast(JobsGateway, SyncToAsyncGatewayAdapter(jobs_sync))
            txn_http_sync = TxnHttpGateway(config=cfg, base_url=base_url, http=http)
            txn_http = cast(TxnDaGatewayAsync, SyncToAsyncGatewayAdapter(txn_http_sync))
            logic_gw = cast(JobsGateway, LogicTransactionsGateway(txn_sql=txn_sql, txn_http=txn_http))
            return cast(JobsGateway, RouterGateway(jobs=jobs_gw, logic=logic_gw))

        gw_lazy = LazyAsyncGateway(_build_gateway)
        return JobsClient(cast(JobsGateway, NoClose(gw_lazy)), _config=cfg)

    sql = build_sql_client(platform.core, error_handlers=ErrorHandlerChain())

    app = require_app_name(platform.core, service="Jobs service")
    jobs_sync = SqlGateway(executor=sql, app=app)
    txn_sql = TxnSqlGateway(executor=sql, app=app, config=cfg)
    logic_sync = LogicTransactionsGatewaySync(txn_sql=txn_sql, txn_http=None)
    router_sync = RouterGatewaySync(jobs=jobs_sync, logic=logic_sync)
    gw = cast(JobsGateway, sync_gateway_to_async(router_sync))
    return JobsClient(gw, _config=cfg)


def build_jobs_client_sync(platform: _SyncPlatform) -> JobsClientSync:
    cfg = platform.core.config

    if cfg.reasoners.backend == "direct_access":
        base_url = _require_da_base_url(cfg)
        router = _build_jobs_da_router_sync(core=platform.core, cfg=cfg, base_url=base_url)
        return JobsClientSync(router, config=cfg)

    sql = build_sql_client(platform.core, error_handlers=ErrorHandlerChain())
    app = require_app_name(platform.core, service="Jobs service")
    jobs = SqlGateway(executor=sql, app=app)
    txn_sql = TxnSqlGateway(executor=sql, app=app, config=cfg)
    logic = LogicTransactionsGatewaySync(txn_sql=txn_sql, txn_http=None)
    router = RouterGatewaySync(jobs=jobs, logic=logic)
    return JobsClientSync(router, config=cfg)


__all__ = ["build_jobs_client", "build_jobs_client_sync"]

