// 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.
// Copyright (c) 2020 Red Hat, Inc.

// Package policy handles policy controller glogic
package policy

import (
	"encoding/json"
	"fmt"
	"reflect"
	"sort"
	"strings"
	"sync"
	"time"

	"github.com/golang/glog"

	policyv1alpha1 "github.com/open-cluster-management/hcm-compliance/pkg/apis/policy/v1alpha1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"k8s.io/client-go/dynamic"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/restmapper"
	"k8s.io/client-go/tools/record"
)

var eventNormal = "Normal"
var eventWarning = "Warning"

func handlePolicyObjects(policyT *policyv1alpha1.PolicyTemplate, namespace string, index int, policy *policyv1alpha1.Policy, clientset *kubernetes.Clientset, config *rest.Config) {
	namespaced := true
	updateNeeded := false

	dd := clientset.Discovery()
	apigroups, err := restmapper.GetAPIGroupResources(dd)
	if err != nil {
		glog.Fatal(err)
	}

	restmapper := restmapper.NewDiscoveryRESTMapper(apigroups)
	//ext := runtime.RawExtension{}
	ext := policyT.ObjectDefinition
	glog.V(9).Infof("reading raw object: %v", string(ext.Raw))
	versions := &runtime.VersionedObjects{}
	_, gvk, err := unstructured.UnstructuredJSONScheme.Decode(ext.Raw, nil, versions)
	if err != nil {
		decodeErr := fmt.Sprintf("Decoding error, please check your policy file! Aborting handling the object template at index [%v] in policy `%v` with error = `%v`", index, policy.Name, err)
		glog.Errorf(decodeErr)
		policy.Status.Message = decodeErr
		updatePolicy(policy, 0)
		return
	}
	mapping, err := restmapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	if err != nil {
		message := fmt.Sprintf("mapping error from raw object: `%v`", err)
		prefix := "no matches for kind \""
		startIdx := strings.Index(err.Error(), prefix)
		if startIdx == -1 {
			glog.Errorf(message, err)
		} else {
			afterPrefix := err.Error()[(startIdx + len(prefix)):len(err.Error())]
			kind := afterPrefix[0:(strings.Index(afterPrefix, "\" "))]
			message = "couldn't find mapping resource with kind " + kind + ", please check if you have corresponding policy controller deployed"
			glog.Errorf(message)
		}
		cond := &policyv1alpha1.Condition{
			Type:               "violation",
			Status:             corev1.ConditionFalse,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s creation error",
			Message:            message,
		}
		if policyT.Status.ComplianceState != policyv1alpha1.NonCompliant {
			updateNeeded = true
		}
		policyT.Status.ComplianceState = policyv1alpha1.NonCompliant
		policyT.Status.Conditions = AppendCondition(policyT.Status.Conditions, cond, gvk.GroupKind().Kind, false)
		if updateNeeded {
			recorder.Event(policy, eventWarning, cond.Reason, cond.Message)
			addForUpdate(policy)
		}
		return
	}
	glog.V(9).Infof("mapping found from raw object: %v", mapping)

	restconfig := config
	restconfig.GroupVersion = &schema.GroupVersion{
		Group:   mapping.GroupVersionKind.Group,
		Version: mapping.GroupVersionKind.Version,
	}
	dclient, err := dynamic.NewForConfig(restconfig)
	if err != nil {
		glog.Fatal(err)
	}

	apiresourcelist, err := dd.ServerResources()
	if err != nil {
		glog.Fatal(err)
	}

	rsrc := mapping.Resource
	for _, apiresourcegroup := range apiresourcelist {
		if apiresourcegroup.GroupVersion == join(mapping.GroupVersionKind.Group, "/", mapping.GroupVersionKind.Version) {
			for _, apiresource := range apiresourcegroup.APIResources {
				if apiresource.Name == mapping.Resource.Resource && apiresource.Kind == mapping.GroupVersionKind.Kind {
					rsrc = mapping.Resource
					namespaced = apiresource.Namespaced
					glog.V(7).Infof("is raw object namespaced? %v", namespaced)
				}
			}
		}
	}
	var unstruct unstructured.Unstructured
	unstruct.Object = make(map[string]interface{})
	var blob interface{}
	if err = json.Unmarshal(ext.Raw, &blob); err != nil {
		glog.Fatal(err)
	}
	unstruct.Object = blob.(map[string]interface{}) //set object to the content of the blob after Unmarshalling

	name := ""
	if md, ok := unstruct.Object["metadata"]; ok {

		metadata := md.(map[string]interface{})
		if objectName, ok := metadata["name"]; ok {
			name = objectName.(string)
		}
	}

	exists := objectExists(namespaced, namespace, name, rsrc, unstruct, dclient)

	if !exists {
		// policy object doesn't exist let's create it
		created, err := createObject(namespaced, namespace, name, rsrc, unstruct, dclient, policy)
		if !created {
			message := fmt.Sprintf("%v `%v` is missing, and cannot be created, reason: `%v`", rsrc.Resource, name, err)
			cond := &policyv1alpha1.Condition{
				Type:               "violation",
				Status:             corev1.ConditionFalse,
				LastTransitionTime: metav1.Now(),
				Reason:             "K8s creation error",
				Message:            message,
			}
			if policyT.Status.ComplianceState != policyv1alpha1.NonCompliant {
				updateNeeded = true
				policyT.Status.ComplianceState = policyv1alpha1.NonCompliant
			}

			if !checkPolicyMessageSimilarity(policyT, cond) {
				policyT.Status.Conditions = AppendCondition(policyT.Status.Conditions, cond, rsrc.Resource, false)
				updateNeeded = true
			}
			if updateNeeded {
				addForUpdate(policy)
			}
		}
		if err != nil {
			glog.Errorf("error creating policy object `%v` from policy `%v`", name, policy.Name)

		}
	} else {
		updated, _, msg := updateTemplate(namespaced, namespace, name, "", rsrc, unstruct, dclient, unstruct.Object["kind"].(string), policy)
		if !updated && msg != "" {
			cond := &policyv1alpha1.Condition{
				Type:               "violation",
				Status:             corev1.ConditionFalse,
				LastTransitionTime: metav1.Now(),
				Reason:             "K8s update template error",
				Message:            msg,
			}
			if policyT.Status.ComplianceState != policyv1alpha1.NonCompliant {
				updateNeeded = true
				policyT.Status.ComplianceState = policyv1alpha1.NonCompliant
			}

			if !checkPolicyMessageSimilarity(policyT, cond) {
				policyT.Status.Conditions = AppendCondition(policyT.Status.Conditions, cond, rsrc.Resource, false)
				updateNeeded = true
			}
			if updateNeeded {
				addForUpdate(policy)
			}
			glog.Errorf(msg)
		}
	}
}

