// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package index

import (
	"errors"
	"testing"

	"github.com/hashicorp/consul/internal/resource"
	"github.com/hashicorp/consul/internal/storage"
	"github.com/hashicorp/consul/proto-public/pbresource"
	"github.com/stretchr/testify/require"
)

func TestIndexFromID(t *testing.T) {
	id := &pbresource.ID{
		Type: &pbresource.Type{
			Group:        "test",
			GroupVersion: "v1",
			Kind:         "fake",
		},
		Tenancy: &pbresource.Tenancy{
			Partition: "foo",
			Namespace: "bar",
		},
		Name: "baz",
		Uid:  "01G9R78VJ5VXAJRA2K7V0RXB87",
	}

	val := IndexFromID(id, false)
	expected := []byte("test\x00fake\x00foo\x00bar\x00baz\x00")
	require.Equal(t, expected, val)

	val = IndexFromID(id, true)
	expected = []byte("test\x00fake\x00foo\x00bar\x00baz\x0001G9R78VJ5VXAJRA2K7V0RXB87\x00")
	require.Equal(t, expected, val)
}

func TestIndexFromRefOrID(t *testing.T) {
	id := &pbresource.ID{
		Type: &pbresource.Type{
			Group:        "test",
			GroupVersion: "v1",
			Kind:         "fake",
		},
		Tenancy: &pbresource.Tenancy{
			Partition: "foo",
			Namespace: "bar",
		},
		Name: "baz",
		Uid:  "01G9R78VJ5VXAJRA2K7V0RXB87",
	}

	ref := resource.Reference(id, "")

	idVal := IndexFromRefOrID(id)
	refVal := IndexFromRefOrID(ref)

	expected := "test\x00fake\x00foo\x00bar\x00baz\x00"
	require.Equal(t, []byte(expected), idVal)
	require.Equal(t, []byte(expected), refVal)
}

func TestPrefixIndexFromRefOrID(t *testing.T) {
	t.Run("no partition", func(t *testing.T) {
		ref := &pbresource.Reference{
			Type: &pbresource.Type{
				Group:        "test",
				GroupVersion: "v1",
				Kind:         "fake",
			},
		}

		require.Equal(t, []byte("test\x00fake\x00"), PrefixIndexFromRefOrID(ref))
	})

	t.Run("no namespace", func(t *testing.T) {
		ref := &pbresource.Reference{
			Type: &pbresource.Type{
				Group:        "test",
				GroupVersion: "v1",
				Kind:         "fake",
			},
			Tenancy: &pbresource.Tenancy{
				Partition: "test",
			},
		}

		require.Equal(t, []byte("test\x00fake\x00test\x00"), PrefixIndexFromRefOrID(ref))
	})

	t.Run("name prefix", func(t *testing.T) {
		id := &pbresource.ID{
			Type: &pbresource.Type{
				Group:        "test",
				GroupVersion: "v1",
				Kind:         "fake",
			},
			Tenancy: &pbresource.Tenancy{
				Partition: "test",
				Namespace: "test",
			},
			Name: "prefix",
		}

		require.Equal(t, []byte("test\x00fake\x00test\x00test\x00prefix"), PrefixIndexFromRefOrID(id))
	})
}

func TestPrefixIndexFromTenancy(t *testing.T) {
	t.Run("nil tenancy", func(t *testing.T) {
		idx, done := prefixIndexFromTenancy(nil)
		require.Len(t, idx, 0)
		require.True(t, done)
	})

	t.Run("partition empty", func(t *testing.T) {
		tenant := &pbresource.Tenancy{}
		idx, done := prefixIndexFromTenancy(tenant)
		require.Len(t, idx, 0)
		require.True(t, done)
	})

	t.Run("partition wildcard", func(t *testing.T) {
		tenant := &pbresource.Tenancy{
			Partition: storage.Wildcard,
		}
		idx, done := prefixIndexFromTenancy(tenant)
		require.Len(t, idx, 0)
		require.True(t, done)
	})

	t.Run("namespace empty", func(t *testing.T) {
		tenant := &pbresource.Tenancy{
			Partition: "test",
			Namespace: "",
		}
		idx, done := prefixIndexFromTenancy(tenant)
		require.Equal(t, []byte("test\x00"), idx)
		require.True(t, done)
	})

	t.Run("namespace wildcard", func(t *testing.T) {
		tenant := &pbresource.Tenancy{
			Partition: "test",
			Namespace: storage.Wildcard,
		}
		idx, done := prefixIndexFromTenancy(tenant)
		require.Equal(t, []byte("test\x00"), idx)
		require.True(t, done)
	})

	t.Run("namespace populated", func(t *testing.T) {
		tenant := &pbresource.Tenancy{
			Partition: "test",
			Namespace: "blah",
		}
		idx, done := prefixIndexFromTenancy(tenant)
		require.Equal(t, []byte("test\x00blah\x00"), idx)
		require.False(t, done)
	})
}

func TestIndexFromType(t *testing.T) {
	val := IndexFromType(&pbresource.Type{
		Group:        "test",
		GroupVersion: "v1",
		Kind:         "fake",
	})

	require.Equal(t, []byte("test\x00fake\x00"), val)

	val = IndexFromType(&pbresource.Type{
		Group:        "test",
		GroupVersion: "v1",
	})

	require.Equal(t, []byte("test\x00\x00"), val)
}

