//! Tracing init with optional OTLP export.
//!
//! Both bin targets (`worker`, `hrrr_point_worker`) call `init(svc)` at
//! startup. The local fmt layer (JSON to stdout) is always on so kubectl
//! logs keeps working. When `OTEL_EXPORTER_OTLP_ENDPOINT` is set we
//! additionally install a `tracing-opentelemetry` layer that ships
//! spans over OTLP/gRPC to the cluster's OTel Collector — see
//! `k8s/deployment-grid-rs.yaml` + `deployment-hrrr-point-rs.yaml`
//! for the env wiring.
//!
//! Returning the `TracerProvider` from `init` is deliberate: the caller
//! must hold it until shutdown and call `.shutdown()` so queued spans
//! flush before the process exits. Dropping it mid-run cancels the
//! background exporter and loses recent spans.
//!
//! Service identity is set via OpenTelemetry resource attributes
//! (`service.name`, `service.namespace`) so Tempo groups spans per
//! binary. `service.namespace=prop` matches the k8s namespace.

use opentelemetry::{global, trace::TracerProvider as _, KeyValue};
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{trace::SdkTracerProvider, Resource};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};

/// Opaque handle the caller must hold until process shutdown; dropping
/// it stops the OTLP exporter. Returns `None` when OTLP is disabled
/// (no endpoint env var) so callers don't need to care which mode is
/// active.
pub struct TelemetryGuard {
    provider: Option<SdkTracerProvider>,
}

impl TelemetryGuard {
    /// Flush queued spans and shut down the exporter. Safe to call
    /// multiple times; subsequent calls are no-ops.
    pub fn shutdown(mut self) {
        if let Some(provider) = self.provider.take() {
            let _ = provider.shutdown();
        }
    }
}

impl Drop for TelemetryGuard {
    fn drop(&mut self) {
        if let Some(provider) = self.provider.take() {
            let _ = provider.shutdown();
        }
    }
}

/// Initialize tracing for a binary. `service_name` becomes the
/// `service.name` attribute in exported spans and the log target
/// prefix.
pub fn init(service_name: &'static str) -> TelemetryGuard {
    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));

    let fmt_layer = tracing_subscriber::fmt::layer().json();

    // If the collector endpoint isn't set, skip the OTLP layer entirely
    // — useful for local cargo runs where there's no collector to ship
    // to. The fmt layer alone keeps dev parity with prod log output.
    let otlp_endpoint = std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").ok();

    match otlp_endpoint {
        Some(endpoint) if !endpoint.is_empty() => {
            let exporter = opentelemetry_otlp::SpanExporter::builder()
                .with_tonic()
                .with_endpoint(endpoint)
                .build()
                .expect("build OTLP span exporter");

            let resource = Resource::builder()
                .with_attributes([
                    KeyValue::new("service.name", service_name),
                    KeyValue::new("service.namespace", "prop"),
                ])
                .build();

            let provider = SdkTracerProvider::builder()
                .with_batch_exporter(exporter)
                .with_resource(resource)
                .build();

            let tracer = provider.tracer(service_name);
            global::set_tracer_provider(provider.clone());

            let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);

            tracing_subscriber::registry()
                .with(env_filter)
                .with(fmt_layer)
                .with(otel_layer)
                .init();

            TelemetryGuard {
                provider: Some(provider),
            }
        }
        _ => {
            tracing_subscriber::registry()
                .with(env_filter)
                .with(fmt_layer)
                .init();

            TelemetryGuard { provider: None }
        }
    }
}