func handleObjects(objectT *policyv1alpha1.ObjectTemplate, namespace string, index int, policy *policyv1alpha1.Policy, clientset *kubernetes.Clientset, config *rest.Config, recorder record.EventRecorder) (objNameList []string, compliant bool, rsrcKind string) {
	updateNeeded := false
	namespaced := true
	dd := clientset.Discovery()
	apigroups, err := restmapper.GetAPIGroupResources(dd)
	if err != nil {
		glog.Fatal(err)
	}

	restmapper := restmapper.NewDiscoveryRESTMapper(apigroups)
	//ext := runtime.RawExtension{}
	ext := objectT.ObjectDefinition
	glog.V(9).Infof("reading raw object: %v", string(ext.Raw))
	versions := &runtime.VersionedObjects{}
	_, gvk, err := unstructured.UnstructuredJSONScheme.Decode(ext.Raw, nil, versions)
	if err != nil {
		decodeErr := fmt.Sprintf("Decoding error, please check your policy file! Aborting handling the object template at index [%v] in policy `%v` with error = `%v`", index, policy.Name, err)
		glog.Errorf(decodeErr)
		policy.Status.Message = decodeErr
		updatePolicy(policy, 0)
		return nil, false, ""
	}
	mapping, err := restmapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	mappingErrMsg := ""
	if err != nil {
		prefix := "no matches for kind \""
		startIdx := strings.Index(err.Error(), prefix)
		if startIdx == -1 {
			glog.Errorf("unidentified mapping error from raw object: `%v`", err)
		} else {
			afterPrefix := err.Error()[(startIdx + len(prefix)):len(err.Error())]
			kind := afterPrefix[0:(strings.Index(afterPrefix, "\" "))]
			mappingErrMsg = "couldn't find mapping resource with kind " + kind + ", please check if you have CRD deployed"
			glog.Errorf(mappingErrMsg)
		}
		errMsg := err.Error()
		if mappingErrMsg != "" {
			errMsg = mappingErrMsg
			cond := &policyv1alpha1.Condition{
				Type:               "violation",
				Status:             corev1.ConditionFalse,
				LastTransitionTime: metav1.Now(),
				Reason:             "K8s creation error",
				Message:            mappingErrMsg,
			}
			if objectT.Status.ComplianceState != policyv1alpha1.NonCompliant {
				updateNeeded = true
			}
			objectT.Status.ComplianceState = policyv1alpha1.NonCompliant

			if !checkMessageSimilarity(objectT, cond) {
				conditions := AppendCondition(objectT.Status.Conditions, cond, gvk.GroupKind().Kind, false)
				objectT.Status.Conditions = conditions
				updateNeeded = true
			}
		}
		if updateNeeded {
			recorder.Event(policy, eventWarning, fmt.Sprintf("policy: %s/%s", policy.GetName(), name), errMsg)
			addForUpdate(policy)
		}
		return nil, false, ""
	}
	glog.V(9).Infof("mapping found from raw object: %v", mapping)

	restconfig := config
	restconfig.GroupVersion = &schema.GroupVersion{
		Group:   mapping.GroupVersionKind.Group,
		Version: mapping.GroupVersionKind.Version,
	}
	dclient, err := dynamic.NewForConfig(restconfig)
	if err != nil {
		glog.Fatal(err)
	}

	apiresourcelist, err := dd.ServerResources()
	if err != nil {
		glog.Fatal(err)
	}

	rsrc := mapping.Resource
	for _, apiresourcegroup := range apiresourcelist {
		if apiresourcegroup.GroupVersion == join(mapping.GroupVersionKind.Group, "/", mapping.GroupVersionKind.Version) {
			for _, apiresource := range apiresourcegroup.APIResources {
				if apiresource.Name == mapping.Resource.Resource && apiresource.Kind == mapping.GroupVersionKind.Kind {
					rsrc = mapping.Resource
					namespaced = apiresource.Namespaced
					glog.V(7).Infof("is raw object namespaced? %v", namespaced)
				}
			}
		}
	}
	var unstruct unstructured.Unstructured
	unstruct.Object = make(map[string]interface{})
	var blob interface{}
	if err = json.Unmarshal(ext.Raw, &blob); err != nil {
		glog.Fatal(err)
	}
	unstruct.Object = blob.(map[string]interface{}) //set object to the content of the blob after Unmarshalling

	//namespace := "default"
	name := ""
	kind := ""
	named := false
	if md, ok := unstruct.Object["metadata"]; ok {

		metadata := md.(map[string]interface{})
		if objectName, ok := metadata["name"]; ok {
			name = objectName.(string)
			named = true
		}
		// override the namespace if specified in objectTemplates
		if objectns, ok := metadata["namespace"]; ok {
			glog.V(5).Infof("overriding the namespace as it is specified in objectTemplates...")
			namespace = objectns.(string)
		}

	}

	if objKind, ok := unstruct.Object["kind"]; ok {
		kind = objKind.(string)
	}

	exists := true
	objNames := []string{}
	remediation := policy.Spec.RemediationAction

	if named {
		exists = objectExists(namespaced, namespace, name, rsrc, unstruct, dclient)
		objNames = append(objNames, name)
	} else if kind != "" {
		objNames = append(objNames, getNamesOfKind(rsrc, namespaced, namespace, dclient)...)
		remediation = "inform"
		if len(objNames) == 0 {
			exists = false
		}
	}
	if len(objNames) == 1 {
		name = objNames[0]
		if !exists && strings.ToLower(string(objectT.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustHave)) {
			//it is a musthave and it does not exist, so it must be created
			if strings.ToLower(string(remediation)) == strings.ToLower(string(policyv1alpha1.Enforce)) {
				updateNeeded, err = handleMissingMustHave(objectT, remediation, namespaced, namespace, name, rsrc, unstruct, dclient)
				if err != nil {
					// violation created for handling error
					glog.Errorf("error handling a missing object `%v` that is a must have according to policy `%v`", name, policy.Name)
				}
			} else { //inform
				compliant = false
			}
		}
		if exists && strings.ToLower(string(objectT.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustNotHave)) {
			//it is a mustnothave but it exist, so it must be deleted
			if strings.ToLower(string(remediation)) == strings.ToLower(string(policyv1alpha1.Enforce)) {
				updateNeeded, err = handleExistsMustNotHave(objectT, remediation, namespaced, namespace, name, rsrc, dclient)
				if err != nil {
					glog.Errorf("error handling a existing object `%v` that is a must NOT have according to policy `%v`", name, policy.Name)
				}
			} else { //inform
				compliant = false
			}
		}
		if !exists && strings.ToLower(string(objectT.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustNotHave)) {
			//it is a must not have and it does not exist, so it is compliant
			updateNeeded = handleMissingMustNotHave(objectT, name, rsrc)
			compliant = true
		}
		if exists && strings.ToLower(string(objectT.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustHave)) {
			//it is a must have and it does exist, so it is compliant
			updateNeeded = handleExistsMustHave(objectT, name, rsrc)
			compliant = true
		}

		if exists {
			updated, throwSpecViolation, msg := updateTemplate(namespaced, namespace, name, remediation, rsrc, unstruct, dclient, unstruct.Object["kind"].(string), nil)
			if !updated && throwSpecViolation {
				compliant = false
			} else if !updated && msg != "" {
				cond := &policyv1alpha1.Condition{
					Type:               "violation",
					Status:             corev1.ConditionFalse,
					LastTransitionTime: metav1.Now(),
					Reason:             "K8s update template error",
					Message:            msg,
				}
				if objectT.Status.ComplianceState != policyv1alpha1.NonCompliant {
					updateNeeded = true
				}
				objectT.Status.ComplianceState = policyv1alpha1.NonCompliant

				if !checkMessageSimilarity(objectT, cond) {
					conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, false)
					objectT.Status.Conditions = conditions
					updateNeeded = true
				}
				glog.Errorf(msg)
			}
		}

		if strings.ToLower(string(remediation)) == strings.ToLower(string(policyv1alpha1.Inform)) {
			return objNames, compliant, rsrc.Resource
		}

		if updateNeeded {
			eventType := eventNormal
			if objectT.Status.ComplianceState == policyv1alpha1.NonCompliant {
				eventType = eventWarning
			}
			recorder.Event(policy, eventType, fmt.Sprintf("policy: %s/%s", policy.GetName(), name), fmt.Sprintf("%s; %s", objectT.Status.ComplianceState, objectT.Status.Conditions[0].Message))
			addForUpdate(policy)
		}
	} else {
		if !exists && strings.ToLower(string(objectT.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustHave)) {
			return objNames, false, rsrc.Resource
		}
		if exists && strings.ToLower(string(objectT.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustNotHave)) {
			return objNames, false, rsrc.Resource
		}
		if !exists && strings.ToLower(string(objectT.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustNotHave)) {
			return objNames, true, rsrc.Resource
		}
		if exists && strings.ToLower(string(objectT.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustHave)) {
			return objNames, true, rsrc.Resource
		}
	}
	return nil, compliant, ""
}

