package pilorama

import (
	"context"
	"strings"
	"testing"

	cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
	"github.com/stretchr/testify/require"
)

func TestDuplicateDirectory(t *testing.T) {
	for i := range providers {
		if providers[i].name == "inmemory" {
			continue
		}
		t.Run(providers[i].name, func(t *testing.T) {
			testDuplicateDirectory(t, providers[i].construct(t))
		})
	}
}

func testDuplicateDirectory(t *testing.T, f Forest) {
	ctx := context.Background()
	d := CIDDescriptor{CID: cidtest.ID(), Size: 1}
	treeID := "sometree"

	treeApply := func(t *testing.T, parent, child uint64, filename string, internal bool) {
		// Nothing magic here, we add items in order and children are unique.
		// This simplifies function interface a bit.
		ts := child

		kv := []KeyValue{{Key: AttributeFilename, Value: []byte(filename)}}
		if !internal {
			kv = append(kv, KeyValue{Key: "uniqueAttr", Value: []byte{byte(child)}})
		}

		err := f.TreeApply(ctx, d.CID, treeID, &Move{
			Parent: parent,
			Child:  child,
			Meta: Meta{
				Time:  ts,
				Items: kv,
			},
		}, true)
		require.NoError(t, err)
	}

	// The following tree is constructed:
	//      0
	// [1]  |-- dir1 (internal)
	// [2]     |-- value1
	// [3]     |-- dir3 (internal)
	// [4]        |-- value3
	// [5]  |-- dir1 (internal)
	// [6]     |-- value2
	// [7]     |-- dir3 (internal)
	// [8]        |-- value4
	// [9]  |-- dir2 (internal)
	// [10] |-- value0
	treeApply(t, RootID, 1, "dir1", true)
	treeApply(t, 1, 2, "value1", false)
	treeApply(t, 1, 3, "dir3", true)
	treeApply(t, 3, 4, "value3", false)
	treeApply(t, RootID, 5, "dir1", true)
	treeApply(t, 5, 6, "value2", false)
	treeApply(t, 5, 7, "dir3", true)
	treeApply(t, 7, 8, "value4", false)
	treeApply(t, RootID, 9, "dir2", true)
	treeApply(t, RootID, 10, "value0", false)

	// The compacted view:
	// 0
	// [1,5] |-- dir1 (internal)
	// [2]      |-- value1
	// [3,7]    |-- dir3 (internal)
	// [4]          |-- value3
	// [8]          |-- value4
	// [6]      |-- value2
	// [9]   |-- dir2 (internal)
	// [10]  |-- value0
	testGetByPath := func(t *testing.T, p string) []byte {
		pp := strings.Split(p, "/")
		nodes, err := f.TreeGetByPath(context.Background(), d.CID, treeID, AttributeFilename, pp, false)
		require.NoError(t, err)
		require.Equal(t, 1, len(nodes))

		meta, _, err := f.TreeGetMeta(ctx, d.CID, treeID, nodes[0])
		require.NoError(t, err)
		require.Equal(t, []byte(pp[len(pp)-1]), meta.GetAttr(AttributeFilename))
		return meta.GetAttr("uniqueAttr")
	}

	require.Equal(t, []byte{2}, testGetByPath(t, "dir1/value1"))
	require.Equal(t, []byte{4}, testGetByPath(t, "dir1/dir3/value3"))
	require.Equal(t, []byte{8}, testGetByPath(t, "dir1/dir3/value4"))
	require.Equal(t, []byte{10}, testGetByPath(t, "value0"))

	testSortedByFilename := func(t *testing.T, root MultiNode, last *string, batchSize int) ([]MultiNodeInfo, *string) {
		res, last, err := f.TreeSortedByFilename(context.Background(), d.CID, treeID, root, last, batchSize)
		require.NoError(t, err)
		return res, last
	}

	t.Run("test sorted listing, full children branch", func(t *testing.T) {
		t.Run("big batch size", func(t *testing.T) {
			res, _ := testSortedByFilename(t, MultiNode{RootID}, nil, 10)
			require.Equal(t, 3, len(res))
			require.Equal(t, MultiNode{1, 5}, res[0].Children)
			require.Equal(t, MultiNode{9}, res[1].Children)
			require.Equal(t, MultiNode{10}, res[2].Children)

			t.Run("multi-root", func(t *testing.T) {
				res, _ := testSortedByFilename(t, MultiNode{1, 5}, nil, 10)
				require.Equal(t, 3, len(res))
				require.Equal(t, MultiNode{3, 7}, res[0].Children)
				require.Equal(t, MultiNode{2}, res[1].Children)
				require.Equal(t, MultiNode{6}, res[2].Children)
			})
		})
		t.Run("small batch size", func(t *testing.T) {
			res, last := testSortedByFilename(t, MultiNode{RootID}, nil, 1)
			require.Equal(t, 1, len(res))
			require.Equal(t, MultiNode{1, 5}, res[0].Children)

			res, last = testSortedByFilename(t, MultiNode{RootID}, last, 1)
			require.Equal(t, 1, len(res))
			require.Equal(t, MultiNode{9}, res[0].Children)

			res, last = testSortedByFilename(t, MultiNode{RootID}, last, 1)
			require.Equal(t, 1, len(res))
			require.Equal(t, MultiNode{10}, res[0].Children)

			res, _ = testSortedByFilename(t, MultiNode{RootID}, last, 1)
			require.Equal(t, 0, len(res))

			t.Run("multi-root", func(t *testing.T) {
				res, last := testSortedByFilename(t, MultiNode{1, 5}, nil, 1)
				require.Equal(t, 1, len(res))
				require.Equal(t, MultiNode{3, 7}, res[0].Children)

				res, last = testSortedByFilename(t, MultiNode{1, 5}, last, 1)
				require.Equal(t, 1, len(res))
				require.Equal(t, MultiNode{2}, res[0].Children)

				res, last = testSortedByFilename(t, MultiNode{1, 5}, last, 1)
				require.Equal(t, 1, len(res))
				require.Equal(t, MultiNode{6}, res[0].Children)

				res, _ = testSortedByFilename(t, MultiNode{RootID}, last, 1)
				require.Equal(t, 0, len(res))
			})
		})
	})
}