package metricutil

import (
	"errors"
	"fmt"
	"strings"

	"github.com/prometheus/client_golang/prometheus"
)

// SanitizeLabelName removes all invalid chars from the label name.
// If the label name is empty or contains only invalid chars, it
// will return an error.
func SanitizeLabelName(name string) (string, error) {
	if len(name) == 0 {
		return "", errors.New("label name cannot be empty")
	}

	out := strings.Builder{}
	for i, b := range name {
		if (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || b == '_' || (b >= '0' && b <= '9' && i > 0) {
			out.WriteRune(b)
		} else if b == ' ' {
			out.WriteRune('_')
		}
	}

	if out.Len() == 0 {
		return "", fmt.Errorf("label name only contains invalid chars: %q", name)
	}

	return out.String(), nil
}

// NewCounterStartingAtZero initializes a new Prometheus counter with an initial
// observation of zero. Used for to guarantee the existence of the specific metric.
func NewCounterStartingAtZero(opts prometheus.CounterOpts) prometheus.Counter {
	counter := prometheus.NewCounter(opts)
	counter.Add(0)
	return counter
}

// NewCounterVecStartingAtZero initializes a new Prometheus counter with an initial
// observation of zero for every possible value of each label. Used for the sake of
// consistency among all the possible labels and values.
func NewCounterVecStartingAtZero(opts prometheus.CounterOpts, labels []string, labelValues map[string][]string) *prometheus.CounterVec {
	counter := prometheus.NewCounterVec(opts, labels)

	for _, ls := range buildLabelSets(labels, labelValues) {
		counter.With(ls).Add(0)
	}

	return counter
}

func buildLabelSets(labels []string, labelValues map[string][]string) []prometheus.Labels {
	var labelSets []prometheus.Labels

	var n func(i int, ls prometheus.Labels)
	n = func(i int, ls prometheus.Labels) {
		if i == len(labels) {
			labelSets = append(labelSets, ls)
			return
		}

		label := labels[i]
		values := labelValues[label]

		for _, v := range values {
			lsCopy := copyLabelSet(ls)
			lsCopy[label] = v
			n(i+1, lsCopy)
		}
	}

	n(0, prometheus.Labels{})
	return labelSets
}

func copyLabelSet(ls prometheus.Labels) prometheus.Labels {
	newLs := make(prometheus.Labels, len(ls))
	for l, v := range ls {
		newLs[l] = v
	}
	return newLs
}