// merge merges the two JSON-marshalable values x1 and x2,
// preferring x1 over x2 except where x1 and x2 are
// JSON objects, in which case the keys from both objects
// are included and their values merged recursively.
//
// It returns an error if x1 or x2 cannot be JSON-marshaled.
func mergeSpecs(x1, x2 interface{}) (interface{}, error) {
	data1, err := json.Marshal(x1)
	if err != nil {
		return nil, err
	}
	data2, err := json.Marshal(x2)
	if err != nil {
		return nil, err
	}
	var j1 interface{}
	err = json.Unmarshal(data1, &j1)
	if err != nil {
		return nil, err
	}
	var j2 interface{}
	err = json.Unmarshal(data2, &j2)
	if err != nil {
		return nil, err
	}
	return mergeSpecsHelper(j1, j2), nil
}

func mergeSpecsHelper(x1, x2 interface{}) interface{} {
	switch x1 := x1.(type) {
	case map[string]interface{}:
		x2, ok := x2.(map[string]interface{})
		if !ok {
			return x1
		}
		for k, v2 := range x2 {
			if v1, ok := x1[k]; ok {
				x1[k] = mergeSpecsHelper(v1, v2)
			} else {
				x1[k] = v2
			}
		}
	case []interface{}:
		x2, ok := x2.([]interface{})
		if !ok {
			return x1
		}
		if len(x2) > 0 {
			_, ok := x2[0].(map[string]interface{})
			if ok {
				for idx, v2 := range x2 {
					v1 := x1[idx]
					x1[idx] = mergeSpecsHelper(v1, v2)
				}
			} else {
				return x1
			}
		} else {
			return x1
		}
	case nil:
		x2, ok := x2.(map[string]interface{})
		if ok {
			return x2
		}
	}
	return x1
}

