package metrics

import (
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode"
	"git.frostfs.info/TrueCloudLab/frostfs-observability/metrics"
	"github.com/prometheus/client_golang/prometheus"
)

type EngineMetrics interface {
	AddMethodDuration(method string, d time.Duration)
	AddToContainerSize(cnrID string, size int64)
	IncErrorCounter(shardID string)
	ClearErrorCounter(shardID string)
	DeleteShardMetrics(shardID string)
	AddToObjectCounter(shardID, objectType string, delta int)
	SetObjectCounter(shardID, objectType string, v uint64)
	AddToPayloadCounter(shardID string, size int64)
	SetMode(shardID string, mode mode.Mode)

	WriteCache() WriteCacheMetrics
	GC() GCMetrics
}

type engineMetrics struct {
	methodDuration *prometheus.HistogramVec
	objectCounter  *prometheus.GaugeVec
	containerSize  *prometheus.GaugeVec
	payloadSize    *prometheus.GaugeVec
	errorCounter   *prometheus.GaugeVec
	mode           *shardIDModeValue

	gc         *gcMetrics
	writeCache *writeCacheMetrics
}

func newEngineMetrics() *engineMetrics {
	return &engineMetrics{
		containerSize: newEngineGaugeVector("container_size_bytes", "Accumulated size of all objects in a container", []string{containerIDLabelKey}),
		payloadSize:   newEngineGaugeVector("payload_size_bytes", "Accumulated size of all objects in a shard", []string{shardIDLabel}),
		errorCounter:  newEngineGaugeVector("errors_total", "Shard's error counter", []string{shardIDLabel}),
		methodDuration: metrics.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: namespace,
			Subsystem: engineSubsystem,
			Name:      "request_duration_seconds",
			Help:      "Duration of Engine requests",
		}, []string{methodLabel}),
		objectCounter: newEngineGaugeVector("objects_total", "Objects counters per shards", []string{shardIDLabel, typeLabel}),
		gc:            newGCMetrics(),
		writeCache:    newWriteCacheMetrics(),
		mode:          newShardIDMode(engineSubsystem, "mode_info", "Shard mode"),
	}
}

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

func (m *engineMetrics) AddMethodDuration(method string, d time.Duration) {
	m.methodDuration.With(prometheus.Labels{
		methodLabel: method,
	}).Observe(d.Seconds())
}

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{shardIDLabel: shardID}).Add(float64(size))
}

func (m *engineMetrics) IncErrorCounter(shardID string) {
	m.errorCounter.With(prometheus.Labels{shardIDLabel: shardID}).Inc()
}

func (m *engineMetrics) ClearErrorCounter(shardID string) {
	m.errorCounter.With(prometheus.Labels{shardIDLabel: shardID}).Set(0)
}

func (m *engineMetrics) DeleteShardMetrics(shardID string) {
	m.errorCounter.Delete(prometheus.Labels{shardIDLabel: shardID})
	m.payloadSize.Delete(prometheus.Labels{shardIDLabel: shardID})
	m.objectCounter.DeletePartialMatch(prometheus.Labels{shardIDLabel: shardID})
	m.mode.Delete(shardID)
}

func (m *engineMetrics) AddToObjectCounter(shardID, objectType string, delta int) {
	m.objectCounter.With(
		prometheus.Labels{
			shardIDLabel: shardID,
			typeLabel:    objectType,
		},
	).Add(float64(delta))
}

func (m *engineMetrics) SetObjectCounter(shardID, objectType string, v uint64) {
	m.objectCounter.With(
		prometheus.Labels{
			shardIDLabel: shardID,
			typeLabel:    objectType,
		},
	).Set(float64(v))
}

func (m *engineMetrics) SetMode(shardID string, mode mode.Mode) {
	m.mode.SetMode(shardID, mode.String())
}

func (m *engineMetrics) WriteCache() WriteCacheMetrics {
	return m.writeCache
}

func (m *engineMetrics) GC() GCMetrics {
	return m.gc
}