"""
OTel SDK bridge for Risicare.

Provides two classes that plug into an existing OpenTelemetry TracerProvider:

1. RisicareSpanExporter — OTel SpanExporter that converts ReadableSpan
   objects to Risicare Spans and forwards them into the Risicare export
   pipeline.

2. RisicareSpanProcessor — OTel SpanProcessor for convenient single-class
   integration.  Wraps conversion + export so the user only needs one
   ``add_span_processor`` call.

All ``opentelemetry.*`` imports live inside method bodies so that
``import risicare`` never triggers an import-time dependency on the
OTel SDK.
"""

from __future__ import annotations

import logging
from typing import Any, Optional, Sequence, TYPE_CHECKING

if TYPE_CHECKING:
    pass  # All OTel types are resolved at runtime

logger = logging.getLogger(__name__)


# =============================================================================
# RisicareSpanExporter (OTel SpanExporter implementation)
# =============================================================================

class RisicareSpanExporter:
    """
    OpenTelemetry SpanExporter that forwards spans to Risicare.

    Usage::

        from opentelemetry.sdk.trace import TracerProvider
        from opentelemetry.sdk.trace.export import BatchSpanProcessor
        from risicare.integrations.otel import RisicareSpanExporter

        provider = TracerProvider()
        provider.add_span_processor(
            BatchSpanProcessor(RisicareSpanExporter())
        )
    """

    _warned_no_client = False

    def export(self, spans: Sequence[Any]) -> Any:
        """
        Export a batch of OTel ReadableSpans to Risicare.

        Args:
            spans: Sequence of ``opentelemetry.sdk.trace.ReadableSpan``.

        Returns:
            ``SpanExportResult.SUCCESS`` or ``SpanExportResult.FAILURE``.
        """
        from opentelemetry.sdk.trace.export import SpanExportResult

        from risicare.client import get_client
        from risicare.integrations.otel._converter import convert_otel_to_risicare

        client = get_client()
        if client is None or not client.is_enabled:
            if not RisicareSpanExporter._warned_no_client:
                logger.warning(
                    "RisicareSpanExporter.export() called but Risicare client "
                    "is not initialised — spans are being discarded. "
                    "Call risicare.init() before creating the OTel TracerProvider."
                )
                RisicareSpanExporter._warned_no_client = True
            return SpanExportResult.SUCCESS

        for otel_span in spans:
            try:
                risicare_span = convert_otel_to_risicare(otel_span)
                client.export_span(risicare_span)
            except Exception as exc:
                logger.debug("Failed to convert/export OTel span: %s", exc)

        return SpanExportResult.SUCCESS

    def shutdown(self) -> None:
        """Flush the Risicare export pipeline on shutdown."""
        from risicare.client import get_client

        client = get_client()
        if client is not None:
            client.flush(timeout_ms=5000)

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Flush pending spans."""
        from risicare.client import get_client

        client = get_client()
        if client is not None:
            return client.flush(timeout_ms=timeout_millis)
        return True


# =============================================================================
# RisicareSpanProcessor (OTel SpanProcessor implementation)
# =============================================================================

class RisicareSpanProcessor:
    """
    OpenTelemetry SpanProcessor that forwards ended spans to Risicare.

    Simpler alternative to ``RisicareSpanExporter`` — handles conversion
    and export in a single class without needing the OTel
    ``BatchSpanProcessor``.

    Usage::

        from opentelemetry.sdk.trace import TracerProvider
        from risicare.integrations.otel import RisicareSpanProcessor

        provider = TracerProvider()
        provider.add_span_processor(RisicareSpanProcessor())
    """

    _warned_no_client = False

    def on_start(
        self,
        span: Any,
        parent_context: Optional[Any] = None,
    ) -> None:
        """Called when a span starts. No-op."""

    def on_end(self, span: Any) -> None:
        """
        Called when a span ends.

        Converts the OTel ReadableSpan to a Risicare Span and exports it
        through the Risicare client pipeline.

        Args:
            span: An ``opentelemetry.sdk.trace.ReadableSpan``.
        """
        from risicare.client import get_client
        from risicare.integrations.otel._converter import convert_otel_to_risicare

        client = get_client()
        if client is None or not client.is_enabled:
            if not RisicareSpanProcessor._warned_no_client:
                logger.warning(
                    "RisicareSpanProcessor.on_end() called but Risicare client "
                    "is not initialised — spans are being discarded. "
                    "Call risicare.init() before creating the OTel TracerProvider."
                )
                RisicareSpanProcessor._warned_no_client = True
            return

        try:
            risicare_span = convert_otel_to_risicare(span)
            client.export_span(risicare_span)
        except Exception as exc:
            logger.debug("Failed to convert/export OTel span: %s", exc)

    def shutdown(self) -> None:
        """Flush the Risicare pipeline on TracerProvider shutdown."""
        from risicare.client import get_client

        client = get_client()
        if client is not None:
            client.flush(timeout_ms=5000)

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Flush pending spans."""
        from risicare.client import get_client

        client = get_client()
        if client is not None:
            return client.flush(timeout_ms=timeout_millis)
        return True


# =============================================================================
# Auto-bridge helper
# =============================================================================

def inject_risicare_processor() -> None:
    """
    Inject a ``RisicareSpanProcessor`` into the global OTel TracerProvider.

    Called by the import hook (``instrument_opentelemetry``) when the user
    has set ``otel_bridge=True``.  Only activates once.
    """
    try:
        from opentelemetry import trace as otel_trace

        provider = otel_trace.get_tracer_provider()

        # The real TracerProvider exposes add_span_processor.
        # The ProxyTracerProvider (pre-configure) does not.
        adder = getattr(provider, "add_span_processor", None)
        if adder is None:
            logger.debug(
                "OTel TracerProvider (%s) has no add_span_processor — "
                "skipping auto-bridge. Configure the TracerProvider before "
                "calling risicare.init().",
                type(provider).__name__,
            )
            return

        adder(RisicareSpanProcessor())
        logger.debug(
            "Injected RisicareSpanProcessor into OTel TracerProvider (%s)",
            type(provider).__name__,
        )
    except ImportError:
        logger.debug("opentelemetry.trace not importable — skipping auto-bridge")
    except Exception as exc:
        logger.debug("Failed to inject Risicare processor: %s", exc)
