use lazy_static::lazy_static;
use prometheus::{
    HistogramVec, IntCounterVec, IntGaugeVec, register_histogram_vec, register_int_counter_vec,
    register_int_gauge_vec,
};
use tici_common::metrics::{OperationMetrics, OperationMetricsGuard, begin_operation_metrics};

// Metrics for meta server
lazy_static! {
    pub static ref GRPC_HANDLE_DURATION_HISTOGRAM: HistogramVec = register_histogram_vec!(
        "tici_grpc_handle_duration",
        "The duration of all kinds of grpc calls in seconds",
        &["service", "type"]
    )
    .unwrap();
    pub static ref GRPC_HANDLE_COUNTER: IntCounterVec = register_int_counter_vec!(
        "tici_grpc_handle_count",
        "The count of all kinds of grpc calls",
        &["service", "type", "result"]
    )
    .unwrap();
    pub static ref GRPC_HANDLE_INFLIGHT_GAUGE: IntGaugeVec = register_int_gauge_vec!(
        "tici_grpc_handle_inflight",
        "The inflight count of all kinds of grpc calls",
        &["service", "type"]
    )
    .unwrap();
}

pub type GrpcHandleMetricsGuard = OperationMetricsGuard;

pub fn begin_grpc_handle_metrics(
    service: &'static str,
    rpc_type: &'static str,
) -> GrpcHandleMetricsGuard {
    begin_operation_metrics(
        OperationMetrics::new(
            &GRPC_HANDLE_DURATION_HISTOGRAM,
            &GRPC_HANDLE_COUNTER,
            &GRPC_HANDLE_INFLIGHT_GAUGE,
        ),
        service,
        rpc_type,
    )
}

#[cfg(test)]
mod tests {
    use super::{GRPC_HANDLE_COUNTER, GRPC_HANDLE_INFLIGHT_GAUGE, begin_grpc_handle_metrics};

    #[test]
    fn test_grpc_handle_counter() {
        let counter = GRPC_HANDLE_COUNTER.with_label_values(&["ut_counter", "ut", "ok"]);
        let before = counter.get();
        counter.inc();
        assert_eq!(counter.get(), before + 1);
    }

    #[test]
    fn test_grpc_handle_inflight_gauge() {
        let gauge = GRPC_HANDLE_INFLIGHT_GAUGE.with_label_values(&["ut_gauge", "ut"]);
        gauge.set(0);
        gauge.inc();
        assert_eq!(gauge.get(), 1);
        gauge.dec();
        assert_eq!(gauge.get(), 0);
    }

    #[test]
    fn test_grpc_handle_guard_ok() {
        let inflight = GRPC_HANDLE_INFLIGHT_GAUGE.with_label_values(&["ut_guard_ok", "ut"]);
        let counter = GRPC_HANDLE_COUNTER.with_label_values(&["ut_guard_ok", "ut", "ok"]);
        let before_inflight = inflight.get();
        let before_counter = counter.get();

        {
            let _guard = begin_grpc_handle_metrics("ut_guard_ok", "ut");
            assert_eq!(inflight.get(), before_inflight + 1);
        }

        assert_eq!(inflight.get(), before_inflight);
        assert_eq!(counter.get(), before_counter + 1);
    }

    #[test]
    fn test_grpc_handle_guard_error() {
        let counter = GRPC_HANDLE_COUNTER.with_label_values(&["ut_guard_err", "ut", "error"]);
        let before_counter = counter.get();

        {
            let guard = begin_grpc_handle_metrics("ut_guard_err", "ut");
            guard.mark_error();
        }

        assert_eq!(counter.get(), before_counter + 1);
    }
}
