package storage

import (
	"testing"

	"github.com/seaweedfs/seaweedfs/weed/storage/needle"
	"github.com/seaweedfs/seaweedfs/weed/storage/super_block"
	"github.com/stretchr/testify/require"
)

func newWorkerTestVolume(t *testing.T) *Volume {
	t.Helper()
	dir := t.TempDir()
	v, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, &super_block.ReplicaPlacement{}, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
	require.NoError(t, err)
	t.Cleanup(v.Close)
	return v
}

// A server holding millions of volumes pays for whatever every mount costs, so
// the batch worker and its channel may not exist until a durable write needs
// them.
func TestMountingAVolumeStartsNoBatchWorker(t *testing.T) {
	v := newWorkerTestVolume(t)
	require.Nil(t, v.asyncRequestsChan, "mounting a volume started a batch worker nothing had asked for")

	_, _, _, err := v.writeNeedle2(newRandomNeedle(1), true, false, false)
	require.NoError(t, err)
	require.Nil(t, v.asyncRequestsChan, "a write that did not ask for fsync started a batch worker")
}

func TestDurableWriteStartsTheBatchWorkerOnce(t *testing.T) {
	v := newWorkerTestVolume(t)

	_, _, _, err := v.writeNeedle2(newRandomNeedle(1), true, true, false)
	require.NoError(t, err)
	started := v.asyncRequestsChan
	require.NotNil(t, started, "a durable write did not start the batch worker")

	_, _, _, err = v.writeNeedle2(newRandomNeedle(2), true, true, false)
	require.NoError(t, err)
	require.Equal(t, started, v.asyncRequestsChan, "a second durable write replaced the worker's channel")
}

// Destroy closes the channel. A write arriving after that has to fall back to
// the inline path rather than queue onto a worker that has gone, and that path
// has to fail rather than crash on what Destroy left behind.
func TestDurableWriteAfterDestroyWritesInline(t *testing.T) {
	dir := t.TempDir()
	v, err := NewVolume(dir, dir, "", 1, NeedleMapInMemory, &super_block.ReplicaPlacement{}, &needle.TTL{}, 0, needle.GetCurrentVersion(), 0, 0)
	require.NoError(t, err)
	_, _, _, err = v.writeNeedle2(newRandomNeedle(1), true, true, false)
	require.NoError(t, err)

	require.NoError(t, v.Destroy(false, false))
	require.Nil(t, v.asyncRequestsChan)
	require.False(t, v.asyncRequestAppend(needle.NewAsyncRequest(newRandomNeedle(2), true)))

	// The inline path it falls back to has to refuse the write, not dereference
	// the needle map and backend Destroy left nil.
	_, _, _, err = v.writeNeedle2(newRandomNeedle(2), true, true, false)
	require.Error(t, err)
	_, _, _, err = v.writeNeedle2(newRandomNeedle(3), true, false, false)
	require.Error(t, err)
}
