// Copyright 2019 The Kubernetes Authors.
// SPDX-License-Identifier: Apache-2.0

package kunstruct

import (
	"reflect"
	"testing"
)

type PathSectionSlice []PathSection

func buildPath(idx int, fields ...string) PathSectionSlice {
	return PathSectionSlice{PathSection{fields: fields, idx: idx}}
}

func (a PathSectionSlice) addSection(idx int, fields ...string) PathSectionSlice {
	return append(a, PathSection{fields: fields, idx: idx})
}

func TestParseField(t *testing.T) {
	tests := []struct {
		name          string
		pathToField   string
		expectedValue []PathSection
		errorExpected bool
		errorMsg      string
	}{
		{
			name:          "oneField",
			pathToField:   "Kind",
			expectedValue: buildPath(-1, "Kind"),
			errorExpected: false,
		},
		{
			name:          "twoFields",
			pathToField:   "metadata.name",
			expectedValue: buildPath(-1, "metadata", "name"),
			errorExpected: false,
		},
		{
			name:          "threeFields",
			pathToField:   "spec.ports.port",
			expectedValue: buildPath(-1, "spec", "ports", "port"),
			errorExpected: false,
		},
		{
			name:          "empty",
			pathToField:   "",
			expectedValue: buildPath(-1, ""),
			errorExpected: false,
		},
		{
			name:          "validStringIndex",
			pathToField:   "that[1]",
			expectedValue: buildPath(1, "that"),
			errorExpected: false,
		},
		{
			name:          "sliceInSlice",
			pathToField:   "that[1][0]",
			expectedValue: buildPath(1, "that").addSection(0),
			errorExpected: false,
		},
		{
			name:          "validStructSubField",
			pathToField:   "those[1].field2",
			expectedValue: buildPath(1, "those").addSection(-1, "field2"),
			errorExpected: false,
		},
		{
			name:          "validStructSubFieldIndex",
			pathToField:   "these[1].field2[0]",
			expectedValue: buildPath(1, "these").addSection(0, "field2"),
			errorExpected: false,
		},
		{
			name:          "validStructSubFieldIndexSubfield",
			pathToField:   "complextree[1].field2[1].stringsubfield",
			expectedValue: buildPath(1, "complextree").addSection(1, "field2").addSection(-1, "stringsubfield"),
			errorExpected: false,
		},
		{
			name:          "validStructDownwardAPI",
			pathToField:   `metadata.labels["app.kubernetes.io/component"]`,
			expectedValue: buildPath(-1, "metadata", "labels", "app.kubernetes.io/component"),
			errorExpected: false,
		},
		{
			name:          "invalidIndexInIndex",
			pathToField:   "complextree[1[0]]",
			errorExpected: true,
			errorMsg:      "nested parentheses are not allowed: complextree[1[0]]",
		},
		{
			name:          "invalidClosingBrackets",
			pathToField:   "complextree[1]]",
			errorExpected: true,
			errorMsg:      "invalid field path complextree[1]]",
		},
		{
			name:          "validFieldsWithQuotes",
			pathToField:   "'complextree'[1].field2[1].'stringsubfield'",
			expectedValue: buildPath(1, "complextree").addSection(1, "field2").addSection(-1, "stringsubfield"),
			errorExpected: false,
		},
	}

	for _, test := range tests {
		s, err := parseFields(test.pathToField)
		if test.errorExpected {
			compareExpectedParserError(t, test.name, test.pathToField, err, test.errorMsg)
			continue
		}
		if err != nil {
			unExpectedParserError(t, test.name, test.pathToField, err)
		}
		compareParserValues(t, test.name, test.pathToField, test.expectedValue, s)
	}
}

// unExpectedError function handles unexpected error
func unExpectedParserError(t *testing.T, name string, pathToField string, err error) {
	t.Fatalf("%q; path %q - unexpected error %v", name, pathToField, err)
}

// compareExpectedError compares the expectedError and the actualError return by parseFields
func compareExpectedParserError(t *testing.T, name string, pathToField string, err error, errorMsg string) {
	if err == nil {
		t.Fatalf("%q; path %q - should return error, but no error returned",
			name, pathToField)
	}

	if errorMsg != err.Error() {
		t.Fatalf("%q; path %q - expected error: \"%s\", got error: \"%v\"",
			name, pathToField, errorMsg, err.Error())
	}
}

// compareValues compares the expectedValue and actualValue returned by parseFields
func compareParserValues(t *testing.T, name string, pathToField string, expectedValue PathSectionSlice, actualValue []PathSection) {
	t.Helper()
	if len(expectedValue) != len(actualValue) {
		t.Fatalf("%q; Path: %s Got: %v Expected: %v", name, pathToField, actualValue, expectedValue)
	}

	for idx, expected := range expectedValue {
		if !reflect.DeepEqual(expected, actualValue[idx]) {
			t.Fatalf("%q; Path: %s idx: %v Fields Got: %v Expected: %v", name, pathToField, idx, actualValue[idx], expected)
		}
	}
}
