package engine

import (
	"context"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard/mode"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/metrics"
	"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
	"go.opentelemetry.io/otel/attribute"
	"go.opentelemetry.io/otel/trace"
)

// FlushWriteCachePrm groups the parameters of FlushWriteCache operation.
type FlushWriteCachePrm struct {
	shardID      *shard.ID
	ignoreErrors bool
}

// SetShardID is an option to set shard ID.
//
// Option is required.
func (p *FlushWriteCachePrm) SetShardID(id *shard.ID) {
	p.shardID = id
}

// SetIgnoreErrors sets errors ignore flag..
func (p *FlushWriteCachePrm) SetIgnoreErrors(ignore bool) {
	p.ignoreErrors = ignore
}

// FlushWriteCacheRes groups the resulting values of FlushWriteCache operation.
type FlushWriteCacheRes struct{}

// FlushWriteCache flushes write-cache on a single shard.
func (e *StorageEngine) FlushWriteCache(ctx context.Context, p FlushWriteCachePrm) (FlushWriteCacheRes, error) {
	ctx, span := tracing.StartSpanFromContext(ctx, "StorageEngine.FlushWriteCache",
		trace.WithAttributes(
			attribute.String("shard)id", p.shardID.String()),
			attribute.Bool("ignore_errors", p.ignoreErrors),
		))
	defer span.End()

	e.mtx.RLock()
	sh, ok := e.shards[p.shardID.String()]
	e.mtx.RUnlock()

	if !ok {
		return FlushWriteCacheRes{}, errShardNotFound
	}

	var prm shard.FlushWriteCachePrm
	prm.SetIgnoreErrors(p.ignoreErrors)

	return FlushWriteCacheRes{}, sh.FlushWriteCache(ctx, prm)
}

type writeCacheMetrics struct {
	shardID string
	metrics metrics.WriteCacheMetrics
}

func (m *writeCacheMetrics) Get(d time.Duration, success bool, st writecache.StorageType) {
	m.metrics.AddMethodDuration(m.shardID, "Get", success, d, st.String())
}

func (m *writeCacheMetrics) Delete(d time.Duration, success bool, st writecache.StorageType) {
	m.metrics.AddMethodDuration(m.shardID, "Delete", success, d, st.String())
	if success {
		m.metrics.DecActualCount(m.shardID, st.String())
	}
}

func (m *writeCacheMetrics) Put(d time.Duration, success bool, st writecache.StorageType) {
	m.metrics.AddMethodDuration(m.shardID, "Put", success, d, st.String())
	if success {
		m.metrics.IncActualCount(m.shardID, st.String())
	}
}

func (m *writeCacheMetrics) SetEstimateSize(db, fstree uint64) {
	m.metrics.SetEstimateSize(m.shardID, db, writecache.StorageTypeDB.String())
	m.metrics.SetEstimateSize(m.shardID, fstree, writecache.StorageTypeFSTree.String())
}

func (m *writeCacheMetrics) SetMode(mode mode.Mode) {
	m.metrics.SetMode(m.shardID, mode.String())
}

func (m *writeCacheMetrics) SetActualCounters(db, fstree uint64) {
	m.metrics.SetActualCount(m.shardID, db, writecache.StorageTypeDB.String())
	m.metrics.SetActualCount(m.shardID, fstree, writecache.StorageTypeFSTree.String())
}

func (m *writeCacheMetrics) Flush(success bool, st writecache.StorageType) {
	m.metrics.IncOperationCounter(m.shardID, "Flush", metrics.NullBool{Bool: success, Valid: true}, st.String())
}

func (m *writeCacheMetrics) Evict(st writecache.StorageType) {
	m.metrics.DecActualCount(m.shardID, st.String())
	m.metrics.IncOperationCounter(m.shardID, "Evict", metrics.NullBool{}, st.String())
}

func (m *writeCacheMetrics) Close() {
	m.metrics.Close(m.shardID)
}