use crate::auth_token::AuthToken;
use crate::otel::auth_interceptor::AuthInterceptor;
use crate::otel::config::{METRICS_READER_INTERVAL_SECONDS, RESOURCE_ATTRIBUTES};
use crate::otel::runtime::tokio_runtime;
use opentelemetry::global;
use opentelemetry::metrics::{Meter, MeterProvider};
use opentelemetry_otlp::{MetricExporter, WithExportConfig, WithTonicConfig};
use opentelemetry_sdk::metrics::SdkMeterProvider;
use opentelemetry_sdk::{Resource, metrics as sdkmetrics};
use std::sync::{Arc, RwLock};

pub fn init_otel_metrics(
    auth_token: Arc<AuthToken>,
    endpoint: &str,
) -> anyhow::Result<sdkmetrics::SdkMeterProvider> {
    let rt = tokio_runtime();

    let meter_provider = rt.block_on(async {
        let metrics_exporter = MetricExporter::builder()
            .with_tonic()
            .with_endpoint(endpoint)
            .with_interceptor(AuthInterceptor {
                token: auth_token.clone(),
            })
            .build()?;

        let reader = sdkmetrics::PeriodicReader::builder(metrics_exporter)
            .with_interval(std::time::Duration::from_secs(
                METRICS_READER_INTERVAL_SECONDS,
            ))
            .build();

        let resource = Resource::builder_empty()
            .with_attributes(RESOURCE_ATTRIBUTES.iter().cloned())
            .build();

        let meter_provider = SdkMeterProvider::builder()
            .with_resource(resource)
            .with_reader(reader)
            .build();

        Ok::<SdkMeterProvider, anyhow::Error>(meter_provider)
    })?;

    global::set_meter_provider(meter_provider.clone());

    Ok(meter_provider)
}

#[derive(Debug)]
pub struct PolicyEngineMetrics {
    pub decision_latency_ms: opentelemetry::metrics::Histogram<f64>,
}

impl PolicyEngineMetrics {
    pub fn new(meter_provider: SdkMeterProvider) -> Self {
        let meter: Meter = meter_provider.meter("policy-engine");

        Self {
            decision_latency_ms: meter
                .f64_histogram("policy_engine.decision_latency_ms")
                .with_description("Latency of policy decisions in milliseconds")
                .with_unit("ms")
                .build(),
        }
    }
}

#[derive(Clone, Debug)]
pub struct MeterProviderManager {
    inner: Arc<RwLock<SdkMeterProvider>>, // current meter provider
    endpoint: String,
}

impl MeterProviderManager {
    pub fn create_from_token(token: Arc<AuthToken>, endpoint: &str) -> anyhow::Result<Self> {
        let provider = init_otel_metrics(token, endpoint)?;
        Ok(Self {
            inner: Arc::new(RwLock::new(provider)),
            endpoint: endpoint.to_string(),
        })
    }
    pub fn get(&self) -> SdkMeterProvider {
        self.inner
            .read()
            .expect("Failed to acquire lock on SdkMeterProvider")
            .clone()
    }

    pub fn shutdown(&self) {
        let guard = self.inner.write().unwrap();
        let _ = guard.shutdown();
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    // Sanity: we can initialize metrics with explicit metadata and record a value.
    #[test]
    fn init_metrics_with_auth_token_and_record() {
        let auth_token = Arc::new(AuthToken::new("Bearer test-token"));
        let provider = init_otel_metrics(auth_token, "http://example.com:4317")
            .expect("init_otel_metrics should succeed");

        let pem = PolicyEngineMetrics::new(provider.clone());
        pem.decision_latency_ms.record(12.34, &[]);

        // Shutdown to clean up background readers/exporters.
        let _ = provider.shutdown();
    }
}
