package metrics

import (
	"fmt"
	"time"

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

type WriteCacheMetrics interface {
	AddMethodDuration(shardID string, method string, success bool, d time.Duration, storageType string)
	SetActualCount(shardID string, count uint64, storageType string)
	SetEstimateSize(shardID string, size uint64, storageType string)
	SetMode(shardID string, mode string)
	IncOperationCounter(shardID string, operation string, success NullBool, storageType string)
	Close(shardID 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}),
		operationCounter: metrics.NewCounterVec(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: writeCacheSubsystem,
			Name:      "operations_total",
			Help:      "The number of writecache operations processed",
		}, []string{shardIDLabel, storageLabel, successLabel, operationLabel}),
		actualCount:   newWCGaugeVec("actual_objects_total", "Actual objects count in writecache", []string{shardIDLabel, storageLabel}),
		estimatedSize: newWCGaugeVec("estimated_size_bytes", "Estimated writecache size", []string{shardIDLabel, storageLabel}),
		mode:          newShardIDMode(writeCacheSubsystem, "mode_info", "Writecache mode value"),
	}
}

func (m *writeCacheMetrics) AddMethodDuration(shardID string, method string, success bool, d time.Duration, storageType string) {
	m.methodDuration.With(
		prometheus.Labels{
			shardIDLabel: shardID,
			successLabel: fmt.Sprintf("%v", success),
			storageLabel: storageType,
			methodLabel:  method,
		},
	).Observe(d.Seconds())
}

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

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

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

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

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

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