package blobovniczatree

import (
	"context"
	"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
	blzShallowDepth    uint64
	blzShallowWidth    uint64
	compression        *compression.Config
	blzOpts            []blobovnicza.Option
	reportError        func(context.Context, string, error) // reportError is the function called when encountering disk errors.
	metrics            Metrics
	waitBeforeDropDB   time.Duration
	blzInitWorkerCount int
	blzMoveBatchSize   int
	// TTL for blobovnicza's cache
	openedCacheTTL time.Duration
	// Interval for deletion expired blobovnicza's
	openedCacheExpInterval time.Duration
}

type Option func(*cfg)

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

func initConfig(c *cfg) {
	*c = cfg{
		log:                    logger.NewLoggerWrapper(zap.L()),
		perm:                   defaultPerm,
		openedCacheSize:        defaultOpenedCacheSize,
		openedCacheTTL:         defaultOpenedCacheTTL,
		openedCacheExpInterval: defaultOpenedCacheInterval,
		blzShallowDepth:        defaultBlzShallowDepth,
		blzShallowWidth:        defaultBlzShallowWidth,
		reportError:            func(context.Context, 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 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 WithOpenedCacheExpInterval(expInterval time.Duration) Option {
	return func(c *cfg) {
		c.openedCacheExpInterval = expInterval
	}
}

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
	}
}