package metrics

import (
	"github.com/prometheus/client_golang/prometheus"
	dto "github.com/prometheus/client_model/go"
)

// Descriptions contains metric Description suitable for further processing.
// The only reason for it to exist is `prometheus.Desc` disallowing field access directly.
// https://github.com/prometheus/client_golang/pull/326
// https://github.com/prometheus/client_golang/issues/516
// https://github.com/prometheus/client_golang/issues/222
type Description struct {
	Name           string            `json:"name"`
	Help           string            `json:"help"`
	Type           string            `json:"type"`
	ConstantLabels prometheus.Labels `json:"constant_labels,omitempty"`
	VariableLabels []string          `json:"variable_labels,omitempty"`
}

// NewGauge returns new registered prometheus.Gauge.
func NewGauge(opts prometheus.GaugeOpts) prometheus.Gauge {
	value := prometheus.NewGauge(opts)
	MustRegister(value, Description{
		Name:           prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
		Type:           dto.MetricType_GAUGE.String(),
		Help:           opts.Help,
		ConstantLabels: opts.ConstLabels,
	})
	return value
}

// NewGaugeVec returns new registered *prometheus.GaugeVec.
func NewGaugeVec(opts prometheus.GaugeOpts, labelNames []string) *prometheus.GaugeVec {
	value := prometheus.NewGaugeVec(opts, labelNames)
	MustRegister(value, Description{
		Name:           prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
		Type:           dto.MetricType_GAUGE.String(),
		Help:           opts.Help,
		ConstantLabels: opts.ConstLabels,
		VariableLabels: labelNames,
	})

	return value
}

// NewGaugeFunc returns new registered prometheus.GaugeFunc.
func NewGaugeFunc(opts prometheus.GaugeOpts, f func() float64) prometheus.GaugeFunc {
	value := prometheus.NewGaugeFunc(opts, f)
	MustRegister(value, Description{
		Name:           prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
		Type:           dto.MetricType_GAUGE.String(),
		Help:           opts.Help,
		ConstantLabels: opts.ConstLabels,
	})
	return value
}

// NewCounter returns new registered prometheus.Counter.
func NewCounter(opts prometheus.CounterOpts) prometheus.Counter {
	value := prometheus.NewCounter(opts)
	MustRegister(value, Description{
		Name:           prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
		Type:           dto.MetricType_COUNTER.String(),
		Help:           opts.Help,
		ConstantLabels: opts.ConstLabels,
	})
	return value
}

// NewCounterVec returns new registered *prometheus.CounterVec.
func NewCounterVec(opts prometheus.CounterOpts, labels []string) *prometheus.CounterVec {
	value := prometheus.NewCounterVec(opts, labels)
	MustRegister(value, Description{
		Name:           prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
		Type:           dto.MetricType_COUNTER.String(),
		Help:           opts.Help,
		ConstantLabels: opts.ConstLabels,
		VariableLabels: labels,
	})
	return value
}

// NewHistogramVec returns new registered *prometheus.HistogramVec.
func NewHistogramVec(opts prometheus.HistogramOpts, labelNames []string) *prometheus.HistogramVec {
	value := prometheus.NewHistogramVec(opts, labelNames)
	MustRegister(value, Description{
		Name:           prometheus.BuildFQName(opts.Namespace, opts.Subsystem, opts.Name),
		Type:           dto.MetricType_HISTOGRAM.String(),
		Help:           opts.Help,
		ConstantLabels: opts.ConstLabels,
		VariableLabels: labelNames,
	})
	return value
}

// DescribeAll returns descriptions for all registered metrics.
func DescribeAll() []Description {
	registeredDescriptionsMtx.Lock()
	defer registeredDescriptionsMtx.Unlock()

	ds := make([]Description, len(registeredDescriptions))
	copy(ds, registeredDescriptions)
	return ds
}