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

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

/// Low-cardinality labels recorded for a SQL query.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct SqlMetricLabels {
    /// Database kind, for example `sqlite`, `postgres` or `mysql`.
    pub db_kind: String,
    /// Repository or component name.
    pub repository: String,
    /// Repository method or operation owner.
    pub method: String,
    /// SQL operation category such as `select`, `insert` or `delete`.
    pub operation: String,
    /// Result category, usually `success` or `error`.
    pub result: String,
}

impl SqlMetricLabels {
    /// Creates a SQL label set without carrying SQL text or parameters.
    pub fn new(
        db_kind: impl Into<String>,
        repository: impl Into<String>,
        method: impl Into<String>,
        operation: impl Into<String>,
        result: impl Into<String>,
    ) -> Self {
        Self {
            db_kind: db_kind.into(),
            repository: repository.into(),
            method: method.into(),
            operation: operation.into(),
            result: result.into(),
        }
    }
}

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

    output.push_str("# HELP rs_zero_sql_query_errors_total Total number of failed SQL queries.\n");
    output.push_str("# TYPE rs_zero_sql_query_errors_total counter\n");
    for (labels, value) in metrics
        .iter()
        .filter(|(labels, _)| labels.result != "success")
    {
        write!(output, "rs_zero_sql_query_errors_total{{").ok();
        write_labels(output, labels, None);
        writeln!(output, "}} {}", value.count).ok();
    }

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

fn write_labels(output: &mut String, labels: &SqlMetricLabels, le: Option<&str>) {
    write!(
        output,
        "db_kind=\"{}\",repository=\"{}\",method=\"{}\",operation=\"{}\",result=\"{}\"",
        escape_label(&labels.db_kind),
        escape_label(&labels.repository),
        escape_label(&labels.method),
        escape_label(&labels.operation),
        escape_label(&labels.result)
    )
    .ok();
    if let Some(le) = le {
        write!(output, ",le=\"{}\"", escape_label(le)).ok();
    }
}
