"""Bootstrap: main entry point for the observability package.

CLI calls setup() once at process startup. Returns an AgentTracer for ctx.tracer.
"""
from __future__ import annotations

import logging
import sys
from typing import Any, Optional

from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.trace import Span
from opentelemetry.context import Context

from .tracer import AgentTracer, NoOpTracer


_setup_done = False
_last_tracer: AgentTracer | None = None


def _suppress_noisy_loggers() -> None:
    """Suppress noisy OTel exporter retry/timeout logs and OpenInference callback errors.

    These loggers produce very verbose output during normal operation:
    - Span/metrics export retries when APM endpoint is unreachable
    - OpenInferenceTracer missing callback method errors
    - BatchSpanProcessor timeout warnings
    """
    for name in (
        "opentelemetry.sdk.trace.export",
        "opentelemetry.sdk.metrics.export",
        "opentelemetry.exporter.otlp",
        "opentelemetry.exporter.otlp.proto.http",
        "opentelemetry.exporter.otlp.proto.http.trace_exporter",
        "opentelemetry.exporter.otlp.proto.http.metric_exporter",
        "openinference.instrumentation.langchain",
        "openinference.instrumentation",
    ):
        logging.getLogger(name).setLevel(logging.CRITICAL)


def setup(
    entry_names: list[str],
    *,
    endpoint: str | None = None,
    service_name: str | None = None,
    apm_token: str | None = None,
    _tracer_provider: TracerProvider | None = None,
) -> AgentTracer:
    """Main entry point: activate instrumentors + initialize telemetry + return tracer.

    Args:
        entry_names: List of framework names to activate (e.g. ["langchain", "openai"])
        endpoint: OTLP endpoint override (default: from env / APM config)
        service_name: Service name override (default: from env)
        apm_token: APM token override (default: from env)
        _tracer_provider: For testing — inject a custom TracerProvider with InMemoryExporter

    Returns:
        AgentTracer instance to be set on context.tracer
    """
    global _setup_done, _last_tracer
    if _setup_done and _tracer_provider is None:
        from .telemetry import get_telemetry
        tel = get_telemetry()
        if tel:
            return _last_tracer or AgentTracer(tracer_provider=tel.tracer_provider)
        return NoOpTracer()  # type: ignore[return-value]
    _setup_done = True

    # 0. Suppress noisy OTel exporter/retry logs
    _suppress_noisy_loggers()

    # 1. Initialize telemetry (sets global TracerProvider)
    from .telemetry import setup_telemetry
    telemetry = setup_telemetry(
        tracer_provider=_tracer_provider,
        endpoint=endpoint,
        service_name=service_name,
        apm_token=apm_token,
    )

    # 2. Activate instrumentors with the provider
    if entry_names:
        from .registry import REGISTRY
        activated: list[str] = []
        for entry in REGISTRY:
            if entry.name not in entry_names:
                continue
            for factory in entry.instrumentor_factories:
                try:
                    instrumentor = factory()
                    instrumentor.instrument(tracer_provider=telemetry.tracer_provider)  # type: ignore
                    activated.append(entry.name)
                except Exception as e:
                    print(f"[observability] failed to activate {entry.name}: {e}",
                          file=sys.stderr, flush=True)

        if activated:
            seen: set[str] = set()
            unique = [n for n in activated if not (n in seen or seen.add(n))]  # type: ignore
            print(f"[observability] enabled: {', '.join(unique)}", file=sys.stderr, flush=True)

    # 3. Apply context propagation patches
    try:
        from .context_patches import apply_context_patches
        apply_context_patches()
    except Exception:
        pass

    _last_tracer = AgentTracer(tracer_provider=telemetry.tracer_provider)
    return _last_tracer


def start_request_span(
    name: str, attributes: dict[str, Any] | None = None
) -> tuple[Span, Context]:
    """Create a per-request root span. Called by adapter for each request."""
    from .telemetry import get_telemetry
    tel = get_telemetry()
    if tel is None:
        from opentelemetry.trace import INVALID_SPAN
        from opentelemetry.context import get_current
        return INVALID_SPAN, get_current()
    return tel.start_request_span(name, attributes)


def end_request_span(span: Span) -> None:
    """End a request span."""
    from .telemetry import get_telemetry
    tel = get_telemetry()
    if tel:
        tel.end_request_span(span)
    else:
        span.end()


def shutdown() -> None:
    """Flush and shutdown telemetry."""
    from .telemetry import get_telemetry
    tel = get_telemetry()
    if tel:
        tel.shutdown()


def get_tracer() -> AgentTracer | None:
    """Return the tracer created by the last setup() call, or None."""
    return _last_tracer


def reset() -> None:
    """Reset all state. For testing only."""
    global _setup_done, _last_tracer
    _setup_done = False
    _last_tracer = None
    from .telemetry import reset_telemetry
    reset_telemetry()
