package metrics

import (
	"strconv"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-observability/metrics"
	"github.com/prometheus/client_golang/prometheus"
)

type WriteCacheMetrics interface {
	AddMethodDuration(shardID, path, storageType, method string, success bool, d time.Duration)
	SetActualCount(shardID, path, storageType string, count uint64)
	SetEstimateSize(shardID, path, storageType string, size uint64)
	SetMode(shardID, mode string)
	IncOperationCounter(shardID, path, storageType, operation string, success NullBool)
	Close(shardID, path string)
}

type writeCacheMetrics struct {
	methodDuration   *prometheus.HistogramVec
	operationCounter *prometheus.CounterVec

	actualCount *prometheus.GaugeVec

	estimatedSize *prometheus.GaugeVec

	mode *shardIDModeValue
}

func newWriteCacheMetrics() *writeCacheMetrics {
	return &writeCacheMetrics{
		methodDuration: metrics.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: namespace,
			Subsystem: writeCacheSubsystem,
			Name:      "request_duration_seconds",
			Help:      "Writecache request process duration",
		}, []string{shardIDLabel, successLabel, storageLabel, methodLabel, pathLabel}),
		operationCounter: metrics.NewCounterVec(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: writeCacheSubsystem,
			Name:      "operations_total",
			Help:      "The number of writecache operations processed",
		}, []string{shardIDLabel, storageLabel, successLabel, operationLabel, pathLabel}),
		actualCount:   newWCGaugeVec("actual_objects_total", "Actual objects count in writecache", []string{shardIDLabel, storageLabel, pathLabel}),
		estimatedSize: newWCGaugeVec("estimated_size_bytes", "Estimated writecache size", []string{shardIDLabel, storageLabel, pathLabel}),
		mode:          newShardIDMode(writeCacheSubsystem, "mode_info", "Writecache mode value"),
	}
}

func (m *writeCacheMetrics) AddMethodDuration(shardID, path, storageType, method string, success bool, d time.Duration) {
	m.methodDuration.With(
		prometheus.Labels{
			shardIDLabel: shardID,
			successLabel: strconv.FormatBool(success),
			storageLabel: storageType,
			methodLabel:  method,
			pathLabel:    path,
		},
	).Observe(d.Seconds())
}

func (m *writeCacheMetrics) SetActualCount(shardID, path, storageType string, count uint64) {
	m.actualCount.With(prometheus.Labels{
		shardIDLabel: shardID,
		storageLabel: storageType,
		pathLabel:    path,
	}).Set(float64(count))
}

func (m *writeCacheMetrics) SetEstimateSize(shardID, path, storageType string, size uint64) {
	m.estimatedSize.With(prometheus.Labels{
		shardIDLabel: shardID,
		storageLabel: storageType,
		pathLabel:    path,
	}).Set(float64(size))
}

func (m *writeCacheMetrics) SetMode(shardID string, mode string) {
	m.mode.SetMode(shardID, mode)
}

func (m *writeCacheMetrics) IncOperationCounter(shardID, path, storageType, operation string, success NullBool) {
	m.operationCounter.With(prometheus.Labels{
		shardIDLabel:   shardID,
		storageLabel:   storageType,
		operationLabel: operation,
		successLabel:   success.String(),
		pathLabel:      path,
	}).Inc()
}

func (m *writeCacheMetrics) Close(shardID, path string) {
	m.mode.Delete(shardID)
	m.methodDuration.DeletePartialMatch(prometheus.Labels{shardIDLabel: shardID, pathLabel: path})
	m.operationCounter.DeletePartialMatch(prometheus.Labels{shardIDLabel: shardID, pathLabel: path})
	m.actualCount.DeletePartialMatch(prometheus.Labels{shardIDLabel: shardID, pathLabel: path})
	m.estimatedSize.DeletePartialMatch(prometheus.Labels{shardIDLabel: shardID, pathLabel: path})
}

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