"""Root-client shared state and transport accessors.

`ClientCore` is the shared implementation detail behind the async `Client` and sync
`ClientSync`. It is responsible for:
- caching transport instances (SQL, sync HTTP, async HTTP)
- lazily constructing transports on first use using client wiring
- holding connection-scoped context like `app_name`, `connection_name`, and tokens

Service clients (e.g. `client.reasoners`, `client.jobs`) are built on top of `ClientCore`
and should not need to know how transports are constructed.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any, TYPE_CHECKING

from .transports.http import AsyncHttpClient, HttpClient
from .transports.sql import SqlClient

if TYPE_CHECKING:
    from relationalai.config.config import Config


@dataclass
class ClientCore:
    """Shared state + transport wiring for `Client` and `ClientSync`.

    The core owns transport instances (SQL / HTTP) and any sensitive connection details
    like auth tokens. Root clients are responsible for exposing an ergonomic API and
    closing owned resources.
    """

    # Config objects may include credentials/connection metadata; avoid showing in `repr(...)`.
    config: "Config" = field(repr=False, metadata={"sensitive": True})

    # Transports are intentionally hidden from `repr`:
    # - they may include sessions with headers/cookies
    # - they tend to be noisy in logs
    _sql: SqlClient | None = field(default=None, repr=False, compare=False)
    _http: HttpClient | None = field(default=None, repr=False, compare=False)
    _http_async: AsyncHttpClient | None = field(default=None, repr=False, compare=False)

    app_name: str | None = None
    _connection_name: str | None = None
    _token: str | None = field(default=None, repr=False, compare=False, metadata={"sensitive": True})

    # ---- execution metrics ----
    @property
    def execution_metrics(self) -> Any | None:
        """Return execution metrics from the active transport.

        Inspects already-constructed transports (without triggering lazy creation).
        Prefers HTTP (DA mode) over SQL.
        """
        if self._http_async is not None:
            m = getattr(self._http_async, "execution_metrics", None)
            if m is not None:
                return m
        if self._http is not None:
            m = getattr(self._http, "execution_metrics", None)
            if m is not None:
                return m
        if self._sql is not None:
            return getattr(self._sql, "execution_metrics", None)
        return None

    # ---- transport accessors ----
    @property
    def sql_executor(self) -> SqlClient | None:
        if self._sql is not None:
            return self._sql
        from .wiring.wiring import build_sql_from_config

        sql, app = build_sql_from_config(self.config, connection_name=self._connection_name)
        self._sql = sql
        if self.app_name is None:
            self.app_name = app
        return self._sql

    @property
    def http_executor(self) -> HttpClient | None:
        if self._http is not None:
            return self._http
        from .wiring.wiring import build_http_sync

        http, _base_url = build_http_sync(self.config, token=self._token)
        self._http = http
        return self._http

    @property
    def http_async_executor(self) -> AsyncHttpClient | None:
        # Async creation requires an event loop; this accessor is intentionally non-building.
        return self._http_async

    async def ensure_http_async(self) -> AsyncHttpClient | None:
        """Ensure the async HTTP transport exists (Direct Access backend only).

        Returns `None` when:
        - backend is not Direct Access
        - aiohttp is not installed
        - Direct Access config/token are missing
        """

        if self._http_async is not None:
            return self._http_async
        from .wiring.wiring import build_http_async

        http_async, _base_url = await build_http_async(self.config, token=self._token)
        # `build_http_async` already returns (None, None) when not configured / aiohttp missing.
        self._http_async = http_async
        return self._http_async

