"""Synchronous root client.

This module defines :class:`~relationalai.client.client_sync.ClientSync`, the synchronous
counterpart to :class:`~relationalai.client.client.Client`.
"""

from __future__ import annotations

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

from .core import ClientCore

from relationalai.util.docutils import include_in_docs

__include_in_docs__ = True

if TYPE_CHECKING:
    from relationalai.config.config import Config
    from relationalai.services.jobs.client_sync import JobsClientSync
    from relationalai.services.reasoners.client_sync import ReasonersClientSync


@include_in_docs
@dataclass
class ClientSync:
    """Synchronous root client.

    Use this client from synchronous applications/scripts:
    - construct via `relationalai.client.connect_sync(...)` or `from_session_sync(...)`
    - close via `client.close()` or `with ...`

    Key differences vs `Client` (async):
    - `.reasoners` returns a synchronous `ReasonersClientSync` (blocking methods)
    - sync entrypoints guard against being called inside a running event loop
      (use `await connect(...)` from async code instead)

    Logging/representation:
    - sensitive fields (config, auth tokens, sessions/transports) are excluded from `repr(...)`
    """

    core: ClientCore = field(repr=False)
    _reasoners: "ReasonersClientSync | None" = field(default=None, init=False, repr=False, compare=False)
    _jobs: "JobsClientSync | None" = field(default=None, init=False, repr=False, compare=False)

    @include_in_docs
    def close(self) -> None:
        """Close the client (best-effort).

        Returns
        -------
        None
            Always returns `None`.
        """
        if self._reasoners is not None:
            try:
                self._reasoners.close()
            except Exception:
                pass
        if self._jobs is not None:
            try:
                self._jobs.close()
            except Exception:
                pass

        if self.core._http is not None:
            try:
                session = getattr(self.core._http, "session", None)
                close = getattr(session, "close", None)
                if callable(close):
                    out = close()
                    # Some session implementations may return an awaitable; ignore in sync close.
                    if inspect.isawaitable(out):
                        pass
            except Exception:
                pass

    def __enter__(self) -> "ClientSync":
        return self

    def __exit__(self, exc_type, exc, tb) -> None:  # noqa: ANN001
        self.close()

    @include_in_docs
    @property
    def reasoners(self) -> "ReasonersClientSync":
        """Reasoners service client (cached, sync).

        Returns a `ReasonersClientSync` whose public API is blocking/synchronous.

        Returns
        -------
        ReasonersClientSync
            A cached synchronous reasoners client.
        """
        if self._reasoners is not None:
            return self._reasoners
        from relationalai.services.reasoners.wiring import build_reasoners_client_sync

        self._reasoners = build_reasoners_client_sync(self)
        return self._reasoners

    @include_in_docs
    def get_reasoners(self) -> "ReasonersClientSync":
        """Return the reasoners service client.

        This is the method form of :attr:`reasoners`, provided so reference docs can
        render an explicit API entry.

        Returns
        -------
        ReasonersClientSync
            A cached synchronous reasoners client.
        """
        return self.reasoners

    @include_in_docs
    @property
    def jobs(self) -> "JobsClientSync":
        """Jobs service client (cached, sync).

        Returns
        -------
        JobsClientSync
            A cached synchronous jobs client.
        """
        if self._jobs is not None:
            return self._jobs
        from relationalai.services.jobs.wiring import build_jobs_client_sync

        self._jobs = build_jobs_client_sync(self)
        return self._jobs

    @include_in_docs
    def get_jobs(self) -> "JobsClientSync":
        """Return the jobs service client.

        This is the method form of :attr:`jobs`, provided so reference docs can
        render an explicit API entry.

        Returns
        -------
        JobsClientSync
            A cached synchronous jobs client.
        """
        return self.jobs

    # ---------------------------------------------------------------------
    # Common state accessors (delegated to `ClientCore`).
    # ---------------------------------------------------------------------
    @include_in_docs
    @property
    def config(self) -> "Config":
        """Return the active configuration.

        Returns
        -------
        Config
            The config used by this client.
        """
        return self.core.config

    @include_in_docs
    @property
    def execution_metrics(self) -> object | None:
        """Execution metrics from the active transport (read-only diagnostic handle)."""
        return self.core.execution_metrics

    @include_in_docs
    @property
    def app_name(self) -> str | None:
        """Return the app name used for SQL routing.

        Returns
        -------
        str | None
            The app name, if known.
        """
        return self.core.app_name

    @app_name.setter
    def app_name(self, v: str | None) -> None:
        self.core.app_name = v

