// 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 logic
package policy

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

	corev1 "k8s.io/api/core/v1"

	"github.com/golang/glog"
	policyv1alpha1 "github.com/open-cluster-management/hcm-compliance/pkg/apis/policy/v1alpha1"
	common "github.com/open-cluster-management/hcm-compliance/pkg/common"

	resourceClient "github.com/open-cluster-management/seed-sdk/pkg/client"
	"github.com/open-cluster-management/seed-sdk/pkg/context"
	resv1 "github.com/open-cluster-management/seed-sdk/pkg/types/apis/resource/v1"
	rbacv1 "k8s.io/api/rbac/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/labels"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/record"

	policyLister "github.com/open-cluster-management/hcm-compliance/pkg/client/listers/policy/v1alpha1"
	eventLister "k8s.io/client-go/listers/core/v1"

	events "k8s.io/api/core/v1"
)

var myFinalizerNames = []string{name + common.Finalizer, common.OldFinalizer}

const name = "policy"

var (
	clusterName string

	mustNotHaveRole  map[string]roleOrigin
	mustHaveRole     map[string]roleOrigin
	mustOnlyHaveRole map[string]roleOrigin

	attributeCheck map[string][]query
	//UpdatePolicyMap includes policies to be updated
	UpdatePolicyMap map[string]*policyv1alpha1.Policy

	//PolicyCompliancyMap includes the compliancy status of each of its templates
	PolicyCompliancyMap map[string]*policyv1alpha1.Policy
	//ResClient to update resources in k8s
	ResClient *resourceClient.ResourceClient
	client    *kubernetes.Clientset
	config    *rest.Config
	//Mx for making the map thread safe
	Mx sync.RWMutex
	//MxUpdateMap for making the map thread safe
	MxUpdateMap sync.RWMutex

	smMustHaveRoleB    syncedMustHaveRBMap
	smMustNotHaveRoleB syncedMustNotHaveRBMap

	plcLister policyLister.PolicyLister
	eveLister eventLister.EventLister
	ctx       context.Context
	//CemWebhookURL a webhook to event management
	CemWebhookURL string
	recorder      record.EventRecorder
)

type roleOrigin struct {
	roleTemplate *policyv1alpha1.RoleTemplate
	policy       *policyv1alpha1.Policy
	namespace    string
	//TODO add flatRole representation here to save on calculation
}
type roleCompareResult struct {
	roleName     string
	missingKeys  map[string]map[string]bool
	missingVerbs map[string]map[string]bool

	AdditionalKeys map[string]map[string]bool
	AddtionalVerbs map[string]map[string]bool
}

//Initialize the maps
func Initialize(kubeconfig *rest.Config, clientset *kubernetes.Clientset, rClient *resourceClient.ResourceClient,
	initiatlized chan int, policyLister policyLister.PolicyLister, clustName string, eventLister eventLister.EventLister) {

	config = kubeconfig
	client = clientset
	plcLister = policyLister
	eveLister = eventLister
	ResClient = rClient
	attributeCheck = make(map[string][]query)
	UpdatePolicyMap = make(map[string]*policyv1alpha1.Policy)
	PolicyCompliancyMap = make(map[string]*policyv1alpha1.Policy)
	if clustName == "" {
		clusterName = "mcm-managed-cluster"
	} else {
		clusterName = clustName
	}

	if mustNotHaveRole == nil {
		mustNotHaveRole = make(map[string]roleOrigin)
	}
	if mustHaveRole == nil {
		mustHaveRole = make(map[string]roleOrigin)
	}
	if mustOnlyHaveRole == nil {
		mustOnlyHaveRole = make(map[string]roleOrigin)
	}
	recorder, _ = common.CreateRecorder(ResClient.KubeClient, "policy-controller")

}

// Reconcile handles Policy Spec changes
func Reconcile(cntx context.Context, obj *policyv1alpha1.Policy) error {
	ctx = cntx
	err := resv1.EnsureFinalizerAndPut(ctx, obj, name+common.Finalizer)
	if err != nil {
		glog.Errorf("Error ensuring finalizer %v\n", err)
	}
	err = HandlePolicy(obj, true)
	if err != nil {
		glog.V(4).Infof("Error handling policy %v", err)
	}

	return err
}

// Finalizer removes the finalizer name
func Finalizer(ctx context.Context, obj *policyv1alpha1.Policy) error {
	err := HandlePolicy(obj, false)
	if err != nil {
		glog.Errorf("Error cleaning up after deletion %v", err)
		return err
	}
	glog.V(3).Info("Deleting resource")
	err = common.RemoveFinalizerAndPut(ctx, obj, myFinalizerNames)
	//err = resv1.RemoveFinalizerAndPut(ctx, obj, name+common.Finalizer)
	if err != nil {
		glog.Errorf("Error removing finalizer %v", err)
	}
	return nil
}

// HandlePolicy will parse the policy and figure out teh actions needed to enforce it.
func HandlePolicy(plc *policyv1alpha1.Policy, added bool) error {
	if added {

		if !checkPolicyValidity(plc) {
			added = false //remove the policy from enforcement since it is invalid
			glog.V(3).Infof("the policy `%v` is invalid", plc.Name)
		} else {
			MxUpdateMap.Lock()
			PolicyCompliancyMap[plc.Name] = plc
			MxUpdateMap.Unlock()
			/*
				for _, tempG := range plc.Spec.GenericTemplates {
					if tempG.Status.Violations != nil {
						tempG.Status.ComplianceState = policyv1alpha1.UnknownCompliancy
						tempG.Status.Violations = nil
					}
				}
			*/
		}
	} else { //not added
		delete(PolicyCompliancyMap, plc.Name)
	}
	relevantNamespaces := getPolicyNamespaces(ctx, plc)
	//for each namespace, check the compliance against the policy:
	for _, ns := range relevantNamespaces {
		handlePolicyPerNamespace(ctx, ns, plc, added)
	}

	return nil
}

func checkPolicyValidity(plc *policyv1alpha1.Policy) bool {
	updateNeeded := false
	policyValidity := true
	cpl := plc.Status.ComplianceState
	//cpl := policyv1alpha1.Compliant
	fullReason := []string{}

	for _, roleT := range plc.Spec.RoleTemplates {
		roleT, updateNeeded = autoCompleteRoleTCompliance(roleT)
		roleTMap := flattenRoleTemplate(*roleT)
		res, isValid := validateRoleTemplate(roleTMap)
		if !isValid {
			policyValidity = false
			fullReason = append(fullReason, res...)
			glog.V(5).Infof("the role template `%v` in policy `%v` is not valid because %v", roleT.Name, plc.Name, res)

		}
		if roleT.Status.ComplianceState == policyv1alpha1.NonCompliant {
			cpl = policyv1alpha1.NonCompliant
		}
		//update is needed
		plc.Status.Valid = isValid
		roleT.Status.Validity.Valid = &isValid
		roleT.Status.Validity.Reason = strings.Join(res, ";")

	}
	for _, roleBindingT := range plc.Spec.RoleBindingTemplates {
		_, updateNeeded = autoCompleteRoleBindingTCompliance(roleBindingT, plc)
	}

	for _, objectT := range plc.Spec.ObjectTemplates {
		_, updateNeeded = autoCompleteObjectTCompliance(objectT, plc)
	}

	if updateNeeded {
		plc.Status.ComplianceState = cpl
		plc.Status.Valid = policyValidity
		glog.V(1).Infof("update needed, validity = %v", policyValidity)

		if !policyValidity {
			glog.V(1).Infof("policy not valid")

			plc.Status.ComplianceState = policyv1alpha1.UnknownCompliancy
			glog.Errorf("The policy `%v` is invalid because: %v", plc.Name, fullReason)
		}
		updatePolicy(plc, 0)
	}
	return policyValidity
}

