package metrics

import (
	"fmt"
	"strings"
	"time"

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

type (
	engineMetrics struct {
		listContainersDuration        prometheus.Counter
		estimateContainerSizeDuration prometheus.Counter
		deleteDuration                prometheus.Counter
		existsDuration                prometheus.Counter
		getDuration                   prometheus.Counter
		headDuration                  prometheus.Counter
		inhumeDuration                prometheus.Counter
		putDuration                   prometheus.Counter
		rangeDuration                 prometheus.Counter
		searchDuration                prometheus.Counter
		listObjectsDuration           prometheus.Counter
		containerSize                 prometheus.GaugeVec
		payloadSize                   prometheus.GaugeVec
	}
)

const engineSubsystem = "engine"

func newEngineMetrics() engineMetrics {
	return engineMetrics{
		listContainersDuration:        newEngineMethodDurationCounter("list_containers_"),
		estimateContainerSizeDuration: newEngineCounter("estimate_container_size_duration", "Accumulated duration of engine container size estimate operations"),
		deleteDuration:                newEngineMethodDurationCounter("delete"),
		existsDuration:                newEngineMethodDurationCounter("exists"),
		getDuration:                   newEngineMethodDurationCounter("get"),
		headDuration:                  newEngineMethodDurationCounter("head"),
		inhumeDuration:                newEngineMethodDurationCounter("inhume"),
		putDuration:                   newEngineMethodDurationCounter("put"),
		rangeDuration:                 newEngineMethodDurationCounter("range"),
		searchDuration:                newEngineMethodDurationCounter("search"),
		listObjectsDuration:           newEngineMethodDurationCounter("list_objects"),
		containerSize:                 *newEngineGaugeVector("container_size", "Accumulated size of all objects in a container", []string{containerIDLabelKey}),
		payloadSize:                   *newEngineGaugeVector("payload_size", "Accumulated size of all objects in a shard", []string{shardIDLabelKey}),
	}
}

func newEngineCounter(name, help string) prometheus.Counter {
	return prometheus.NewCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Subsystem: engineSubsystem,
		Name:      name,
		Help:      help,
	})
}

func newEngineMethodDurationCounter(method string) prometheus.Counter {
	return newEngineCounter(
		fmt.Sprintf("%s_duration", method),
		fmt.Sprintf("Accumulated duration of engine %s operations", strings.ReplaceAll(method, "_", " ")),
	)
}

func newEngineGaugeVector(name, help string, labels []string) *prometheus.GaugeVec {
	return prometheus.NewGaugeVec(prometheus.GaugeOpts{
		Namespace: namespace,
		Subsystem: engineSubsystem,
		Name:      name,
		Help:      help,
	}, labels)
}

func (m engineMetrics) register() {
	prometheus.MustRegister(m.listContainersDuration)
	prometheus.MustRegister(m.estimateContainerSizeDuration)
	prometheus.MustRegister(m.deleteDuration)
	prometheus.MustRegister(m.existsDuration)
	prometheus.MustRegister(m.getDuration)
	prometheus.MustRegister(m.headDuration)
	prometheus.MustRegister(m.inhumeDuration)
	prometheus.MustRegister(m.putDuration)
	prometheus.MustRegister(m.rangeDuration)
	prometheus.MustRegister(m.searchDuration)
	prometheus.MustRegister(m.listObjectsDuration)
	prometheus.MustRegister(m.containerSize)
	prometheus.MustRegister(m.payloadSize)
}

func (m engineMetrics) AddListContainersDuration(d time.Duration) {
	m.listObjectsDuration.Add(float64(d))
}

func (m engineMetrics) AddEstimateContainerSizeDuration(d time.Duration) {
	m.estimateContainerSizeDuration.Add(float64(d))
}

func (m engineMetrics) AddDeleteDuration(d time.Duration) {
	m.deleteDuration.Add(float64(d))
}

func (m engineMetrics) AddExistsDuration(d time.Duration) {
	m.existsDuration.Add(float64(d))
}

func (m engineMetrics) AddGetDuration(d time.Duration) {
	m.getDuration.Add(float64(d))
}

func (m engineMetrics) AddHeadDuration(d time.Duration) {
	m.headDuration.Add(float64(d))
}

func (m engineMetrics) AddInhumeDuration(d time.Duration) {
	m.inhumeDuration.Add(float64(d))
}

func (m engineMetrics) AddPutDuration(d time.Duration) {
	m.putDuration.Add(float64(d))
}

func (m engineMetrics) AddRangeDuration(d time.Duration) {
	m.rangeDuration.Add(float64(d))
}

func (m engineMetrics) AddSearchDuration(d time.Duration) {
	m.searchDuration.Add(float64(d))
}

func (m engineMetrics) AddListObjectsDuration(d time.Duration) {
	m.listObjectsDuration.Add(float64(d))
}

func (m engineMetrics) AddToContainerSize(cnrID string, size int64) {
	m.containerSize.With(prometheus.Labels{containerIDLabelKey: cnrID}).Add(float64(size))
}

func (m engineMetrics) AddToPayloadCounter(shardID string, size int64) {
	m.payloadSize.With(prometheus.Labels{shardIDLabelKey: shardID}).Add(float64(size))
}