"""OpenTelemetry integration for Beacon.

Provides span exporters for the OpenTelemetry pipeline:
- OTLP HTTP export is handled via OpenTelemetry's native OTLPSpanExporter
- File export is handled via FileSpanExporter (for file-only mode)

The BeaconClient automatically configures the appropriate exporter when
auto_instrument_opentelemetry=True (default).

Example:
    >>> from lumenova_beacon import BeaconClient
    >>>
    >>> # HTTP mode - uses OTLPSpanExporter
    >>> client = BeaconClient(
    ...     endpoint="http://localhost:8000",
    ...     api_key="your-api-key",
    ... )
    >>>
    >>> # File mode - uses FileSpanExporter
    >>> client = BeaconClient(
    ...     file_directory="./traces",
    ... )
"""

from __future__ import annotations

import json
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Sequence

try:
    from opentelemetry.sdk.trace.export import SpanExporter, SpanExportResult
except ImportError:
    SpanExporter = object
    SpanExportResult = None

logger = logging.getLogger(__name__)


class FileSpanExporter(SpanExporter):
    """OpenTelemetry SpanExporter that writes spans to JSON files.

    This exporter plugs into the OTel pipeline (via BatchSpanProcessor)
    and writes each span as a JSON file, using the same format as
    Beacon's Span.to_dict().
    """

    def __init__(
        self,
        directory: str,
        filename_pattern: str = "{span_id}.json",
        pretty_print: bool = True,
    ):
        self._directory = Path(directory)
        self._directory.mkdir(parents=True, exist_ok=True)
        self._filename_pattern = filename_pattern
        self._pretty_print = pretty_print

    def export(self, spans: Sequence) -> SpanExportResult:
        """Export spans by writing each to a JSON file.

        Args:
            spans: Sequence of ReadableSpan objects from the OTel pipeline.

        Returns:
            SpanExportResult.SUCCESS (0) or SpanExportResult.FAILURE (1)
        """
        try:
            for span in spans:
                self._write_span(span)
            return SpanExportResult.SUCCESS
        except Exception as e:
            logger.error(f"Failed to export spans to files: {e}")
            return SpanExportResult.FAILURE

    def _write_span(self, span) -> None:
        """Write a single OTel ReadableSpan to a JSON file."""
        span_context = span.context if hasattr(span, 'context') else span.get_span_context()
        trace_id = format(span_context.trace_id, '032x')
        span_id = format(span_context.span_id, '016x')

        # Build parent_id
        parent_id = None
        if span.parent and span.parent.span_id:
            parent_id = format(span.parent.span_id, '016x')

        # Map OTel SpanKind int to string
        kind_names = {0: "INTERNAL", 1: "SERVER", 2: "CLIENT", 3: "PRODUCER", 4: "CONSUMER"}
        kind_value = span.kind.value if hasattr(span.kind, 'value') else int(span.kind)
        kind_name = kind_names.get(kind_value, "INTERNAL")

        # Build status
        status_dict = {"status_code": "UNSET"}
        if hasattr(span, 'status') and span.status:
            code = span.status.status_code
            code_value = code.value if hasattr(code, 'value') else int(code)
            status_names = {0: "UNSET", 1: "OK", 2: "ERROR"}
            status_dict["status_code"] = status_names.get(code_value, "UNSET")
            if span.status.description:
                status_dict["description"] = span.status.description

        # Convert nanosecond timestamps to ISO format
        start_iso = datetime.fromtimestamp(span.start_time / 1e9, tz=timezone.utc).isoformat()
        end_iso = datetime.fromtimestamp(span.end_time / 1e9, tz=timezone.utc).isoformat()

        # Build attributes (convert to JSON-safe types)
        attributes = {}
        if span.attributes:
            for k, v in span.attributes.items():
                attributes[k] = v

        data = {
            "name": span.name,
            "context": {
                "trace_id": trace_id,
                "span_id": span_id,
                "trace_state": "",
            },
            "kind": kind_name,
            "start_time": start_iso,
            "end_time": end_iso,
            "status": status_dict,
            "attributes": attributes,
        }

        if parent_id:
            data["parent_id"] = parent_id

        # Generate filename
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
        filename = self._filename_pattern.format(
            span_id=span_id,
            trace_id=trace_id,
            name=span.name.replace("/", "_").replace("\\", "_"),
            timestamp=timestamp,
        )
        filepath = self._directory / filename

        with open(filepath, "w", encoding="utf-8") as f:
            if self._pretty_print:
                json.dump(data, f, indent=2, ensure_ascii=False, default=str)
            else:
                json.dump(data, f, ensure_ascii=False, default=str)

        logger.debug(f"Saved span {span_id} to {filepath}")

    def shutdown(self) -> None:
        """Shut down the exporter."""
        pass

    def force_flush(self, timeout_millis: int = 30000) -> bool:
        """Force flush (no-op for file exporter)."""
        return True