func handlePolicyPerNamespace(ctx context.Context, namespace string, plc *policyv1alpha1.Policy, added bool) {
	//for each template we should handle each Kind of objects. I.e. iterate on all the items
	//of a given Kind
	glog.V(6).Infof("Policy: %v, namespace: %v\n", plc.Name, namespace)

	//add the role-namespace to a map fo musthave, mustnothave, or mustonylhave
	for _, roleT := range plc.Spec.RoleTemplates {

		roleN := []string{roleT.Name, namespace} //TODO the name can contain a wildcard, so I must handle that
		roleNamespace := strings.Join(roleN, "-")

		switch strings.ToLower(string(roleT.ComplianceType)) {
		case "musthave":

			if added {
				//add the role
				Mx.Lock()
				mustHaveRole[roleNamespace] = roleOrigin{
					roleT,
					plc,
					namespace,
				}
				Mx.Unlock()
				glog.V(4).Infof("the role: %s is added to the 'mustHave' list\n", roleNamespace)
			} else {
				Mx.Lock()
				delete(mustHaveRole, roleNamespace)
				Mx.Unlock()
			}

		case "mustnothave":
			if added {
				//add the role
				Mx.Lock()
				mustNotHaveRole[roleNamespace] = roleOrigin{
					roleT,
					plc,
					namespace,
				}
				Mx.Unlock()
				glog.V(4).Infof("the role: %s is added to the 'mustNotHave' list\n", roleNamespace)
			} else {
				Mx.Lock()
				delete(mustNotHaveRole, roleNamespace)
				Mx.Unlock()
			}

		case "mustonlyhave":
			if added {
				//add the role
				Mx.Lock()
				mustOnlyHaveRole[roleNamespace] = roleOrigin{
					roleT,
					plc,
					namespace,
				}
				Mx.Unlock()
				glog.V(4).Infof("the role: %s is added to the 'mustOnlyHave' list\n", roleNamespace)
			} else {
				Mx.Lock()
				delete(mustOnlyHaveRole, roleNamespace)
				Mx.Unlock()
			}

		}
	}

	for _, genericT := range plc.Spec.GenericTemplates {

		for _, attr := range genericT.Rules {

			qry, err := parseQuery(attr, genericT.Selector)
			if err != nil {
				glog.Errorf("invialid query in policy `%v`, Generic template `%v`, query `%v`", plc.Name, genericT.Name, attr)
				continue
			}
			qry.parentTemplate = genericT
			qry.parentPolicy = plc
			key := fmt.Sprintf("%v.%v.%v.%v", qry.apiVersion, qry.kind, namespace, genericT.Name)
			if added {

				Mx.Lock()
				attributeCheck[key] = append(attributeCheck[key], qry)
				Mx.Unlock()
			} else {
				if _, ok := attributeCheck[key]; ok {
					for indx := len(attributeCheck[key]) - 1; indx >= 0; indx-- {

						if reflect.DeepEqual(qry.parentPolicy.Name, plc.Name) {
							Mx.Lock()
							attributeCheck[key] = append(attributeCheck[key][:indx], attributeCheck[key][indx+1:]...)
							Mx.Unlock()
						}
					}
					if len(attributeCheck[key]) == 0 {
						Mx.Lock()
						delete(attributeCheck, key)
						Mx.Unlock()
					}
				}
			}
		}
	}
	for _, roleBindT := range plc.Spec.RoleBindingTemplates {
		roleBN := []string{roleBindT.RoleBinding.Name, namespace} //TODO the name can contain a wildcard, so I must handle that
		roleBNamespace := strings.Join(roleBN, "-")

		switch strings.ToLower(string(roleBindT.ComplianceType)) {
		case strings.ToLower(string(policyv1alpha1.MustHave)):
			if added {
				rbo := roleBindingOrigin{
					roleBindT,
					plc,
					namespace,
				}
				smMustHaveRoleB.addMustHaveRB(roleBNamespace, rbo)
				smMustNotHaveRoleB.removeMustNotHaveRB(roleBNamespace) //for consistency
			} else {
				smMustHaveRoleB.removeMustHaveRB(roleBNamespace)
				smMustNotHaveRoleB.removeMustNotHaveRB(roleBNamespace)
			}

		case strings.ToLower(string(policyv1alpha1.MustNotHave)):
			if added {
				rbo := roleBindingOrigin{
					roleBindT,
					plc,
					namespace,
				}
				smMustNotHaveRoleB.addMustNotHaveRB(roleBNamespace, rbo)
				smMustHaveRoleB.removeMustHaveRB(roleBNamespace) //for consistency
			} else {
				smMustNotHaveRoleB.removeMustNotHaveRB(roleBNamespace)
				smMustHaveRoleB.removeMustHaveRB(roleBNamespace)
			}
		}
		//TODO add mustonlyhave logic if needed
	}
	/*
		for indx, objectT := range plc.Spec.ObjectTemplates {
			handleObjects(objectT, namespace, indx, plc, client, config)

				object := []string{roleBindT.RoleBinding.Name, namespace} //TODO the name can contain a wildcard, so I must handle that
				roleBNamespace := strings.Join(roleBN, "-")

				switch strings.ToLower(string(roleBindT.ComplianceType)) {
				case strings.ToLower(string(policyv1alpha1.MustHave)):
					if added {
						rbo := roleBindingOrigin{
							roleBindT,
							plc,
							namespace,
						}
						smMustHaveRoleB.addMustHaveRB(roleBNamespace, rbo)
						smMustNotHaveRoleB.removeMustNotHaveRB(roleBNamespace) //for consistency
					} else {
						smMustHaveRoleB.removeMustHaveRB(roleBNamespace)
						smMustNotHaveRoleB.removeMustNotHaveRB(roleBNamespace)
					}

				case strings.ToLower(string(policyv1alpha1.MustNotHave)):
					if added {
						rbo := roleBindingOrigin{
							roleBindT,
							plc,
							namespace,
						}
						smMustNotHaveRoleB.addMustNotHaveRB(roleBNamespace, rbo)
						smMustHaveRoleB.removeMustHaveRB(roleBNamespace) //for consistency
					} else {
						smMustNotHaveRoleB.removeMustNotHaveRB(roleBNamespace)
						smMustHaveRoleB.removeMustHaveRB(roleBNamespace)
					}
				}
		}*/
	//Then I handle other types...
}

func getPolicyNamespaces(ctx context.Context, policy *policyv1alpha1.Policy) []string {
	//get all namespaces
	allNamespaces := getAllNamespaces(ctx)
	//then get the list of included
	includedNamespaces := []string{}
	included := policy.Spec.Namespaces.Include
	for _, value := range included {
		found := common.FindPattern(value, allNamespaces)
		if found != nil {
			includedNamespaces = append(includedNamespaces, found...)
		}

	}
	//then get the list of excluded
	excludedNamespaces := []string{}
	excluded := policy.Spec.Namespaces.Exclude
	for _, value := range excluded {
		found := common.FindPattern(value, allNamespaces)
		if found != nil {
			excludedNamespaces = append(excludedNamespaces, found...)
		}

	}

	//then get the list of deduplicated
	finalList := common.DeduplicateItems(includedNamespaces, excludedNamespaces)
	if len(finalList) == 0 {
		finalList = append(finalList, "default")
	}
	return finalList
}

func getAllNamespaces(ctx context.Context) (list []string) {
	listOpt := &metav1.ListOptions{}

	nsList, err := client.CoreV1().Namespaces().List(*listOpt)
	//nsList, err := ctx.KubeClientset().CoreV1().Namespaces().List(*listOpt)
	if err != nil {
		glog.Errorf("Error fetching namespaces from the API server: %v", err)
	}
	namespacesNames := []string{}
	for _, n := range nsList.Items {
		namespacesNames = append(namespacesNames, n.Name)
	}

	return namespacesNames
}

//	^ -->
//	|	|
//	|   |
//	<-- v

// PeriodicallyUpdate a loop for poriodically updating the policies that had a change in status
func PeriodicallyUpdate() {

	for _, policy := range PolicyCompliancyMap {
		udpate := false
		compliant := true
		for _, tempG := range policy.Spec.GenericTemplates {
			if len(tempG.Status.Violations) == 0 {
				if tempG.Status.ComplianceState != policyv1alpha1.Compliant {
					Mx.Lock()
					tempG.Status.ComplianceState = policyv1alpha1.Compliant
					Mx.Unlock()
					udpate = true
				}
			}
			if tempG.Status.ComplianceState == policyv1alpha1.NonCompliant {
				compliant = false
				if policy.Status.ComplianceState == policyv1alpha1.Compliant {
					udpate = true
				}
			}
		}

		for _, tempR := range policy.Spec.RoleTemplates {
			if tempR.Status.ComplianceState == policyv1alpha1.NonCompliant {
				compliant = false
			} else {
				if tempR.Status.ComplianceState != policyv1alpha1.Compliant {
					Mx.Lock()
					tempR.Status.ComplianceState = policyv1alpha1.Compliant
					Mx.Unlock()
					udpate = true
				}
			}
		}

		for _, tempRB := range policy.Spec.RoleBindingTemplates {
			if tempRB.Status.ComplianceState == policyv1alpha1.NonCompliant {
				compliant = false
			} else {
				if tempRB.Status.ComplianceState != policyv1alpha1.Compliant {
					Mx.Lock()
					tempRB.Status.ComplianceState = policyv1alpha1.Compliant
					Mx.Unlock()
					udpate = true
				}
			}
		}

		for _, tempOT := range policy.Spec.ObjectTemplates {
			if tempOT.Status.ComplianceState == policyv1alpha1.NonCompliant {
				compliant = false
				break //no need to continue, only one NonCompliant is enough to determine the NonCompliant state
			} else {
				if tempOT.Status.ComplianceState != policyv1alpha1.Compliant {
					Mx.Lock()
					tempOT.Status.ComplianceState = policyv1alpha1.Compliant
					Mx.Unlock()
					udpate = true
				}
			}
		}

		if compliant {
			if policy.Status.ComplianceState != policyv1alpha1.Compliant {
				Mx.Lock()
				policy.Status.ComplianceState = policyv1alpha1.Compliant
				Mx.Unlock()
				udpate = true
			}
		} else {
			if policy.Status.ComplianceState == policyv1alpha1.Compliant {
				policy.Status.ComplianceState = policyv1alpha1.NonCompliant
				udpate = true
			}
		}
		if udpate {

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

		}

	}

	for _, plc := range UpdatePolicyMap {
		//update policy
		err := updatePolicy(plc, 0)
		if err == nil {
			MxUpdateMap.Lock()
			delete(UpdatePolicyMap, plc.Name)
			MxUpdateMap.Unlock()
		}
	}

}