func compareSpecs(newSpec map[string]interface{}, oldSpec map[string]interface{}) (updatedSpec map[string]interface{}, err error) {
	merged, err := mergeSpecs(newSpec, oldSpec)
	if err != nil {
		return merged.(map[string]interface{}), err
	}
	return merged.(map[string]interface{}), nil
}

func updateTemplate(
	namespaced bool, namespace string, name string, remediation policyv1alpha1.RemediationAction,
	rsrc schema.GroupVersionResource, unstruct unstructured.Unstructured, dclient dynamic.Interface,
	typeStr string, parent *policyv1alpha1.Policy) (success bool, throwSpecViolation bool, message string) {
	updateNeeded := false
	if namespaced {
		res := dclient.Resource(rsrc).Namespace(namespace)
		existingObj, err := res.Get(name, metav1.GetOptions{})
		if err != nil {
			glog.Errorf("object `%v` cannot be retrieved from the api server\n", name)
		} else {
			newObj := unstruct.Object["spec"]
			oldObj := existingObj.UnstructuredContent()["spec"]
			if newObj == nil || oldObj == nil {
				return false, false, ""
			}
			if parent != nil {
				// overwrite remediation from parent
				newObj.(map[string]interface{})["remediationAction"] = parent.Spec.RemediationAction
			}
			//merge changes into new spec
			newObj, err = compareSpecs(newObj.(map[string]interface{}), oldObj.(map[string]interface{}))
			if err != nil {
				message := fmt.Sprintf("Error merging changes into spec: %s", err)
				return false, false, message
			}
			//check if merged spec has changed
			nJSON, err := json.Marshal(newObj)
			if err != nil {
				message := fmt.Sprintf("Error converting updated spec to JSON: %s", err)
				return false, false, message
			}
			oJSON, err := json.Marshal(oldObj)
			if err != nil {
				message := fmt.Sprintf("Error converting updated spec to JSON: %s", err)
				return false, false, message
			}
			if !reflect.DeepEqual(nJSON, oJSON) {
				updateNeeded = true
			}
			mapMtx := sync.RWMutex{}
			mapMtx.Lock()
			existingObj.UnstructuredContent()["spec"] = newObj
			mapMtx.Unlock()
			if updateNeeded {
				if strings.ToLower(string(remediation)) == strings.ToLower(string(policyv1alpha1.Inform)) {
					return false, true, ""
				}
				//enforce
				glog.V(4).Infof("Updating %v template `%v`...", typeStr, name)
				_, err = res.Update(existingObj, metav1.UpdateOptions{})
				if errors.IsNotFound(err) {
					message := fmt.Sprintf("`%v` is not present and must be created", typeStr)
					return false, false, message
				}
				if err != nil {
					message := fmt.Sprintf("Error updating the object `%v`, the error is `%v`", name, err)
					return false, false, message
				}
				glog.V(4).Infof("Resource `%v` updated\n", name)
				return false, false, ""
			}
		}
	} else {
		res := dclient.Resource(rsrc)
		existingObj, err := res.Get(name, metav1.GetOptions{})
		if err != nil {
			glog.Errorf("object `%v` cannot be retrieved from the api server\n", name)
		} else {
			newObj := unstruct.Object["spec"]
			oldObj := existingObj.UnstructuredContent()["spec"]
			if newObj == nil || oldObj == nil {
				return false, false, ""
			}
			updateNeeded := !(reflect.DeepEqual(newObj, oldObj))
			oldMap := existingObj.UnstructuredContent()["metadata"].(map[string]interface{})
			resVer := oldMap["resourceVersion"]
			mapMtx := sync.RWMutex{}
			mapMtx.Lock()
			unstruct.Object["metadata"].(map[string]interface{})["resourceVersion"] = resVer
			mapMtx.Unlock()
			if updateNeeded {
				if strings.ToLower(string(remediation)) == strings.ToLower(string(policyv1alpha1.Inform)) {
					return false, true, ""
				}
				//enforce
				glog.V(4).Infof("Updating %v template `%v`...", typeStr, name)
				_, err = res.Update(&unstruct, metav1.UpdateOptions{})
				if errors.IsNotFound(err) {
					message := fmt.Sprintf("`%v` is not present and must be created", typeStr)
					return false, false, message
				}
				if err != nil {
					message := fmt.Sprintf("Error updating the object `%v`, the error is `%v`", name, err)
					return false, false, message
				}
				glog.V(4).Infof("Resource `%v` updated\n", name)
				return false, false, ""
			}
		}
	}
	return false, false, ""
}

