package shard

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/writecache"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util"
	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/util/logger/test"
	objectSDK "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"
)

type epochState struct {
	Value uint64
}

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

type shardOptions struct {
	rootPath    string
	wcOpts      []writecache.Option
	bsOpts      []blobstor.Option
	metaOptions []meta.Option

	additionalShardOptions []Option
}

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

func newCustomShard(t testing.TB, enableWriteCache bool, o shardOptions) *Shard {
	if o.rootPath == "" {
		o.rootPath = t.TempDir()
	}

	var sh *Shard
	if enableWriteCache {
		o.wcOpts = append(
			[]writecache.Option{writecache.WithPath(filepath.Join(o.rootPath, "wcache"))},
			o.wcOpts...)
	}

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

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

	sh = New(opts...)

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

	return sh
}