//! A module for managing metrics and collecting system metrics.

use std::{
  sync::{Arc, Mutex},
  time::Duration,
};

use opentelemetry::{KeyValue, Value, global};
use opentelemetry_otlp::WithExportConfig;
use opentelemetry_sdk::{
  Resource,
  metrics::{PeriodicReader, SdkMeterProvider},
};
use sysinfo::RefreshKind;

/// Configuration for metrics integration.
#[derive(Clone)]
pub struct MetricsConfig {
  /// The endpoint URL where metrics are sent.
  pub endpoint: String,

  /// The interval at which metrics are collected and sent.
  pub interval: Duration,
}

impl Default for MetricsConfig {
  fn default() -> Self {
    Self {
      endpoint: String::from("https://limonen.click:5130/v1/metrics"),
      interval: Duration::from_secs(30),
    }
  }
}

pub(crate) struct MetricsSdk;

impl MetricsSdk {
  pub fn init(config: MetricsConfig) {
    let exporter = opentelemetry_otlp::MetricExporter::builder()
      .with_http()
      .with_endpoint(config.endpoint)
      .build()
      .expect("opentelemetry exporter failed");

    let reader = PeriodicReader::builder(exporter)
      .with_interval(config.interval)
      .build();

    let provider = SdkMeterProvider::builder()
      .with_resource(
        Resource::builder()
          .with_service_name(env!("CARGO_PKG_NAME"))
          .build(),
      )
      .with_reader(reader)
      .build();

    global::set_meter_provider(provider);

    #[cfg(feature = "sysinfo")]
    Self::setup_system_metrics()
  }

  #[cfg(feature = "sysinfo")]
  fn setup_system_metrics() {
    let meter = global::meter("system.runtime");
    let system = Arc::new(Mutex::new(sysinfo::System::new_with_specifics(
      RefreshKind::everything().without_processes(),
    )));

    meter
      .u64_observable_gauge("system.memory.usage")
      .with_description("Amount of used RAM in KB")
      .with_callback({
        let system = Arc::clone(&system);

        move |observer| {
          let mut s = system.lock().unwrap();

          s.refresh_memory();

          observer.observe(s.used_memory(), &[]);
        }
      })
      .build();

    meter
      .u64_observable_gauge("system.memory.total")
      .with_description("Amount of total RAM in KB")
      .with_callback({
        let system = Arc::clone(&system);

        move |observer| {
          let mut s = system.lock().unwrap();

          s.refresh_memory();

          observer.observe(s.total_memory(), &[]);
        }
      })
      .build();

    meter
      .f64_observable_gauge("system.cpu.usage")
      .with_description("CPU load in percentage across all cores")
      .with_callback({
        let system = Arc::clone(&system);

        move |observer| {
          let mut s = system.lock().unwrap();

          s.refresh_cpu_usage();

          for (i, cpu) in s.cpus().iter().enumerate() {
            observer.observe(
              cpu.cpu_usage() as f64,
              &[KeyValue::new("core", Value::I64(i as i64))],
            );
          }
        }
      })
      .build();
  }
}