func TestReferenceOrIDFromArgs(t *testing.T) {
	ref := &pbresource.Reference{
		Type: &pbresource.Type{
			Group:        "test",
			GroupVersion: "v1",
			Kind:         "fake",
		},
		Tenancy: &pbresource.Tenancy{
			Partition: "default",
			Namespace: "default",
		},
		Name: "foo",
	}
	t.Run("invalid length", func(t *testing.T) {
		// the second arg will cause a validation error
		val, err := ReferenceOrIDFromArgs(ref, 2)
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("invalid type", func(t *testing.T) {
		val, err := ReferenceOrIDFromArgs("string type unexpected")
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("ok", func(t *testing.T) {
		val, err := ReferenceOrIDFromArgs(ref)
		require.NoError(t, err)
		require.Equal(t, IndexFromRefOrID(ref), val)
	})
}

func TestPrefixReferenceOrIDFromArgs(t *testing.T) {
	ref := &pbresource.Reference{
		Type: &pbresource.Type{
			Group:        "test",
			GroupVersion: "v1",
			Kind:         "fake",
		},
		Tenancy: &pbresource.Tenancy{
			Partition: "default",
		},
	}
	t.Run("invalid length", func(t *testing.T) {
		// the second arg will cause a validation error
		val, err := PrefixReferenceOrIDFromArgs(ref, 2)
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("invalid type", func(t *testing.T) {
		val, err := PrefixReferenceOrIDFromArgs("string type unexpected")
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("ok", func(t *testing.T) {
		val, err := PrefixReferenceOrIDFromArgs(ref)
		require.NoError(t, err)
		require.Equal(t, PrefixIndexFromRefOrID(ref), val)
	})
}

func TestMaybePrefixReferenceOrIDFromArgs(t *testing.T) {
	ref := &pbresource.Reference{
		Type: &pbresource.Type{
			Group:        "test",
			GroupVersion: "v1",
			Kind:         "fake",
		},
		Tenancy: &pbresource.Tenancy{
			Partition: "default",
		},
	}
	t.Run("invalid length", func(t *testing.T) {
		// the second arg will cause a validation error
		val, err := MaybePrefixReferenceOrIDFromArgs(ref, IndexQueryOptions{Prefix: false}, 3)
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("invalid type arg 1", func(t *testing.T) {
		val, err := MaybePrefixReferenceOrIDFromArgs("string type unexpected", IndexQueryOptions{Prefix: true})
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("invalid type arg 2", func(t *testing.T) {
		val, err := MaybePrefixReferenceOrIDFromArgs(ref, "string type unexpected")
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("ok single arg", func(t *testing.T) {
		val, err := MaybePrefixReferenceOrIDFromArgs(ref)
		require.NoError(t, err)
		require.Equal(t, IndexFromRefOrID(ref), val)
	})

	t.Run("ok two args no prefix", func(t *testing.T) {
		val, err := MaybePrefixReferenceOrIDFromArgs(ref, IndexQueryOptions{Prefix: false})
		require.NoError(t, err)
		require.Equal(t, IndexFromRefOrID(ref), val)
	})

	t.Run("ok two args with prefix", func(t *testing.T) {
		val, err := MaybePrefixReferenceOrIDFromArgs(ref, IndexQueryOptions{Prefix: true})
		require.NoError(t, err)
		require.Equal(t, PrefixIndexFromRefOrID(ref), val)
	})
}

func TestSingleValueFromArgs(t *testing.T) {
	injectedError := errors.New("injected test error")

	idx := SingleValueFromArgs(func(val string) ([]byte, error) {
		return []byte(val), nil
	})

	errIdx := SingleValueFromArgs(func(val string) ([]byte, error) {
		return nil, injectedError
	})

	t.Run("invalid length", func(t *testing.T) {
		// the second arg will cause a validation error
		val, err := idx("blah", 2)
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("invalid type", func(t *testing.T) {
		val, err := idx(3)
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("propagate error", func(t *testing.T) {
		val, err := errIdx("blah")
		require.Nil(t, val)
		require.ErrorIs(t, err, injectedError)
	})

	t.Run("ok", func(t *testing.T) {
		val, err := idx("blah")
		require.NoError(t, err)
		require.Equal(t, []byte("blah"), val)
	})
}

func TestSingleValueFromOneOrTwoArgs(t *testing.T) {
	injectedError := errors.New("injected test error")

	idx := SingleValueFromOneOrTwoArgs(func(val string, optional bool) ([]byte, error) {
		if optional {
			return []byte(val + val), nil
		}
		return []byte(val), nil
	})

	errIdx := SingleValueFromOneOrTwoArgs(func(val string, optional bool) ([]byte, error) {
		return nil, injectedError
	})

	t.Run("invalid length", func(t *testing.T) {
		// the third arg will cause a validation error
		val, err := idx("blah", true, 4)
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("invalid first type", func(t *testing.T) {
		val, err := idx(4, true)
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("invalid second type", func(t *testing.T) {
		val, err := idx("blah", 3)
		require.Nil(t, val)
		require.Error(t, err)
	})

	t.Run("propagate error", func(t *testing.T) {
		val, err := errIdx("blah")
		require.Nil(t, val)
		require.ErrorIs(t, err, injectedError)
	})

	t.Run("one arg ok", func(t *testing.T) {
		val, err := idx("blah")
		require.NoError(t, err)
		require.Equal(t, []byte("blah"), val)
	})

	t.Run("two arg ok", func(t *testing.T) {
		val, err := idx("blah", true)
		require.NoError(t, err)
		require.Equal(t, []byte("blahblah"), val)
	})

}
