package file

import (
	"testing"

	"github.com/containous/traefik/v2/pkg/config/parser"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func Test_decodeRawToNode(t *testing.T) {
	testCases := []struct {
		desc     string
		data     map[string]interface{}
		expected *parser.Node
	}{
		{
			desc: "empty",
			data: map[string]interface{}{},
			expected: &parser.Node{
				Name: "traefik",
			},
		},
		{
			desc: "string",
			data: map[string]interface{}{
				"foo": "bar",
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "bar"},
				},
			},
		},
		{
			desc: "string named type",
			data: map[string]interface{}{
				"foo": bar("bar"),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "bar"},
				},
			},
		},
		{
			desc: "bool",
			data: map[string]interface{}{
				"foo": true,
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "true"},
				},
			},
		},
		{
			desc: "int",
			data: map[string]interface{}{
				"foo": 1,
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "int8",
			data: map[string]interface{}{
				"foo": int8(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "int16",
			data: map[string]interface{}{
				"foo": int16(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "int32",
			data: map[string]interface{}{
				"foo": int32(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "int64",
			data: map[string]interface{}{
				"foo": int64(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "uint",
			data: map[string]interface{}{
				"foo": uint(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "uint8",
			data: map[string]interface{}{
				"foo": uint8(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "uint16",
			data: map[string]interface{}{
				"foo": uint16(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "uint32",
			data: map[string]interface{}{
				"foo": uint32(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "uint64",
			data: map[string]interface{}{
				"foo": uint64(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "float32",
			data: map[string]interface{}{
				"foo": float32(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "float64",
			data: map[string]interface{}{
				"foo": float64(1),
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1"},
				},
			},
		},
		{
			desc: "string slice",
			data: map[string]interface{}{
				"foo": []string{"A", "B"},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "A,B"},
				},
			},
		},
		{
			desc: "int slice",
			data: map[string]interface{}{
				"foo": []int{1, 2},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1,2"},
				},
			},
		},
		{
			desc: "int8 slice",
			data: map[string]interface{}{
				"foo": []int8{1, 2},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1,2"},
				},
			},
		},
		{
			desc: "int16 slice",
			data: map[string]interface{}{
				"foo": []int16{1, 2},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1,2"},
				},
			},
		},
		{
			desc: "int32 slice",
			data: map[string]interface{}{
				"foo": []int32{1, 2},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1,2"},
				},
			},
		},
		{
			desc: "int64 slice",
			data: map[string]interface{}{
				"foo": []int64{1, 2},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1,2"},
				},
			},
		},
		{
			desc: "bool slice",
			data: map[string]interface{}{
				"foo": []bool{true, false},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "true,false"},
				},
			},
		},
		{
			desc: "interface (string) slice",
			data: map[string]interface{}{
				"foo": []interface{}{"A", "B"},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "A,B"},
				},
			},
		},
		{
			desc: "interface (int) slice",
			data: map[string]interface{}{
				"foo": []interface{}{1, 2},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Value: "1,2"},
				},
			},
		},
		{
			desc: "2 strings",
			data: map[string]interface{}{
				"foo": "bar",
				"fii": "bir",
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "fii", Value: "bir"},
					{Name: "foo", Value: "bar"},
				},
			},
		},
		{
			desc: "string, level 2",
			data: map[string]interface{}{
				"fii": map[interface{}]interface{}{
					"fuu": "bur",
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "bur"}}},
				},
			},
		},
		{
			desc: "int, level 2",
			data: map[string]interface{}{
				"fii": map[interface{}]interface{}{
					"fuu": 1,
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "1"}}},
				},
			},
		},
		{
			desc: "uint, level 2",
			data: map[string]interface{}{
				"fii": map[interface{}]interface{}{
					"fuu": uint(1),
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "1"}}},
				},
			},
		},
		{
			desc: "bool, level 2",
			data: map[string]interface{}{
				"fii": map[interface{}]interface{}{
					"fuu": true,
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "true"}}},
				},
			},
		},
		{
			desc: "string, level 3",
			data: map[string]interface{}{
				"foo": map[interface{}]interface{}{
					"fii": map[interface{}]interface{}{
						"fuu": "bur",
					},
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Children: []*parser.Node{
						{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "bur"}}},
					}},
				},
			},
		},
		{
			desc: "int, level 3",
			data: map[string]interface{}{
				"fii": map[interface{}]interface{}{
					"fuu": 1,
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "1"}}},
				},
			},
		},
		{
			desc: "uint, level 3",
			data: map[string]interface{}{
				"fii": map[interface{}]interface{}{
					"fuu": uint(1),
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "1"}}},
				},
			},
		},
		{
			desc: "bool, level 3",
			data: map[string]interface{}{
				"fii": map[interface{}]interface{}{
					"fuu": true,
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "fii", Children: []*parser.Node{{Name: "fuu", Value: "true"}}},
				},
			},
		},
		{
			desc: "struct",
			data: map[string]interface{}{
				"foo": map[interface{}]interface{}{
					"field1": "C",
					"field2": "C",
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Children: []*parser.Node{
						{Name: "field1", Value: "C"},
						{Name: "field2", Value: "C"},
					}},
				},
			},
		},
		{
			desc: "slice struct 1",
			data: map[string]interface{}{
				"foo": []map[string]interface{}{
					{"field1": "A", "field2": "A"},
					{"field1": "B", "field2": "B"},
					{"field2": "C", "field1": "C"},
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Children: []*parser.Node{
						{Name: "[0]", Children: []*parser.Node{
							{Name: "field1", Value: "A"},
							{Name: "field2", Value: "A"},
						}},
						{Name: "[1]", Children: []*parser.Node{
							{Name: "field1", Value: "B"},
							{Name: "field2", Value: "B"},
						}},
						{Name: "[2]", Children: []*parser.Node{
							{Name: "field1", Value: "C"},
							{Name: "field2", Value: "C"},
						}},
					}},
				},
			},
		},
		{
			desc: "slice struct 2",
			data: map[string]interface{}{
				"foo": []interface{}{
					map[interface{}]interface{}{
						"field2": "A",
						"field1": "A",
					},
					map[interface{}]interface{}{
						"field1": "B",
						"field2": "B",
					},
					map[interface{}]interface{}{
						"field1": "C",
						"field2": "C",
					},
				},
			},
			expected: &parser.Node{
				Name: "traefik",
				Children: []*parser.Node{
					{Name: "foo", Children: []*parser.Node{
						{Name: "[0]", Children: []*parser.Node{
							{Name: "field1", Value: "A"},
							{Name: "field2", Value: "A"},
						}},
						{Name: "[1]", Children: []*parser.Node{
							{Name: "field1", Value: "B"},
							{Name: "field2", Value: "B"},
						}},
						{Name: "[2]", Children: []*parser.Node{
							{Name: "field1", Value: "C"},
							{Name: "field2", Value: "C"},
						}},
					}},
				},
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			node, err := decodeRawToNode(test.data, parser.DefaultRootName)
			require.NoError(t, err)

			assert.Equal(t, test.expected, node)
		})
	}
}

func Test_decodeRawToNode_errors(t *testing.T) {
	testCases := []struct {
		desc string
		data map[string]interface{}
	}{
		{
			desc: "invalid type",
			data: map[string]interface{}{
				"foo": struct{}{},
			},
		},
	}

	for _, test := range testCases {
		test := test
		t.Run(test.desc, func(t *testing.T) {
			t.Parallel()

			_, err := decodeRawToNode(test.data, parser.DefaultRootName)
			require.Error(t, err)
		})
	}
}
