// Copyright Istio Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
	"errors"
	"testing"
)

func TestParseValue(t *testing.T) {
	tests := []struct {
		desc string
		in   string
		want any
	}{
		{
			desc: "empty",
			in:   "",
			want: "",
		},
		{
			desc: "true",
			in:   "true",
			want: true,
		},
		{
			desc: "false",
			in:   "false",
			want: false,
		},
		{
			desc: "numeric-one",
			in:   "1",
			want: 1,
		},
		{
			desc: "numeric-zero",
			in:   "0",
			want: 0,
		},
		{
			desc: "numeric-large",
			in:   "12345678",
			want: 12345678,
		},
		{
			desc: "numeric-negative",
			in:   "-12345678",
			want: -12345678,
		},
		{
			desc: "float",
			in:   "1.23456",
			want: 1.23456,
		},
		{
			desc: "float-zero",
			in:   "0.00",
			want: 0.00,
		},
		{
			desc: "float-negative",
			in:   "-6.54321",
			want: -6.54321,
		},
		{
			desc: "string",
			in:   "foobar",
			want: "foobar",
		},
		{
			desc: "string-number-prefix",
			in:   "123foobar",
			want: "123foobar",
		},
	}
	for _, tt := range tests {
		t.Run(tt.desc, func(t *testing.T) {
			if got, want := ParseValue(tt.in), tt.want; !(got == want) {
				t.Errorf("%s: got:%v, want:%v", tt.desc, got, want)
			}
		})
	}
}

func TestConsolidateLog(t *testing.T) {
	tests := []struct {
		desc string
		in   string
		want string
	}{
		{
			desc: "empty",
			in:   "",
			want: "",
		},
		{
			desc: "2 errors once",
			in:   "err1\nerr2\n",
			want: "err1 (repeated 1 times)\nerr2 (repeated 1 times)\n",
		},
		{
			desc: "3 errors multiple times",
			in:   "err1\nerr2\nerr3\nerr1\nerr2\nerr3\nerr3\nerr3\n",
			want: "err1 (repeated 2 times)\nerr2 (repeated 2 times)\nerr3 (repeated 4 times)\n",
		},
	}
	for _, tt := range tests {
		t.Run(tt.desc, func(t *testing.T) {
			if got, want := ConsolidateLog(tt.in), tt.want; !(got == want) {
				t.Errorf("%s: got:%s, want:%s", tt.desc, got, want)
			}
		})
	}
}

func TestStringBoolMapToSlice(t *testing.T) {
	tests := []struct {
		desc string
		in   map[string]bool
		want []string
	}{
		{
			desc: "empty",
			in:   make(map[string]bool),
			want: make([]string, 0),
		},
		{
			desc: "",
			in: map[string]bool{
				"yo":           true,
				"yolo":         false,
				"test1":        true,
				"water bottle": false,
				"baseball hat": true,
			},
			want: []string{
				"yo",
				"test1",
				"baseball hat",
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.desc, func(t *testing.T) {
			if got, want := StringBoolMapToSlice(tt.in), tt.want; !(sameStringSlice(got, want)) {
				t.Errorf("%s: got:%s, want: %s", tt.desc, got, want)
			}
		})
	}
}

// Helper function to check if values in 2 slices are the same,
// no correspondence to order
func sameStringSlice(x, y []string) bool {
	if len(x) != len(y) {
		return false
	}
	diff := make(map[string]int, len(x))
	for _, _x := range x {
		diff[_x]++
	}
	for _, _y := range y {
		if _, ok := diff[_y]; !ok {
			return false
		}
		diff[_y]--
		if diff[_y] == 0 {
			delete(diff, _y)
		}
	}
	return len(diff) == 0
}

func TestRenderTemplate(t *testing.T) {
	type tmplValue struct {
		Name  string
		Proxy string
	}
	tests := []struct {
		desc     string
		template string
		in       tmplValue
		want     string
		err      error
	}{
		{
			desc:     "valid-template",
			template: "{{.Name}} uses {{.Proxy}} as sidecar",
			in: tmplValue{
				Name:  "istio",
				Proxy: "envoy",
			},
			want: "istio uses envoy as sidecar",
			err:  nil,
		},
		{
			desc:     "empty-template",
			template: "",
			in: tmplValue{
				Name:  "istio",
				Proxy: "envoy",
			},
			want: "",
			err:  nil,
		},
		{
			desc:     "template with no template strings",
			template: "this template is without handlebar expressions",
			in: tmplValue{
				Name:  "istio",
				Proxy: "envoy",
			},
			want: "this template is without handlebar expressions",
			err:  nil,
		},
		{
			desc:     "template with missing variable",
			template: "{{ .Name }} has replaced its control plane with {{ .Istiod }} component",
			in: tmplValue{
				Name:  "istio",
				Proxy: "envoy",
			},
			want: "",
			err:  errors.New(""),
		},
	}
	for _, tt := range tests {
		t.Run(tt.desc, func(t *testing.T) {
			got, err := RenderTemplate(tt.template, tt.in)
			if got != tt.want {
				t.Errorf("%s: got :%v, wanted output: %v", tt.desc, got, tt.want)
			}

			if (err == nil && tt.err != nil) || (err != nil && tt.err == nil) {
				t.Errorf("%s: got error :%v, wanted error: %v", tt.desc, err, tt.err)
			}
		})
	}
}
