package placement_test

import (
	"strconv"
	"testing"

	"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/object_manager/placement"
	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
	netmapSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
	"github.com/stretchr/testify/require"
)

func TestContainerNodesCache(t *testing.T) {
	const size = 3

	nodes := [6]netmapSDK.NodeInfo{}
	for i := range nodes {
		nodes[i].SetAttribute("ATTR", strconv.Itoa(i))
	}

	nm := func(epoch uint64, nodes []netmapSDK.NodeInfo) *netmapSDK.NetMap {
		var nm netmapSDK.NetMap
		nm.SetEpoch(epoch)
		nm.SetNodes(nodes)
		return &nm
	}

	var pp netmapSDK.PlacementPolicy
	require.NoError(t, pp.DecodeString("REP 1"))

	t.Run("update netmap on the new epoch", func(t *testing.T) {
		c := placement.NewContainerNodesCache(size)

		cnr := cidtest.ID()
		res, err := c.ContainerNodes(nm(1, nodes[0:1]), cnr, pp)
		require.NoError(t, err)

		// Use other nodes in the argument to ensure the result is taken from cache.
		resCached, err := c.ContainerNodes(nm(1, nodes[1:2]), cnr, pp)
		require.NoError(t, err)
		require.Equal(t, res, resCached)

		// Update epoch, netmap should be purged.
		resCached, err = c.ContainerNodes(nm(2, nodes[2:3]), cnr, pp)
		require.NoError(t, err)
		require.NotEqual(t, res, resCached)
	})
	t.Run("cache uses container as a key", func(t *testing.T) {
		c := placement.NewContainerNodesCache(size)

		res1, err := c.ContainerNodes(nm(1, nodes[0:1]), cidtest.ID(), pp)
		require.NoError(t, err)

		res2, err := c.ContainerNodes(nm(1, nodes[1:2]), cidtest.ID(), pp)
		require.NoError(t, err)

		require.NotEqual(t, res1, res2)
	})
	t.Run("cache respects size parameter", func(t *testing.T) {
		c := placement.NewContainerNodesCache(size)

		nm1 := nm(1, nodes[0:1])
		nm2 := nm(1, nodes[1:2])
		cnr := [size * 2]cid.ID{}
		res := [size * 2][][]netmapSDK.NodeInfo{}
		for i := range size * 2 {
			cnr[i] = cidtest.ID()

			var err error
			res[i], err = c.ContainerNodes(nm1, cnr[i], pp)
			require.NoError(t, err)
		}

		for i := size; i < size*2; i++ {
			r, err := c.ContainerNodes(nm2, cnr[i], pp)
			require.NoError(t, err)
			require.Equal(t, res[i], r)
		}
		for i := range size {
			r, err := c.ContainerNodes(nm2, cnr[i], pp)
			require.NoError(t, err)
			require.NotEqual(t, res[i], r)
		}
	})
	t.Run("the error is propagated", func(t *testing.T) {
		var pp netmapSDK.PlacementPolicy
		require.NoError(t, pp.DecodeString("REP 1 SELECT 1 FROM X FILTER ATTR EQ 42 AS X"))

		c := placement.NewContainerNodesCache(size)
		_, err := c.ContainerNodes(nm(1, nodes[0:1]), cidtest.ID(), pp)
		require.Error(t, err)
	})
}