// ReconcileResources a loop for reconciliation of roles
func ReconcileResources(client kubernetes.Clientset, frequency time.Duration) {
	//TODO add the must only have!!!
	//var selector labels.Selector
	for {

		glog.V(2).Infof("reconciling ...\n")
		Mx.Lock()

		for _, rtValue := range mustHaveRole {

			handleMustHaveRole(rtValue)

		}
		Mx.Unlock() //giving the other GO-routine a chance to advance, and modify the map if needed
		Mx.Lock()
		for _, rtValue := range mustNotHaveRole {

			handleMustNotHaveRole(rtValue)

		}
		Mx.Unlock()

		Mx.Lock()
		for key, qry := range attributeCheck {

			HandleAttrCheck(key, qry)

		}
		Mx.Unlock()

		Mx.Lock()
		for _, rbtValue := range smMustHaveRoleB.mustHaveRoleBinding {
			handleMustHaveRoleBinding(rbtValue, ResClient, &smMustHaveRoleB)
		}
		Mx.Unlock()
		Mx.Lock()
		for _, rbtValue := range smMustNotHaveRoleB.mustNotHaveRoleBinding {
			handleMustNotHaveRoleBinding(rbtValue, ResClient, &smMustNotHaveRoleB)
		}
		Mx.Unlock()

		Mx.Lock()
		handleObjectTemplates()
		Mx.Unlock()

		Mx.Lock()
		handlePolicyTemplates()
		Mx.Unlock()

		Mx.Lock()
		handleEventsUpdate()
		Mx.Unlock()
		// PeriodicallyUpdate() //temp remove this and observe

		time.Sleep(frequency)
	}
}

//eveLister

func handleEventsUpdate() {
	eventMap := make(map[string][]events.Event)
	allEvents, _ := eveLister.List(labels.Everything())
	for _, event := range allEvents {
		eventKind := event.InvolvedObject.Kind
		if reflect.DeepEqual(eventKind, "Policy") && strings.HasPrefix(event.Reason, "policy:") {
			plcName := event.InvolvedObject.Name
			if eventMap[plcName] == nil {
				var s []events.Event
				eventMap[plcName] = s
			}
			eventMap[plcName] = append(eventMap[plcName], *event)
		}
	}

	allPolicies, _ := plcLister.List(labels.Everything())
	for _, plc := range allPolicies {
		if reflect.DeepEqual(plc.Labels["ignore"], "true") {
			plc.Status = policyv1alpha1.PolicyStatus{
				ComplianceState: policyv1alpha1.UnknownCompliancy,
				Valid:           true,
				Message:         "policy is part of a compliance that is being ignored now",
				Reason:          "ignored",
				State:           "Unknown",
			}
			continue
		}
		for _, policyT := range plc.Spec.PolicyTemplates {
			handleEvents(policyT, plc, client, eventMap)
		}
	}
}

func createViolation(objectT *policyv1alpha1.ObjectTemplate, reason string, message string) (result bool) {
	var update bool
	var cond *policyv1alpha1.Condition
	cond = &policyv1alpha1.Condition{
		Type:               "violation",
		Status:             corev1.ConditionTrue,
		LastTransitionTime: metav1.Now(),
		Reason:             reason,
		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, "", false)
		objectT.Status.Conditions = conditions
		update = true
	}
	return update
}

func createNotification(objectT *policyv1alpha1.ObjectTemplate, reason string, message string) (result bool) {
	var update bool
	var cond *policyv1alpha1.Condition
	cond = &policyv1alpha1.Condition{
		Type:               "notification",
		Status:             corev1.ConditionTrue,
		LastTransitionTime: metav1.Now(),
		Reason:             reason,
		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, "", false)
		objectT.Status.Conditions = conditions
		update = true
	}
	return update
}

func handleObjectTemplates() {
	allPolicies, _ := plcLister.List(labels.Everything())
	for _, plc := range allPolicies {
		if reflect.DeepEqual(plc.Labels["ignore"], "true") {
			plc.Status = policyv1alpha1.PolicyStatus{
				ComplianceState: policyv1alpha1.UnknownCompliancy,
				Valid:           true,
				Message:         "policy is part of a compliance that is being ignored now",
				Reason:          "ignored",
				State:           "Unknown",
			}
			continue
		}
		plcNamespaces := getPolicyNamespaces(ctx, plc)
		for indx, objectT := range plc.Spec.ObjectTemplates {
			nonCompliantObjects := map[string][]string{}
			mustHave := strings.ToLower(string(objectT.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustHave))
			enforce := strings.ToLower(string(plc.Spec.RemediationAction)) == strings.ToLower(string(policyv1alpha1.Enforce))
			relevantNamespaces := plcNamespaces
			kind := "unknown"
			desiredName := ""

			//override policy namespaces if one is present in object template
			var unstruct unstructured.Unstructured
			unstruct.Object = make(map[string]interface{})
			var blob interface{}
			ext := objectT.ObjectDefinition
			if jsonErr := json.Unmarshal(ext.Raw, &blob); jsonErr != nil {
				glog.Fatal(jsonErr)
			}
			unstruct.Object = blob.(map[string]interface{})
			if md, ok := unstruct.Object["metadata"]; ok {
				metadata := md.(map[string]interface{})
				if objectns, ok := metadata["namespace"]; ok {
					relevantNamespaces = []string{objectns.(string)}
				}
				if objectname, ok := metadata["name"]; ok {
					desiredName = objectname.(string)
				}
			}

			numCompliant := 0
			numNonCompliant := 0

			for _, ns := range relevantNamespaces {
				glog.V(5).Infof("Handling Object template [%v] from Policy `%v` in namespace `%v`", indx, plc.Name, ns)
				names, compliant, objKind := handleObjects(objectT, ns, indx, plc, client, config, recorder)
				if objKind != "" {
					kind = objKind
				}
				if names == nil {
					//object template enforced, already handled in handleObjects
					continue
				} else {
					enforce = false
					if !compliant {
						numNonCompliant += len(names)
						nonCompliantObjects[ns] = names
					} else {
						numCompliant += len(names)
					}
				}
			}

			if !enforce {
				update := false
				if mustHave && numCompliant == 0 {
					//noncompliant; musthave and objects do not exist
					message := fmt.Sprintf("No instances of `%v` exist as specified, and one should be created", kind)
					if desiredName != "" {
						message = fmt.Sprintf("%v `%v` is missing, and should be created", kind, desiredName)
					}
					update = createViolation(objectT, "K8s missing a must have object", message)
				}
				if !mustHave && numNonCompliant > 0 {
					//noncompliant; mustnothave and objects exist
					nameStr := ""
					for ns, names := range nonCompliantObjects {
						nameStr += "["
						for i, name := range names {
							nameStr += name
							if i != len(names)-1 {
								nameStr += ", "
							}
						}
						nameStr += "] in namespace " + ns + "; "
					}
					nameStr = nameStr[:len(nameStr)-2]
					message := fmt.Sprintf("%v exist and should be deleted: %v", kind, nameStr)
					update = createViolation(objectT, "K8s has a must `not` have object", message)
				}
				if mustHave && numCompliant > 0 {
					//compliant; musthave and objects exist
					message := fmt.Sprintf("%d instances of %v exist as specified, therefore this Object template is compliant", numCompliant, kind)
					update = createNotification(objectT, "K8s must `not` have object already missing", message)
				}
				if !mustHave && numNonCompliant == 0 {
					//compliant; mustnothave and no objects exist
					message := fmt.Sprintf("no instances of `%v` exist as specified, therefore this Object template is compliant", kind)
					update = createNotification(objectT, "K8s `must have` object already exists", message)
				}
				if update {
					//update parent policy with violation
					eventType := eventNormal
					if objectT.Status.ComplianceState == policyv1alpha1.NonCompliant {
						eventType = eventWarning
					}
					recorder.Event(plc, eventType, fmt.Sprintf("policy: %s", plc.GetName()), fmt.Sprintf("%s; %s", objectT.Status.ComplianceState, objectT.Status.Conditions[0].Message))
					addForUpdate(plc)
				}
			}
		}
	}
}

func handlePolicyTemplates() {
	glog.V(6).Infof("handlePolicyTemplates")
	allPolicies, _ := plcLister.List(labels.Everything())
	for _, plc := range allPolicies {
		if reflect.DeepEqual(plc.Labels["ignore"], "true") {
			plc.Status = policyv1alpha1.PolicyStatus{
				ComplianceState: policyv1alpha1.UnknownCompliancy,
				Valid:           true,
				Message:         "policy is part of a compliance that is being ignored now",
				Reason:          "ignored",
				State:           "Unknown",
			}
			continue
		}
		namespace := plc.Namespace
		// relevantNamespaces := getPolicyNamespaces(ctx, plc)
		for indx, policyT := range plc.Spec.PolicyTemplates {
			glog.V(5).Infof("Handling Policy template [%v] from Policy `%v` in namespace `%v`", indx, plc.Name, namespace)
			handlePolicyObjects(policyT, namespace, indx, plc, client, config)
		}

	}
}