func handleEvents(policyT *policyv1alpha1.PolicyTemplate, policy *policyv1alpha1.Policy,
	clientset *kubernetes.Clientset, events map[string][]corev1.Event) {
	updateNeeded := false
	dd := clientset.Discovery()
	apigroups, err := restmapper.GetAPIGroupResources(dd)
	if err != nil {
		glog.Fatal(err)
	}

	ext := policyT.ObjectDefinition
	versions := &runtime.VersionedObjects{}
	restmapper := restmapper.NewDiscoveryRESTMapper(apigroups)
	_, gvk, _ := unstructured.UnstructuredJSONScheme.Decode(ext.Raw, nil, versions)

	mapping, _ := restmapper.RESTMapping(gvk.GroupKind(), gvk.Version)

	var rsrc schema.GroupVersionResource
	if mapping != nil {
		rsrc = mapping.Resource
	}

	policyEvents := events[policy.Name]
	sort.Slice(policyEvents, func(i, j int) bool {
		return policyEvents[i].LastTimestamp.Time.After(policyEvents[j].LastTimestamp.Time)
	})

	if policyEvents != nil {
		targetEvent := policyEvents[0]
		policyReason := targetEvent.Reason
		s := strings.Split(policyReason, ":")
		if len(s) <= 1 {
			return
		}
		templateName := strings.Split(s[1], "/")[1]

		var unstruct unstructured.Unstructured
		unstruct.Object = make(map[string]interface{})
		var blob interface{}
		if err = json.Unmarshal(ext.Raw, &blob); err != nil {
			glog.Fatal(err)
		}
		unstruct.Object = blob.(map[string]interface{}) //set object to the content of the blob after Unmarshalling

		md := unstruct.Object["metadata"]
		metadata := md.(map[string]interface{})
		mdName := metadata["name"]

		note := targetEvent.Message
		note = strings.TrimSpace(strings.TrimPrefix(note, "(combined from similar events):"))
		status := strings.Split(note, ";")[0]

		if mdName == templateName {
			resolved := false
			if status == string(policyv1alpha1.NonCompliant) && policyT.Status.ComplianceState != policyv1alpha1.NonCompliant {
				policyT.Status.ComplianceState = policyv1alpha1.NonCompliant
				updateNeeded = true
			} else if status == string(policyv1alpha1.Compliant) && policyT.Status.ComplianceState != policyv1alpha1.Compliant {
				policyT.Status.ComplianceState = policyv1alpha1.Compliant
				updateNeeded = true
				resolved = true

			}
			cond := &policyv1alpha1.Condition{
				Type:               "completed",
				Status:             corev1.ConditionTrue,
				LastTransitionTime: metav1.Now(),
				Reason:             targetEvent.Reason,
				Message:            note,
			}

			if !checkPolicyMessageSimilarity(policyT, cond) {
				policyT.Status.Conditions = AppendCondition(policyT.Status.Conditions, cond, rsrc.Resource, resolved)
				updateNeeded = true
			}
		}
	}
	if updateNeeded {
		addForUpdate(policy)
	}
}

func checkMessageSimilarity(objectT *policyv1alpha1.ObjectTemplate, cond *policyv1alpha1.Condition) bool {
	same := true
	lastIndex := len(objectT.Status.Conditions)
	if lastIndex > 0 {
		oldCond := objectT.Status.Conditions[lastIndex-1]
		if !IsSimilarToLastCondition(oldCond, *cond) {
			// objectT.Status.Conditions = AppendCondition(objectT.Status.Conditions, cond, "object", false)
			same = false
		}
	} else {
		// objectT.Status.Conditions = AppendCondition(objectT.Status.Conditions, cond, "object", false)
		same = false
	}
	return same
}

func checkPolicyMessageSimilarity(policyT *policyv1alpha1.PolicyTemplate, cond *policyv1alpha1.Condition) bool {
	same := true
	lastIndex := len(policyT.Status.Conditions)
	if lastIndex > 0 {
		oldCond := policyT.Status.Conditions[lastIndex-1]
		if !IsSimilarToLastCondition(oldCond, *cond) {
			// policyT.Status.Conditions = AppendCondition(policyT.Status.Conditions, cond, "policy", false)
			same = false
		}
	} else {
		// policyT.Status.Conditions = AppendCondition(policyT.Status.Conditions, cond, "policy", false)
		same = false
	}
	return same
}

func handleMissingMustNotHave(objectT *policyv1alpha1.ObjectTemplate, name string, rsrc schema.GroupVersionResource) bool {
	glog.V(7).Infof("entering `does not exists` & ` must not have`")
	var cond *policyv1alpha1.Condition
	var update bool
	message := fmt.Sprintf("%v `%v` is missing as it should be, therefore this Object template is compliant", rsrc.Resource, name)
	cond = &policyv1alpha1.Condition{
		Type:               "succeeded",
		Status:             corev1.ConditionTrue,
		LastTransitionTime: metav1.Now(),
		Reason:             "K8s must `not` have object already missing",
		Message:            message,
	}
	if objectT.Status.ComplianceState != policyv1alpha1.Compliant {
		update = true
	}
	objectT.Status.ComplianceState = policyv1alpha1.Compliant

	if !checkMessageSimilarity(objectT, cond) {
		conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, true)
		objectT.Status.Conditions = conditions
		update = true
	}
	return update
}

