package metrics

import (
	"fmt"
	"strings"
	"time"

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

type (
	engineMetrics struct {
		listContainersDuration        metric[prometheus.Counter]
		estimateContainerSizeDuration metric[prometheus.Counter]
		deleteDuration                metric[prometheus.Counter]
		existsDuration                metric[prometheus.Counter]
		getDuration                   metric[prometheus.Counter]
		headDuration                  metric[prometheus.Counter]
		inhumeDuration                metric[prometheus.Counter]
		putDuration                   metric[prometheus.Counter]
		rangeDuration                 metric[prometheus.Counter]
		searchDuration                metric[prometheus.Counter]
		listObjectsDuration           metric[prometheus.Counter]
		containerSize                 metric[*prometheus.GaugeVec]
		payloadSize                   metric[*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) metric[prometheus.Counter] {
	return newCounter(prometheus.CounterOpts{
		Namespace: namespace,
		Subsystem: engineSubsystem,
		Name:      name,
		Help:      help,
	})
}

func newEngineMethodDurationCounter(method string) metric[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) metric[*prometheus.GaugeVec] {
	return newGaugeVec(prometheus.GaugeOpts{
		Namespace: namespace,
		Subsystem: engineSubsystem,
		Name:      name,
		Help:      help,
	}, labels)
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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