use std::collections::HashMap;
use std::vec;
pub use metrics;
#[cfg(feature = "system")]
use metrics_process::register_sysinfo_event;
use metrics_prometheus::failure::strategy::{self, NoOp};
use metrics_util::layers::FanoutBuilder;
pub use middleware::HttpMetricMiddleware;
use poem::EndpointExt;
use poem::{
    handler,
    web::{Data, Json, Query},
    Route,
};
#[cfg(not(feature = "embed"))]
use poem::endpoint::StaticFilesEndpoint;
#[cfg(feature = "embed")]
use poem::endpoint::{EmbeddedFileEndpoint, EmbeddedFilesEndpoint};
#[cfg(feature = "embed")]
use rust_embed::RustEmbed;
use recorder::{DashboardRecorder, MetricMeta, MetricValue};
use serde::{Deserialize, Serialize};
#[cfg(feature = "system")]
pub mod metrics_process;
mod middleware;
pub mod recorder;
#[cfg(feature = "embed")]
#[derive(RustEmbed)]
#[folder = "public"]
pub struct Files;
#[derive(Debug, Deserialize)]
struct MetricQuery {
    keys: String,
}
#[derive(Debug, Clone, Default)]
pub struct DashboardOptions {
    pub custom_charts: Vec<ChartType>,
    pub include_default: bool,
}
#[derive(Debug, Serialize, Clone)]
#[serde(tag = "type", content = "meta")]
pub enum ChartType {
    Line {
        metrics: Vec<String>,
        desc: String,
        unit: String,
    },
    Bar {
        metrics: Vec<String>,
        desc: String,
        unit: String,
    },
}
impl ChartType {
    pub fn metrics(&self) -> &[String] {
        match self {
            ChartType::Line { metrics, .. } => metrics,
            ChartType::Bar { metrics, .. } => metrics,
        }
    }
}
#[handler]
fn prometheus_metrics(Data(recorder): Data<&metrics_prometheus::Recorder<NoOp>>) -> String {
    prometheus::TextEncoder::new()
        .encode_to_string(&recorder.registry().gather())
        .expect("Should generate")
}
#[handler]
fn api_charts(Data(recorder): Data<&DashboardRecorder>) -> Json<Vec<ChartType>> {
    let option = &recorder.options;
    let mut res: Vec<ChartType> = vec![];
    let mut included_metrics = HashMap::new();
    for chart in option.custom_charts.iter() {
        res.push(chart.clone());
        for metric in chart.metrics() {
            included_metrics.insert(metric.clone(), true);
        }
    }
    if option.include_default {
        let metrics = recorder.metrics();
        for meta in metrics.iter() {
            if included_metrics.contains_key(&meta.key) {
                continue;
            }
            let chart = ChartType::Line {
                metrics: vec![meta.key.clone()],
                desc: meta.desc.clone().unwrap_or_else(|| meta.key.clone()),
                unit: meta.unit.clone().unwrap_or_else(|| "".to_string()),
            };
            res.push(chart.clone());
        }
    }
    Json(res)
}
#[handler]
fn api_metrics(Data(recorder): Data<&DashboardRecorder>) -> Json<Vec<MetricMeta>> {
    Json(recorder.metrics())
}
#[handler]
fn api_metrics_value(
    Data(recorder): Data<&DashboardRecorder>,
    Query(query): Query<MetricQuery>,
) -> Json<Vec<MetricValue>> {
    let keys = query.keys.split(';').collect::<Vec<&str>>();
    Json(recorder.metrics_value(keys))
}
pub fn build_dashboard_route(opts: DashboardOptions) -> Route {
    build_dashboard_route_with_recorder(opts).1
}
pub fn build_dashboard_route_with_recorder(opts: DashboardOptions) -> (DashboardRecorder, Route) {
    let recorder1 = metrics_prometheus::Recorder::builder()
        .with_failure_strategy(strategy::NoOp)
        .build();
    let recorder2 = DashboardRecorder::new(opts);
    let recoder_fanout = FanoutBuilder::default()
        .add_recorder(recorder1.clone())
        .add_recorder(recorder2.clone())
        .build();
    metrics::set_global_recorder(recoder_fanout).expect("Should register a recorder successfull");
    #[cfg(feature = "system")]
    register_sysinfo_event();
    let route = Route::new()
        .at("/prometheus", prometheus_metrics.data(recorder1))
        .at("/api/metrics", api_metrics.data(recorder2.clone()))
        .at("/api/charts", api_charts.data(recorder2.clone()))
        .at(
            "/api/metrics_value",
            api_metrics_value.data(recorder2.clone()),
        );
    #[cfg(not(feature = "embed"))]
    let route = route.nest(
        "/",
        StaticFilesEndpoint::new("./public/").index_file("index.html"),
    );
    #[cfg(feature = "embed")]
    let route = route.at("/", EmbeddedFileEndpoint::<Files>::new("index.html"));
    #[cfg(feature = "embed")]
    let route = route.nest("/", EmbeddedFilesEndpoint::<Files>::new());
    (recorder2, route)
}
#[allow(unused)]
pub(crate) fn round_up_f64_2digits(input: f64) -> f64 {
    (input * 100.0).round() / 100.0
}