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

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

/// Low-cardinality labels recorded for each HTTP request.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct HttpMetricLabels {
    /// HTTP method.
    pub method: String,
    /// Route pattern or explicit route name. Raw request paths should not be used.
    pub route: String,
    /// HTTP status code.
    pub status: u16,
}

impl HttpMetricLabels {
    /// Creates a label set for one HTTP request.
    pub fn new(method: impl Into<String>, route: impl Into<String>, status: u16) -> Self {
        Self {
            method: method.into(),
            route: route.into(),
            status,
        }
    }
}

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

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

    output.push_str("# HELP rs_zero_http_requests_in_flight Current in-flight HTTP requests.\n");
    output.push_str("# TYPE rs_zero_http_requests_in_flight gauge\n");
    writeln!(output, "rs_zero_http_requests_in_flight {in_flight}").ok();
}

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