package com.sap.cloud.cf.monitoring.spring.configuration.metrics;

import java.text.DecimalFormat;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.sap.cloud.cf.monitoring.client.model.Metric;

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.distribution.HistogramSnapshot;
import io.micrometer.core.instrument.distribution.ValueAtPercentile;

public abstract class MetricsConverter<T extends Meter> {
    private static final DecimalFormat PERCENTILE_FORMAT = new DecimalFormat("#.####");

    abstract public Stream<Metric> getMetrics(T meter);

    protected Stream<Metric> getMetrics(Meter meter, HistogramSnapshot snapshot) {
        return Stream.concat(
            Stream.of(toMetric(withStatistic(meter, "count"), snapshot.count()),
                toMetric(withStatistic(meter, "max"), snapshot.max(getBaseTimeUnit())),
                toMetric(withStatistic(meter, "mean"), snapshot.mean(getBaseTimeUnit())),
                toMetric(withStatistic(meter, "totalTime"), snapshot.total(getBaseTimeUnit()))),
            getMetrics(meter, snapshot.percentileValues()));
    }

    protected Stream<Metric> getMetrics(Meter meter, ValueAtPercentile[] percentiles) {
        return Arrays.stream(percentiles)
            .map(percentile -> toMetric(withPercentile(meter, percentile), percentile.value(getBaseTimeUnit())));
    }

    protected Metric toMetric(Meter.Id id, double value) {
        if (Double.isNaN(value)) {
            return null;
        }

        Map<String, String> tags = id.getTags().stream().collect(Collectors.toMap(Tag::getKey, Tag::getValue));
        return new Metric(id.getName(), value, System.currentTimeMillis(), tags);
    }

    protected Meter.Id withPercentile(Meter meter, ValueAtPercentile percentile) {
        return withStatistic(meter,
            String.format("%spercentile", PERCENTILE_FORMAT.format(percentile.percentile() * 100)));
    }

    protected Meter.Id withStatistic(Meter meter, String type) {
        return meter.getId().withTag(Tag.of("statistic", type));
    }

    public static TimeUnit getBaseTimeUnit() {
        return TimeUnit.MILLISECONDS;
    }
}