package blobtree

import (
	"errors"
	"path/filepath"
	"strings"
	"sync/atomic"
	"syscall"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/common"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/compression"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util"
	utilSync "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/sync"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
)

var _ common.Storage = &BlobTree{}

type BlobTree struct {
	cfg        cfg
	dirLock    *utilSync.KeyLocker[string]
	fileLock   *utilSync.KeyLocker[string]
	compressor *compression.Config
	dispatcher *rootDispatcher
	suffix     atomic.Uint64
}

func New(opts ...Option) *BlobTree {
	b := &BlobTree{
		cfg: cfg{
			targetFileSizeBytes: 4 * 1024 * 1024,
			rootPath:            "./",
			depth:               3,
			permissions:         0700,
			initWorkersCount:    1000,
			metrics:             &noopMetrics{},
		},
		dirLock:  utilSync.NewKeyLocker[string](),
		fileLock: utilSync.NewKeyLocker[string](),
	}

	for _, opt := range opts {
		opt(&b.cfg)
	}

	b.dispatcher = newRootDispatcher()

	return b
}

func (b *BlobTree) getDirectoryPath(addr oid.Address) string {
	sAddr := addr.Object().EncodeToString() + "." + addr.Container().EncodeToString()
	var sb strings.Builder
	size := int(1+b.cfg.depth*(directoryLength+1)) + len(b.cfg.rootPath) // /path + slash + (character + slash for every level)
	sb.Grow(size)
	sb.WriteString(b.cfg.rootPath)

	for i := uint64(0); i < b.cfg.depth; i++ {
		sb.WriteRune(filepath.Separator)
		sb.WriteString(sAddr[:directoryLength])
		sAddr = sAddr[directoryLength:]
	}

	sb.WriteRune(filepath.Separator)
	return sb.String()
}

func (b *BlobTree) createDir(dir string) error {
	b.dirLock.Lock(dir)
	defer b.dirLock.Unlock(dir)

	if err := util.MkdirAllX(dir, b.cfg.permissions); err != nil {
		if errors.Is(err, syscall.ENOSPC) {
			err = common.ErrNoSpace
			return err
		}
		return err
	}

	return nil
}