use std::{collections::BTreeMap, fmt::Write};

use super::{DurationMetricValue, escape_label, write_duration_metric};

/// Low-cardinality labels recorded for each gRPC unary call.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct RpcMetricLabels {
    /// gRPC service name.
    pub service: String,
    /// gRPC method name.
    pub method: String,
    /// Tonic status code name.
    pub code: String,
}

impl RpcMetricLabels {
    /// Creates a label set for one gRPC call.
    pub fn new(
        service: impl Into<String>,
        method: impl Into<String>,
        code: impl Into<String>,
    ) -> Self {
        Self {
            service: service.into(),
            method: method.into(),
            code: code.into(),
        }
    }
}

pub(crate) fn render(
    output: &mut String,
    metrics: &BTreeMap<RpcMetricLabels, DurationMetricValue>,
) {
    output.push_str("# HELP rs_zero_rpc_requests_total Total number of gRPC requests.\n");
    output.push_str("# TYPE rs_zero_rpc_requests_total counter\n");
    for (labels, value) in metrics {
        write!(output, "rs_zero_rpc_requests_total{{").ok();
        write_labels(output, labels, None);
        writeln!(output, "}} {}", value.count).ok();
    }

    output.push_str("# HELP rs_zero_rpc_request_duration_seconds gRPC request duration.\n");
    output.push_str("# TYPE rs_zero_rpc_request_duration_seconds histogram\n");
    for (labels, value) in metrics {
        write_duration_metric(
            output,
            "rs_zero_rpc_request_duration_seconds",
            labels,
            value,
            write_labels,
        );
    }
}

fn write_labels(output: &mut String, labels: &RpcMetricLabels, le: Option<&str>) {
    write!(
        output,
        "service=\"{}\",method=\"{}\",code=\"{}\"",
        escape_label(&labels.service),
        escape_label(&labels.method),
        escape_label(&labels.code)
    )
    .ok();
    if let Some(le) = le {
        write!(output, ",le=\"{}\"", escape_label(le)).ok();
    }
}
