package datasource

import (
	"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/v6/internal/datakinds"
	"github.com/launchdarkly/go-server-sdk/v6/internal/sharedtest"
)

func TestParsePutData(t *testing.T) {
	allDataJSON := `{
 "flags": {
  "flag1": {"key": "flag1", "version": 1},
  "flag2": {"key": "flag2", "version": 2}
 },
 "segments": {
  "segment1": {"key": "segment1","version": 3}
 }
}`
	expectedAllData := sharedtest.NewDataSetBuilder().
		Flags(ldbuilders.NewFlagBuilder("flag1").Version(1).Build(),
			ldbuilders.NewFlagBuilder("flag2").Version(2).Build()).
		Segments(ldbuilders.NewSegmentBuilder("segment1").Version(3).Build()).
		Build()

	t.Run("valid", func(t *testing.T) {
		input := []byte(`{"path": "/", "data": ` + allDataJSON + `}`)

		result, err := parsePutData(input)
		require.NoError(t, err)

		assert.Equal(t, "/", result.Path)
		assert.Equal(t, sharedtest.NormalizeDataSet(expectedAllData), sharedtest.NormalizeDataSet(result.Data))
	})

	t.Run("missing path", func(t *testing.T) {
		input := []byte(`{"data": ` + allDataJSON + `}`)
		result, err := parsePutData(input)
		require.NoError(t, err) // we don't consider this an error; some versions of Relay don't send a path
		assert.Equal(t, "", result.Path)
		assert.Equal(t, sharedtest.NormalizeDataSet(expectedAllData), sharedtest.NormalizeDataSet(result.Data))
	})

	t.Run("missing data", func(t *testing.T) {
		input := []byte(`{"path": "/"}`)
		_, err := parsePutData(input)
		require.Error(t, err)
	})
}

func TestParsePatchData(t *testing.T) {
	flag := ldbuilders.NewFlagBuilder("flagkey").Version(2).On(true).Build()
	segment := ldbuilders.NewSegmentBuilder("segmentkey").Version(3).Included("x").Build()
	flagJSON := `{"key": "flagkey", "version": 2, "on": true}`
	segmentJSON := `{"key": "segmentkey", "version": 3, "included": ["x"]}`

	t.Run("valid flag", func(t *testing.T) {
		input := []byte(`{"path": "/flags/flagkey", "data": ` + flagJSON + `}`)
		result, err := parsePatchData(input)
		require.NoError(t, err)

		assert.Equal(t, datakinds.Features, result.Kind)
		assert.Equal(t, "flagkey", result.Key)
		assert.Equal(t, sharedtest.FlagDescriptor(flag), result.Data)
	})

	t.Run("valid segment", func(t *testing.T) {
		input := []byte(`{"path": "/segments/segmentkey", "data": ` + segmentJSON + `}`)
		result, err := parsePatchData(input)
		require.NoError(t, err)

		assert.Equal(t, datakinds.Segments, result.Kind)
		assert.Equal(t, "segmentkey", result.Key)
		assert.Equal(t, sharedtest.SegmentDescriptor(segment), result.Data)
	})

	t.Run("valid but data property appears before path", func(t *testing.T) {
		input := []byte(`{"data": ` + flagJSON + `, "path": "/flags/flagkey"}`)
		result, err := parsePatchData(input)
		require.NoError(t, err)

		assert.Equal(t, datakinds.Features, result.Kind)
		assert.Equal(t, "flagkey", result.Key)
		assert.Equal(t, sharedtest.FlagDescriptor(flag), result.Data)
	})

	t.Run("unrecognized path", func(t *testing.T) {
		input := []byte(`{"path": "/cats/lucy", "data": ` + flagJSON + `}`)
		result, err := parsePatchData(input)
		require.NoError(t, err)

		assert.Nil(t, result.Kind)
		assert.Equal(t, "", result.Key)
	})

	t.Run("missing path", func(t *testing.T) {
		input := []byte(`{"data": ` + flagJSON + `}`)
		_, err := parsePatchData(input)
		require.Error(t, err)
	})

	t.Run("missing data", func(t *testing.T) {
		input := []byte(`{"path": "/flags/flagkey"}`)
		_, err := parsePatchData(input)
		require.Error(t, err)
	})
}

func TestParseDeleteData(t *testing.T) {
	t.Run("valid flag", func(t *testing.T) {
		input := []byte(`{"path": "/flags/flagkey", "version": 3}`)
		result, err := parseDeleteData(input)
		require.NoError(t, err)

		assert.Equal(t, datakinds.Features, result.Kind)
		assert.Equal(t, "flagkey", result.Key)
		assert.Equal(t, 3, result.Version)
	})

	t.Run("valid segment", func(t *testing.T) {
		input := []byte(`{"path": "/segments/segmentkey", "version": 4}`)
		result, err := parseDeleteData(input)
		require.NoError(t, err)

		assert.Equal(t, datakinds.Segments, result.Kind)
		assert.Equal(t, "segmentkey", result.Key)
		assert.Equal(t, 4, result.Version)
	})

	t.Run("unrecognized path", func(t *testing.T) {
		input := []byte(`{"path": "/cats/macavity", "version": 9}`)
		result, err := parseDeleteData(input)
		require.NoError(t, err)

		assert.Nil(t, result.Kind)
		assert.Equal(t, "", result.Key)
	})

	t.Run("missing path", func(t *testing.T) {
		input := []byte(`{"version": 1}`)
		_, err := parseDeleteData(input)
		require.Error(t, err)
	})

	t.Run("missing version", func(t *testing.T) {
		input := []byte(`{"path": "/flags/flagkey"}`)
		_, err := parseDeleteData(input)
		require.Error(t, err)
	})
}
