//! Tracing initialization for Common Agent Runtime.
//!
//! Provides a layered tracing subscriber:
//! - `fmt` layer: human-readable stderr output (always on)
//! - `opentelemetry` layer: OTLP export (enabled when `OTEL_EXPORTER_OTLP_ENDPOINT` is set)

use opentelemetry::trace::TracerProvider;
use opentelemetry_sdk::trace::SdkTracerProvider;
use opentelemetry_sdk::Resource;
use std::sync::OnceLock;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use tracing_subscriber::EnvFilter;

/// Holds the tracer provider so we can shut it down explicitly.
static TRACER_PROVIDER: OnceLock<SdkTracerProvider> = OnceLock::new();

/// Idempotency guard — ensures `init_tracing` only runs once.
static INIT: OnceLock<()> = OnceLock::new();

/// Initialize the global tracing subscriber.
///
/// - Always installs a `fmt` layer writing to stderr with `RUST_LOG` env filter.
/// - If `OTEL_EXPORTER_OTLP_ENDPOINT` is set, installs an OpenTelemetry layer
///   that exports spans via OTLP gRPC to the configured endpoint.
/// - If the env var is absent, no OTLP connection is attempted (zero overhead).
/// - Safe to call more than once; second and subsequent calls return immediately.
pub fn init_tracing(service_name: &str) {
    if INIT.set(()).is_err() {
        return; // already initialized
    }

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

    let fmt_layer = tracing_subscriber::fmt::layer()
        .with_target(false)
        .with_writer(std::io::stderr);

    let registry = tracing_subscriber::registry()
        .with(env_filter)
        .with(fmt_layer);

    if std::env::var("OTEL_EXPORTER_OTLP_ENDPOINT").is_ok() {
        let exporter = match opentelemetry_otlp::SpanExporter::builder()
            .with_tonic()
            .build()
        {
            Ok(e) => e,
            Err(err) => {
                eprintln!("[car-telemetry] failed to build OTLP exporter: {err}");
                registry.init();
                return;
            }
        };

        let provider = SdkTracerProvider::builder()
            .with_batch_exporter(exporter)
            .with_resource(
                Resource::builder()
                    .with_service_name(service_name.to_string())
                    .build(),
            )
            .build();

        let tracer = provider.tracer(service_name.to_string());
        let _ = TRACER_PROVIDER.set(provider.clone());
        let _ = opentelemetry::global::set_tracer_provider(provider);

        let otel_layer = tracing_opentelemetry::layer().with_tracer(tracer);
        registry.with(otel_layer).init();
    } else {
        registry.init();
    }
}

/// Flush and shut down the OpenTelemetry exporter.
/// Call before process exit to ensure all spans are exported.
pub fn shutdown_tracing() {
    if let Some(provider) = TRACER_PROVIDER.get() {
        if let Err(err) = provider.shutdown() {
            eprintln!("[car-telemetry] error shutting down tracer provider: {err}");
        }
    }
}

// --- Event vocabulary ---
// Typed helpers for common domain events. These produce structured tracing
// events that show up in Phoenix/Jaeger. Callers can also use raw
// tracing::info!(event = "custom.thing", ...) directly.

pub fn emit_proposal_received(proposal_id: &str, action_count: usize) {
    tracing::info!(event = "proposal.received", proposal_id, action_count);
}

pub fn emit_action_succeeded(action_id: &str, tool: &str, duration_ms: u64) {
    tracing::info!(event = "action.succeeded", action_id, tool, duration_ms);
}

pub fn emit_action_failed(action_id: &str, tool: &str, error: &str) {
    tracing::warn!(event = "action.failed", action_id, tool, error);
}

pub fn emit_policy_blocked(action_id: &str, policy_name: &str) {
    tracing::warn!(event = "policy.blocked", action_id, policy_name);
}

pub fn emit_model_routed(model: &str, complexity: &str, reason: &str) {
    tracing::info!(event = "model.routed", model, complexity, reason);
}

pub fn emit_skill_distilled(skill_name: &str, success_rate: f64) {
    tracing::info!(event = "skill.distilled", skill_name, success_rate);
}

pub fn emit_conversation_compacted(tokens_before: usize, tokens_after: usize) {
    tracing::info!(
        event = "conversation.compacted",
        tokens_before,
        tokens_after
    );
}
