package graphqlfields

import (
	"strings"
	"testing"

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

var registry = []Field{
	{Name: "id", Fragment: "id"},
	{Name: "title", Fragment: "title"},
	{Name: "author", Fragment: `author { username }`},
	{Name: "assignees", Fragment: `assignees { nodes { username } }`, Container: "features"},
	{Name: "weight", Fragment: "weight { weight }", Container: "features"},
	{Name: "notes", Fragment: "notes { count }", Container: "features"},
}

func TestBuildFragment_EmptyRegistryStillValidatesSelection(t *testing.T) {
	t.Parallel()

	// No selection: valid, empty result.
	got, err := BuildFragment(nil, nil)
	require.NoError(t, err)
	assert.Empty(t, got)

	// Any selection against an empty registry is invalid.
	_, err = BuildFragment(nil, []string{"anything"})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "unknown field")
}

func TestBuildFragment_EmptySelectionReturnsEmptyBody(t *testing.T) {
	t.Parallel()

	got, err := BuildFragment(registry, nil)
	require.NoError(t, err)
	assert.Equal(t, "\n", got)
}

func TestBuildFragment_TopLevelOnly(t *testing.T) {
	t.Parallel()

	got, err := BuildFragment(registry, []string{"id", "title"})
	require.NoError(t, err)

	assert.NotContains(t, got, "features {")
	assert.Contains(t, got, "\n\tid")
	assert.Contains(t, got, "\n\ttitle")
}

func TestBuildFragment_ContainerGroupsUnderSingleBlock(t *testing.T) {
	t.Parallel()

	got, err := BuildFragment(registry, []string{"assignees", "weight"})
	require.NoError(t, err)

	// One container block, not one per field.
	assert.Equal(t, 1, strings.Count(got, "features {"))
	assert.Contains(t, got, "assignees { nodes { username } }")
	assert.Contains(t, got, "weight { weight }")
	assert.Contains(t, got, "\n\t}")
}

func TestBuildFragment_MixedTopLevelAndContainerPreserveRegistryOrder(t *testing.T) {
	t.Parallel()

	got, err := BuildFragment(registry, []string{"weight", "id", "title", "assignees"})
	require.NoError(t, err)

	// Registry order wins over selection order.
	assert.Less(t, strings.Index(got, "\n\tid"), strings.Index(got, "\n\ttitle"))
	assert.Less(t, strings.Index(got, "assignees {"), strings.Index(got, "weight { weight }"))
	assert.Less(t, strings.Index(got, "\n\ttitle"), strings.Index(got, "\n\tfeatures {"))
}

func TestBuildFragment_UnknownFieldReturnsError(t *testing.T) {
	t.Parallel()

	_, err := BuildFragment(registry, []string{"id", "totallyMadeUp"})
	require.Error(t, err)
	assert.Contains(t, err.Error(), "unknown field")
	assert.Contains(t, err.Error(), "totallyMadeUp")
}

func TestBuildFragment_MultipleContainersRenderSeparateBlocks(t *testing.T) {
	t.Parallel()

	twoContainer := []Field{
		{Name: "alpha", Fragment: "alpha", Container: "groupA"},
		{Name: "beta", Fragment: "beta", Container: "groupB"},
	}

	got, err := BuildFragment(twoContainer, []string{"alpha", "beta"})
	require.NoError(t, err)

	assert.Contains(t, got, "\n\tgroupA {\n\t\talpha")
	assert.Contains(t, got, "\n\tgroupB {\n\t\tbeta")
	assert.Equal(t, 2, strings.Count(got, "\n\t}"))
}

// Template references must pass through so the outer template tree can bind them.
func TestBuildFragment_FragmentTextIsCopiedVerbatim(t *testing.T) {
	t.Parallel()

	reg := []Field{
		{Name: "author", Fragment: `author { {{ template "UserCoreBasic" }} }`},
	}

	got, err := BuildFragment(reg, []string{"author"})
	require.NoError(t, err)
	assert.Contains(t, got, `{{ template "UserCoreBasic" }}`)
}