func handleMustNotHaveRole(rtValue roleOrigin) {
	updateNeeded := false
	//Get the list of roles that satisfy the label:
	opt := &metav1.ListOptions{}
	if rtValue.roleTemplate.Selector != nil {
		if rtValue.roleTemplate.Selector.MatchLabels != nil {
			lbl := createKeyValuePairs(rtValue.roleTemplate.Selector.MatchLabels)
			if lbl == "" {
				opt = &metav1.ListOptions{LabelSelector: lbl}
			}
		}
	}
	roleList, err := ResClient.KubeClient.RbacV1().Roles(rtValue.namespace).List(*opt) //namespace scoped list
	if err != nil {
		rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.UnknownCompliancy
		rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("accessError", rtValue, err, rtValue.roleTemplate.Status.Conditions, "")
		err = updatePolicy(rtValue.policy, 0)
		if err != nil {
			glog.Errorf("Error update policy %v, the error is: %v", rtValue.policy.Name, err)
		}
		glog.Errorf("Error fetching the list Rbac roles from K8s Api-server, the error is: %v", err)
		return
	}

	//I have the list of filtered roles by label, now I need to filter by name pattern
	roleNames := getRoleNames(roleList.Items)
	foundRoles := common.FindPattern(rtValue.roleTemplate.Name, roleNames)

	if len(foundRoles) > 0 { //I need to do delete those roles
		for _, fRole := range foundRoles {
			opt := &metav1.DeleteOptions{}
			if (strings.ToLower(string(rtValue.policy.Spec.RemediationAction)) == strings.ToLower(string(policyv1alpha1.Enforce))) &&
				(strings.ToLower(string(rtValue.roleTemplate.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustNotHave))) {
				err = ResClient.KubeClient.RbacV1().Roles(rtValue.namespace).Delete(fRole, opt)
				if err != nil {
					glog.Errorf("Error deleting role `%v` in namespace `%v` according to policy `%v`, the error is: %v", fRole, rtValue.namespace, rtValue.policy.Name, err)
					rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("failedDeletingExtraRole", rtValue, err, rtValue.roleTemplate.Status.Conditions, fRole)
					updateNeeded = true
				} else {
					glog.V(2).Infof("Deleted role `%v` in namespace `%v` according to policy `%v`", fRole, rtValue.namespace, rtValue.policy.Name)
					rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("deletedExtraRole", rtValue, err, rtValue.roleTemplate.Status.Conditions, fRole)
					rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.Compliant
					updateNeeded = true
				}
			} else if strings.ToLower(string(rtValue.roleTemplate.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustNotHave)) { //inform only
				if rtValue.roleTemplate.Status.ComplianceState != policyv1alpha1.NonCompliant {
					rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.NonCompliant
					updateNeeded = true
				}
				rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("ExtraRole", rtValue, err, rtValue.roleTemplate.Status.Conditions, fRole)
			}
		}
	} else { // role doesn't exists
		if rtValue.roleTemplate.Status.ComplianceState != policyv1alpha1.Compliant {
			rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.Compliant
			rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("notExists", rtValue, err, rtValue.roleTemplate.Status.Conditions, "")
			updateNeeded = true
		}
	}

	if updateNeeded {
		if rtValue.roleTemplate.Status.ComplianceState == policyv1alpha1.NonCompliant {
			recorder.Event(rtValue.policy, "Warning", fmt.Sprintf("policy: %s/%s", rtValue.policy.GetName(), rtValue.roleTemplate.ObjectMeta.GetName()), fmt.Sprintf("%s; %s", rtValue.roleTemplate.Status.ComplianceState, rtValue.roleTemplate.Status.Conditions[0].Message))
		} else {
			recorder.Event(rtValue.policy, "Normal", fmt.Sprintf("policy: %s/%s", rtValue.policy.GetName(), rtValue.roleTemplate.ObjectMeta.GetName()), fmt.Sprintf("%s; %s", rtValue.roleTemplate.Status.ComplianceState, rtValue.roleTemplate.Status.Conditions[0].Message))
		}

		err = updatePolicy(rtValue.policy, 0)
		if err != nil {
			glog.Errorf("Error update policy %v, the error is: %v", rtValue.policy.Name, err)
		} else {
			glog.V(6).Infof("Updated the status in policy %v", rtValue.policy.Name)
		}
	}

}

func autoCompleteRoleTCompliance(rt *policyv1alpha1.RoleTemplate) (rl *policyv1alpha1.RoleTemplate, update bool) {
	update = false
	for index, rule := range rt.Rules {
		if rule.ComplianceType == "" {
			rt.Rules[index].ComplianceType = rt.ComplianceType
			update = true
		}
	}
	return rt, update

}

func autoCompleteRoleBindingTCompliance(rbt *policyv1alpha1.RoleBindingTemplate, plc *policyv1alpha1.Policy) (rbtemp *policyv1alpha1.RoleBindingTemplate, update bool) {
	update = false

	if rbt.ComplianceType == "" {
		rbt.ComplianceType = plc.Spec.ComplianceType
		update = true

	}
	return rbt, update
}

func autoCompleteObjectTCompliance(ot *policyv1alpha1.ObjectTemplate, plc *policyv1alpha1.Policy) (otemp *policyv1alpha1.ObjectTemplate, update bool) {
	update = false

	if ot.ComplianceType == "" {
		ot.ComplianceType = plc.Spec.ComplianceType
		update = true

	}
	return ot, update
}

