//! OpenTelemetry initialization for traces and logs.
//!
//! Exports traces and logs via OTLP/HTTP when `OTEL_EXPORTER_OTLP_ENDPOINT`
//! is set. Always includes a `tracing_subscriber::fmt` layer for console output.
//!
//! Compatible with SigNoz, Grafana Tempo/Loki, Jaeger, and any OTLP-compatible
//! collector.
//!
//! # Environment Variables
//!
//! | Variable | Description | Default |
//! |---|---|---|
//! | `OTEL_EXPORTER_OTLP_ENDPOINT` | OTLP collector endpoint (e.g. `https://otel.example.com`) | *(disabled)* |
//! | `OTEL_SERVICE_NAME` | Service name for resource | `service_name` param |
//! | `RUST_LOG` / standard `EnvFilter` | Log level filter | `info` |

use opentelemetry::global;
use opentelemetry::trace::TracerProvider as _;
use opentelemetry::KeyValue;
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_otlp::{LogExporter, SpanExporter, WithExportConfig};
use opentelemetry_sdk::{logs::SdkLoggerProvider, trace::SdkTracerProvider, Resource};
use tracing_opentelemetry::OpenTelemetryLayer;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter, Layer as _};

/// Guard that shuts down OTel providers on drop.
///
/// Must be held for the lifetime of the application. When dropped,
/// flushes and shuts down the tracer and logger providers.
pub struct TelemetryGuard {
    tracer_provider: Option<SdkTracerProvider>,
    logger_provider: Option<SdkLoggerProvider>,
}

impl Drop for TelemetryGuard {
    fn drop(&mut self) {
        if let Some(tp) = self.tracer_provider.take() {
            if let Err(e) = tp.shutdown() {
                eprintln!("Failed to shutdown tracer provider: {e}");
            }
        }
        if let Some(lp) = self.logger_provider.take() {
            if let Err(e) = lp.shutdown() {
                eprintln!("Failed to shutdown logger provider: {e}");
            }
        }
    }
}

fn build_resource(service_name: &str) -> Resource {
    let name = std::env::var("OTEL_SERVICE_NAME").unwrap_or_else(|_| service_name.to_string());
    Resource::builder()
        .with_service_name(name)
        .with_attributes([KeyValue::new("service.version", env!("CARGO_PKG_VERSION"))])
        .build()
}

/// Initialize telemetry (tracing + logging).
///
/// If `OTEL_EXPORTER_OTLP_ENDPOINT` is set, traces and logs are exported
/// via OTLP/HTTP. Console fmt output is always enabled.
///
/// Returns a [`TelemetryGuard`] that must be held alive for the application
/// lifetime. Dropping it triggers a graceful shutdown of OTel providers.
///
/// # Panics
///
/// Panics if the tracing subscriber cannot be initialized (e.g. called twice).
pub fn init_telemetry(service_name: &str) -> TelemetryGuard {
    let otel_endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok();

    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
    let fmt_layer = tracing_subscriber::fmt::layer();

    match otel_endpoint {
        Some(ref endpoint) => {
            let resource = build_resource(service_name);

            let traces_endpoint = format!("{endpoint}/v1/traces");
            let logs_endpoint = format!("{endpoint}/v1/logs");

            // Traces
            #[allow(clippy::expect_used)]
            let span_exporter = SpanExporter::builder()
                .with_http()
                .with_endpoint(&traces_endpoint)
                .build()
                .expect("failed to create OTLP span exporter");

            let tracer_provider = SdkTracerProvider::builder()
                .with_batch_exporter(span_exporter)
                .with_resource(resource.clone())
                .build();

            global::set_tracer_provider(tracer_provider.clone());
            let tracer = tracer_provider.tracer(service_name.to_string());
            let otel_trace_layer = OpenTelemetryLayer::new(tracer);

            // Logs
            #[allow(clippy::expect_used)]
            let log_exporter = LogExporter::builder()
                .with_http()
                .with_endpoint(&logs_endpoint)
                .build()
                .expect("failed to create OTLP log exporter");

            let logger_provider = SdkLoggerProvider::builder()
                .with_batch_exporter(log_exporter)
                .with_resource(resource)
                .build();

            let otel_log_layer = OpenTelemetryTracingBridge::new(&logger_provider);

            // Filter to prevent telemetry-induced-telemetry loops
            let otel_filter = EnvFilter::new("info")
                .add_directive("hyper=off".parse().unwrap_or_default())
                .add_directive("h2=off".parse().unwrap_or_default())
                .add_directive("reqwest=off".parse().unwrap_or_default());

            tracing_subscriber::registry()
                .with(env_filter)
                .with(fmt_layer)
                .with(otel_trace_layer)
                .with(otel_log_layer.with_filter(otel_filter))
                .init();

            tracing::info!(
                endpoint = endpoint.as_str(),
                service = service_name,
                "OpenTelemetry OTLP/HTTP export enabled"
            );

            TelemetryGuard {
                tracer_provider: Some(tracer_provider),
                logger_provider: Some(logger_provider),
            }
        }
        None => {
            tracing_subscriber::registry()
                .with(env_filter)
                .with(fmt_layer)
                .init();

            tracing::info!("OpenTelemetry disabled (set OTEL_EXPORTER_OTLP_ENDPOINT to enable)");

            TelemetryGuard {
                tracer_provider: None,
                logger_provider: None,
            }
        }
    }
}
