"""OpenTelemetry backend for distributed tracing."""

import asyncio
from dataclasses import dataclass
from typing import Any

from ..core.backend import BackendConfig, LoggingBackend, LogRecord
from ..formatters import JSONFormatter, LogFormatter

try:
    from opentelemetry import trace
    from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    from opentelemetry.trace import SpanKind, Status, StatusCode
    OTEL_AVAILABLE = True
except ImportError:
    OTEL_AVAILABLE = False
    trace = None
    OTLPSpanExporter = None
    TracerProvider = None
    Resource = None
    BatchSpanProcessor = None
    SpanKind = None
    Status = None
    StatusCode = None


@dataclass
class OTelBackendConfig(BackendConfig):
    """Configuration for OpenTelemetry backend."""

    enabled: bool = True
    timeout: float = 5.0
    retry_attempts: int = 3
    retry_delay: float = 1.0
    endpoint: str = "http://localhost:4318/v1/traces"
    service_name: str = "unified-logger"
    batch_size: int = 100


class OTelBackend(LoggingBackend):
    """Backend that sends logs as traces to OpenTelemetry collectors (Jaeger, etc.)."""

    def __init__(
        self,
        name: str = "otel",
        config: OTelBackendConfig | None = None,
        formatter: LogFormatter | None = None,
        **kwargs: Any
    ) -> None:
        """Initialize OpenTelemetry backend.
        
        Args:
            name: Backend name
            config: Backend configuration
            formatter: Log formatter to use (defaults to JSON)
            **kwargs: Additional configuration
        """
        self.config = config or OTelBackendConfig()
        super().__init__(name, self.config, **kwargs)

        self.formatter = formatter or JSONFormatter()
        self._tracer_provider = None
        self._tracer = None
        self._span_processor = None

    async def connect(self) -> bool:
        """Connect to OpenTelemetry collector."""
        if not OTEL_AVAILABLE:
            raise ImportError(
                "OpenTelemetry backend requires 'opentelemetry-api', "
                "'opentelemetry-sdk', and 'opentelemetry-exporter-otlp-proto-http' packages. "
                "Install with: pip install opentelemetry-api opentelemetry-sdk "
                "opentelemetry-exporter-otlp-proto-http"
            )

        try:
            # Create resource with service name
            resource = Resource.create({
                "service.name": self.config.service_name,
                "service.version": "1.0.0"
            })

            # Create tracer provider
            self._tracer_provider = TracerProvider(resource=resource)

            # Create OTLP exporter
            otlp_exporter = OTLPSpanExporter(
                endpoint=self.config.endpoint,
                timeout=int(self.config.timeout)
            )

            # Add batch span processor
            self._span_processor = BatchSpanProcessor(otlp_exporter)
            self._tracer_provider.add_span_processor(self._span_processor)

            # Set as global tracer provider
            trace.set_tracer_provider(self._tracer_provider)

            # Get tracer
            self._tracer = trace.get_tracer(__name__)

            self._connected = True
            return True

        except Exception as e:
            raise ConnectionError(f"Failed to connect to OpenTelemetry collector: {e}") from e

    async def disconnect(self) -> None:
        """Disconnect from OpenTelemetry collector."""
        try:
            if self._span_processor:
                # Force flush any pending spans
                self._span_processor.force_flush(timeout_millis=int(self.config.timeout * 1000))
                self._span_processor.shutdown()
                self._span_processor = None

            if self._tracer_provider:
                self._tracer_provider.shutdown()
                self._tracer_provider = None

            self._tracer = None
            self._connected = False

        except Exception:
            # Ignore errors during disconnect
            pass

    async def send_log(self, record: LogRecord) -> bool:
        """Send log record as a trace span to OpenTelemetry.
        
        Args:
            record: The log record to send
            
        Returns:
            True if successfully sent, False otherwise
        """
        if not self._tracer or not self._connected:
            return False

        try:
            # Convert log record to trace span
            import logging
            level_name = logging.getLevelName(record.level)
            
            # Create span
            with self._tracer.start_as_current_span(
                name=f"{record.logger_name}.{level_name}",
                kind=SpanKind.INTERNAL,
                start_time=int(record.timestamp.timestamp() * 1e9)  # nanoseconds
            ) as span:
                # Set basic attributes
                span.set_attribute("log.level", level_name)
                span.set_attribute("log.logger", record.logger_name)
                span.set_attribute("log.message", record.message)

                # Add extra fields as attributes
                if record.extra:
                    for key, value in record.extra.items():
                        # Convert value to appropriate type
                        if isinstance(value, (str, int, float, bool)):
                            span.set_attribute(f"log.{key}", value)
                        else:
                            span.set_attribute(f"log.{key}", str(value))

                # Handle exceptions
                if record.exc_info:
                    span.set_status(Status(StatusCode.ERROR))
                    span.set_attribute("error", True)
                    if record.exc_info[0]:
                        span.set_attribute("error.type", record.exc_info[0].__name__)
                    if record.exc_info[1]:
                        span.set_attribute("error.message", str(record.exc_info[1]))
                    
                    # Add exception event
                    import traceback
                    exception_text = ''.join(traceback.format_exception(*record.exc_info))
                    span.add_event(
                        name="exception",
                        attributes={
                            "exception.type": record.exc_info[0].__name__ if record.exc_info[0] else "Unknown",
                            "exception.message": str(record.exc_info[1]) if record.exc_info[1] else "",
                            "exception.stacktrace": exception_text
                        }
                    )

            return True

        except Exception as e:
            import logging
            logging.getLogger(__name__).error(f"Failed to send log to OpenTelemetry: {e}")
            return False

    async def send_logs_batch(self, records: list[LogRecord]) -> list[bool]:
        """Send multiple log records as trace spans to OpenTelemetry.
        
        Args:
            records: List of log records to send
            
        Returns:
            List of success/failure status for each record
        """
        # Send records individually in parallel
        tasks = [self.send_log(record) for record in records]
        results = await asyncio.gather(*tasks, return_exceptions=True)

        # Convert exceptions to False
        return [
            result if isinstance(result, bool) else False
            for result in results
        ]

    async def health_check(self) -> bool:
        """Check OpenTelemetry collector connectivity.
        
        Returns:
            True if collector is reachable, False otherwise
        """
        if not self._tracer or not self._connected:
            return False

        try:
            # Create a test span to verify tracer is working
            with self._tracer.start_as_current_span("health_check") as span:
                span.set_attribute("health_check", True)

            # Force flush to ensure span is sent
            if self._span_processor:
                self._span_processor.force_flush(timeout_millis=int(self.config.timeout * 1000))

            return True

        except Exception as e:
            import logging
            logging.getLogger(__name__).error(f"Health check failed: {e}")
            return False
