use crate::observability::config::{LogRemoteConfig, OtlpProtocol, TraceConfig};
use crate::observability::filter::SharedOrderedFilter;
use crate::observability::otlp_http::build_no_proxy_http_client;
use anyhow::{Context as AnyhowContext, Result};
use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge;
use opentelemetry_otlp::{LogExporter, Protocol, WithExportConfig, WithHttpConfig};
use opentelemetry_sdk::{logs::SdkLoggerProvider, Resource};
use tracing_subscriber::{Layer, Registry};

/// Builds the remote OpenTelemetry logs layer and provider.
///
/// The supplied `filter` is the shared ordered filter used by all log outputs.
pub fn build_remote_log_layer(
    remote: &LogRemoteConfig,
    trace: &TraceConfig,
    filter: SharedOrderedFilter,
) -> Result<(Box<dyn Layer<Registry> + Send + Sync>, SdkLoggerProvider)> {
    let exporter = match remote.protocol {
        OtlpProtocol::HttpBinary => LogExporter::builder()
            .with_http()
            .with_http_client(build_no_proxy_http_client()?)
            .with_protocol(Protocol::HttpBinary)
            .with_endpoint(append_otlp_path(&remote.endpoint, "/v1/logs"))
            .build(),
        OtlpProtocol::Grpc => LogExporter::builder()
            .with_tonic()
            .with_endpoint(remote.endpoint.clone())
            .build(),
    }
    .context("failed to create OpenTelemetry log exporter")?;

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

    let layer = OpenTelemetryTracingBridge::new(&provider)
        .with_filter(filter)
        .boxed();

    Ok((layer, provider))
}

pub(crate) fn resource(trace: &TraceConfig) -> Resource {
    let mut builder = Resource::builder().with_service_name(trace.service_name.clone());
    if let Some(version) = &trace.service_version {
        builder = builder.with_attribute(opentelemetry::KeyValue::new(
            "service.version",
            version.clone(),
        ));
    }
    builder.build()
}

pub(crate) fn append_otlp_path(endpoint: &str, path: &str) -> String {
    if endpoint.ends_with(path) {
        endpoint.to_string()
    } else {
        format!("{}{}", endpoint.trim_end_matches('/'), path)
    }
}
