from typing import Optional

import opik as _opik
from opik import *  # noqa: F401, F403

# Report OUR distribution version rather than opik's. ``from opik import *``
# binds opik's ``__version__`` into this namespace; override it with this
# package's installed metadata so there is a single source of truth
# (pyproject -> built dist -> importlib.metadata).
from importlib.metadata import (
    PackageNotFoundError as _PackageNotFoundError,
    version as _dist_version,
)

try:
    __version__ = _dist_version("acceldata-aio-tracer")
except _PackageNotFoundError:  # running from source without an installed dist
    __version__ = "0.0.0+unknown"

del _dist_version, _PackageNotFoundError

# Brand-neutral alias for opik's ``opik_context`` module, so consumer code can
# use ``acceldata_aio_tracer.aio_context`` (top-level) instead of the
# opik-branded name. The submodule form (``from acceldata_aio_tracer.aio_context
# import ...``) is handled by the namespace forwarder. ``opik_context`` stays
# available for compatibility.
aio_context = opik_context  # noqa: F405  (bound by `from opik import *`)

from . import _log_rebrand

# Rebrand opik's console logging ("OPIK: ...") to "[acceldata_aio_tracer] ..."
# before any opik log can fire. Must run after `import opik` (which installs
# opik's console handler) and is idempotent.
_log_rebrand.apply()

from . import _eval_report_rebrand

# opik's evaluation reporter prints "...Opik..." via rich (not logging), so
# _log_rebrand cannot reach it; rebrand that console output too. Idempotent.
_eval_report_rebrand.apply()

from . import _namespace_forward

# Forward the brand-neutral opik subtrees (evaluation / rest_api /
# opik_context) into this namespace so consumer code never imports `opik`.
# Must run at import time, before any `acceldata_aio_tracer.<sub>` import.
_namespace_forward.install()

from . import _opik_bridge
from . import _streamer_patch as _mt_streamer_patch
from . import _batcher_patch as _mt_batcher_patch
from . import _processor_patch as _mt_processor_patch
from . import _replay_patch as _mt_replay_patch
from .auth import (
    AcceldataAuth,
    setup_acceldata_auth,
    setup_acceldata_auth_hook,
)
from .client import AcceldataTracer
from .multitenancy import (
    TenantContext,
    TenantManager,
    TenantResolver,
)
from .multitenant_auth import (
    MultiTenantAuth,
    setup_multitenant_auth_hook,
    tenant_request_context,
)


def _require_project_name(project_name: str) -> None:
    """Guard: ``project_name`` is required and must be a non-empty string.

    Without it, opik silently routes traces to its generic "Default Project";
    we fail loudly instead so every caller names the project its traces land in.
    """
    if project_name is None or not str(project_name).strip():
        raise ValueError(
            'project_name is required and must be a non-empty string, '
            'e.g. configure(..., project_name="my-agent").'
        )


def apply_multitenancy_patches() -> None:
    """Apply the in-memory pipeline patches that make Opik tenant-aware.

    This covers the three opik internals that have no public extension
    point and must be patched directly:

    - ``opik.message_processing.streamer.Streamer.put`` to stamp
      ``message.tenant`` on outgoing trace/span messages.
    - ``opik.message_processing.messages.BaseMessage.as_payload_dict``
      to strip ``tenant`` from REST payloads.
    - ``opik.message_processing.batching.batchers.CreateSpan/TraceMessageBatcher``
      to coerce each message's ``tenant`` via ``TenantContext.from_value``
      then split accumulated messages by ``ctx.group_key()`` and emit
      one batch per tenant.
    - The six trace/span handlers in
      ``opik.message_processing.processors.online_message_processor.OpikMessageProcessor``
      to enter ``tenant_request_context(message.tenant)`` around the
      REST call.

    HTTP-layer header injection is handled by :class:`MultiTenantAuth`
    via :func:`setup_multitenant_auth_hook`, not by a patch here.

    Idempotent — safe to call multiple times.
    """
    _mt_streamer_patch.apply()
    _mt_batcher_patch.apply()
    _mt_processor_patch.apply()
    _mt_replay_patch.apply()


