package blobovniczatree

import (
	"io/fs"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobovnicza"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/compression"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
	"go.uber.org/zap"
)

type cfg struct {
	log                *logger.Logger
	perm               fs.FileMode
	readOnly           bool
	rootPath           string
	openedCacheSize    int
	openedCacheTTL     time.Duration
	blzShallowDepth    uint64
	blzShallowWidth    uint64
	blzLeafWidth       uint64
	compression        *compression.Config
	blzOpts            []blobovnicza.Option
	reportError        func(string, error) // reportError is the function called when encountering disk errors.
	metrics            Metrics
	waitBeforeDropDB   time.Duration
	blzInitWorkerCount int
	blzMoveBatchSize   int
	createDBInAdvance  bool
}

type Option func(*cfg)

const (
	defaultPerm               = 0o700
	defaultOpenedCacheSize    = 50
	defaultOpenedCacheTTL     = 0 // means expiring is off
	defaultBlzShallowDepth    = 2
	defaultBlzShallowWidth    = 16
	defaultWaitBeforeDropDB   = 10 * time.Second
	defaultBlzInitWorkerCount = 5
	defaulBlzMoveBatchSize    = 10000
)

func initConfig(c *cfg) {
	*c = cfg{
		log:                &logger.Logger{Logger: zap.L()},
		perm:               defaultPerm,
		openedCacheSize:    defaultOpenedCacheSize,
		openedCacheTTL:     defaultOpenedCacheTTL,
		blzShallowDepth:    defaultBlzShallowDepth,
		blzShallowWidth:    defaultBlzShallowWidth,
		reportError:        func(string, error) {},
		metrics:            &noopMetrics{},
		waitBeforeDropDB:   defaultWaitBeforeDropDB,
		blzInitWorkerCount: defaultBlzInitWorkerCount,
		blzMoveBatchSize:   defaulBlzMoveBatchSize,
	}
}

func WithLogger(l *logger.Logger) Option {
	return func(c *cfg) {
		c.log = l
		c.blzOpts = append(c.blzOpts, blobovnicza.WithLogger(l))
	}
}

func WithPermissions(perm fs.FileMode) Option {
	return func(c *cfg) {
		c.perm = perm
	}
}

func WithBlobovniczaShallowWidth(width uint64) Option {
	return func(c *cfg) {
		c.blzShallowWidth = width
	}
}

func WithBlobovniczaLeafWidth(w uint64) Option {
	return func(c *cfg) {
		c.blzLeafWidth = w
	}
}

func WithBlobovniczaShallowDepth(depth uint64) Option {
	return func(c *cfg) {
		c.blzShallowDepth = depth
	}
}

func WithRootPath(p string) Option {
	return func(c *cfg) {
		c.rootPath = p
	}
}

func WithBlobovniczaSize(sz uint64) Option {
	return func(c *cfg) {
		c.blzOpts = append(c.blzOpts, blobovnicza.WithFullSizeLimit(sz))
	}
}

func WithOpenedCacheSize(sz int) Option {
	return func(c *cfg) {
		c.openedCacheSize = sz
	}
}

func WithOpenedCacheTTL(ttl time.Duration) Option {
	return func(c *cfg) {
		c.openedCacheTTL = ttl
	}
}

func WithObjectSizeLimit(sz uint64) Option {
	return func(c *cfg) {
		c.blzOpts = append(c.blzOpts, blobovnicza.WithObjectSizeLimit(sz))
	}
}

func WithMetrics(m Metrics) Option {
	return func(c *cfg) {
		c.metrics = m
	}
}

func WithWaitBeforeDropDB(t time.Duration) Option {
	return func(c *cfg) {
		c.waitBeforeDropDB = t
	}
}

func WithMoveBatchSize(v int) Option {
	return func(c *cfg) {
		c.blzMoveBatchSize = v
	}
}

// WithInitWorkerCount sets maximum workers count to init blobovnicza tree.
//
// Negative or zero value means no limit.
func WithInitWorkerCount(v int) Option {
	if v <= 0 {
		v = -1
	}
	return func(c *cfg) {
		c.blzInitWorkerCount = v
	}
}

// WithInitInAdvance returns an option to create blobovnicza tree DB's in advance.
func WithInitInAdvance(v bool) Option {
	return func(c *cfg) {
		c.createDBInAdvance = v
	}
}