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

package accumulator

import (
	"reflect"
	"testing"

	"sigs.k8s.io/kustomize/api/resmap"
	resmaptest_test "sigs.k8s.io/kustomize/api/testutils/resmaptest"
	"sigs.k8s.io/kustomize/api/types"
	"sigs.k8s.io/kustomize/kyaml/resid"
)

func TestRefVarTransformer(t *testing.T) {
	type given struct {
		varMap map[string]interface{}
		fs     []types.FieldSpec
		res    resmap.ResMap
	}
	type expected struct {
		res    resmap.ResMap
		unused []string
	}
	testCases := map[string]struct {
		given      given
		expected   expected
		errMessage string
	}{
		"var replacement in map[string]": {
			given: given{
				varMap: map[string]interface{}{
					"FOO": "replacementForFoo",
					"BAR": "replacementForBar",
					"BAZ": int64(5),
					"BOO": true,
				},
				fs: []types.FieldSpec{
					{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/map"},
					{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/slice"},
					{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/interface"},
					{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/num"},
				},
				res: resmaptest_test.NewRmBuilderDefault(t).
					Add(map[string]interface{}{
						"apiVersion": "v1",
						"kind":       "ConfigMap",
						"metadata": map[string]interface{}{
							"name": "cm1",
						},
						"data": map[string]interface{}{
							"map": map[string]interface{}{
								"item1": "$(FOO)",
								"item2": "bla",
								"item3": "$(BAZ)",
								"item4": "$(BAZ)+$(BAZ)",
								"item5": "$(BOO)",
								"item6": "if $(BOO)",
								"item7": int64(2019),
							},
							"slice": []interface{}{
								"$(FOO)",
								"bla",
								"$(BAZ)",
								"$(BAZ)+$(BAZ)",
								"$(BOO)",
								"if $(BOO)",
							},
							"interface": "$(FOO)",
							"num":       int64(2019),
						}}).ResMap(),
			},
			expected: expected{
				res: resmaptest_test.NewRmBuilderDefault(t).
					Add(map[string]interface{}{
						"apiVersion": "v1",
						"kind":       "ConfigMap",
						"metadata": map[string]interface{}{
							"name": "cm1",
						},
						"data": map[string]interface{}{
							"map": map[string]interface{}{
								"item1": "replacementForFoo",
								"item2": "bla",
								"item3": int64(5),
								"item4": "5+5",
								"item5": true,
								"item6": "if true",
								"item7": int64(2019),
							},
							"slice": []interface{}{
								"replacementForFoo",
								"bla",
								int64(5),
								"5+5",
								true,
								"if true",
							},
							"interface": "replacementForFoo",
							"num":       int64(2019),
						}}).ResMap(),
				unused: []string{"BAR"},
			},
		},
		"var replacement panic in map[string]": {
			given: given{
				varMap: map[string]interface{}{},
				fs: []types.FieldSpec{
					{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/slice"},
				},
				res: resmaptest_test.NewRmBuilderDefault(t).
					Add(map[string]interface{}{
						"apiVersion": "v1",
						"kind":       "ConfigMap",
						"metadata": map[string]interface{}{
							"name": "cm1",
						},
						"data": map[string]interface{}{
							"slice": []interface{}{5}, // noticeably *not* a []string
						}}).ResMap(),
			},
			errMessage: `considering field 'data/slice' of object
apiVersion: v1
data:
  slice:
  - 5
kind: ConfigMap
metadata:
  name: cm1
: invalid value type expect a string`,
		},
		"var replacement in nil": {
			given: given{
				varMap: map[string]interface{}{},
				fs: []types.FieldSpec{
					{Gvk: resid.Gvk{Version: "v1", Kind: "ConfigMap"}, Path: "data/nil"},
				},
				res: resmaptest_test.NewRmBuilderDefault(t).
					Add(map[string]interface{}{
						"apiVersion": "v1",
						"kind":       "ConfigMap",
						"metadata": map[string]interface{}{
							"name": "cm1",
						},
						"data": map[string]interface{}{
							"nil": nil, // noticeably *not* a []string
						}}).ResMap(),
			},
			expected: expected{
				res: resmaptest_test.NewRmBuilderDefault(t).
					Add(map[string]interface{}{
						"apiVersion": "v1",
						"kind":       "ConfigMap",
						"metadata": map[string]interface{}{
							"name": "cm1",
						},
						"data": map[string]interface{}{
							"nil": nil, // noticeably *not* a []string
						}}).ResMap(),
			},
		},
	}

	for tn, tc := range testCases {
		t.Run(tn, func(t *testing.T) {
			tr := newRefVarTransformer(tc.given.varMap, tc.given.fs)
			err := tr.Transform(tc.given.res)
			if tc.errMessage != "" {
				if err == nil {
					t.Fatalf("missing expected error %v", tc.errMessage)
				} else if err.Error() != tc.errMessage {
					t.Fatalf(`actual error doesn't match expected error:
ACTUAL: %v
EXPECTED: %v`,
						err.Error(), tc.errMessage)
				}
			} else {
				if err != nil {
					t.Errorf("unexpected error: %v", err)
				}

				a, e := tc.given.res, tc.expected.res
				if !reflect.DeepEqual(a, e) {
					err = e.ErrorIfNotEqualLists(a)
					t.Fatalf(`actual doesn't match expected:
ACTUAL:
%v
EXPECTED:
%v
ERR: %v`,
						a, e, err)
				}
			}
		})
	}
}