func handleMissingMustNotHaveList(objectT *policyv1alpha1.ObjectTemplate, names []string, rsrc schema.GroupVersionResource) bool {
	glog.V(7).Infof("entering `does not exists` & ` must not have`")
	var cond *policyv1alpha1.Condition
	var update bool
	message := fmt.Sprintf("no instances of `%v` exist, therefore this Object template is compliant", rsrc.Resource)
	cond = &policyv1alpha1.Condition{
		Type:               "succeeded",
		Status:             corev1.ConditionTrue,
		LastTransitionTime: metav1.Now(),
		Reason:             "K8s must `not` have object already missing",
		Message:            message,
	}
	if objectT.Status.ComplianceState != policyv1alpha1.Compliant {
		update = true
	}
	objectT.Status.ComplianceState = policyv1alpha1.Compliant

	if !checkMessageSimilarity(objectT, cond) {
		conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, true)
		objectT.Status.Conditions = conditions
		update = true
	}
	return update
}

func handleExistsMustNotHave(objectT *policyv1alpha1.ObjectTemplate, action policyv1alpha1.RemediationAction, namespaced bool, namespace string, name string, rsrc schema.GroupVersionResource, dclient dynamic.Interface) (result bool, erro error) {
	glog.V(7).Infof("entering `exists` & ` must not have`")
	var cond *policyv1alpha1.Condition
	var update, deleted bool
	var err error

	if strings.ToLower(string(action)) == strings.ToLower(string(policyv1alpha1.Enforce)) {
		if deleted, err = deleteObject(namespaced, namespace, name, rsrc, dclient); !deleted {
			message := fmt.Sprintf("%v `%v` exists, and cannot be deleted, reason: `%v`", rsrc.Resource, name, err)
			cond = &policyv1alpha1.Condition{
				Type:               "violation",
				Status:             corev1.ConditionFalse,
				LastTransitionTime: metav1.Now(),
				Reason:             "K8s deletion error",
				Message:            message,
			}
			if objectT.Status.ComplianceState != policyv1alpha1.NonCompliant {
				update = true
			}
			objectT.Status.ComplianceState = policyv1alpha1.NonCompliant

			if !checkMessageSimilarity(objectT, cond) {
				conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, false)
				objectT.Status.Conditions = conditions
				update = true
			}
		} else { //deleted successfully
			message := fmt.Sprintf("%v `%v` existed, and was deleted successfully", rsrc.Resource, name)
			cond = &policyv1alpha1.Condition{
				Type:               "notification",
				Status:             corev1.ConditionTrue,
				LastTransitionTime: metav1.Now(),
				Reason:             "K8s deletion success",
				Message:            message,
			}
			if objectT.Status.ComplianceState != policyv1alpha1.Compliant {
				update = true
			}
			objectT.Status.ComplianceState = policyv1alpha1.Compliant
			if !checkMessageSimilarity(objectT, cond) {
				conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, true)
				objectT.Status.Conditions = conditions
				update = true
			}
		}
	}
	return update, err
}

func handleExistsMustNotHaveList(objectT *policyv1alpha1.ObjectTemplate, action policyv1alpha1.RemediationAction, namespaced bool, namespace string, names []string, rsrc schema.GroupVersionResource, dclient dynamic.Interface) (result bool, erro error) {
	glog.V(7).Infof("entering `exists` & ` must not have`")
	var cond *policyv1alpha1.Condition
	var update bool
	var err error
	nameStr := ""
	for i, name := range names {
		if i == len(names)-1 {
			nameStr += " and " + name
		} else if i != 0 {
			nameStr += ", " + name
		} else {
			nameStr += name
		}
	}
	message := fmt.Sprintf("%v `%v` exist, and should be deleted", rsrc.Resource, nameStr)
	cond = &policyv1alpha1.Condition{
		Type:               "notification",
		Status:             corev1.ConditionTrue,
		LastTransitionTime: metav1.Now(),
		Reason:             "K8s has a must `not` have object",
		Message:            message,
	}
	if objectT.Status.ComplianceState != policyv1alpha1.NonCompliant {
		update = true
	}
	objectT.Status.ComplianceState = policyv1alpha1.NonCompliant
	if !checkMessageSimilarity(objectT, cond) {
		conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, false)
		objectT.Status.Conditions = conditions
		return true, err
	}

	return update, err
}

func handleMissingMustHave(objectT *policyv1alpha1.ObjectTemplate, action policyv1alpha1.RemediationAction, namespaced bool, namespace string, name string, rsrc schema.GroupVersionResource, unstruct unstructured.Unstructured, dclient dynamic.Interface) (result bool, erro error) {
	glog.V(7).Infof("entering `does not exists` & ` must have`")

	var update, created bool
	var err error
	var cond *policyv1alpha1.Condition
	if strings.ToLower(string(action)) == strings.ToLower(string(policyv1alpha1.Enforce)) {
		if created, err = createObject(namespaced, namespace, name, rsrc, unstruct, dclient, nil); !created {
			message := fmt.Sprintf("%v `%v` is missing, and cannot be created, reason: `%v`", rsrc.Resource, name, err)
			cond = &policyv1alpha1.Condition{
				Type:               "violation",
				Status:             corev1.ConditionFalse,
				LastTransitionTime: metav1.Now(),
				Reason:             "K8s creation error",
				Message:            message,
			}
			if objectT.Status.ComplianceState != policyv1alpha1.NonCompliant {
				update = true
			}
			objectT.Status.ComplianceState = policyv1alpha1.NonCompliant

			if !checkMessageSimilarity(objectT, cond) {
				objectT.Status.Conditions = AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, false)
				update = true
			}
		} else { //created successfully
			glog.V(8).Infof("entering `%v` created successfully", name)
			message := fmt.Sprintf("%v `%v` was missing, and was created successfully", rsrc.Resource, name)
			cond = &policyv1alpha1.Condition{
				Type:               "notification",
				Status:             corev1.ConditionTrue,
				LastTransitionTime: metav1.Now(),
				Reason:             "K8s creation success",
				Message:            message,
			}
			if objectT.Status.ComplianceState != policyv1alpha1.Compliant {
				update = true
			}
			objectT.Status.ComplianceState = policyv1alpha1.Compliant

			if !checkMessageSimilarity(objectT, cond) {
				conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, true)
				objectT.Status.Conditions = conditions
				update = true
			}
		}
	}
	return update, err
}

