"""
SDK Initialization

Single entry point customers call. One line sets up the entire OTel pipeline:
TracerProvider -> BatchSpanProcessor -> PendoSpanExporter -> OTLP HTTP POST.

The agent thread never blocks on export. BatchSpanProcessor buffers completed
spans in memory and flushes asynchronously on a background thread.
"""

import logging
import warnings
from typing import Optional

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import BatchSpanProcessor

from .exporter import PendoSpanExporter
from .patchers import patch_all, unpatch_all

logger = logging.getLogger("pendo_sdk")

_SDK_NAME = "pendo_sdk"
_SDK_VERSION = "0.1.0"
_DEFAULT_ENDPOINT = "https://app.pendo.io"
_DEFAULT_BATCH_SIZE = 512
_DEFAULT_FLUSH_INTERVAL_MS = 2000

# Module-level state
_provider: Optional[TracerProvider] = None
_exporter: Optional[PendoSpanExporter] = None
_initialized: bool = False
_visitor_id: Optional[str] = None
_account_id: Optional[str] = None


def _is_noop_provider(provider: trace.TracerProvider) -> bool:
    return isinstance(provider, trace.ProxyTracerProvider)


def init(
    api_key: str,
    *,
    project_id: Optional[str] = None,
    agent_id: Optional[str] = None,
    redact: bool = True,
    visitor_id: Optional[str] = None,
    account_id: Optional[str] = None,
    endpoint: Optional[str] = None,
    url: Optional[str] = None,
    auto_patch: bool = True,
    batch_size: int = _DEFAULT_BATCH_SIZE,
    flush_interval_ms: int = _DEFAULT_FLUSH_INTERVAL_MS,
) -> None:
    """
    Initialize the Pendo Agent tracing SDK.

    This is the only setup the customer needs. Sets up the OTel TracerProvider,
    the BatchSpanProcessor, and optionally activates monkey-patching.

    Args:
        api_key: Required. Pendo API key, sent as X-API-Key on OTLP export.
        project_id: Maps to OTel Resource service.name. Identifies which agent
            this is in multi-agent systems. Defaults to "default".
        redact: Enable PII redaction before network export. Default True.
        visitor_id: Pendo visitor ID. Injected as pendo.visitor_id on every span.
        account_id: Pendo account ID. Injected as pendo.account_id on every span.
        endpoint: Ingestion base URL. Default: https://app.pendo.io
        auto_patch: Monkey-patch LLM client libraries at init time. Default True.
        batch_size: Max spans per export batch. Default 512.
        flush_interval_ms: Flush interval in milliseconds. Default 2000.
    """
    global _provider, _exporter, _initialized, _visitor_id, _account_id

    if not api_key:
        raise ValueError("api_key is required")

    # Step 1: Check if TracerProvider has already been set. If so, warn and return.
    if _initialized:
        warnings.warn(
            "pendo_sdk.init() has already been called. "
            "Ignoring duplicate initialization.",
            stacklevel=2,
        )
        return

    current_provider = trace.get_tracer_provider()
    if not _is_noop_provider(current_provider):
        warnings.warn(
            "A TracerProvider is already registered globally. "
            "pendo_sdk will not override it. Call pendo_sdk.shutdown() first "
            "if you want to re-initialize.",
            stacklevel=2,
        )
        return

    # Step 2: Create the Resource. Tags every span with project identity.
    resolved_project_id = project_id or "default"
    resource = Resource.create({
        "service.name": resolved_project_id,
        "pendo.sdk.name": _SDK_NAME,
        "pendo.sdk.version": _SDK_VERSION,
    })

    # Step 3: Create the TracerProvider.
    _provider = TracerProvider(resource=resource)

    # Step 4: Create the PendoSpanExporter.
    resolved_endpoint = endpoint or _DEFAULT_ENDPOINT
    _exporter = PendoSpanExporter(
        api_key=api_key,
        endpoint=resolved_endpoint,
        redact_pii=redact,
        visitor_id=visitor_id,
        account_id=account_id,
        agent_id=agent_id,
        url=url,
    )

    # Step 5: Attach the BatchSpanProcessor.
    # Buffers completed spans in memory and flushes asynchronously on a
    # background thread. The agent thread never blocks on network I/O.
    processor = BatchSpanProcessor(
        _exporter,
        max_export_batch_size=batch_size,
        schedule_delay_millis=flush_interval_ms,
    )
    _provider.add_span_processor(processor)

    # Step 6: Set as the process-wide global TracerProvider.
    # All subsequent trace.get_tracer() calls will use this.
    trace.set_tracer_provider(_provider)

    # Step 7: Persist identity. Injected as span attributes on every span.
    _visitor_id = visitor_id
    _account_id = account_id

    # Step 8: Activate monkey-patching if requested.
    if auto_patch:
        _auto_patch_clients()

    # Step 9: Mark init complete.
    _initialized = True

    logger.info(
        "Pendo Agent SDK initialized. project=%s endpoint=%s",
        resolved_project_id,
        resolved_endpoint,
    )


def _auto_patch_clients() -> None:
    """
    Detect installed LLM client libraries and monkey-patch them.

    Uses the patcher registry to apply all registered patchers. Each patcher
    checks if its library is importable before patching — missing libraries
    are silently skipped.
    """
    patched = patch_all()
    if patched:
        logger.info("Auto-patched: %s", ", ".join(patched))


def get_tracer(name: Optional[str] = None) -> trace.Tracer:
    """
    Convenience wrapper for trace.get_tracer().

    Returns a Tracer bound to the global TracerProvider. Use this to create
    spans via context managers:

        tracer = pendo_sdk.get_tracer()
        with tracer.start_as_current_span("my_operation") as span:
            span.set_attribute("type", "CHAIN")
            ...

    Args:
        name: Tracer name. Defaults to "pendo_sdk".
    """
    return trace.get_tracer(name or _SDK_NAME)


def get_visitor_id() -> Optional[str]:
    """Return the configured visitor ID, or None if not set."""
    return _visitor_id


def get_account_id() -> Optional[str]:
    """Return the configured account ID, or None if not set."""
    return _account_id


def flush() -> None:
    """Force-flush all buffered spans immediately."""
    if _provider:
        _provider.force_flush()


def shutdown() -> None:
    """Flush remaining spans and shut down the SDK.

    After shutdown, init() can be called again to re-initialize.
    """
    global _provider, _exporter, _initialized, _visitor_id, _account_id
    if _provider:
        unpatch_all()
        _provider.shutdown()
        _provider = None
        _exporter = None
        _initialized = False
        _visitor_id = None
        _account_id = None
        logger.info("Pendo Agent SDK shut down.")
