// Licensed Materials - Property of IBM
// (c) Copyright IBM Corporation 2018, 2019. All Rights Reserved.
// Note to U.S. Government Users Restricted Rights:
// Use, duplication or disclosure restricted by GSA ADP Schedule
// Contract with IBM Corp.

// Package policy handles policy controller logic
package policy

import (
	"reflect"
	"testing"

	yaml "gopkg.in/yaml.v2"
	appsv1 "k8s.io/api/apps/v1"
	"k8s.io/apimachinery/pkg/api/resource"

	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var testDeployment = &appsv1.Deployment{
	ObjectMeta: metav1.ObjectMeta{
		Name:      "deployment",
		Namespace: "default",
		Labels: map[string]string{
			"department": "finance",
		},
	},
	Spec: appsv1.DeploymentSpec{
		Replicas: func(i int32) *int32 { return &i }(3),
		Selector: &metav1.LabelSelector{
			MatchLabels: map[string]string{"hmc": "true"},
		},
		Template: corev1.PodTemplateSpec{
			ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"hmc": "true"}},
			Spec: corev1.PodSpec{
				Containers: []corev1.Container{
					{
						Name:  "nginx",
						Image: "nginx",
					},
				},
			},
		},
	},
}

var testPodRequests = &corev1.Pod{
	ObjectMeta: metav1.ObjectMeta{
		Name:      "requests",
		Namespace: "default",
	},
	Spec: corev1.PodSpec{
		Containers: []corev1.Container{
			{
				Name:            "web",
				Image:           "nginx:1.12",
				ImagePullPolicy: corev1.PullIfNotPresent,
				Ports: []corev1.ContainerPort{
					{
						Name:          "http",
						Protocol:      corev1.ProtocolTCP,
						ContainerPort: 80,
					},
				},
				Resources: corev1.ResourceRequirements{
					Requests: corev1.ResourceList{
						corev1.ResourceCPU:    resource.MustParse("100m"),
						corev1.ResourceMemory: resource.MustParse("1Gi"),
					},
				},
			},
		},
	},
}

var testPodLimits = &corev1.Pod{
	ObjectMeta: metav1.ObjectMeta{
		Name:      "limits",
		Namespace: "default",
	},
	Spec: corev1.PodSpec{
		Containers: []corev1.Container{
			{
				Name:            "web",
				Image:           "nginx:1.12",
				ImagePullPolicy: corev1.PullIfNotPresent,
				Ports: []corev1.ContainerPort{
					{
						Name:          "http",
						Protocol:      corev1.ProtocolTCP,
						ContainerPort: 80,
					},
				},
				Resources: corev1.ResourceRequirements{
					Limits: corev1.ResourceList{
						corev1.ResourceCPU:    resource.MustParse("100m"),
						corev1.ResourceMemory: resource.MustParse("1Gi"),
					},
				},
			},
		},
	},
}

var testPodRequestsAndLimits = &corev1.Pod{
	ObjectMeta: metav1.ObjectMeta{
		Name:      "reqAndLimits",
		Namespace: "default",
	},
	Spec: corev1.PodSpec{
		Containers: []corev1.Container{
			{
				Name:            "web",
				Image:           "nginx:1.12",
				ImagePullPolicy: corev1.PullIfNotPresent,
				Ports: []corev1.ContainerPort{
					{
						Name:          "http",
						Protocol:      corev1.ProtocolTCP,
						ContainerPort: 80,
					},
				},
				Resources: corev1.ResourceRequirements{
					Requests: corev1.ResourceList{
						corev1.ResourceCPU:    resource.MustParse("100m"),
						corev1.ResourceMemory: resource.MustParse("1Gi"),
					},
					Limits: corev1.ResourceList{
						corev1.ResourceCPU:    resource.MustParse("200m"),
						corev1.ResourceMemory: resource.MustParse("2Gi"),
					},
				},
			},
		},
	},
}

var testPodNoResourcesNoImg = &corev1.Pod{
	ObjectMeta: metav1.ObjectMeta{
		Name:      "noResource",
		Namespace: "default",
		Labels: map[string]string{
			"hcm": "true",
		},
	},
	Spec: corev1.PodSpec{
		Containers: []corev1.Container{
			{
				Name:  "web",
				Image: "nginx:1.12",
				Ports: []corev1.ContainerPort{
					{
						Name:          "http",
						Protocol:      corev1.ProtocolTCP,
						ContainerPort: 80,
					},
				},
			},
		},
	},
}