func handleMustHaveRole(rtValue roleOrigin) {
	var lbl string
	updateNeeded := false
	//Get the list of roles that satisfy the label:
	opt := &metav1.ListOptions{}
	if rtValue.roleTemplate.Selector != nil {
		if rtValue.roleTemplate.Selector.MatchLabels != nil {
			lbl = createKeyValuePairs(rtValue.roleTemplate.Selector.MatchLabels)
			if lbl == "" {
				opt = &metav1.ListOptions{LabelSelector: lbl}
			}
		}
	}

	roleList, err := ResClient.KubeClient.RbacV1().Roles(rtValue.namespace).List(*opt) //namespace scoped list
	if err != nil {

		rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.UnknownCompliancy
		rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("accessError", rtValue, err, rtValue.roleTemplate.Status.Conditions, "")
		err = updatePolicy(rtValue.policy, 0)
		if err != nil {
			glog.Errorf("Error update policy %v, the error is: %v", rtValue.policy.Name, err)
		}
		glog.Errorf("Error fetching the list Rbac roles from K8s Api-server, the error is: %v", err)
		return
	}
	rMap := listToRoleMap(roleList.Items)
	//I have the list of filtered roles by label, now I need to filter by name pattern
	roleNames := getRoleNames(roleList.Items)
	foundRoles := common.FindPattern(rtValue.roleTemplate.Name, roleNames)
	if !strings.Contains(rtValue.roleTemplate.Name, "*") && len(foundRoles) == 0 {
		//it is an exact roles name that must exit, however it was not found => we must create it.
		if strings.ToLower(string(rtValue.policy.Spec.RemediationAction)) == strings.ToLower(string(policyv1alpha1.Enforce)) {
			role := buildRole(rtValue)
			_, err = ResClient.KubeClient.RbacV1().Roles(rtValue.namespace).Create(role)
			if err != nil {

				rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.NonCompliant

				rtValue.policy.Status.ComplianceState = policyv1alpha1.NonCompliant
				rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("createRoleError", rtValue, err, rtValue.roleTemplate.Status.Conditions, "")
				updateNeeded = true
				glog.V(2).Infof("the Rbac role %v in namespace %v from policy %v, was not found among the role list filtered by labels: %v", role.Name,
					role.Namespace, rtValue.policy.Name, lbl)
				glog.Errorf("Error creating the Rbac role %v in namespace %v from policy %v, the error is: %v", role.Name,
					role.Namespace, rtValue.policy.Name, err)

			} else {
				rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.Compliant
				rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("createdRole", rtValue, err, rtValue.roleTemplate.Status.Conditions, "")
				updateNeeded = true
				glog.V(2).Infof("created the Rbac role %v in namespace %v from policy %v", role.Name,
					role.Namespace, rtValue.policy.Name)
			}

		} else { //it is inform only
			rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.NonCompliant

			rtValue.policy.Status.ComplianceState = policyv1alpha1.NonCompliant
			rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("missingRole", rtValue, err, rtValue.roleTemplate.Status.Conditions, rtValue.roleTemplate.Name)
			updateNeeded = true
			glog.V(2).Infof("the Rbac role %v in namespace %v is missing! it should be created according to role template %v in policy %v", rtValue.roleTemplate.Name,
				rtValue.namespace, rtValue.roleTemplate.Name, rtValue.policy.Name)
		}
	} else if len(foundRoles) > 0 { //I need to do a deep comparison after flattening

		for _, fRole := range foundRoles {
			roleN := []string{fRole, rtValue.namespace}
			roleNamespace := strings.Join(roleN, "-")
			actualRole := rMap[roleNamespace]
			actualRoleMap := flattenRole(rMap[roleNamespace])
			roleTMap := flattenRoleTemplate(*rtValue.roleTemplate)
			match, res := deepCompareRoleTtoRole(roleTMap, actualRoleMap)

			message := prettyPrint(*res, rtValue.roleTemplate.Name)
			if !match { // role permission doesn't match
				if strings.ToLower(string(rtValue.policy.Spec.RemediationAction)) == strings.ToLower(string(policyv1alpha1.Enforce)) {
					//I need to update the actual role

					copyActualRole := actualRole.DeepCopy()

					copyActualRole.Rules = getdesiredRules(*rtValue.roleTemplate)

					_, err = ResClient.KubeClient.RbacV1().Roles(rtValue.namespace).Update(copyActualRole)
					if err != nil {
						rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.NonCompliant

						rtValue.policy.Status.ComplianceState = policyv1alpha1.NonCompliant
						rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("mismatch", rtValue, err, rtValue.roleTemplate.Status.Conditions, message)
						updateNeeded = true
						glog.V(2).Infof("Error updating the Rbac role %v in namespace %v from policy %v, the error is: %v", copyActualRole.Name,
							copyActualRole.Namespace, rtValue.policy.Name, err)

					} else {
						rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.Compliant
						rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("mismatchFixed", rtValue, err, rtValue.roleTemplate.Status.Conditions, message)
						updateNeeded = true

						glog.V(2).Infof("Role updated %v to comply to policy %v", copyActualRole.Name, rtValue.policy.Name)

					}

				} else { //it is inform only
					if rtValue.roleTemplate.Status.ComplianceState != policyv1alpha1.NonCompliant {
						rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.NonCompliant
						updateNeeded = true
					}
					if rtValue.policy.Status.ComplianceState != policyv1alpha1.NonCompliant {
						rtValue.policy.Status.ComplianceState = policyv1alpha1.NonCompliant
						updateNeeded = true
					}

					rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("mismatch", rtValue, err, rtValue.roleTemplate.Status.Conditions, message)
					glog.V(2).Infof("INFORM: the Rbac role %v in namespace %v is Patched! it is updated according to role template %v in policy %v", actualRole.Name,
						rtValue.namespace, rtValue.roleTemplate.Name, rtValue.policy.Name)

				}
			} else { // role permission matches
				if rtValue.roleTemplate.Status.ComplianceState != policyv1alpha1.Compliant {
					glog.V(2).Infof("Role %s exists according policy %s", rtValue.roleTemplate.Name, rtValue.policy.Name)
					rtValue.roleTemplate.Status.ComplianceState = policyv1alpha1.Compliant
					rtValue.roleTemplate.Status.Conditions = createRoleTemplateCondition("match", rtValue, err, rtValue.roleTemplate.Status.Conditions, message)
					updateNeeded = true
				}
			}
		}
	}
	if updateNeeded {
		if rtValue.roleTemplate.Status.ComplianceState == policyv1alpha1.NonCompliant {
			recorder.Event(rtValue.policy, "Warning", fmt.Sprintf("policy: %s/%s", rtValue.policy.GetName(), rtValue.roleTemplate.ObjectMeta.GetName()), fmt.Sprintf("%s; %s", rtValue.roleTemplate.Status.ComplianceState, rtValue.roleTemplate.Status.Conditions[0].Message))
		} else {
			recorder.Event(rtValue.policy, "Normal", fmt.Sprintf("policy: %s/%s", rtValue.policy.GetName(), rtValue.roleTemplate.ObjectMeta.GetName()), fmt.Sprintf("%s; %s", rtValue.roleTemplate.Status.ComplianceState, rtValue.roleTemplate.Status.Conditions[0].Message))
		}

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

func hasValidRules(plc policyv1alpha1.Policy) bool {
	valid := true
	for _, value := range plc.Spec.RoleTemplates {
		for _, rule := range value.Rules {
			if len(rule.PolicyRule.Verbs) == 0 {
				valid = false
				break
			}
		}
		if !valid {
			break
		}
	}
	return valid
}
func setStatus(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
		}
	}
	for _, roleT := range policy.Spec.RoleTemplates {

		if roleT.Status.ComplianceState == policyv1alpha1.NonCompliant {
			compliant = false
		}
	}
	if compliant {
		policy.Status.ComplianceState = policyv1alpha1.Compliant
	} else {
		policy.Status.ComplianceState = policyv1alpha1.NonCompliant
	}
}
func updatePolicy(plc *policyv1alpha1.Policy, retry int) error {
	setStatus(plc)
	copy := plc.DeepCopy()

	var tmp policyv1alpha1.Policy
	tmp = *plc
	err := ResClient.Get(&tmp)
	if err != nil {
		glog.Errorf("Error fetching policy %v, from the K8s API server the error is: %v", plc.Name, err)
	}
	//plc.Spec.RemediationAction = tmp.Spec.RemediationAction

	//glog.V(2).Infof("the policy `%v` RemediationAction %v ", plc.Name, plc.Spec.RemediationAction)
	//glog.V(2).Infof("------------>the policy `%v` status %v ", plc.Name, plc.Status.ComplianceState)

	if !hasValidRules(*copy) {
		return fmt.Errorf("invalid number of verbs in the rules of policy: `%v`", copy.Name)
	}
	if copy.ResourceVersion != tmp.ResourceVersion {
		copy.ResourceVersion = tmp.ResourceVersion
	}

	err = ResClient.Update(copy) //changed from copy
	if err != nil {
		glog.Errorf("Error update policy %v, the error is: %v", plc.Name, err)
		/*
			tmp.Status = copy.Status
			if !hasValidRules(tmp) {
				return fmt.Errorf("invalid number of verbs in the rules of policy: `%v`", tmp.Name)
			}
			err = ResClient.Update(&tmp)
			if err != nil {
				glog.Errorf("Error retrying to update policy %v, the error is: %v", plc.Name, err)
			}
			return err
		*/
	}
	glog.V(2).Infof("Updated the policy `%v` in namespace `%v`", plc.Name, plc.Namespace)

	return err
}