func handleMissingMustHaveList(objectT *policyv1alpha1.ObjectTemplate, action policyv1alpha1.RemediationAction, namespaced bool, namespace string, names []string, rsrc schema.GroupVersionResource, unstruct unstructured.Unstructured, dclient dynamic.Interface) (result bool, erro error) {
	glog.V(7).Infof("entering `does not exists` & ` must have`")

	var update bool
	var err error
	var cond *policyv1alpha1.Condition
	message := fmt.Sprintf("No instances of `%v` exist, and one should be created", rsrc.Resource)
	cond = &policyv1alpha1.Condition{
		Type:               "violation",
		Status:             corev1.ConditionTrue,
		LastTransitionTime: metav1.Now(),
		Reason:             "K8s missing a must have object",
		Message:            message,
	}
	if objectT.Status.ComplianceState != policyv1alpha1.NonCompliant {
		update = true
	}
	objectT.Status.ComplianceState = policyv1alpha1.NonCompliant

	if !checkMessageSimilarity(objectT, cond) {
		conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, false)
		objectT.Status.Conditions = conditions
		update = true
	}
	return update, err
}

func handleExistsMustHave(objectT *policyv1alpha1.ObjectTemplate, name string, rsrc schema.GroupVersionResource) (updateNeeded bool) {
	var cond *policyv1alpha1.Condition
	var update bool
	message := fmt.Sprintf("%v `%v` exists as it should be, therefore this Object template is compliant", rsrc.Resource, name)
	cond = &policyv1alpha1.Condition{
		Type:               "notification",
		Status:             corev1.ConditionTrue,
		LastTransitionTime: metav1.Now(),
		Reason:             "K8s `must have` object already exists",
		Message:            message,
	}
	if objectT.Status.ComplianceState != policyv1alpha1.Compliant {
		update = true
	}
	objectT.Status.ComplianceState = policyv1alpha1.Compliant

	if !checkMessageSimilarity(objectT, cond) {
		conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, true) //true = resolved
		objectT.Status.Conditions = conditions
		update = true
	}
	return update
}

func handleExistsMustHaveList(objectT *policyv1alpha1.ObjectTemplate, names []string, rsrc schema.GroupVersionResource) (updateNeeded bool) {
	var cond *policyv1alpha1.Condition
	var update bool
	message := fmt.Sprintf("%d instances of %v exist as they should, therefore this Object template is compliant", len(names), rsrc.Resource)
	cond = &policyv1alpha1.Condition{
		Type:               "notification",
		Status:             corev1.ConditionTrue,
		LastTransitionTime: metav1.Now(),
		Reason:             "K8s `must have` object already exists",
		Message:            message,
	}
	if objectT.Status.ComplianceState != policyv1alpha1.Compliant {
		update = true
	}
	objectT.Status.ComplianceState = policyv1alpha1.Compliant

	if !checkMessageSimilarity(objectT, cond) {
		conditions := AppendCondition(objectT.Status.Conditions, cond, rsrc.Resource, true) //true = resolved
		objectT.Status.Conditions = conditions
		update = true
	}
	return update
}

func getNamesOfKind(rsrc schema.GroupVersionResource, namespaced bool, ns string, dclient dynamic.Interface) (kindNameList []string) {
	if namespaced {
		res := dclient.Resource(rsrc).Namespace(ns)
		resList, err := res.List(metav1.ListOptions{})
		if err != nil {
			glog.Error(err)
			return []string{}
		}
		kindNameList = []string{}
		for _, uObj := range resList.Items {
			kindNameList = append(kindNameList, uObj.Object["metadata"].(map[string]interface{})["name"].(string))
		}
		return kindNameList
	}
	res := dclient.Resource(rsrc)
	resList, err := res.List(metav1.ListOptions{})
	if err != nil {
		glog.Error(err)
		return []string{}
	}
	kindNameList = []string{}
	for _, uObj := range resList.Items {
		kindNameList = append(kindNameList, uObj.Object["metadata"].(map[string]interface{})["name"].(string))
	}
	return kindNameList
}

func objectExists(namespaced bool, namespace string, name string, rsrc schema.GroupVersionResource, unstruct unstructured.Unstructured, dclient dynamic.Interface) (result bool) {
	exists := false
	if !namespaced {
		res := dclient.Resource(rsrc)
		_, err := res.Get(name, metav1.GetOptions{})
		if err != nil {
			if errors.IsNotFound(err) {
				glog.V(6).Infof("response to retrieve a non namespaced object `%v` from the api-server: %v", name, err)
				exists = false
				return exists
			}
			glog.Errorf("object `%v` cannot be retrieved from the api server\n", name)

		} else {
			exists = true
			glog.V(6).Infof("object `%v` retrieved from the api server\n", name)
		}
	} else {
		res := dclient.Resource(rsrc).Namespace(namespace)
		_, err := res.Get(name, metav1.GetOptions{})
		if err != nil {
			if errors.IsNotFound(err) {
				exists = false
				glog.V(6).Infof("response to retrieve a namespaced object `%v` from the api-server: %v", name, err)
				return exists
			}
			glog.Errorf("object `%v` cannot be retrieved from the api server\n", name)
		} else {
			exists = true
			glog.V(6).Infof("object `%v` retrieved from the api server\n", name)
		}
	}
	return exists
}