var testRole = &rbacv1.Role{
	ObjectMeta: metav1.ObjectMeta{
		Namespace: "namespace",
		Name:      "name",
		Labels: map[string]string{
			"hcm": "true",
		},
	},
}

func TestCompareAttr(t *testing.T) {
	var s *metav1.LabelSelector
	var res string
	m := make(map[string]interface{})

	// testing requests
	out, err := yaml.Marshal(testPodRequests)
	if err != nil {
		panic(err)
	}
	err = yaml.Unmarshal(out, &m)
	if err != nil {
		panic(err)
	}

	qry, _ := parseQuery("corev1.pod.spec.containers.resources.requests.cpu", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.requests.memory", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.imagepullpolicy: ifnotpresent", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.imagepullpolicy: always", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute initialized but we have a value mismatch. desired = `always`, actual = `ifnotpresent`", res)

	// testing limits
	out, err = yaml.Marshal(testPodLimits)
	if err != nil {
		panic(err)
	}
	err = yaml.Unmarshal(out, &m)
	if err != nil {
		panic(err)
	}
	qry, _ = parseQuery("corev1.pod.spec.containers.resources.limits.cpu", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.limits.memory", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	// testing requests and limits
	out, err = yaml.Marshal(testPodRequestsAndLimits)
	if err != nil {
		panic(err)
	}
	err = yaml.Unmarshal(out, &m)
	if err != nil {
		panic(err)
	}
	qry, _ = parseQuery("corev1.pod.spec.containers.resources.limits.cpu", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.limits.memory", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.requests.cpu", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.requests.memory", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.requests.foo", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `foo` is missing", res)

	// testing absence of requests and limits and imagepullpolicy
	out, err = yaml.Marshal(testPodNoResourcesNoImg)
	if err != nil {
		panic(err)
	}
	err = yaml.Unmarshal(out, &m)
	if err != nil {
		panic(err)
	}
	qry, _ = parseQuery("corev1.pod.spec.containers.resources.limits.cpu", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `cpu` is missing", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.limits.memory", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `memory` is missing", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.requests.cpu", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `cpu` is missing", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.requests.memory", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `memory` is missing", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.imagepullpolicy", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `imagepullpolicy` value is missing", res)

	//testing other pod attributes
	qry, _ = parseQuery("corev1.pod.foo", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `foo` is missing", res)

	qry, _ = parseQuery("corev1.pod.spec.foo", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `foo` is missing", res)

	qry, _ = parseQuery("corev1.pod.objectmeta", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.objectmeta.labels", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.objectmeta.labels.hcm: true", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.objectmeta.labels.hcm: bar", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute initialized but we have a value mismatch. desired = `bar`, actual = `true`", res)

	qry, _ = parseQuery("corev1.pod.objectmeta.labels.foo", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `foo` is missing", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.ports", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("corev1.pod.spec.containers.ports.containerport", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	//testing roles
	out, err = yaml.Marshal(testRole)
	if err != nil {
		panic(err)
	}
	err = yaml.Unmarshal(out, &m)
	if err != nil {
		panic(err)
	}
	qry, _ = parseQuery("rbacv1.role.objectmeta.labels.foo", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `foo` is missing", res)

	//testing deployment
	out, err = yaml.Marshal(testDeployment)
	if err != nil {
		panic(err)
	}
	err = yaml.Unmarshal(out, &m)
	if err != nil {
		panic(err)
	}
	qry, _ = parseQuery("appsv1.deployment.objectmeta.labels.hcm", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute `hcm` is missing", res)

	qry, _ = parseQuery("appsv1.deployment.objectmeta.labels.department", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

	qry, _ = parseQuery("appsv1.deployment.spec.replicas", s)
	res, _ = compareAttr(qry, m)
	assert.Equal(t, "attribute exist and correctly initialized", res)

}

func TestParseQuery(t *testing.T) {
	s := &metav1.LabelSelector{}
	qry, _ := parseQuery("corev1.pod.spec.containers.resources.requests.cpu", s)
	assert.Equal(t, "corev1", qry.apiVersion)
	assert.Equal(t, "pod", qry.kind)
	assert.Equal(t, []string{"spec", "containers", "resources", "requests", "cpu"}, qry.path)
	assert.Equal(t, "cpu", qry.attribute)
	assert.Equal(t, "", qry.desiredValue)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.requests.memory: 500Mi  ", s)
	assert.Equal(t, "corev1", qry.apiVersion)
	assert.Equal(t, "pod", qry.kind)
	assert.Equal(t, []string{"spec", "containers", "resources", "requests", "memory"}, qry.path)
	assert.Equal(t, "memory", qry.attribute)
	assert.Equal(t, "500mi", qry.desiredValue)

	qry, _ = parseQuery("corev1.pod.spec.containers.resources.requests.memory: 500Mi  ", s)
	assert.Equal(t, "corev1", qry.apiVersion)
	assert.Equal(t, "pod", qry.kind)
	assert.Equal(t, []string{"spec", "containers", "resources", "requests", "memory"}, qry.path)
	assert.Equal(t, "memory", qry.attribute)
	assert.Equal(t, "500mi", qry.desiredValue)

	qry, _ = parseQuery("corev1.pod.spec.containers.imagePullPolicy: Always", s)
	assert.Equal(t, "imagepullpolicy", qry.attribute)
	assert.Equal(t, "always", qry.desiredValue)

	qry, _ = parseQuery("corev1.pod", s)
	assert.Equal(t, []string(nil), qry.path)
	assert.Equal(t, "", qry.attribute)
	assert.Equal(t, "", qry.desiredValue)

	qry, _ = parseQuery("corev1.pod.spec.  containers  .  imagePullPolicy : Always  ", s)
	assert.Equal(t, []string{"spec", "containers", "imagepullpolicy"}, qry.path)
	assert.Equal(t, "imagepullpolicy", qry.attribute)
	assert.Equal(t, "always", qry.desiredValue)
}

func TestFindAttrPattern(t *testing.T) {

	//testing PREFIX
	actualResult := FindAttrPattern("Hello*", "Hello-World")
	expectedResult := true

	if !reflect.DeepEqual(actualResult, expectedResult) {
		t.Fatalf("Expected %v but got %v", expectedResult, actualResult)
	}

	//testing SUFFIX
	actualResult = FindAttrPattern("*Hello", "World-Hello")
	expectedResult = true

	if !reflect.DeepEqual(actualResult, expectedResult) {
		t.Fatalf("Expected %v but got %v", expectedResult, actualResult)
	}

	//testing if it CONTAINS the pattern
	actualResult = FindAttrPattern("*Hello*", "Hello-World")
	expectedResult = true

	if !reflect.DeepEqual(actualResult, expectedResult) {
		t.Fatalf("Expected %v but got %v", expectedResult, actualResult)
	}

	//testing if it does NOT contain the pattern
	actualResult = FindAttrPattern("*xxx*", "Hello-World")
	expectedResult = false

	if !reflect.DeepEqual(actualResult, expectedResult) {
		t.Fatalf("Expected %v but got %v", expectedResult, actualResult)
	}

	//testing if it  contains the EXACT pattern
	actualResult = FindAttrPattern("Hello-World", "Hello-World")
	expectedResult = true

	if !reflect.DeepEqual(actualResult, expectedResult) {
		t.Fatalf("Expected %v but got %v", expectedResult, actualResult)
	}

	//testing corner case
	actualResult = FindAttrPattern("*ku*be", "Hello-World")
	expectedResult = false

	if !reflect.DeepEqual(actualResult, expectedResult) {
		t.Fatalf("Expected %v but got %v", expectedResult, actualResult)
	}

	//testing corner case
	actualResult = FindAttrPattern("*", "Hello-World")
	expectedResult = true

	if !reflect.DeepEqual(actualResult, expectedResult) {
		t.Fatalf("Expected %v but got %v", expectedResult, actualResult)
	}

	//testing corner case
	actualResult = FindAttrPattern("", "Hello-World")
	expectedResult = false

	if !reflect.DeepEqual(actualResult, expectedResult) {
		t.Fatalf("Expected %v but got %v", expectedResult, actualResult)
	}

}
