"""
OpenTelemetry configuration for tracing and metrics.

This module provides functionality to initialize and configure OpenTelemetry
for both tracing (Jaeger via OTLP) and metrics (Prometheus) in the AMQ adapter.
"""

import logging
import os
from dataclasses import dataclass
from typing import Any, Dict, Optional

from opentelemetry import metrics, trace
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
from opentelemetry.sdk.resources import SERVICE_NAME, SERVICE_VERSION, Resource
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from opentelemetry.trace import Tracer
from prometheus_client import start_http_server

logger = logging.getLogger(__name__)


@dataclass
class TracingConfig:
    """Configuration for OpenTelemetry tracing."""

    service_name: str
    service_version: str
    otlp_endpoint: str = "http://localhost:4317"  # Default Jaeger OTLP endpoint
    insecure: bool = True
    debug: bool = False
    additional_attributes: Optional[Dict[str, Any]] = None


@dataclass
class MetricsConfig:
    """Configuration for OpenTelemetry metrics."""

    prometheus_port: int = 9464
    prometheus_host: str = "localhost"
    export_interval_millis: int = 30000
    additional_attributes: Optional[Dict[str, Any]] = None


def create_resource(config: TracingConfig) -> Resource:
    """Create an OpenTelemetry resource with service information."""
    attributes = {
        SERVICE_NAME: config.service_name,
        SERVICE_VERSION: config.service_version,
    }

    if config.additional_attributes:
        attributes.update(config.additional_attributes)

    return Resource.create(attributes)


def setup_otlp_exporter(config: TracingConfig) -> OTLPSpanExporter:
    """Configure and create an OTLP exporter for Jaeger."""
    return OTLPSpanExporter(endpoint=config.otlp_endpoint, insecure=config.insecure)


def setup_trace_provider(config: TracingConfig, resource: Resource) -> TracerProvider:
    """Set up the TracerProvider with OTLP exporter."""
    provider = TracerProvider(resource=resource)

    # Add OTLP exporter for Jaeger
    otlp_exporter = setup_otlp_exporter(config)
    provider.add_span_processor(BatchSpanProcessor(otlp_exporter))

    # Add console exporter in debug mode
    if config.debug:
        provider.add_span_processor(BatchSpanProcessor(ConsoleSpanExporter()))

    return provider


def setup_metrics_provider(config: MetricsConfig, resource: Resource) -> MeterProvider:
    """Set up the MeterProvider with Prometheus export."""
    try:
        # Start Prometheus HTTP server
        start_http_server(port=config.prometheus_port, addr=config.prometheus_host)
        logger.info(
            f"Started Prometheus metrics server on {config.prometheus_host}:{config.prometheus_port}"
        )

        # Create Prometheus reader
        prometheus_reader = PrometheusMetricReader()

        # Create periodic reader for custom metrics
        periodic_reader = PeriodicExportingMetricReader(
            prometheus_reader, export_interval_millis=config.export_interval_millis
        )

        # Create and configure meter provider
        return MeterProvider(resource=resource, metric_readers=[prometheus_reader, periodic_reader])
    except Exception as e:
        logger.error(f"Failed to set up metrics provider: {str(e)}")
        # Return a no-op meter provider
        return MeterProvider(resource=resource)


def initialize_telemetry(
    tracing_config: Optional[TracingConfig] = None,
    metrics_config: Optional[MetricsConfig] = None,
) -> Tracer:
    """
    Initialize OpenTelemetry with tracing and metrics.

    Args:
        tracing_config: Configuration for tracing
        metrics_config: Configuration for metrics

    Returns:
        Tracer: Configured OpenTelemetry tracer

    Example:
        ```python
        config = TracingConfig(
            service_name="amq-adapter",
            service_version="1.0.0",
            otlp_endpoint="http://jaeger:4317",
            debug=True
        )

        metrics_config = MetricsConfig(
            prometheus_port=9464,
            prometheus_host="0.0.0.0"
        )

        tracer = initialize_telemetry(config, metrics_config)
        ```
    """
    # Use default configs if none provided
    if tracing_config is None:
        tracing_config = TracingConfig(
            service_name=os.getenv("SERVICE_NAME", "amq-adapter"),
            service_version=os.getenv("SERVICE_VERSION", "unknown"),
            otlp_endpoint=os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT", "http://localhost:4317"),
            insecure=os.getenv("OTEL_INSECURE_MODE", "true").lower() == "true",
            debug=os.getenv("OTEL_DEBUG", "false").lower() == "true",
        )

    if metrics_config is None:
        metrics_config = MetricsConfig(
            prometheus_port=int(os.getenv("PROMETHEUS_PORT", "9464")),
            prometheus_host=os.getenv("PROMETHEUS_HOST", "localhost"),
            export_interval_millis=int(os.getenv("METRICS_EXPORT_INTERVAL", "30000")),
        )

    try:
        # Create resource
        resource = create_resource(tracing_config)

        # Set up tracing
        provider = setup_trace_provider(tracing_config, resource)
        trace.set_tracer_provider(provider)
        logger.info(f"Initialized tracing for service {tracing_config.service_name}")

        # Set up metrics
        metrics_provider = setup_metrics_provider(metrics_config, resource)
        metrics.set_meter_provider(metrics_provider)
        logger.info("Initialized metrics export")

        # Return configured tracer
        return trace.get_tracer(
            tracing_config.service_name,
            tracing_config.service_version,
        )

    except Exception as e:
        logger.error(f"Failed to initialize telemetry: {str(e)}")
        # Return a no-op tracer in case of failure
        return trace.get_tracer("no-op")


def create_span(tracer: Tracer, name: str, attributes: Optional[Dict[str, Any]] = None):
    """
    Create a new span with the given name and attributes.

    Args:
        tracer: The OpenTelemetry tracer
        name: Name of the span
        attributes: Optional attributes to add to the span

    Returns:
        A context manager that creates and manages a span
    """
    return tracer.start_as_current_span(
        name,
        attributes=attributes or {},
    )
