/*
Copyright 2015 The Kubernetes 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 helper

import (
	"fmt"
	"reflect"
	"testing"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/labels"
)

func TestIsNativeResource(t *testing.T) {
	testCases := []struct {
		resourceName v1.ResourceName
		expectVal    bool
	}{
		{
			resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo",
			expectVal:    true,
		},
		{
			resourceName: "kubernetes.io/resource-foo",
			expectVal:    true,
		},
		{
			resourceName: "foo",
			expectVal:    true,
		},
		{
			resourceName: "a/b",
			expectVal:    false,
		},
		{
			resourceName: "",
			expectVal:    true,
		},
	}

	for _, tc := range testCases {
		t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) {
			t.Parallel()
			v := IsNativeResource(tc.resourceName)
			if v != tc.expectVal {
				t.Errorf("Got %v but expected %v", v, tc.expectVal)
			}
		})
	}
}

func TestHugePageSizeFromResourceName(t *testing.T) {
	expected100m, _ := resource.ParseQuantity("100m")
	testCases := []struct {
		resourceName v1.ResourceName
		expectVal    resource.Quantity
		expectErr    bool
	}{
		{
			resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo",
			expectVal:    resource.Quantity{},
			expectErr:    true,
		},
		{
			resourceName: "hugepages-",
			expectVal:    resource.Quantity{},
			expectErr:    true,
		},
		{
			resourceName: "hugepages-100m",
			expectVal:    expected100m,
			expectErr:    false,
		},
		{
			resourceName: "",
			expectVal:    resource.Quantity{},
			expectErr:    true,
		},
	}

	for i, tc := range testCases {
		t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) {
			t.Parallel()
			v, err := HugePageSizeFromResourceName(tc.resourceName)
			if err == nil && tc.expectErr {
				t.Errorf("[%v]expected error but got none.", i)
			}
			if err != nil && !tc.expectErr {
				t.Errorf("[%v]did not expect error but got: %v", i, err)
			}
			if v != tc.expectVal {
				t.Errorf("Got %v but expected %v", v, tc.expectVal)
			}
		})
	}
}

func TestHugePageSizeFromMedium(t *testing.T) {
	testCases := []struct {
		description string
		medium      v1.StorageMedium
		expectVal   resource.Quantity
		expectErr   bool
	}{
		{
			description: "Invalid hugepages medium",
			medium:      "Memory",
			expectVal:   resource.Quantity{},
			expectErr:   true,
		},
		{
			description: "Invalid hugepages medium",
			medium:      "Memory",
			expectVal:   resource.Quantity{},
			expectErr:   true,
		},
		{
			description: "Invalid: HugePages without size",
			medium:      "HugePages",
			expectVal:   resource.Quantity{},
			expectErr:   true,
		},
		{
			description: "Invalid: HugePages without size",
			medium:      "HugePages",
			expectVal:   resource.Quantity{},
			expectErr:   true,
		},
		{
			description: "Valid: HugePages-1Gi",
			medium:      "HugePages-1Gi",
			expectVal:   resource.MustParse("1Gi"),
			expectErr:   false,
		},
		{
			description: "Valid: HugePages-2Mi",
			medium:      "HugePages-2Mi",
			expectVal:   resource.MustParse("2Mi"),
			expectErr:   false,
		},
		{
			description: "Valid: HugePages-64Ki",
			medium:      "HugePages-64Ki",
			expectVal:   resource.MustParse("64Ki"),
			expectErr:   false,
		},
	}
	for i, tc := range testCases {
		t.Run(tc.description, func(t *testing.T) {
			t.Parallel()
			v, err := HugePageSizeFromMedium(tc.medium)
			if err == nil && tc.expectErr {
				t.Errorf("[%v]expected error but got none.", i)
			}
			if err != nil && !tc.expectErr {
				t.Errorf("[%v]did not expect error but got: %v", i, err)
			}
			if v != tc.expectVal {
				t.Errorf("Got %v but expected %v", v, tc.expectVal)
			}
		})
	}
}

func TestIsOvercommitAllowed(t *testing.T) {
	testCases := []struct {
		resourceName v1.ResourceName
		expectVal    bool
	}{
		{
			resourceName: "pod.alpha.kubernetes.io/opaque-int-resource-foo",
			expectVal:    true,
		},
		{
			resourceName: "kubernetes.io/resource-foo",
			expectVal:    true,
		},
		{
			resourceName: "hugepages-100m",
			expectVal:    false,
		},
		{
			resourceName: "",
			expectVal:    true,
		},
	}

	for _, tc := range testCases {
		t.Run(fmt.Sprintf("resourceName input=%s, expected value=%v", tc.resourceName, tc.expectVal), func(t *testing.T) {
			t.Parallel()
			v := IsOvercommitAllowed(tc.resourceName)
			if v != tc.expectVal {
				t.Errorf("Got %v but expected %v", v, tc.expectVal)
			}
		})
	}
}

func TestGetAccessModesFromString(t *testing.T) {
	modes := GetAccessModesFromString("ROX")
	if !ContainsAccessMode(modes, v1.ReadOnlyMany) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
	}

	modes = GetAccessModesFromString("ROX,RWX")
	if !ContainsAccessMode(modes, v1.ReadOnlyMany) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
	}
	if !ContainsAccessMode(modes, v1.ReadWriteMany) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
	}

	modes = GetAccessModesFromString("RWO,ROX,RWX")
	if !ContainsAccessMode(modes, v1.ReadWriteOnce) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOnce, modes)
	}
	if !ContainsAccessMode(modes, v1.ReadOnlyMany) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
	}
	if !ContainsAccessMode(modes, v1.ReadWriteMany) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
	}

	modes = GetAccessModesFromString("RWO,ROX,RWX,RWOP")
	if !ContainsAccessMode(modes, v1.ReadWriteOnce) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOnce, modes)
	}
	if !ContainsAccessMode(modes, v1.ReadOnlyMany) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadOnlyMany, modes)
	}
	if !ContainsAccessMode(modes, v1.ReadWriteMany) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteMany, modes)
	}
	if !ContainsAccessMode(modes, v1.ReadWriteOncePod) {
		t.Errorf("Expected mode %s, but got %+v", v1.ReadWriteOncePod, modes)
	}
}

func TestRemoveDuplicateAccessModes(t *testing.T) {
	modes := []v1.PersistentVolumeAccessMode{
		v1.ReadWriteOnce, v1.ReadOnlyMany, v1.ReadOnlyMany, v1.ReadOnlyMany,
	}
	modes = removeDuplicateAccessModes(modes)
	if len(modes) != 2 {
		t.Errorf("Expected 2 distinct modes in set but found %v", len(modes))
	}
}

func TestTopologySelectorRequirementsAsSelector(t *testing.T) {
	mustParse := func(s string) labels.Selector {
		out, e := labels.Parse(s)
		if e != nil {
			panic(e)
		}
		return out
	}
	tc := []struct {
		in        []v1.TopologySelectorLabelRequirement
		out       labels.Selector
		expectErr bool
	}{
		{in: nil, out: labels.Nothing()},
		{in: []v1.TopologySelectorLabelRequirement{}, out: labels.Nothing()},
		{
			in: []v1.TopologySelectorLabelRequirement{{
				Key:    "foo",
				Values: []string{"bar", "baz"},
			}},
			out: mustParse("foo in (baz,bar)"),
		},
		{
			in: []v1.TopologySelectorLabelRequirement{{
				Key:    "foo",
				Values: []string{},
			}},
			expectErr: true,
		},
		{
			in: []v1.TopologySelectorLabelRequirement{
				{
					Key:    "foo",
					Values: []string{"bar", "baz"},
				},
				{
					Key:    "invalid",
					Values: []string{},
				},
			},
			expectErr: true,
		},
		{
			in: []v1.TopologySelectorLabelRequirement{{
				Key:    "/invalidkey",
				Values: []string{"bar", "baz"},
			}},
			expectErr: true,
		},
	}

	for i, tc := range tc {
		out, err := TopologySelectorRequirementsAsSelector(tc.in)
		if err == nil && tc.expectErr {
			t.Errorf("[%v]expected error but got none.", i)
		}
		if err != nil && !tc.expectErr {
			t.Errorf("[%v]did not expect error but got: %v", i, err)
		}
		if !reflect.DeepEqual(out, tc.out) {
			t.Errorf("[%v]expected:\n\t%+v\nbut got:\n\t%+v", i, tc.out, out)
		}
	}
}

func TestMatchTopologySelectorTerms(t *testing.T) {
	type args struct {
		topologySelectorTerms []v1.TopologySelectorTerm
		labels                labels.Set
	}

	tests := []struct {
		name string
		args args
		want bool
	}{
		{
			name: "nil term list",
			args: args{
				topologySelectorTerms: nil,
				labels:                nil,
			},
			want: true,
		},
		{
			name: "nil term",
			args: args{
				topologySelectorTerms: []v1.TopologySelectorTerm{
					{
						MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{},
					},
				},
				labels: nil,
			},
			want: false,
		},
		{
			name: "label matches MatchLabelExpressions terms",
			args: args{
				topologySelectorTerms: []v1.TopologySelectorTerm{
					{
						MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{
							Key:    "label_1",
							Values: []string{"label_1_val"},
						}},
					},
				},
				labels: map[string]string{"label_1": "label_1_val"},
			},
			want: true,
		},
		{
			name: "label does not match MatchLabelExpressions terms",
			args: args{
				topologySelectorTerms: []v1.TopologySelectorTerm{
					{
						MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{
							Key:    "label_1",
							Values: []string{"label_1_val"},
						}},
					},
				},
				labels: map[string]string{"label_1": "label_1_val-failed"},
			},
			want: false,
		},
		{
			name: "multi-values in one requirement, one matched",
			args: args{
				topologySelectorTerms: []v1.TopologySelectorTerm{
					{
						MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{
							Key:    "label_1",
							Values: []string{"label_1_val1", "label_1_val2"},
						}},
					},
				},
				labels: map[string]string{"label_1": "label_1_val2"},
			},
			want: true,
		},
		{
			name: "multi-terms was set, one matched",
			args: args{
				topologySelectorTerms: []v1.TopologySelectorTerm{
					{
						MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{
							Key:    "label_1",
							Values: []string{"label_1_val"},
						}},
					},
					{
						MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{{
							Key:    "label_2",
							Values: []string{"label_2_val"},
						}},
					},
				},
				labels: map[string]string{
					"label_2": "label_2_val",
				},
			},
			want: true,
		},
		{
			name: "multi-requirement in one term, fully matched",
			args: args{
				topologySelectorTerms: []v1.TopologySelectorTerm{
					{
						MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
							{
								Key:    "label_1",
								Values: []string{"label_1_val"},
							},
							{
								Key:    "label_2",
								Values: []string{"label_2_val"},
							},
						},
					},
				},
				labels: map[string]string{
					"label_1": "label_1_val",
					"label_2": "label_2_val",
				},
			},
			want: true,
		},
		{
			name: "multi-requirement in one term, partial matched",
			args: args{
				topologySelectorTerms: []v1.TopologySelectorTerm{
					{
						MatchLabelExpressions: []v1.TopologySelectorLabelRequirement{
							{
								Key:    "label_1",
								Values: []string{"label_1_val"},
							},
							{
								Key:    "label_2",
								Values: []string{"label_2_val"},
							},
						},
					},
				},
				labels: map[string]string{
					"label_1": "label_1_val-failed",
					"label_2": "label_2_val",
				},
			},
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := MatchTopologySelectorTerms(tt.args.topologySelectorTerms, tt.args.labels); got != tt.want {
				t.Errorf("MatchTopologySelectorTermsORed() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestNodeSelectorRequirementKeyExistsInNodeSelectorTerms(t *testing.T) {
	tests := []struct {
		name   string
		reqs   []v1.NodeSelectorRequirement
		terms  []v1.NodeSelectorTerm
		exists bool
	}{
		{
			name:   "empty set of keys in empty set of terms",
			reqs:   []v1.NodeSelectorRequirement{},
			terms:  []v1.NodeSelectorTerm{},
			exists: false,
		},
		{
			name: "key existence in terms with all keys specified",
			reqs: []v1.NodeSelectorRequirement{
				{
					Key:      "key1",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value1"},
				},
				{
					Key:      "key2",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value2"},
				},
			},
			terms: []v1.NodeSelectorTerm{
				{
					MatchExpressions: []v1.NodeSelectorRequirement{
						{
							Key:      "key2",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value2"},
						},
						{
							Key:      "key3",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value3"},
						},
					},
				},
				{
					MatchExpressions: []v1.NodeSelectorRequirement{
						{
							Key:      "key1",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value11, test-value12"},
						},
						{
							Key:      "key4",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value41, test-value42"},
						},
					},
				},
			},
			exists: true,
		},
		{
			name: "key existence in terms with one of the keys specfied",
			reqs: []v1.NodeSelectorRequirement{
				{
					Key:      "key1",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value1"},
				},
				{
					Key:      "key2",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value2"},
				},
				{
					Key:      "key3",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value3"},
				},
				{
					Key:      "key6",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value6"},
				},
			},
			terms: []v1.NodeSelectorTerm{
				{
					MatchExpressions: []v1.NodeSelectorRequirement{
						{
							Key:      "key2",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value2"},
						}, {
							Key:      "key4",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value4"},
						},
					},
				},
				{
					MatchExpressions: []v1.NodeSelectorRequirement{
						{
							Key:      "key5",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value5"},
						},
					},
				},
			},
			exists: true,
		},
		{
			name: "key existence in terms without any of the keys specified",
			reqs: []v1.NodeSelectorRequirement{
				{
					Key:      "key2",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value2"},
				},
				{
					Key:      "key3",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value3"},
				},
			},
			terms: []v1.NodeSelectorTerm{
				{
					MatchExpressions: []v1.NodeSelectorRequirement{
						{
							Key:      "key4",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value"},
						},
						{
							Key:      "key5",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value"},
						},
					},
				},
				{
					MatchExpressions: []v1.NodeSelectorRequirement{
						{
							Key:      "key6",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value"},
						},
					},
				},
				{
					MatchExpressions: []v1.NodeSelectorRequirement{
						{
							Key:      "key7",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value"},
						},
						{
							Key:      "key8",
							Operator: v1.NodeSelectorOpIn,
							Values:   []string{"test-value"},
						},
					},
				},
			},
			exists: false,
		},
		{
			name: "key existence in empty set of terms",
			reqs: []v1.NodeSelectorRequirement{
				{
					Key:      "key2",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value2"},
				},
				{
					Key:      "key3",
					Operator: v1.NodeSelectorOpIn,
					Values:   []string{"test-value3"},
				},
			},
			terms:  []v1.NodeSelectorTerm{},
			exists: false,
		},
	}
	for _, test := range tests {
		keyExists := NodeSelectorRequirementKeysExistInNodeSelectorTerms(test.reqs, test.terms)
		if test.exists != keyExists {
			t.Errorf("test %s failed. Expected %v but got %v", test.name, test.exists, keyExists)
		}
	}
}

func TestHugePageUnitSizeFromByteSize(t *testing.T) {
	tests := []struct {
		size     int64
		expected string
		wantErr  bool
	}{
		{
			size:     1024,
			expected: "1KB",
			wantErr:  false,
		},
		{
			size:     33554432,
			expected: "32MB",
			wantErr:  false,
		},
		{
			size:     3221225472,
			expected: "3GB",
			wantErr:  false,
		},
		{
			size:     1024 * 1024 * 1023 * 3,
			expected: "3069MB",
			wantErr:  true,
		},
	}
	for _, test := range tests {
		size := test.size
		result, err := HugePageUnitSizeFromByteSize(size)
		if err != nil {
			if test.wantErr {
				t.Logf("HugePageUnitSizeFromByteSize() expected error = %v", err)
			} else {
				t.Errorf("HugePageUnitSizeFromByteSize() error = %v, wantErr %v", err, test.wantErr)
			}
			continue
		}
		if test.expected != result {
			t.Errorf("HugePageUnitSizeFromByteSize() expected %v but got %v", test.expected, result)
		}
	}
}

func TestLoadBalancerStatusEqual(t *testing.T) {

	testCases := []struct {
		left      *v1.LoadBalancerStatus
		right     *v1.LoadBalancerStatus
		name      string
		expectVal bool
	}{{
		name: "left equals right",
		left: &v1.LoadBalancerStatus{
			Ingress: []v1.LoadBalancerIngress{{
				IP:       "1.1.1.1",
				Hostname: "host1",
			}},
		},
		right: &v1.LoadBalancerStatus{
			Ingress: []v1.LoadBalancerIngress{{
				IP:       "1.1.1.1",
				Hostname: "host1",
			}},
		},
		expectVal: true,
	}, {
		name: "length of LoadBalancerIngress slice is not equal",
		left: &v1.LoadBalancerStatus{
			Ingress: []v1.LoadBalancerIngress{{
				IP:       "1.1.1.1",
				Hostname: "host1",
			}, {
				IP:       "1.1.1.2",
				Hostname: "host1",
			}},
		},
		right: &v1.LoadBalancerStatus{
			Ingress: []v1.LoadBalancerIngress{{
				IP:       "1.1.1.1",
				Hostname: "host1",
			}},
		},
		expectVal: false,
	}, {
		name: "LoadBalancerIngress ip is not equal",
		left: &v1.LoadBalancerStatus{
			Ingress: []v1.LoadBalancerIngress{{
				IP:       "1.1.1.2",
				Hostname: "host1",
			}},
		},
		right: &v1.LoadBalancerStatus{
			Ingress: []v1.LoadBalancerIngress{{
				IP:       "1.1.1.1",
				Hostname: "host1",
			}},
		},
		expectVal: false,
	}, {
		name: "LoadBalancerIngress hostname is not equal",
		left: &v1.LoadBalancerStatus{
			Ingress: []v1.LoadBalancerIngress{{
				IP:       "1.1.1.1",
				Hostname: "host2",
			}},
		},
		right: &v1.LoadBalancerStatus{
			Ingress: []v1.LoadBalancerIngress{{
				IP:       "1.1.1.1",
				Hostname: "host1",
			}},
		},
		expectVal: false,
	}}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			v := LoadBalancerStatusEqual(tc.left, tc.right)
			if v != tc.expectVal {
				t.Errorf("test %s failed. left input=%v, right input=%v, Got %v but expected %v",
					tc.name, tc.left, tc.right, v, tc.expectVal)
			}
		})
	}
}
