package datakinds

import (
	"encoding/json"
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldbuilders"
	"github.com/launchdarkly/go-server-sdk-evaluation/v2/ldmodel"
	"github.com/launchdarkly/go-server-sdk/v6/subsystems/ldstoretypes"
)

func TestAllKinds(t *testing.T) {
	assert.Equal(t, []ldstoretypes.DataKind{Features, Segments}, AllDataKinds())
}

func TestDataKindFeatures(t *testing.T) {
	kind := Features

	t.Run("name", func(t *testing.T) {
		assert.Equal(t, "features", kind.GetName())
		assert.Equal(t, "features", fmt.Sprintf("%s", kind))
	})

	t.Run("serialize", func(t *testing.T) {
		flag := ldbuilders.NewFlagBuilder("flagkey").Version(2).Build()
		bytes := kind.Serialize(ldstoretypes.ItemDescriptor{Version: flag.Version, Item: &flag})
		assert.Contains(t, string(bytes), `"key":"flagkey"`)
		assert.Contains(t, string(bytes), `"version":2`)
	})

	t.Run("deserialize", func(t *testing.T) {
		json := `{"key":"flagkey","version":2}`
		item, err := kind.Deserialize([]byte(json))
		assert.NoError(t, err)
		assert.Equal(t, 2, item.Version)
		require.NotNil(t, item.Item)
		flag := item.Item.(*ldmodel.FeatureFlag)
		assert.Equal(t, "flagkey", flag.Key)
		assert.Equal(t, 2, flag.Version)
	})

	t.Run("serialize deleted item", func(t *testing.T) {
		// It's important that the SDK provides a placeholder JSON object for deleted items, because most
		// of our existing database integrations aren't able to store the version number separately from
		// the JSON data.
		bytes := kind.Serialize(ldstoretypes.ItemDescriptor{Version: 2, Item: nil})
		expected, _ := json.Marshal(ldmodel.FeatureFlag{Key: deletedItemPlaceholderKey, Version: 2, Deleted: true})
		assert.JSONEq(t, string(expected), string(bytes))
	})

	t.Run("deserialize deleted item", func(t *testing.T) {
		json := `{"key":"flagkey","version":2,"deleted":true}`
		item, err := kind.Deserialize([]byte(json))
		assert.NoError(t, err)
		assert.Equal(t, 2, item.Version)
		require.Nil(t, item.Item)
	})

	t.Run("will not serialize wrong type", func(t *testing.T) {
		bytes := kind.Serialize(ldstoretypes.ItemDescriptor{Version: 1, Item: "not a flag"})
		assert.Nil(t, bytes)
	})

	t.Run("deserialization error", func(t *testing.T) {
		json := `{"key":"flagkey"`
		item, err := kind.Deserialize([]byte(json))
		assert.Error(t, err)
		require.Nil(t, item.Item)
	})
}

func TestDataKindSegments(t *testing.T) {
	kind := Segments

	t.Run("name", func(t *testing.T) {
		assert.Equal(t, "segments", kind.GetName())
		assert.Equal(t, "segments", fmt.Sprintf("%s", kind))
	})

	t.Run("serialize", func(t *testing.T) {
		segment := ldbuilders.NewSegmentBuilder("segmentkey").Version(2).Build()
		bytes := kind.Serialize(ldstoretypes.ItemDescriptor{Version: segment.Version, Item: &segment})
		assert.Contains(t, string(bytes), `"key":"segmentkey"`)
		assert.Contains(t, string(bytes), `"version":2`)
	})

	t.Run("deserialize", func(t *testing.T) {
		json := `{"key":"segmentkey","version":2}`
		item, err := kind.Deserialize([]byte(json))
		assert.NoError(t, err)
		require.NotNil(t, item.Item)
		segment := item.Item.(*ldmodel.Segment)
		assert.Equal(t, "segmentkey", segment.Key)
		assert.Equal(t, 2, segment.Version)
	})

	t.Run("serialize deleted item", func(t *testing.T) {
		// It's important that the SDK provides a placeholder JSON object for deleted items, because most
		// of our existing database integrations aren't able to store the version number separately from
		// the JSON data.
		bytes := kind.Serialize(ldstoretypes.ItemDescriptor{Version: 2, Item: nil})
		expected, _ := json.Marshal(ldmodel.Segment{Key: deletedItemPlaceholderKey, Version: 2, Deleted: true})
		assert.JSONEq(t, string(expected), string(bytes))
	})

	t.Run("deserialize deleted item", func(t *testing.T) {
		json := `{"key":"segmentkey","version":2,"deleted":true}`
		item, err := kind.Deserialize([]byte(json))
		assert.NoError(t, err)
		assert.Equal(t, 2, item.Version)
		require.Nil(t, item.Item)
	})

	t.Run("will not serialize wrong type", func(t *testing.T) {
		bytes := kind.Serialize(ldstoretypes.ItemDescriptor{Version: 1, Item: "not a flag"})
		assert.Nil(t, bytes)
	})

	t.Run("deserialization error", func(t *testing.T) {
		json := `{"key":"segmentkey"`
		item, err := kind.Deserialize([]byte(json))
		assert.Error(t, err)
		require.Nil(t, item.Item)
	})
}