func createRoleTemplateCondition(event string, rtValue roleOrigin, err error, conditions []policyv1alpha1.Condition, myMessage string) (condR []policyv1alpha1.Condition) {

	switch event {
	case "accessError":
		message := fmt.Sprintf("Error accessing K8s Api-server, the error is: %v", err)

		cond := &policyv1alpha1.Condition{
			Type:               "failed",
			Status:             corev1.ConditionFalse,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s access error",
			Message:            message,
		}
		conditions = AppendCondition(conditions, cond, "accessError", false)

	case "createRoleError":
		message := fmt.Sprintf("Error creating a k8s RBAC role, the error is: %v", err)

		cond := &policyv1alpha1.Condition{
			Type:               "failed",
			Status:             corev1.ConditionFalse,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s create role error",
			Message:            message,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role", false)
	case "createdRole":
		message := fmt.Sprintf("k8s RBAC role \"%v\" was missing ", rtValue.roleTemplate.Name)

		cond := &policyv1alpha1.Condition{
			Type:               "completed",
			Status:             corev1.ConditionTrue,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s RBAC role created",
			Message:            message,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role Created", true)
	case "mismatch":
		message := fmt.Sprintf("Role must not include these permissions: ")

		cond := &policyv1alpha1.Condition{
			Type:               "failed",
			Status:             corev1.ConditionFalse,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s RBAC role has a mismatch",
			Message:            message + myMessage,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role Mismatch", false)
	case "mismatchFixed":
		message := fmt.Sprintf("Role must not include these permissions: ")

		cond := &policyv1alpha1.Condition{
			Type:               "completed",
			Status:             corev1.ConditionTrue,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s RBAC role updated and mismatch was fixed",
			Message:            message + myMessage,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role MismatchFixed", true)
	case "missingRole":
		cls := ""
		if rtValue.roleTemplate.ObjectMeta.ClusterName != "" {
			cls = fmt.Sprintf(" Cluster %v,", rtValue.roleTemplate.ObjectMeta.ClusterName)
		}
		message := fmt.Sprintf("Following roles must exist:%v %v", cls, rtValue.roleTemplate.Name)

		cond := &policyv1alpha1.Condition{
			Type:               "completed",
			Status:             corev1.ConditionTrue,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s RBAC role is missing",
			Message:            message + myMessage,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role", false)

	case "failedDeletingExtraRole":
		cls := ""
		if rtValue.roleTemplate.ObjectMeta.ClusterName != "" {
			cls = fmt.Sprintf(" Cluster %v,", rtValue.roleTemplate.ObjectMeta.ClusterName)
		}
		message := fmt.Sprintf("Following roles must not exist:%v %v", cls, rtValue.roleTemplate.Name)

		cond := &policyv1alpha1.Condition{
			Type:               "completed",
			Status:             corev1.ConditionTrue,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s RBAC extra role exists",
			Message:            message,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role", false)
	case "deletedExtraRole":
		cls := ""
		if rtValue.roleTemplate.ObjectMeta.ClusterName != "" {
			cls = fmt.Sprintf(" Cluster %v,", rtValue.roleTemplate.ObjectMeta.ClusterName)
		}
		message := fmt.Sprintf("Following roles must not exist:%v %v", cls, rtValue.roleTemplate.Name)

		cond := &policyv1alpha1.Condition{
			Type:               "completed",
			Status:             corev1.ConditionTrue,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s RBAC extra role",
			Message:            message,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role", true)

	case "ExtraRole":
		cls := ""
		if rtValue.roleTemplate.ObjectMeta.ClusterName != "" {
			cls = fmt.Sprintf(" Cluster %v,", rtValue.roleTemplate.ObjectMeta.ClusterName)
		}
		message := fmt.Sprintf("Following roles must not exist:%v %v", cls, rtValue.roleTemplate.Name)

		cond := &policyv1alpha1.Condition{
			Type:               "completed",
			Status:             corev1.ConditionTrue,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s RBAC extra role",
			Message:            message,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role", true)

	case "match":
		message := fmt.Sprintf("k8s RBAC role \"%v\" exists and matches", rtValue.roleTemplate.Name)

		cond := &policyv1alpha1.Condition{
			Type:               "completed",
			Status:             corev1.ConditionTrue,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s RBAC role matches",
			Message:            message,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role Matches", true)

	case "notExists":
		cls := ""
		if rtValue.roleTemplate.ObjectMeta.ClusterName != "" {
			cls = fmt.Sprintf(" Cluster %v,", rtValue.roleTemplate.ObjectMeta.ClusterName)
		}
		message := fmt.Sprintf("Following roles must exist:%v %v", cls, rtValue.roleTemplate.Name)

		cond := &policyv1alpha1.Condition{
			Type:               "completed",
			Status:             corev1.ConditionTrue,
			LastTransitionTime: metav1.Now(),
			Reason:             "K8s RBAC role doesn't exist",
			Message:            message,
		}
		conditions = AppendCondition(conditions, cond, "RBAC Role doesn't exist", true)

	}

	return conditions
}

// AppendCondition check and appends conditions
func AppendCondition(conditions []policyv1alpha1.Condition, newCond *policyv1alpha1.Condition, resourceType string, resolved ...bool) (conditionsRes []policyv1alpha1.Condition) {
	defer recoverFlow()
	lastIndex := len(conditions)
	if lastIndex > 0 {
		oldCond := conditions[lastIndex-1]
		if IsSimilarToLastCondition(oldCond, *newCond) {
			conditions[lastIndex-1] = *newCond
			return conditions
		}

	} else {
		conditions = append(conditions, *newCond)
		return conditions
	}
	conditions[lastIndex-1] = *newCond
	return conditions
}

// HandleCEMWebhookURL populate the webhook value from a CRD

// GetCEMWebhookURL populate the webhook value from a CRD
func GetCEMWebhookURL() (url string, err error) {

	if CemWebhookURL == "" {
		return "", fmt.Errorf("undefined CEM webhook: %s", CemWebhookURL)
	}
	return CemWebhookURL, nil
}

func triggerEvent(cond policyv1alpha1.Condition, resourceType string, resolved []bool) (res string, err error) {

	resolutionResult := false
	eventType := "notification"
	eventSeverity := "Normal"
	if len(resolved) > 0 {
		resolutionResult = resolved[0]
	}
	if !resolutionResult {
		eventSeverity = "Critical"
		eventType = "violation"
	}
	WebHookURL, err := GetCEMWebhookURL()
	if err != nil {
		return "", err
	}
	event := common.CEMEvent{
		Resource: common.Resource{
			Name:    "compliance-issue",
			Cluster: clusterName,
			Type:    resourceType,
		},
		Summary:    cond.Message,
		Severity:   eventSeverity,
		Timestamp:  cond.LastTransitionTime.String(),
		Resolution: resolutionResult,
		Sender: common.Sender{
			Name:    "MCM Policy Controller",
			Cluster: clusterName,
			Type:    "K8s controller",
		},
		Type: common.Type{
			StatusOrThreshold: cond.Reason,
			EventType:         eventType,
		},
	}
	payload, err := json.Marshal(event)
	if err != nil {
		return "", err
	}
	result, err := common.PostEvent(WebHookURL, payload)
	return result, err
}

//IsSimilarToLastCondition checks the diff, so that we don't keep updating with the same info
func IsSimilarToLastCondition(oldCond policyv1alpha1.Condition, newCond policyv1alpha1.Condition) bool {
	if reflect.DeepEqual(oldCond.Status, newCond.Status) &&
		reflect.DeepEqual(oldCond.Reason, newCond.Reason) &&
		reflect.DeepEqual(oldCond.Message, newCond.Message) &&
		reflect.DeepEqual(oldCond.Type, newCond.Type) {
		return true
	}
	return false
}

func getdesiredRules(rtValue policyv1alpha1.RoleTemplate) []rbacv1.PolicyRule {
	pr := []rbacv1.PolicyRule{}
	for _, rule := range rtValue.Rules {
		if strings.ToLower(string(rule.ComplianceType)) == strings.ToLower(string(policyv1alpha1.MustNotHave)) {
			glog.Infof("skipping a mustnothave rule")
			continue
		} else {
			pr = append(pr, rule.PolicyRule)
		}

	}
	return pr
}

func buildRole(ro roleOrigin) *rbacv1.Role {
	role := &rbacv1.Role{}
	role.Name = ro.roleTemplate.Name

	if ro.roleTemplate.Selector != nil {
		if ro.roleTemplate.Selector.MatchLabels != nil {
			role.Labels = ro.roleTemplate.Selector.MatchLabels
		}
	}

	role.Namespace = ro.namespace

	for _, rl := range ro.roleTemplate.Rules {
		if strings.ToLower(string(rl.ComplianceType)) != "mustnothave" {
			role.Rules = append(role.Rules, rl.PolicyRule)
			glog.V(2).Infof("adding rule: %v ", rl.PolicyRule)
		}
	}

	return role
}

func getRoleNames(list []rbacv1.Role) []string {
	roleNames := []string{}
	for _, n := range list {
		roleNames = append(roleNames, n.Name)
	}
	return roleNames
}

func createKeyValuePairs(m map[string]string) string {

	if m == nil {
		return ""
	}
	b := new(bytes.Buffer)
	for key, value := range m {
		fmt.Fprintf(b, "%s=%s,", key, value)
	}
	s := strings.TrimSuffix(b.String(), ",")
	return s
}

func listToRoleMap(rlist []rbacv1.Role) map[string]rbacv1.Role {
	roleMap := make(map[string]rbacv1.Role)
	for _, role := range rlist {
		roleN := []string{role.Name, role.Namespace}
		roleNamespace := strings.Join(roleN, "-")
		roleMap[roleNamespace] = role
	}
	return roleMap
}

func deepCompareRoles(desired, actual map[string]map[string]bool) bool {
	//Takes as input two roles, flattens them and returns a list of differences
	//This is doing a mustonlyhave comparison. I.e. it can detect if they are a match or not

	match := true
	if len(desired) != len(actual) {
		match = false
		glog.V(2).Infof(" the expected results and the actual results have different length")
	}
	if !match {
		return match
	}
	for key, desG := range desired {
		if _, ok := actual[key]; ok {
			// check the lenght of the maps
			if len(actual[key]) > len(desG) {
				glog.V(2).Infof("The verbs %v in actual , are MORE than the ones %v in desired ", actual[key], desG)
				match = false
			} else if len(actual[key]) < len(desG) {
				glog.V(2).Infof("The verbs %v in actual , are LESS than the ones %v in desired ", actual[key], desG)
				match = false
			}
			for keyVerb := range desG {
				if _, ok := actual[key][keyVerb]; ok {
					// the verb in desG exists in actual results
				} else {
					glog.V(2).Infof("The verb %s is not found in actual, when looking into key %s", keyVerb, key)
					match = false
				}
			}
		} else {
			// the key is not there, we have no match
			glog.V(4).Infof("The Key %s is not found in actual ", key)
			match = false
		}
	}
	return match
}

func compareRoleMustHave(desired map[string]map[string]bool, actual map[string]map[string]bool, compRes *roleCompareResult) (match bool, res *roleCompareResult) {
	match = true
	for key, desG := range desired {
		// must have it means that's the minimum set of rules the role must have, if a rule has more verbes that's ok
		if _, ok := actual[key]; ok {
			for keyVerb := range desG {
				if _, ok := actual[key][keyVerb]; ok {
					// the verb in desG exists in actualresults
				} else {
					//glog.V(2).Infof("INFORM:The verb %s is not found in actual, when looking into key %s", keyVerb, key)
					if compRes.missingVerbs == nil { //initialize the map
						compRes.missingVerbs = make(map[string]map[string]bool)
						if compRes.missingVerbs[key] == nil {
							compRes.missingVerbs[key] = make(map[string]bool)
						}
						compRes.missingVerbs[key][keyVerb] = false
					} else {
						if compRes.missingVerbs[key] == nil {
							compRes.missingVerbs[key] = make(map[string]bool)
						}
						compRes.missingVerbs[key][keyVerb] = false
					}
					match = false
				}
			}
		} else {
			if compRes.missingKeys == nil { //initialize the map
				compRes.missingKeys = make(map[string]map[string]bool)
				if compRes.missingKeys[key] == nil {
					compRes.missingKeys[key] = make(map[string]bool)
				}
				compRes.missingKeys[key] = desired[key]
			} else {
				if compRes.missingKeys[key] == nil {
					compRes.missingKeys[key] = make(map[string]bool)
				}
				compRes.missingKeys[key] = desired[key]
			}
			match = false
		}
	}
	return match, compRes
}

func compareRoleMustNotHave(desired map[string]map[string]bool, actual map[string]map[string]bool, compRes *roleCompareResult) (match bool, res *roleCompareResult) {
	match = true
	for key, desG := range desired {
		// must not have it means that's the set of rules the role NOT must have, if a rule has different verbes that's ok
		if _, ok := actual[key]; ok {
			glog.V(2).Infof("The Key %s is found in actual, and has a value of: %v ", key, actual)
			for keyVerb := range desG {
				if _, ok := actual[key][keyVerb]; ok {
					// the verb in desG exists in actualresults
					if compRes.AddtionalVerbs == nil { //initialize the map
						compRes.AddtionalVerbs = make(map[string]map[string]bool)
						if compRes.AddtionalVerbs[key] == nil {
							compRes.AddtionalVerbs[key] = make(map[string]bool)
						}
						compRes.AddtionalVerbs[key][keyVerb] = false
					} else {
						if compRes.AddtionalVerbs[key] == nil {
							compRes.AddtionalVerbs[key] = make(map[string]bool)
						}
						compRes.AddtionalVerbs[key][keyVerb] = false
					}
					match = false
				}
			}
		}
	}
	return match, compRes
}

func compareRoleMustOnlyHave(desired map[string]map[string]bool, actual map[string]map[string]bool, compRes *roleCompareResult) (match bool, res *roleCompareResult) {
	match = true
	for key, desG := range desired {
		// must have it means that's the minimum set of rules the role must have, if a rule has more verbes that's ok
		if _, ok := actual[key]; ok {
			for keyVerb := range desG {
				if _, ok := actual[key][keyVerb]; ok {
					// the verb in desG exists in actualresults
				} else {
					if compRes.missingVerbs == nil { //initialize the map
						compRes.missingVerbs = make(map[string]map[string]bool)
						if compRes.missingVerbs[key] == nil {
							compRes.missingVerbs[key] = make(map[string]bool)
						}
						compRes.missingVerbs[key][keyVerb] = false
					} else {
						if compRes.missingVerbs[key] == nil {
							compRes.missingVerbs[key] = make(map[string]bool)
						}
						compRes.missingVerbs[key][keyVerb] = false
					}
					match = false
				}
			}
		} else {
			if compRes.missingKeys == nil { //initialize the map
				compRes.missingKeys = make(map[string]map[string]bool)
				if compRes.missingKeys[key] == nil {
					compRes.missingKeys[key] = make(map[string]bool)
				}
				compRes.missingKeys[key] = desired[key]
			} else {
				if compRes.missingKeys[key] == nil {
					compRes.missingKeys[key] = make(map[string]bool)
				}
				compRes.missingKeys[key] = desired[key]
			}
			match = false
		}
	}

	// now we reverse the order

	for key, actl := range actual {
		// must have it means that's the minimum set of rules the role must have, if a rule has more verbes that's ok
		if _, ok := desired[key]; ok {
			for keyVerb := range actl {
				if _, ok := desired[key][keyVerb]; ok {
					// the verb in desG exists in actualresults
				} else {
					//glog.V(2).Infof("INFORM:The verb %s is not found in actual, when looking into key %s", keyVerb, key)
					if compRes.AddtionalVerbs == nil { //initialize the map
						compRes.AddtionalVerbs = make(map[string]map[string]bool)
						if compRes.AddtionalVerbs[key] == nil {
							compRes.AddtionalVerbs[key] = make(map[string]bool)
						}
						compRes.AddtionalVerbs[key][keyVerb] = false
					} else {
						if compRes.AddtionalVerbs[key] == nil {
							compRes.AddtionalVerbs[key] = make(map[string]bool)
						}
						compRes.AddtionalVerbs[key][keyVerb] = false
					}
					match = false
				}
			}
		} else {
			//the key exists in actual, but does not exist in desired, i.e. mustonlyhave
			//based on the latest discussion with Kuan, we will allow this to exist, if we change this, and instead not allow any other keys to exist,
			//we can uncomment the code below
			/*
				if compRes.AdditionalKeys == nil { //initialize the map
					compRes.AdditionalKeys = make(map[string]map[string]bool)
					if compRes.AdditionalKeys[key] == nil {
						compRes.AdditionalKeys[key] = make(map[string]bool)
					}
					compRes.AdditionalKeys[key] = actual[key]
				} else {
					if compRes.AdditionalKeys[key] == nil {
						compRes.AdditionalKeys[key] = make(map[string]bool)
					}
					compRes.AdditionalKeys[key] = actual[key]
				}
				match = false
			*/

		}
	}
	return match, compRes
}

func deepCompareRoleTtoRole(desired map[string]map[string]map[string]bool, actual map[string]map[string]bool) (match bool, res *roleCompareResult) {
	/*TODO: consider the ["*"] case. e.g.
		apiGroups: ["*"]
	  	resources: ["*"]
	  	verbs: ["*"]
	*/
	matched := true
	compRes := &roleCompareResult{}
	//result := []string{}
	if desired["musthave"] != nil && len(desired["musthave"]) > 0 {
		match, compRes = compareRoleMustHave(desired["musthave"], actual, compRes)
		if !match {
			matched = false
		}
	}
	if desired["mustnothave"] != nil && len(desired["mustnothave"]) > 0 {
		match, compRes = compareRoleMustNotHave(desired["mustnothave"], actual, compRes)
		if !match {
			matched = false
		}
	}
	if desired["mustonlyhave"] != nil && len(desired["mustonlyhave"]) > 0 {
		match, compRes = compareRoleMustOnlyHave(desired["mustonlyhave"], actual, compRes)
		if !match {
			matched = false
		}
	}

	return matched, compRes
}

/*
func deepCompareRoleTtoRole(desired map[string]map[string]map[string]bool, actual map[string]map[string]bool) (match bool, res []string) {
	/*TODO: consider the ["*"] case. e.g.
		apiGroups: ["*"]
	  	resources: ["*"]
	  	verbs: ["*"]

	result := []string{}
	match = true

	for key, desG := range desired["musthave"] {
		missingKeys := ""
		// must have it means that's the minimum set of rules the role must have, it a rule has more verbes that's ok
		if _, ok := actual[key]; ok {
			// check the lenght of the maps
			if len(actual[key]) < len(desG) {
				glog.V(2).Infof("INFORM:The verbs %v in actual , are LESS than the ones %v in desired ", actual[key], desG)
				match = false
			}
			missingVerbs := ""
			for keyVerb := range desG {
				if _, ok := actual[key][keyVerb]; ok {
					// the verb in desG exists in actualresults
				} else {
					glog.V(2).Infof("INFORM:The verb %s is not found in actual, when looking into key %s", keyVerb, key)
					if missingVerbs == "" {
						missingVerbs += keyVerb
					} else {
						missingVerbs += keyVerb + ", "
					}

					match = false
				}
			}
			missingVerbs = fmt.Sprintf("the resource.apiGroup %s is missing these must have verbs: ", key) + missingVerbs
			result = append(result, missingVerbs)
		} else {
			if missingKeys == "" {
				missingKeys += fmt.Sprintf("%s and its verbs %v", key, desired["musthave"][key])

			} else {
				missingKeys += fmt.Sprintf("%s and its verbs %v, ", key, desired["musthave"][key])
			}
			result = append(result, missingKeys)
			match = false
		}
	}
	for key, desG := range desired["mustnothave"] {
		// must have it means that's the minimum set of rules the role must have, it a rule has more verbes that's ok
		if _, ok := actual[key]; ok {
			additionalVerbs := ""
			for keyVerb := range desG {
				if _, ok := actual[key][keyVerb]; ok {
					// the verb in desG exists in actualresults
					glog.V(2).Infof("INFORM:The verb %s is found in actual, when looking into key %s", keyVerb, key)
					if additionalVerbs == "" {
						additionalVerbs += keyVerb
					} else {
						additionalVerbs += keyVerb + ", "
					}
					match = false

				} else {
					//we are good
				}
			}
			additionalVerbs = fmt.Sprintf("the resource.apiGroup %s has the following must NOT have verbs: ", key) + additionalVerbs
			result = append(result, additionalVerbs)
		} else {
			//actual does not have this "mustnothave" key, so we are good

		}
	}
	//The must only have part


	for key, desG := range desired["mustonlyhave"] {
		missingVerbs, missingKeys := "", ""
		if _, ok := actual[key]; ok {
			// the desired key exist, now we should check the verbs
			for keyVerb := range desG {
				if _, ok := actual[key][keyVerb]; ok {
					// the verb in desG exists in actual
				} else {
					glog.V(2).Infof("INFORM:The verb %s is not found in actual, when looking into key %s", keyVerb, key)
					match = false
					if missingVerbs == "" {
						missingVerbs += keyVerb
					} else {
						missingVerbs += keyVerb + ", "
					}
				}
			}
			result = append(result, missingVerbs)
		} else {
			//the mustonlyhave key is missing
			if missingKeys == "" {
				missingKeys += fmt.Sprintf("%s and its verbs %v", key, desired["musthave"][key])

			} else {
				missingKeys += fmt.Sprintf("%s and its verbs %v, ", key, desired["musthave"][key])
			}
			result = append(result, missingKeys)
			match = false
		}
	}
	//now we reverse the order and check the additional key that should exist

	for key, actl := range actual {
		additionalVerbs, additionalKeys := "", ""
		if _, ok := desired["mustonlyhave"][key]; ok {
			// the desired key exist, now we should check the verbs
			for keyVerb := range actl {
				if _, ok := desired["mustonlyhave"][key][keyVerb]; ok {
					// the verb in actual exists in desired
				} else {
					glog.V(2).Infof("INFORM:The verb %s is not found in desired mustonlyhave, when looking into key %s", keyVerb, key)

					if additionalVerbs == "" {
						additionalVerbs += keyVerb
					} else {
						additionalVerbs += keyVerb + ", "
					}
					match = false
				}
			}
			result = append(result, additionalVerbs)
		} else {
			//the mustonlyhave key is missing
			if additionalKeys == "" {
				additionalKeys += fmt.Sprintf("%s and its verbs %v", key, desired["musthave"][key])

			} else {
				additionalKeys += fmt.Sprintf("%s and its verbs %v, ", key, desired["musthave"][key])
			}
			result = append(result, additionalKeys)
			match = false
		}
	}
	return false, result
}
*/

func flattenRole(role rbacv1.Role) map[string]map[string]bool {
	//takes as input a role, and flattens it out
	//from a role we create a map of apigroups. each group has a map of resources, each resource has a map of verbs
	flat := make(map[string]map[string]bool)

	//run thru the roles apigroup and resource and generate a combination
	for _, rule := range role.Rules {
		for _, apiG := range rule.APIGroups {
			for _, res := range rule.Resources {
				key := fmt.Sprintf("%s.%s", res, apiG)
				if _, ok := flat[key]; !ok {
					//the key does not exist, add it
					flat[key] = make(map[string]bool)
				}
				for _, verb := range rule.Verbs {
					if _, ok := flat[key][verb]; ok {
						//the verbs exist
					} else {
						// the verb does not exist
						flat[key][verb] = true
					}
				}
			}
		}
	}
	return flat
}

func validateRoleTemplate(flatT map[string]map[string]map[string]bool) (reason []string, valid bool) {
	isValid := true
	allReasons := []string{}
	if flatT["musthave"] != nil {
		if flatT["mustnothave"] != nil {
			for key, value := range flatT["musthave"] {
				if _, ok := flatT["mustnothave"][key]; ok { //key exists in mustnothave, compare the verbs
					for k := range value {
						if _, ok := flatT["mustnothave"][key][k]; ok {
							//the same verb exists in both must and mustnot so it is not valid
							reason := fmt.Sprintf("`mustnothave` rule includes the verb `%v` in the key `%v` which is specifed as `musthave`;", k, key)
							isValid = false
							allReasons = append(allReasons, reason)

						}
					}
				}
				if _, ok := flatT["mustonlyhave"][key]; ok { //key exists in mustnothave, compare the verbs
					for k := range value {
						if _, ok := flatT["mustonlyhave"][key][k]; !ok {
							//the verb does NOT exist in mustonlyhave so it is invalid
							reason := fmt.Sprintf("`mustonlyhave` rule does NOT includes the verb `%v` in the key `%v` which is specifed as `musthave`;", k, key)
							isValid = false
							allReasons = append(allReasons, reason)

						}
					}
				}
			}
		}
	}
	//go over the mustnothave
	if flatT["mustnothave"] != nil {
		for key, value := range flatT["mustnothave"] {
			if _, ok := flatT["mustonlyhave"][key]; ok { //key exists in mustnothave, compare the verbs
				for k := range value {
					if _, ok := flatT["mustonlyhave"][key][k]; ok {
						//the same verb exists in both mustonly and mustnot so it is invalid
						reason := fmt.Sprintf("`mustonlyhave` rule includes the verb `%v` in the key `%v` which is specifed as `mustnothave`;", k, key)
						isValid = false
						allReasons = append(allReasons, reason)

					}
				}
			}
		}
	}

	return allReasons, isValid
}

func flattenRoleTemplate(roleT policyv1alpha1.RoleTemplate) map[string]map[string]map[string]bool {
	//TODO make sure the verbs in mustnothave are not present in the musthave for the same keys
	//TODO make sure that the keys in mustonlyhave are deleted from the musthave and mustnothave
	flatT := make(map[string]map[string]map[string]bool)
	//run thru the roles template apigroup and resource and generate a combination
	for _, rule := range roleT.Rules {
		switch strings.ToLower(string(rule.ComplianceType)) {
		case strings.ToLower(string(policyv1alpha1.MustHave)):
			if flatT["musthave"] == nil {
				flatT["musthave"] = make(map[string]map[string]bool)
			}
			for _, apiG := range rule.PolicyRule.APIGroups {
				for _, res := range rule.PolicyRule.Resources {
					key := fmt.Sprintf("%s.%s", res, apiG)
					if _, ok := flatT["musthave"][key]; !ok {
						//the key does not exist, add it
						flatT["musthave"][key] = make(map[string]bool)
					}
					for _, verb := range rule.PolicyRule.Verbs {
						if _, ok := flatT["musthave"][key][verb]; ok {
							//the verbs exist
						} else {
							// the verb does not exist
							flatT["musthave"][key][verb] = true
						}
					}
				}
			}
		case strings.ToLower(string(policyv1alpha1.MustNotHave)):
			if flatT["mustnothave"] == nil {
				flatT["mustnothave"] = make(map[string]map[string]bool)
			}
			for _, apiG := range rule.PolicyRule.APIGroups {
				for _, res := range rule.PolicyRule.Resources {
					key := fmt.Sprintf("%s.%s", res, apiG)
					if _, ok := flatT["mustnothave"][key]; !ok {
						//the key does not exist, add it
						flatT["mustnothave"][key] = make(map[string]bool)
					}
					for _, verb := range rule.PolicyRule.Verbs {
						if _, ok := flatT["mustnothave"][key][verb]; ok {
							//the verbs exist
						} else {
							// the verb does not exist
							flatT["mustnothave"][key][verb] = true
						}
					}
				}
			}
		case strings.ToLower(string(policyv1alpha1.MustOnlyHave)):
			if flatT["mustonlyhave"] == nil {
				flatT["mustonlyhave"] = make(map[string]map[string]bool)
			}
			for _, apiG := range rule.PolicyRule.APIGroups {
				for _, res := range rule.PolicyRule.Resources {
					key := fmt.Sprintf("%s.%s", res, apiG)
					if _, ok := flatT["mustonlyhave"][key]; !ok {
						//the key does not exist, add it
						flatT["mustonlyhave"][key] = make(map[string]bool)
					}
					for _, verb := range rule.PolicyRule.Verbs {
						if _, ok := flatT["mustonlyhave"][key][verb]; ok {
							//the verbs exist
						} else {
							// the verb does not exist
							flatT["mustonlyhave"][key][verb] = true
						}
					}
				}
			}
		}
	}
	return flatT
}

func prettyPrint(res roleCompareResult, roleTName string) string {
	message := fmt.Sprintf("the role \" %v \" has ", roleTName)
	if len(res.missingKeys) > 0 {
		missingKeys := "missing keys: "
		for key, value := range res.missingKeys {

			missingKeys += (key + "{")
			for k := range value {
				missingKeys += (k + ",")
			}
			missingKeys = strings.TrimSuffix(missingKeys, ",")
			missingKeys += "} "
		}
		message += missingKeys
	}
	if len(res.missingVerbs) > 0 {
		missingVerbs := "missing verbs: "
		for key, value := range res.missingVerbs {

			missingVerbs += (key + "{")
			for k := range value {
				missingVerbs += (k + ",")
			}
			missingVerbs = strings.TrimSuffix(missingVerbs, ",")
			missingVerbs += "} "
		}
		message += missingVerbs
	}
	if len(res.AdditionalKeys) > 0 {
		AdditionalKeys := "additional keys: "
		for key, value := range res.AdditionalKeys {

			AdditionalKeys += (key + "{")
			for k := range value {
				AdditionalKeys += (k + ",")
			}
			AdditionalKeys = strings.TrimSuffix(AdditionalKeys, ",")
			AdditionalKeys += "} "
		}
		message += AdditionalKeys
	}
	if len(res.AddtionalVerbs) > 0 {
		AddtionalVerbs := "additional verbs: "
		for key, value := range res.AddtionalVerbs {

			AddtionalVerbs += (key + "{")
			for k := range value {
				AddtionalVerbs += (k + ",")
			}
			AddtionalVerbs = strings.TrimSuffix(AddtionalVerbs, ",")
			AddtionalVerbs += "} "
		}
		message += AddtionalVerbs
	}

	return message
}

func recoverFlow() {
	if r := recover(); r != nil {
		fmt.Println("ALERT!!!! -> recovered from ", r)
	}
}
