package metrics

import (
	"strconv"
	"time"

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

type BlobobvnizcaMetrics interface {
	SetBlobobvnizcaTreeMode(shardID, path string, readOnly bool)
	CloseBlobobvnizcaTree(shardID, path string)
	BlobobvnizcaTreeMethodDuration(shardID, path string, method string, d time.Duration, success bool, withStorageID NullBool)
	AddBlobobvnizcaTreePut(shardID, path string, size int)
	AddBlobobvnizcaTreeGet(shardID, path string, size int)

	AddOpenBlobovniczaSize(shardID, path string, size uint64)
	SubOpenBlobovniczaSize(shardID, path string, size uint64)

	AddOpenBlobovniczaItems(shardID, path string, items uint64)
	SubOpenBlobovniczaItems(shardID, path string, items uint64)

	IncOpenBlobovniczaCount(shardID, path string)
	DecOpenBlobovniczaCount(shardID, path string)

	BlobovniczaTreeRebuildStatus(shardID, path, status string)
	BlobovniczaTreeRebuildPercent(shardID, path string, value uint32)
	BlobovniczaTreeObjectMoved(shardID, path string, d time.Duration)
}

type blobovnicza struct {
	treeMode               *shardIDPathModeValue
	treeReqDuration        *prometheus.HistogramVec
	treePut                *prometheus.CounterVec
	treeGet                *prometheus.CounterVec
	treeOpenSize           *prometheus.GaugeVec
	treeOpenItems          *prometheus.GaugeVec
	treeOpenCounter        *prometheus.GaugeVec
	treeObjectMoveDuration *prometheus.HistogramVec
	treeRebuildStatus      *shardIDPathModeValue
	treeRebuildPercent     *prometheus.GaugeVec
}

func newBlobovnicza() *blobovnicza {
	return &blobovnicza{
		treeMode: newShardIDPathMode(blobovniczaTreeSubSystem, "mode", "Blobovnicza tree mode"),

		treeReqDuration: metrics.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: namespace,
			Subsystem: blobovniczaTreeSubSystem,
			Name:      "request_duration_seconds",
			Help:      "Accumulated Blobovnicza tree request process duration",
		}, []string{shardIDLabel, pathLabel, successLabel, methodLabel, withStorageIDLabel}),
		treePut: metrics.NewCounterVec(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: blobovniczaTreeSubSystem,
			Name:      "put_bytes",
			Help:      "Accumulated payload size written to Blobovnicza tree",
		}, []string{shardIDLabel, pathLabel}),
		treeGet: metrics.NewCounterVec(prometheus.CounterOpts{
			Namespace: namespace,
			Subsystem: blobovniczaTreeSubSystem,
			Name:      "get_bytes",
			Help:      "Accumulated payload size read from Blobovnicza tree",
		}, []string{shardIDLabel, pathLabel}),
		treeOpenSize: metrics.NewGaugeVec(prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: blobovniczaTreeSubSystem,
			Name:      "open_blobovnicza_size_bytes",
			Help:      "Size of opened blobovniczas of Blobovnicza tree",
		}, []string{shardIDLabel, pathLabel}),
		treeOpenItems: metrics.NewGaugeVec(prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: blobovniczaTreeSubSystem,
			Name:      "open_blobovnicza_items_total",
			Help:      "Count of items in opened blobovniczas of Blobovnicza tree",
		}, []string{shardIDLabel, pathLabel}),
		treeOpenCounter: metrics.NewGaugeVec(prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: blobovniczaTreeSubSystem,
			Name:      "open_blobovnicza_count",
			Help:      "Count of opened blobovniczas of Blobovnicza tree",
		}, []string{shardIDLabel, pathLabel}),
		treeObjectMoveDuration: metrics.NewHistogramVec(prometheus.HistogramOpts{
			Namespace: namespace,
			Subsystem: blobovniczaTreeSubSystem,
			Name:      "object_move_duration_seconds",
			Help:      "Accumulated Blobovnicza tree object move duration",
		}, []string{shardIDLabel, pathLabel}),
		treeRebuildStatus: newShardIDPathMode(blobovniczaTreeSubSystem, "rebuild_status", "Blobovnicza tree rebuild status"),
		treeRebuildPercent: metrics.NewGaugeVec(prometheus.GaugeOpts{
			Namespace: namespace,
			Subsystem: blobovniczaTreeSubSystem,
			Name:      "rebuild_complete_percent",
			Help:      "Percent of rebuild completeness",
		}, []string{shardIDLabel, pathLabel}),
	}
}

func (b *blobovnicza) SetBlobobvnizcaTreeMode(shardID, path string, readOnly bool) {
	b.treeMode.SetMode(shardID, path, modeFromBool(readOnly))
}

func (b *blobovnicza) CloseBlobobvnizcaTree(shardID, path string) {
	b.treeMode.SetMode(shardID, path, closedMode)
	b.treeReqDuration.DeletePartialMatch(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	})
	b.treeGet.DeletePartialMatch(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	})
	b.treePut.DeletePartialMatch(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	})
	b.treeObjectMoveDuration.DeletePartialMatch(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	})
	b.treeRebuildPercent.DeletePartialMatch(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	})
	b.treeRebuildStatus.SetMode(shardID, path, undefinedStatus)
}

func (b *blobovnicza) BlobobvnizcaTreeMethodDuration(shardID, path string, method string, d time.Duration, success bool, withStorageID NullBool) {
	b.treeReqDuration.With(prometheus.Labels{
		shardIDLabel:       shardID,
		pathLabel:          path,
		successLabel:       strconv.FormatBool(success),
		methodLabel:        method,
		withStorageIDLabel: withStorageID.String(),
	}).Observe(d.Seconds())
}

func (b *blobovnicza) AddBlobobvnizcaTreePut(shardID, path string, size int) {
	b.treePut.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Add(float64(size))
}

func (b *blobovnicza) AddBlobobvnizcaTreeGet(shardID, path string, size int) {
	b.treeGet.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Add(float64(size))
}

func (b *blobovnicza) AddOpenBlobovniczaSize(shardID, path string, size uint64) {
	b.treeOpenSize.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Add(float64(size))
}

func (b *blobovnicza) SubOpenBlobovniczaSize(shardID, path string, size uint64) {
	b.treeOpenSize.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Sub(float64(size))
}

func (b *blobovnicza) IncOpenBlobovniczaCount(shardID, path string) {
	b.treeOpenCounter.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Inc()
}

func (b *blobovnicza) DecOpenBlobovniczaCount(shardID, path string) {
	b.treeOpenCounter.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Dec()
}

func (b *blobovnicza) AddOpenBlobovniczaItems(shardID, path string, items uint64) {
	b.treeOpenItems.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Add(float64(items))
}

func (b *blobovnicza) SubOpenBlobovniczaItems(shardID, path string, items uint64) {
	b.treeOpenItems.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Sub(float64(items))
}

func (b *blobovnicza) BlobovniczaTreeRebuildStatus(shardID, path, status string) {
	b.treeRebuildStatus.SetMode(shardID, path, status)
}

func (b *blobovnicza) BlobovniczaTreeObjectMoved(shardID, path string, d time.Duration) {
	b.treeObjectMoveDuration.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Observe(d.Seconds())
}

func (b *blobovnicza) BlobovniczaTreeRebuildPercent(shardID, path string, value uint32) {
	b.treeRebuildPercent.With(prometheus.Labels{
		shardIDLabel: shardID,
		pathLabel:    path,
	}).Set(float64(value))
}