package erasurecode_test

import (
	"testing"

	objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/erasurecode"
	oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
	"github.com/stretchr/testify/require"
)

// The library can behave differently for big shard counts.
// This test checks we support the maximum number of chunks we promise.
func TestSplitMaxShardCount(t *testing.T) {
	pk, err := keys.NewPrivateKey()
	require.NoError(t, err)

	original := newObject(t, 1024, pk)

	t.Run("only data", func(t *testing.T) {
		c, err := erasurecode.NewConstructor(erasurecode.MaxShardCount, 0)
		require.NoError(t, err)

		parts, err := c.Split(original, &pk.PrivateKey)
		require.NoError(t, err)
		require.Len(t, parts, erasurecode.MaxShardCount)

		for _, part := range parts {
			require.NoError(t, objectSDK.CheckHeaderVerificationFields(part))
		}
	})
	t.Run("data + parity", func(t *testing.T) {
		c, err := erasurecode.NewConstructor(1, erasurecode.MaxShardCount-1)
		require.NoError(t, err)

		parts, err := c.Split(original, &pk.PrivateKey)
		require.NoError(t, err)
		require.Len(t, parts, erasurecode.MaxShardCount)

		for _, part := range parts {
			require.NoError(t, objectSDK.CheckHeaderVerificationFields(part))
		}
	})
	t.Run("ec parents are children of last Split part", func(t *testing.T) {
		c, err := erasurecode.NewConstructor(1, erasurecode.MaxShardCount-1)
		require.NoError(t, err)

		splitted1 := newObject(t, 1024, pk)
		splitted2 := newObject(t, 1024, pk)

		splittedId1, _ := splitted1.ID()
		splittedId2, _ := splitted2.ID()

		splitID := objectSDK.NewSplitID()
		parent := objectSDK.New()
		parent.SetID(oidtest.ID())
		parent.SetChildren(splittedId1, splittedId2)
		parent.SetSplitID(splitID)

		splitted1.SetSplitID(splitID)
		splitted1.SetParent(parent)
		splitted2.SetSplitID(splitID)
		splitted2.SetParent(parent)

		parts1, err := c.Split(splitted1, &pk.PrivateKey)
		require.NoError(t, err)
		require.Len(t, parts1, erasurecode.MaxShardCount)

		for _, part := range parts1 {
			require.NoError(t, objectSDK.CheckHeaderVerificationFields(part))

			require.NotNil(t, part.ECHeader().ParentSplitID())
			require.Equal(t, *splitID, *part.ECHeader().ParentSplitID())
			require.NotNil(t, part.ECHeader().ParentSplitParentID())
		}

		parts2, err := c.Split(splitted2, &pk.PrivateKey)
		require.NoError(t, err)
		require.Len(t, parts1, erasurecode.MaxShardCount)

		for _, part := range parts2 {
			require.NoError(t, objectSDK.CheckHeaderVerificationFields(part))

			require.NotNil(t, part.ECHeader().ParentSplitID())
			require.Equal(t, *splitID, *part.ECHeader().ParentSplitID())
			require.NotNil(t, part.ECHeader().ParentSplitParentID())
		}

	})
	t.Run("ec parents are children of non-last Split part", func(t *testing.T) {
		c, err := erasurecode.NewConstructor(1, erasurecode.MaxShardCount-1)
		require.NoError(t, err)

		splitted1 := newObject(t, 1024, pk)
		splitted2 := newObject(t, 1024, pk)

		splittedId1, _ := splitted1.ID()
		splittedId2, _ := splitted2.ID()

		splitID := objectSDK.NewSplitID()
		parent := objectSDK.New()
		// Such parent has got no ID.
		parent.SetChildren(splittedId1, splittedId2)
		parent.SetSplitID(splitID)

		splitted1.SetSplitID(splitID)
		splitted1.SetParent(parent)
		splitted2.SetSplitID(splitID)
		splitted2.SetParent(parent)

		parts1, err := c.Split(splitted1, &pk.PrivateKey)
		require.NoError(t, err)
		require.Len(t, parts1, erasurecode.MaxShardCount)

		for _, part := range parts1 {
			require.NoError(t, objectSDK.CheckHeaderVerificationFields(part))

			require.NotNil(t, part.ECHeader().ParentSplitID())
			require.Equal(t, *splitID, *part.ECHeader().ParentSplitID())
			require.Nil(t, part.ECHeader().ParentSplitParentID())
		}

		parts2, err := c.Split(splitted2, &pk.PrivateKey)
		require.NoError(t, err)
		require.Len(t, parts1, erasurecode.MaxShardCount)

		for _, part := range parts2 {
			require.NoError(t, objectSDK.CheckHeaderVerificationFields(part))

			require.NotNil(t, part.ECHeader().ParentSplitID())
			require.Equal(t, *splitID, *part.ECHeader().ParentSplitID())
			require.Nil(t, part.ECHeader().ParentSplitParentID())
		}

	})

}