def configure(
    url: str,
    access_key: str,
    secret_key: str,
    *,
    project_name: str,
    debug: bool = False,
    check_tls_certificate: bool = True,
) -> None:
    """Acceldata equivalent of opik.configure().

    Registers the Acceldata auth hook so every subsequent Opik client
    carries accessKey/secretKey headers, applies the URL/project settings,
    and optionally installs an HTTP debug interceptor.

    The first three parameters (``url``, ``access_key``, ``secret_key``) are
    the SDK's stable public surface and may be passed positionally or by
    name. All other options are keyword-only so future flags can be added,
    deprecated, or removed without breaking customer call sites.

    Each argument has a matching ``ACCELDATA_AIO_*`` env variable that can
    be used instead of an explicit call. If both are present the explicit
    argument wins; if neither, ``configure()`` cannot be skipped. Upstream
    ``OPIK_*`` names are accepted as a fallback (see README "Environment
    variables").

    Args:
        url: Acceldata gateway URL, e.g.
            ``https://acme.acceldata.local:5443/aio``.
            Env: ``ACCELDATA_AIO_URL`` (fallback: ``OPIK_URL_OVERRIDE``).
        access_key: Acceldata API access key.
            Env: ``ACCELDATA_AIO_ACCESS_KEY``
            (fallback: ``OPIK_ACCESS_KEY``).
        secret_key: Acceldata API secret key.
            Env: ``ACCELDATA_AIO_SECRET_KEY``
            (fallback: ``OPIK_SECRET_KEY``).
        project_name: Required. Non-empty project name that traces are routed
            to. Passing None or an empty string raises ValueError. (On the
            auto-init-from-env path, set ``ACCELDATA_AIO_PROJECT_NAME`` instead.)
        debug: When True, install an httpx interceptor that logs every Opik
            HTTP call and, on >=400 responses, dumps the request body to
            disk and prints a copy-paste curl for replay. Off by default.
            Env: ``ACCELDATA_AIO_DEBUG`` accepts ``1`` / ``true`` / ``yes``
            / ``on`` (case-insensitive) to enable. Wrapper-specific — has
            no upstream ``OPIK_*`` fallback.
        check_tls_certificate: When False, disables TLS certificate
            verification for outbound HTTP calls. Use only for local dev
            against self-signed gateways. Default True.
            Env: ``ACCELDATA_AIO_CHECK_TLS_CERTIFICATE``
            (fallback: ``OPIK_CHECK_TLS_CERTIFICATE``).
    """
    _require_project_name(project_name)
    _opik_bridge.apply_configure(
        url=url,
        access_key=access_key,
        secret_key=secret_key,
        project_name=project_name,
        debug=debug,
        check_tls_certificate=check_tls_certificate,
    )


def configure_multitenant_client(
    url: str,
    *,
    project_name: str,
    debug: bool = False,
    check_tls_certificate: bool = True,
    # TODO(remove-when-aio-patched): see multitenant_auth.py for context.
    # Optional fallback tenant for /is-alive/ping probes; remove this
    # parameter when the opik-backend exemptPaths YAML is fixed
    # everywhere this SDK runs.
    service_tenant: Optional[TenantContext] = None,
) -> None:
    """Acceldata equivalent of opik.configure() for multi-tenant mode.

    Parallel to :func:`configure`. Use this when the gateway-level
    ``accessKey``/``secretKey`` auth is bypassed and the Opik backend
    routes traces by per-request ``X-Tenant-ID`` / ``X-Tenant-Name`` /
    ``X-User-ID`` / ``X-User-Name`` headers instead.

    Side effects, all at call time:

    - Sets ``OPIK_URL_OVERRIDE`` / ``OPIK_PROJECT_NAME`` /
      ``OPIK_CHECK_TLS_CERTIFICATE`` env vars.
    - Registers :class:`MultiTenantAuth` as the httpx auth on every
      Opik client (via :func:`setup_multitenant_auth_hook`). This
      replaces — does NOT compose with — :class:`AcceldataAuth`.
    - Applies the in-memory pipeline patches required for
      tenant-aware batching and per-request header injection (see
      :func:`apply_multitenancy_patches`).
    - Installs the HTTP debug interceptor when ``debug=True``.

    Tenant identity is read from a resolver registered via
    :func:`TenantManager.set_resolver`; the app supplies the resolver
    at startup. The patched ``Streamer.put`` calls the resolver inside
    each request so the ``ContextVar`` it reads is request-scoped.

    Args:
        url: Acceldata gateway URL, e.g.
            ``https://acme.acceldata.local:5443/aio``.
        project_name: Required. Non-empty project name that traces are routed
            to. Passing None or an empty string raises ValueError.
        debug: When True, install an httpx interceptor that logs every
            Opik HTTP call and dumps request bodies on >=400 responses.
        check_tls_certificate: When False, disables TLS verification
            for outbound HTTP calls. Use only for local dev.
    """
    _require_project_name(project_name)
    _opik_bridge.apply_configure_multitenant(
        url=url,
        project_name=project_name,
        debug=debug,
        check_tls_certificate=check_tls_certificate,
        # TODO(remove-when-aio-patched): drop this kwarg.
        service_tenant=service_tenant,
    )
    apply_multitenancy_patches()


# Bootstrap ACCELDATA_AIO_* env vars and auto-register the auth hook at
# import time. All upstream-naming details live in _opik_bridge so this
# module stays free of OPIK_* references.
_opik_bridge.bootstrap_from_env()


__all__ = (
    "AcceldataTracer",
    "AcceldataAuth",
    "configure",
    "configure_multitenant_client",
    "setup_acceldata_auth",
    "setup_acceldata_auth_hook",
    "MultiTenantAuth",
    "setup_multitenant_auth_hook",
    "tenant_request_context",
    "TenantContext",
    "TenantManager",
    "TenantResolver",
    "apply_multitenancy_patches",
    "aio_context",
    *_opik.__all__,
)

del _opik
