package io.prometheus.metrics.expositionformats;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.Assertions.assertThat;

import io.prometheus.metrics.config.EscapingScheme;
import io.prometheus.metrics.model.snapshots.ClassicHistogramBuckets;
import io.prometheus.metrics.model.snapshots.CounterSnapshot;
import io.prometheus.metrics.model.snapshots.CounterSnapshot.CounterDataPointSnapshot;
import io.prometheus.metrics.model.snapshots.Exemplar;
import io.prometheus.metrics.model.snapshots.Exemplars;
import io.prometheus.metrics.model.snapshots.GaugeSnapshot;
import io.prometheus.metrics.model.snapshots.GaugeSnapshot.GaugeDataPointSnapshot;
import io.prometheus.metrics.model.snapshots.HistogramSnapshot;
import io.prometheus.metrics.model.snapshots.InfoSnapshot;
import io.prometheus.metrics.model.snapshots.Labels;
import io.prometheus.metrics.model.snapshots.MetricSnapshot;
import io.prometheus.metrics.model.snapshots.MetricSnapshots;
import io.prometheus.metrics.model.snapshots.NativeHistogramBuckets;
import io.prometheus.metrics.model.snapshots.PrometheusNaming;
import io.prometheus.metrics.model.snapshots.Quantiles;
import io.prometheus.metrics.model.snapshots.StateSetSnapshot;
import io.prometheus.metrics.model.snapshots.SummarySnapshot;
import io.prometheus.metrics.model.snapshots.SummarySnapshot.SummaryDataPointSnapshot;
import io.prometheus.metrics.model.snapshots.Unit;
import io.prometheus.metrics.model.snapshots.UnknownSnapshot;
import io.prometheus.metrics.model.snapshots.UnknownSnapshot.UnknownDataPointSnapshot;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class ExpositionFormatsTest {

  private final String exemplar1String =
      "{env=\"prod\",span_id=\"12345\",trace_id=\"abcde\"} 1.7 1672850685.829";
  private final String exemplar2String =
      "{env=\"dev\",span_id=\"23456\",trace_id=\"bcdef\"} 2.4 1672850685.830";
  private final String exemplarWithDotsString =
      "{some_exemplar_key=\"some value\"} 3.0 1690298864.383";

  private final String exemplar1protoString =
      "exemplar { "
          + "label { name: \"env\" value: \"prod\" } "
          + "label { name: \"span_id\" value: \"12345\" } "
          + "label { name: \"trace_id\" value: \"abcde\" } "
          + "value: 1.7 "
          + "timestamp { seconds: 1672850685 nanos: 829000000 } }";

  private final String exemplar2protoString =
      "exemplar { "
          + "label { name: \"env\" value: \"dev\" } "
          + "label { name: \"span_id\" value: \"23456\" } "
          + "label { name: \"trace_id\" value: \"bcdef\" } "
          + "value: 2.4 "
          + "timestamp { seconds: 1672850685 nanos: 830000000 } }";

  private final String exemplarWithDotsProtoString =
      "exemplar { "
          + "label { name: \"some_exemplar_key\" value: \"some value\" } "
          + "value: 3.0 "
          + "timestamp { seconds: 1690298864 nanos: 383000000 } }";

  private final String createdTimestamp1s = "1672850385.800";
  private final long createdTimestamp1 = (long) (1000 * Double.parseDouble(createdTimestamp1s));
  private final String createdTimestamp2s = "1672850285.000";
  private final long createdTimestamp2 = (long) (1000 * Double.parseDouble(createdTimestamp2s));
  private final String scrapeTimestamp1s = "1672850685.829";
  private final long scrapeTimestamp1 = (long) (1000 * Double.parseDouble(scrapeTimestamp1s));
  private final String scrapeTimestamp2s = "1672850585.820";
  private final long scrapeTimestamp2 = (long) (1000 * Double.parseDouble(scrapeTimestamp2s));

  private final Exemplar exemplar1 =
      Exemplar.builder()
          .spanId("12345")
          .traceId("abcde")
          .labels(Labels.of("env", "prod"))
          .value(1.7)
          .timestampMillis(1672850685829L)
          .build();

  private final Exemplar exemplar2 =
      Exemplar.builder()
          .spanId("23456")
          .traceId("bcdef")
          .labels(Labels.of("env", "dev"))
          .value(2.4)
          .timestampMillis(1672850685830L)
          .build();

  private final Exemplar exemplarWithDots =
      Exemplar.builder()
          .labels(Labels.of("some.exemplar.key", "some value"))
          .value(3.0)
          .timestampMillis(1690298864383L)
          .build();

  @Test
  void init() {
    ExpositionFormats formats = ExpositionFormats.init();
    assertThat(formats.findWriter("application/openmetrics-text")).isNotNull();
    assertThat(formats.findWriter("application/vnd.google.protobuf")).isNotNull();
    assertThat(formats.findWriter("text/plain")).isNotNull();
    assertThat(formats.getOpenMetricsTextFormatWriter()).isNotNull();
    assertThat(formats.getPrometheusProtobufWriter()).isNotNull();
    assertThat(formats.getPrometheusTextFormatWriter()).isNotNull();
  }

  @Test
  public void testCounterComplete() throws IOException {
    String openMetricsText =
        "# TYPE service:time_seconds counter\n"
            + "# UNIT service:time_seconds seconds\n"
            + "# HELP service:time_seconds total time spent serving\n"
            + "service:time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 "
            + scrapeTimestamp1s
            + " # "
            + exemplar1String
            + "\n"
            + "service:time_seconds_created{path=\"/hello\",status=\"200\"} "
            + createdTimestamp1s
            + " "
            + scrapeTimestamp1s
            + "\n"
            + "service:time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 "
            + scrapeTimestamp2s
            + " # "
            + exemplar2String
            + "\n"
            + "service:time_seconds_created{path=\"/hello\",status=\"500\"} "
            + createdTimestamp2s
            + " "
            + scrapeTimestamp2s
            + "\n"
            + "# EOF\n";
    String prometheusText =
        "# HELP service:time_seconds_total total time spent serving\n"
            + "# TYPE service:time_seconds_total counter\n"
            + "service:time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 "
            + scrapeTimestamp1s
            + "\n"
            + "service:time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 "
            + scrapeTimestamp2s
            + "\n"
            + "# HELP service:time_seconds_created total time spent serving\n"
            + "# TYPE service:time_seconds_created gauge\n"
            + "service:time_seconds_created{path=\"/hello\",status=\"200\"} "
            + createdTimestamp1s
            + " "
            + scrapeTimestamp1s
            + "\n"
            + "service:time_seconds_created{path=\"/hello\",status=\"500\"} "
            + createdTimestamp2s
            + " "
            + scrapeTimestamp2s
            + "\n";
    String openMetricsTextWithoutCreated =
        "# TYPE service:time_seconds counter\n"
            + "# UNIT service:time_seconds seconds\n"
            + "# HELP service:time_seconds total time spent serving\n"
            + "service:time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 "
            + scrapeTimestamp1s
            + " # "
            + exemplar1String
            + "\n"
            + "service:time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 "
            + scrapeTimestamp2s
            + " # "
            + exemplar2String
            + "\n"
            + "# EOF\n";
    String prometheusTextWithoutCreated =
        "# HELP service:time_seconds_total total time spent serving\n"
            + "# TYPE service:time_seconds_total counter\n"
            + "service:time_seconds_total{path=\"/hello\",status=\"200\"} 0.8 "
            + scrapeTimestamp1s
            + "\n"
            + "service:time_seconds_total{path=\"/hello\",status=\"500\"} 0.9 "
            + scrapeTimestamp2s
            + "\n";
    String prometheusProtobuf =
        // @formatter:off
        "name: \"service:time_seconds_total\" "
            + "help: \"total time spent serving\" "
            + "type: COUNTER "
            + "metric { "
            + "label { name: \"path\" value: \"/hello\" } "
            + "label { name: \"status\" value: \"200\" } "
            + "counter { "
            + "value: 0.8 "
            + exemplar1protoString
            + " "
            + "created_timestamp { seconds: 1672850385 nanos: 800000000 } "
            + "} "
            + "timestamp_ms: 1672850685829 "
            + "} "
            + "metric { "
            + "label { name: \"path\" value: \"/hello\" } "
            + "label { name: \"status\" value: \"500\" } "
            + "counter { "
            + "value: 0.9 "
            + exemplar2protoString
            + " "
            + "created_timestamp { seconds: 1672850285 } "
            + "} "
            + "timestamp_ms: 1672850585820 "
            + "}";
    // @formatter:on

    CounterSnapshot counter =
        CounterSnapshot.builder()
            .name("service:time_seconds")
            .help("total time spent serving")
            .unit(Unit.SECONDS)
            .dataPoint(
                CounterDataPointSnapshot.builder()
                    .value(0.8)
                    .labels(Labels.builder().label("path", "/hello").label("status", "200").build())
                    .exemplar(exemplar1)
                    .createdTimestampMillis(createdTimestamp1)
                    .scrapeTimestampMillis(scrapeTimestamp1)
                    .build())
            .dataPoint(
                CounterDataPointSnapshot.builder()
                    .value(0.9)
                    .labels(Labels.builder().label("path", "/hello").label("status", "500").build())
                    .exemplar(exemplar2)
                    .createdTimestampMillis(createdTimestamp2)
                    .scrapeTimestampMillis(scrapeTimestamp2)
                    .build())
            .build();
    assertOpenMetricsText(openMetricsText, counter);
    assertPrometheusText(prometheusText, counter);
    assertOpenMetricsTextWithoutCreated(openMetricsTextWithoutCreated, counter);
    assertPrometheusTextWithoutCreated(prometheusTextWithoutCreated, counter);
    assertPrometheusProtobuf(prometheusProtobuf, counter);
  }

  @Test
  public void testCounterMinimal() throws IOException {
    String openMetricsText =
        """
        # TYPE my_counter counter
        my_counter_total 1.1
        # EOF
        """;
    String prometheusText =
        """
        # TYPE my_counter_total counter
        my_counter_total 1.1
        """;
    String prometheusProtobuf =
        "name: \"my_counter_total\" type: COUNTER metric { counter { value: 1.1 } }";
    CounterSnapshot counter =
        CounterSnapshot.builder()
            .name("my_counter")
            .dataPoint(CounterDataPointSnapshot.builder().value(1.1).build())
            .build();
    assertOpenMetricsText(openMetricsText, counter);
    assertPrometheusText(prometheusText, counter);
    assertOpenMetricsTextWithoutCreated(openMetricsText, counter);
    assertPrometheusTextWithoutCreated(prometheusText, counter);
    assertPrometheusProtobuf(prometheusProtobuf, counter);
  }

  @Test
  public void testCounterWithDots() throws IOException {
    String openMetricsText =
        """
        # TYPE U__my_2e_request_2e_count counter
        U__my_2e_request_2e_count_total{U__http_2e_path="/hello"} 3.0 # {U__some_2e_exemplar_2e_key="some value"} 3.0 1690298864.383
        # EOF
        """;
    String prometheusText =
        """
        # TYPE my_request_count_total counter
        my_request_count_total{http_path="/hello"} 3.0
        """;
    String prometheusProtobuf =
        // @formatter:off
        "name: \"my_request_count_total\" "
            + "type: COUNTER "
            + "metric { "
            + "label { name: \"http_path\" value: \"/hello\" } "
            + "counter { "
            + "value: 3.0 "
            + exemplarWithDotsProtoString
            + " "
            + "} "
            + "}";
    // @formatter:on

    CounterSnapshot counter =
        CounterSnapshot.builder()
            .name("my.request.count")
            .dataPoint(
                CounterDataPointSnapshot.builder()
                    .value(3.0)
                    .labels(Labels.builder().label("http.path", "/hello").build())
                    .exemplar(exemplarWithDots)
                    .build())
            .build();
    assertOpenMetricsText(openMetricsText, counter);
    assertPrometheusText(prometheusText, counter);
    assertPrometheusProtobuf(prometheusProtobuf, counter);
  }

  @Test
  public void testGaugeComplete() throws IOException {
    String openMetricsText =
        "# TYPE disk_usage_ratio gauge\n"
            + "# UNIT disk_usage_ratio ratio\n"
            + "# HELP disk_usage_ratio percentage used\n"
            + "disk_usage_ratio{device=\"/dev/sda1\"} 0.2 "
            + scrapeTimestamp1s
            + "\n"
            + "disk_usage_ratio{device=\"/dev/sda2\"} 0.7 "
            + scrapeTimestamp2s
            + "\n"
            + "# EOF\n";
    String openMetricsTextWithExemplarsOnAllTimeSeries =
        "# TYPE disk_usage_ratio gauge\n"
            + "# UNIT disk_usage_ratio ratio\n"
            + "# HELP disk_usage_ratio percentage used\n"
            + "disk_usage_ratio{device=\"/dev/sda1\"} 0.2 "
            + scrapeTimestamp1s
            + " # "
            + exemplar1String
            + "\n"
            + "disk_usage_ratio{device=\"/dev/sda2\"} 0.7 "
            + scrapeTimestamp2s
            + " # "
            + exemplar2String
            + "\n"
            + "# EOF\n";
    String prometheusText =
        "# HELP disk_usage_ratio percentage used\n"
            + "# TYPE disk_usage_ratio gauge\n"
            + "disk_usage_ratio{device=\"/dev/sda1\"} 0.2 "
            + scrapeTimestamp1s
            + "\n"
            + "disk_usage_ratio{device=\"/dev/sda2\"} 0.7 "
            + scrapeTimestamp2s
            + "\n";
    String prometheusProtobuf =
        // @formatter:off
        "name: \"disk_usage_ratio\" "
            + "help: \"percentage used\" "
            + "type: GAUGE "
            + "metric { "
            + "label { name: \"device\" value: \"/dev/sda1\" } "
            + "gauge { value: 0.2 } "
            + "timestamp_ms: 1672850685829 "
            + "} metric { "
            + "label { name: \"device\" value: \"/dev/sda2\" } "
            + "gauge { value: 0.7 } "
            + "timestamp_ms: 1672850585820 "
            + "}";
    // @formatter:on
    GaugeSnapshot gauge =
        GaugeSnapshot.builder()
            .name("disk_usage_ratio")
            .help("percentage used")
            .unit(new Unit("ratio"))
            .dataPoint(
                GaugeDataPointSnapshot.builder()
                    .value(0.7)
                    .labels(Labels.builder().label("device", "/dev/sda2").build())
                    .exemplar(exemplar2)
                    .scrapeTimestampMillis(scrapeTimestamp2)
                    .build())
            .dataPoint(
                GaugeDataPointSnapshot.builder()
                    .value(0.2)
                    .labels(Labels.builder().label("device", "/dev/sda1").build())
                    .exemplar(exemplar1)
                    .scrapeTimestampMillis(scrapeTimestamp1)
                    .build())
            .build();
    assertOpenMetricsText(openMetricsText, gauge);
    assertOpenMetricsTextWithExemplarsOnAllTimeSeries(
        openMetricsTextWithExemplarsOnAllTimeSeries, gauge);
    assertPrometheusText(prometheusText, gauge);
    assertOpenMetricsTextWithoutCreated(openMetricsText, gauge);
    assertPrometheusTextWithoutCreated(prometheusText, gauge);
    assertPrometheusProtobuf(prometheusProtobuf, gauge);
  }

  @Test
  public void testGaugeMinimal() throws IOException {
    String openMetricsText =
        """
        # TYPE temperature_centigrade gauge
        temperature_centigrade 22.3
        # EOF
        """;
    String prometheusText =
        """
        # TYPE temperature_centigrade gauge
        temperature_centigrade 22.3
        """;
    String prometheusProtobuf =
        "name: \"temperature_centigrade\" type: GAUGE metric { gauge { value: 22.3 } }";
    GaugeSnapshot gauge =
        GaugeSnapshot.builder()
            .name("temperature_centigrade")
            .dataPoint(GaugeDataPointSnapshot.builder().value(22.3).build())
            .build();
    assertOpenMetricsText(openMetricsText, gauge);
    assertPrometheusText(prometheusText, gauge);
    assertOpenMetricsTextWithoutCreated(openMetricsText, gauge);
    assertPrometheusTextWithoutCreated(prometheusText, gauge);
    assertPrometheusProtobuf(prometheusProtobuf, gauge);
  }

  @Test
  public void testGaugeWithDots() throws IOException {
    String openMetricsText =
        """
        # TYPE U__my_2e_temperature_2e_celsius gauge
        # UNIT U__my_2e_temperature_2e_celsius celsius
        # HELP U__my_2e_temperature_2e_celsius Temperature
        U__my_2e_temperature_2e_celsius{U__location_2e_id="data-center-1"} 23.0
        # EOF
        """;
    String openMetricsTextWithExemplarsOnAllTimeSeries =
        "# TYPE my_temperature_celsius gauge\n"
            + "# UNIT my_temperature_celsius celsius\n"
            + "# HELP my_temperature_celsius Temperature\n"
            + "my_temperature_celsius{location_id=\"data-center-1\"} 23.0 # "
            + exemplarWithDotsString
            + "\n"
            + "# EOF\n";
    String prometheusText =
        """
        # HELP my_temperature_celsius Temperature
        # TYPE my_temperature_celsius gauge
        my_temperature_celsius{location_id="data-center-1"} 23.0
        """;
    String prometheusProtobuf =
        // @formatter:off
        "name: \"my_temperature_celsius\" "
            + "help: \"Temperature\" "
            + "type: GAUGE "
            + "metric { "
            + "label { name: \"location_id\" value: \"data-center-1\" } "
            + "gauge { "
            + "value: 23.0 "
            + "} "
            + "}";
    // @formatter:on

    GaugeSnapshot gauge =
        GaugeSnapshot.builder()
            .name("my.temperature.celsius")
            .help("Temperature")
            .unit(Unit.CELSIUS)
            .dataPoint(
                GaugeDataPointSnapshot.builder()
                    .value(23.0)
                    .labels(Labels.builder().label("location.id", "data-center-1").build())
                    .exemplar(exemplarWithDots)
                    .build())
            .build();
    assertOpenMetricsText(openMetricsText, gauge);
    assertOpenMetricsTextWithExemplarsOnAllTimeSeries(
        openMetricsTextWithExemplarsOnAllTimeSeries, gauge);
    assertPrometheusText(prometheusText, gauge);
    assertPrometheusProtobuf(prometheusProtobuf, gauge);
  }

  @Test
  public void testGaugeUTF8() throws IOException {
    String prometheusText =
        """
        # HELP "gauge.name" gauge\\ndoc\\nstr"ing
        # TYPE "gauge.name" gauge
        {"gauge.name","name.1"="Björn","name*2"="佖佥"} 3.14E42
        {"gauge.name","name.1"="val with\\nnew line","name*2"="val with \\\\backslash and \\"quotes\\""} +Inf
        """;
    GaugeSnapshot gauge =
        GaugeSnapshot.builder()
            .name("gauge.name")
            .help("gauge\ndoc\nstr\"ing")
            .dataPoint(
                GaugeDataPointSnapshot.builder()
                    .value(Double.POSITIVE_INFINITY)
                    .labels(
                        Labels.builder()
                            .label("name.1", "val with\nnew line")
                            .label("name*2", "val with \\backslash and \"quotes\"")
                            .build())
                    .build())
            .dataPoint(
                GaugeDataPointSnapshot.builder()
                    .value(3.14e42)
                    .labels(Labels.builder().label("name.1", "Björn").label("name*2", "佖佥").build())
                    .build())
            .build();
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    getPrometheusWriter(PrometheusTextFormatWriter.builder().setIncludeCreatedTimestamps(true))
        .write(out, MetricSnapshots.of((MetricSnapshot) gauge), EscapingScheme.ALLOW_UTF8);
    assertThat(out).hasToString(prometheusText);
  }

  @Test
  public void testSummaryComplete() throws IOException {
    String openMetricsText =
        "# TYPE http_request_duration_seconds summary\n"
            + "# UNIT http_request_duration_seconds seconds\n"
            + "# HELP http_request_duration_seconds request duration\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_count{status=\"200\"} 3 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_sum{status=\"200\"} 1.2 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_created{status=\"200\"} "
            + createdTimestamp1s
            + " "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_count{status=\"500\"} 7 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_sum{status=\"500\"} 2.2 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_created{status=\"500\"} "
            + createdTimestamp2s
            + " "
            + scrapeTimestamp2s
            + "\n"
            + "# EOF\n";
    String openMetricsTextWithExemplarsOnAllTimeSeries =
        "# TYPE http_request_duration_seconds summary\n"
            + "# UNIT http_request_duration_seconds seconds\n"
            + "# HELP http_request_duration_seconds request duration\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp1s
            + " # "
            + exemplar1String
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp1s
            + " # "
            + exemplar1String
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp1s
            + " # "
            + exemplar1String
            + "\n"
            + "http_request_duration_seconds_count{status=\"200\"} 3 "
            + scrapeTimestamp1s
            + " # "
            + exemplar1String
            + "\n"
            + "http_request_duration_seconds_sum{status=\"200\"} 1.2 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_created{status=\"200\"} "
            + createdTimestamp1s
            + " "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp2s
            + " # "
            + exemplar2String
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp2s
            + " # "
            + exemplar2String
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp2s
            + " # "
            + exemplar2String
            + "\n"
            + "http_request_duration_seconds_count{status=\"500\"} 7 "
            + scrapeTimestamp2s
            + " # "
            + exemplar2String
            + "\n"
            + "http_request_duration_seconds_sum{status=\"500\"} 2.2 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_created{status=\"500\"} "
            + createdTimestamp2s
            + " "
            + scrapeTimestamp2s
            + "\n"
            + "# EOF\n";
    String prometheusText =
        "# HELP http_request_duration_seconds request duration\n"
            + "# TYPE http_request_duration_seconds summary\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_count{status=\"200\"} 3 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_sum{status=\"200\"} 1.2 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_count{status=\"500\"} 7 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_sum{status=\"500\"} 2.2 "
            + scrapeTimestamp2s
            + "\n"
            + "# HELP http_request_duration_seconds_created request duration\n"
            + "# TYPE http_request_duration_seconds_created gauge\n"
            + "http_request_duration_seconds_created{status=\"200\"} "
            + createdTimestamp1s
            + " "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_created{status=\"500\"} "
            + createdTimestamp2s
            + " "
            + scrapeTimestamp2s
            + "\n";
    String openMetricsTextWithoutCreated =
        "# TYPE http_request_duration_seconds summary\n"
            + "# UNIT http_request_duration_seconds seconds\n"
            + "# HELP http_request_duration_seconds request duration\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_count{status=\"200\"} 3 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_sum{status=\"200\"} 1.2 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_count{status=\"500\"} 7 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_sum{status=\"500\"} 2.2 "
            + scrapeTimestamp2s
            + "\n"
            + "# EOF\n";
    String prometheusTextWithoutCreated =
        "# HELP http_request_duration_seconds request duration\n"
            + "# TYPE http_request_duration_seconds summary\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"200\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_count{status=\"200\"} 3 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds_sum{status=\"200\"} 1.2 "
            + scrapeTimestamp1s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.5\"} 225.3 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.9\"} 240.7 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds{status=\"500\",quantile=\"0.95\"} 245.1 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_count{status=\"500\"} 7 "
            + scrapeTimestamp2s
            + "\n"
            + "http_request_duration_seconds_sum{status=\"500\"} 2.2 "
            + scrapeTimestamp2s
            + "\n";
    String prometheusProtobuf =
        // @formatter:off
        "name: \"http_request_duration_seconds\" "
            + "help: \"request duration\" "
            + "type: SUMMARY "
            + "metric { "
            + "label { name: \"status\" value: \"200\" } "
            + "summary { "
            + "sample_count: 3 "
            + "sample_sum: 1.2 "
            + "quantile { quantile: 0.5 value: 225.3 } "
            + "quantile { quantile: 0.9 value: 240.7 } "
            + "quantile { quantile: 0.95 value: 245.1 } "
            + "} "
            + "timestamp_ms: 1672850685829 "
            + "} metric { "
            + "label { name: \"status\" value: \"500\" } "
            + "summary { "
            + "sample_count: 7 "
            + "sample_sum: 2.2 "
            + "quantile { quantile: 0.5 value: 225.3 } "
            + "quantile { quantile: 0.9 value: 240.7 } "
            + "quantile { quantile: 0.95 value: 245.1 } "
            + "} "
            + "timestamp_ms: 1672850585820 "
            + "}";
    // @formatter:on
    SummarySnapshot summary =
        SummarySnapshot.builder()
            .name("http_request_duration_seconds")
            .help("request duration")
            .unit(Unit.SECONDS)
            .dataPoint(
                SummaryDataPointSnapshot.builder()
                    .count(7)
                    .sum(2.2)
                    .quantiles(
                        Quantiles.builder()
                            .quantile(0.5, 225.3)
                            .quantile(0.9, 240.7)
                            .quantile(0.95, 245.1)
                            .build())
                    .labels(Labels.builder().label("status", "500").build())
                    .exemplars(Exemplars.of(exemplar2))
                    .createdTimestampMillis(createdTimestamp2)
                    .scrapeTimestampMillis(scrapeTimestamp2)
                    .build())
            .dataPoint(
                SummaryDataPointSnapshot.builder()
                    .count(3)
                    .sum(1.2)
                    .quantiles(
                        Quantiles.builder()
                            .quantile(0.5, 225.3)
                            .quantile(0.9, 240.7)
                            .quantile(0.95, 245.1)
                            .build())
                    .labels(Labels.builder().label("status", "200").build())
                    .exemplars(Exemplars.of(exemplar1))
                    .createdTimestampMillis(createdTimestamp1)
                    .scrapeTimestampMillis(scrapeTimestamp1)
                    .build())
            .build();
    assertOpenMetricsText(openMetricsText, summary);
    assertOpenMetricsTextWithExemplarsOnAllTimeSeries(
        openMetricsTextWithExemplarsOnAllTimeSeries, summary);
    assertPrometheusText(prometheusText, summary);
    assertOpenMetricsTextWithoutCreated(openMetricsTextWithoutCreated, summary);
    assertPrometheusTextWithoutCreated(prometheusTextWithoutCreated, summary);
