package metrics

import (
	"encoding/json"

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

var appMetricsDesc = map[string]map[string]Description{
	poolSubsystem: {
		overallErrorsMetric: Description{
			Type:      dto.MetricType_GAUGE,
			Namespace: namespace,
			Subsystem: poolSubsystem,
			Name:      overallErrorsMetric,
			Help:      "Total number of errors in pool",
		},
		overallNodeErrorsMetric: Description{
			Type:           dto.MetricType_GAUGE,
			Namespace:      namespace,
			Subsystem:      poolSubsystem,
			Name:           overallNodeErrorsMetric,
			Help:           "Total number of errors for connection in pool",
			VariableLabels: []string{"node"},
		},
		overallNodeRequestsMetric: Description{
			Type:           dto.MetricType_GAUGE,
			Namespace:      namespace,
			Subsystem:      poolSubsystem,
			Name:           overallNodeRequestsMetric,
			Help:           "Total number of requests to specific node in pool",
			VariableLabels: []string{"node"},
		},
		currentErrorMetric: Description{
			Type:           dto.MetricType_GAUGE,
			Namespace:      namespace,
			Subsystem:      poolSubsystem,
			Name:           currentErrorMetric,
			Help:           "Number of errors on current connections that will be reset after the threshold",
			VariableLabels: []string{"node"},
		},
		avgRequestDurationMetric: Description{
			Type:           dto.MetricType_GAUGE,
			Namespace:      namespace,
			Subsystem:      poolSubsystem,
			Name:           avgRequestDurationMetric,
			Help:           "Average request duration (in milliseconds) for specific method on node in pool",
			VariableLabels: []string{"node", "method"},
		},
	},
	stateSubsystem: {
		healthMetric: Description{
			Type:      dto.MetricType_GAUGE,
			Namespace: namespace,
			Subsystem: stateSubsystem,
			Name:      healthMetric,
			Help:      "Current HTTP gateway state",
		},
		versionInfoMetric: Description{
			Type:           dto.MetricType_GAUGE,
			Namespace:      namespace,
			Subsystem:      stateSubsystem,
			Name:           versionInfoMetric,
			Help:           "Version of current FrostFS HTTP Gate instance",
			VariableLabels: []string{"version"},
		},
	},
}

type Description struct {
	Type           dto.MetricType
	Namespace      string
	Subsystem      string
	Name           string
	Help           string
	ConstantLabels prometheus.Labels
	VariableLabels []string
}

func (d *Description) MarshalJSON() ([]byte, error) {
	return json.Marshal(&struct {
		Type           string            `json:"type"`
		FQName         string            `json:"name"`
		Help           string            `json:"help"`
		ConstantLabels prometheus.Labels `json:"constant_labels,omitempty"`
		VariableLabels []string          `json:"variable_labels,omitempty"`
	}{
		Type:           d.Type.String(),
		FQName:         d.BuildFQName(),
		Help:           d.Help,
		ConstantLabels: d.ConstantLabels,
		VariableLabels: d.VariableLabels,
	})
}

func (d *Description) BuildFQName() string {
	return prometheus.BuildFQName(d.Namespace, d.Subsystem, d.Name)
}

// DescribeAll returns descriptions for metrics.
func DescribeAll() []Description {
	var list []Description
	for _, m := range appMetricsDesc {
		for _, description := range m {
			list = append(list, description)
		}
	}

	return list
}

func newOpts(description Description) prometheus.Opts {
	return prometheus.Opts{
		Namespace:   description.Namespace,
		Subsystem:   description.Subsystem,
		Name:        description.Name,
		Help:        description.Help,
		ConstLabels: description.ConstantLabels,
	}
}

func mustNewGauge(description Description) prometheus.Gauge {
	if description.Type != dto.MetricType_GAUGE {
		panic("invalid metric type")
	}
	return prometheus.NewGauge(
		prometheus.GaugeOpts(newOpts(description)),
	)
}

func mustNewGaugeVec(description Description) *prometheus.GaugeVec {
	if description.Type != dto.MetricType_GAUGE {
		panic("invalid metric type")
	}
	return prometheus.NewGaugeVec(
		prometheus.GaugeOpts(newOpts(description)),
		description.VariableLabels,
	)
}