package shard_test

import (
	"context"
	"path/filepath"
	"testing"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/blobovniczatree"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/blobstor/fstree"
	meta "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/metabase"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/pilorama"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/shard"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/local_object_storage/writecache"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	"github.com/panjf2000/ants/v2"
	"github.com/stretchr/testify/require"
	"go.uber.org/zap"
	"go.uber.org/zap/zaptest"
)

type epochState struct {
	Value uint64
}

func (s epochState) CurrentEpoch() uint64 {
	return s.Value
}

func newShard(t testing.TB, enableWriteCache bool) *shard.Shard {
	return newCustomShard(t, t.TempDir(), enableWriteCache,
		nil,
		nil,
		nil)
}

func newCustomShard(t testing.TB, rootPath string, enableWriteCache bool, wcOpts []writecache.Option, bsOpts []blobstor.Option, metaOptions []meta.Option) *shard.Shard {
	var sh *shard.Shard
	if enableWriteCache {
		rootPath = filepath.Join(rootPath, "wc")
	} else {
		rootPath = filepath.Join(rootPath, "nowc")
	}

	if bsOpts == nil {
		bsOpts = []blobstor.Option{
			blobstor.WithLogger(&logger.Logger{Logger: zaptest.NewLogger(t)}),
			blobstor.WithStorages([]blobstor.SubStorage{
				{
					Storage: blobovniczatree.NewBlobovniczaTree(
						blobovniczatree.WithLogger(&logger.Logger{Logger: zaptest.NewLogger(t)}),
						blobovniczatree.WithRootPath(filepath.Join(rootPath, "blob", "blobovnicza")),
						blobovniczatree.WithBlobovniczaShallowDepth(1),
						blobovniczatree.WithBlobovniczaShallowWidth(1)),
					Policy: func(_ *object.Object, data []byte) bool {
						return len(data) <= 1<<20
					},
				},
				{
					Storage: fstree.New(
						fstree.WithPath(filepath.Join(rootPath, "blob"))),
				},
			}),
		}
	}

	opts := []shard.Option{
		shard.WithID(shard.NewIDFromBytes([]byte{})),
		shard.WithLogger(&logger.Logger{Logger: zap.L()}),
		shard.WithBlobStorOptions(bsOpts...),
		shard.WithMetaBaseOptions(
			append([]meta.Option{
				meta.WithPath(filepath.Join(rootPath, "meta")), meta.WithEpochState(epochState{})},
				metaOptions...)...,
		),
		shard.WithPiloramaOptions(pilorama.WithPath(filepath.Join(rootPath, "pilorama"))),
		shard.WithWriteCache(enableWriteCache),
		shard.WithWriteCacheOptions(
			append(
				[]writecache.Option{writecache.WithPath(filepath.Join(rootPath, "wcache"))},
				wcOpts...)...,
		),
		shard.WithDeletedLockCallback(func(_ context.Context, addresses []oid.Address) {
			sh.HandleDeletedLocks(addresses)
		}),
		shard.WithExpiredLocksCallback(func(ctx context.Context, epoch uint64, a []oid.Address) {
			sh.HandleExpiredLocks(ctx, epoch, a)
		}),
		shard.WithGCWorkerPoolInitializer(func(sz int) util.WorkerPool {
			pool, err := ants.NewPool(sz)
			require.NoError(t, err)
			return pool
		}),
		shard.WithGCRemoverSleepInterval(1 * time.Millisecond),
	}

	sh = shard.New(opts...)

	require.NoError(t, sh.Open())
	require.NoError(t, sh.Init(context.Background()))

	return sh
}

func releaseShard(s *shard.Shard, t testing.TB) {
	require.NoError(t, s.Close())
}