"""OpenTelemetry span processor and exporter for LangSmith."""

import logging
import warnings
from typing import Any, Optional
from urllib.parse import urljoin

from langsmith import utils as ls_utils

try:
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.trace.export import BatchSpanProcessor

    OTEL_AVAILABLE = True
except ImportError:
    warnings.warn(
        "OpenTelemetry packages are not installed. "
        "Install optional OpenTelemetry dependencies with: "
        "pip install langsmith[otel]",
        UserWarning,
        stacklevel=2,
    )

    class OTLPSpanExporter:  # type: ignore[no-redef]
        """Mock otlp span exporter class."""

        def __init__(self, *args, **kwargs):
            """Mock init method."""
            raise ImportError(
                "OpenTelemetry packages are not installed. "
                "Install optional OpenTelemetry dependencies with: "
                "pip install langsmith[otel]"
            )

    class BatchSpanProcessor:  # type: ignore[no-redef]
        """Mock batch span processor class."""

        def __init__(self, *args, **kwargs):
            """Mock init method."""
            raise ImportError(
                "OpenTelemetry packages are not installed. "
                "Install optional OpenTelemetry dependencies with: "
                "pip install langsmith[otel]"
            )

    class trace:
        """Mock trace class."""

        @staticmethod
        def get_tracer_provider():
            """Mock get tracer provider method."""
            raise ImportError(
                "OpenTelemetry packages are not installed. "
                "Install optional OpenTelemetry dependencies with: "
                "pip install langsmith[otel]"
            )

    OTEL_AVAILABLE = False

LANGSMITH_METADATA_PREFIX = "langsmith.metadata"


class OtelExporter(OTLPSpanExporter):
    """A subclass of `OTLPSpanExporter` configured for LangSmith.

    Environment Variables:

    - `LANGSMITH_API_KEY`: Your LangSmith API key.
    - `LANGSMITH_ENDPOINT`: Base URL for LangSmith API (defaults to `https://api.smith.langchain.com`).
    - `LANGSMITH_PROJECT`: Project identifier.
    """

    def __init__(
        self,
        url: Optional[str] = None,
        api_key: Optional[str] = None,
        project: Optional[str] = None,
        headers: Optional[dict[str, str]] = None,
        **kwargs,
    ):
        """Initialize the `OtelExporter`.

        Args:
            url: OTLP endpoint URL. Defaults to `{LANGSMITH_ENDPOINT}/otel/v1/traces`.
            api_key: LangSmith API key. Defaults to `LANGSMITH_API_KEY` env var.
            parent: Parent identifier (e.g., `'project_name:test'`).

                Defaults to `LANGSMITH_PARENT` env var.
            headers: Additional headers to include in requests.
            **kwargs: Additional arguments passed to `OTLPSpanExporter`.
        """
        base_url = ls_utils.get_api_url(None)
        # Ensure base_url ends with / for proper joining
        if not base_url.endswith("/"):
            base_url += "/"
        endpoint = url or urljoin(base_url, "otel/v1/traces")
        api_key = api_key or ls_utils.get_api_key(None)
        project = project or ls_utils.get_tracer_project()
        headers = headers or {}

        if not api_key:
            raise ValueError(
                "API key is required. Provide it via api_key parameter or "
                "LANGSMITH_API_KEY environment variable."
            )

        if not project:
            project = "default"
            logging.info(
                "No project specified, using default. "
                "Configure with LANGSMITH_PROJECT environment variable or "
                "project parameter."
            )

        exporter_headers = {
            "x-api-key": api_key,
            **headers,
        }

        if project:
            exporter_headers["Langsmith-Project"] = project

        self.project = project

        super().__init__(endpoint=endpoint, headers=exporter_headers, **kwargs)


class OtelSpanProcessor:
    """A span processor for adding LangSmith to OpenTelemetry setups.

    This class combines the `OtelExporter` and `BatchSpanProcessor`
    into a single processor that can be added to any `TracerProvider`.

    Use this when:

    1. You already have OpenTelemetry initialized with other tools
    2. You want to add LangSmith alongside existing OTEL exporters

    Examples:
        # Fresh OpenTelemetry setup (LangSmith only):
        from langsmith.integrations.otel import configure
        configure(api_key="your-key", project="your-project")

        # Add LangSmith to existing OpenTelemetry setup:
        from opentelemetry import trace
        from langsmith.integrations.otel.processor import OtelSpanProcessor

        # Get your existing TracerProvider (already set by other tools)
        provider = trace.get_tracer_provider()

        # Add LangSmith processor alongside existing processors
        langsmith_processor = OtelSpanProcessor(
            project="your-project",
        )
        provider.add_span_processor(langsmith_processor)
    """

    def __init__(
        self,
        api_key: Optional[str] = None,
        project: Optional[str] = None,
        url: Optional[str] = None,
        headers: Optional[dict[str, str]] = None,
        SpanProcessor: Optional[type] = None,
    ):
        """Initialize the `OtelSpanProcessor`.

        Args:
            api_key: LangSmith API key. Defaults to `LANGSMITH_API_KEY` env var.
            project: Project identifier. Defaults to `LANGSMITH_PROJECT` env var.
            url: Base URL for LangSmith API. Defaults to `LANGSMITH_ENDPOINT` env var
                or `https://api.smith.langchain.com`.
            headers: Additional headers to include in requests.
            SpanProcessor: Optional span processor class. Defaults to
                `BatchSpanProcessor`.
        """
        # Create the exporter
        # Convert url to the full endpoint URL that OtelExporter expects
        exporter_url = None
        if url:
            exporter_url = f"{url.rstrip('/')}/otel/v1/traces"

        self._exporter = OtelExporter(
            url=exporter_url, api_key=api_key, project=project, headers=headers
        )

        # Create the processor chain
        if not OTEL_AVAILABLE:
            raise ImportError(
                "OpenTelemetry packages are not installed. "
                "Install optional OpenTelemetry dependencies with: "
                "pip install langsmith[otel]"
            )

        if SpanProcessor is None:
            SpanProcessor = BatchSpanProcessor

        self._processor = SpanProcessor(self._exporter)
        self._metadata: dict[str, Any] = {}

    def set_metadata(self, metadata: dict[str, Any]) -> None:
        """Set metadata attributes to propagate to all spans.

        Use this to ensure metadata like ``thread_id`` appears on every span
        in a trace, not just the root span.  This is required for LangSmith
        features (e.g. the threads view) that query runs by metadata fields.
        """
        self._metadata = metadata.copy()

    def on_start(self, span, parent_context=None):
        """Forward span start events to the inner processor."""
        if self._metadata:
            for key, value in self._metadata.items():
                if value is not None:
                    span.set_attribute(f"{LANGSMITH_METADATA_PREFIX}.{key}", value)
        self._processor.on_start(span, parent_context)

    def on_end(self, span):
        """Forward span end events to the inner processor."""
        self._processor.on_end(span)

    def _on_ending(self, span):
        """Forward span ending events to the inner processor."""
        if hasattr(self._processor, "_on_ending"):
            self._processor._on_ending(span)

    def shutdown(self):
        """Shutdown processor."""
        self._processor.shutdown()

    def force_flush(self, timeout_millis=30000):
        """Force flush the inner processor."""
        return self._processor.force_flush(timeout_millis)

    @property
    def exporter(self):
        """The underlying OtelExporter."""
        return self._exporter

    @property
    def processor(self):
        """The underlying span processor."""
        return self._processor