func deleteObject(namespaced bool, namespace string, name string, rsrc schema.GroupVersionResource, dclient dynamic.Interface) (result bool, erro error) {
	deleted := false
	var err error
	if !namespaced {
		res := dclient.Resource(rsrc)
		err = res.Delete(name, &metav1.DeleteOptions{})
		if err != nil {
			if errors.IsNotFound(err) {
				deleted = true
				glog.V(6).Infof("response to delete object `%v` from the api-server: %v", name, err)
			}
			glog.Errorf("object `%v` cannot be deleted from the api server, the error is: `%v`\n", name, err)
		} else {
			deleted = true
			glog.V(5).Infof("object `%v` deleted from the api server\n", name)
		}
	} else {
		res := dclient.Resource(rsrc).Namespace(namespace)
		err = res.Delete(name, &metav1.DeleteOptions{})
		if err != nil {
			if errors.IsNotFound(err) {
				deleted = true
				glog.V(6).Infof("response to delete object `%v` from the api-server: %v", name, err)
			}
			glog.Errorf("object `%v` cannot be deleted from the api server, the error is: `%v`\n", name, err)
		} else {
			deleted = true
			glog.V(5).Infof("object `%v` deleted from the api server\n", name)
		}
	}
	return deleted, err
}

func createObject(namespaced bool, namespace string, name string, rsrc schema.GroupVersionResource, unstruct unstructured.Unstructured, dclient dynamic.Interface, parent *policyv1alpha1.Policy) (result bool, erro error) {
	var err error
	created := false
	// set ownerReference for mutaionPolicy and override remediationAction
	if parent != nil {
		plcOwnerReferences := *metav1.NewControllerRef(parent, schema.GroupVersionKind{
			Group:   policyv1alpha1.SchemeGroupVersion.Group,
			Version: policyv1alpha1.SchemeGroupVersion.Version,
			Kind:    "Policy",
		})
		labels := unstruct.GetLabels()
		if labels == nil {
			labels = map[string]string{"cluster-namespace": namespace}
		} else {
			labels["cluster-namespace"] = namespace
		}
		unstruct.SetLabels(labels)
		unstruct.SetOwnerReferences([]metav1.OwnerReference{plcOwnerReferences})
		if spec, ok := unstruct.Object["spec"]; ok {
			specObject := spec.(map[string]interface{})
			if _, ok := specObject["remediationAction"]; ok {
				specObject["remediationAction"] = parent.Spec.RemediationAction
			}
		}
	}

	glog.V(6).Infof("createObject:  `%s`", unstruct)

	if !namespaced {
		res := dclient.Resource(rsrc)

		_, err = res.Create(&unstruct, metav1.CreateOptions{})
		if err != nil {
			if errors.IsAlreadyExists(err) {
				created = true
				glog.V(9).Infof("%v\n", err.Error())
			} else {
				glog.Errorf("Error creating the object `%v`, the error is `%v`", name, errors.ReasonForError(err))
			}
		} else {
			created = true
			glog.V(4).Infof("Resource `%v` created\n", name)
		}
	} else {
		res := dclient.Resource(rsrc).Namespace(namespace)
		_, err = res.Create(&unstruct, metav1.CreateOptions{})
		if err != nil {
			if errors.IsAlreadyExists(err) {
				created = true
				glog.V(9).Infof("%v\n", err.Error())
			} else {
				glog.Errorf("Error creating the object `%v`, the error is `%v`", name, errors.ReasonForError(err))
			}
		} else {
			created = true
			glog.V(4).Infof("Resource `%v` created\n", name)

		}
	}
	return created, err
}

func join(strs ...string) string {
	var result string
	if strs[0] == "" {
		return strs[len(strs)-1]
	}
	for _, str := range strs {
		result += str
	}
	return result
}

func addForUpdate(policy *policyv1alpha1.Policy) {
	compliant := true
	for _, objectT := range policy.Spec.ObjectTemplates {
		if objectT.Status.ComplianceState == policyv1alpha1.NonCompliant {
			compliant = false
		}
	}
	for _, policyT := range policy.Spec.PolicyTemplates {
		if policyT.Status.ComplianceState == policyv1alpha1.NonCompliant {
			compliant = false
		}
	}
	if compliant {
		policy.Status.ComplianceState = policyv1alpha1.Compliant
	} else {
		policy.Status.ComplianceState = policyv1alpha1.NonCompliant
	}

	err := updatePolicy(policy, 0)
	if err != nil {
		time.Sleep(100) //giving enough time to sync
	}
	/*
		if _, ok := UpdatePolicyMap[policy.Name]; !ok {
			glog.V(5).Infof("ok? %v update", ok)
			MxUpdateMap.Lock()
			UpdatePolicyMap[policy.Name] = policy
			MxUpdateMap.Unlock()
		}
	*/
}

func removeFromUpdate(policy *policyv1alpha1.Policy) {

	if _, ok := UpdatePolicyMap[policy.Name]; ok {
		MxUpdateMap.Lock()
		delete(UpdatePolicyMap, policy.Name)
		MxUpdateMap.Unlock()
	}
}
