package opaprocessor

import (
	"github.com/armosec/kubescape/v2/core/cautils"
	logger "github.com/dwertent/go-logger"

	"github.com/armosec/k8s-interface/k8sinterface"
	"github.com/armosec/k8s-interface/workloadinterface"
	"github.com/armosec/opa-utils/reporthandling"
	"github.com/armosec/opa-utils/reporthandling/apis"
	"github.com/armosec/opa-utils/reporthandling/results/v1/reportsummary"
	resources "github.com/armosec/opa-utils/resources"
)

// updateResults update the results objects and report objects. This is a critical function - DO NOT CHANGE
/*
	- remove sensible data
	- adding exceptions
	- summarize results
*/
func (opap *OPAProcessor) updateResults() {

	// remove data from all objects
	for i := range opap.AllResources {
		removeData(opap.AllResources[i])
	}

	// set exceptions
	for i := range opap.ResourcesResult {

		t := opap.ResourcesResult[i]

		// first set exceptions
		if resource, ok := opap.AllResources[i]; ok {
			t.SetExceptions(resource, opap.Exceptions, cautils.ClusterName)
		}

		// summarize the resources
		opap.Report.AppendResourceResultToSummary(&t)

		// Add score
		// TODO

		// save changes
		opap.ResourcesResult[i] = t
	}

	// set result summary
	// map control to error
	controlToInfoMap := mapControlToInfo(opap.ResourceToControlsMap, opap.InfoMap, opap.Report.SummaryDetails.Controls)
	opap.Report.SummaryDetails.InitResourcesSummary(controlToInfoMap)
	// for f := range opap.PostureReport.FrameworkReports {
	// 	// set exceptions
	// 	exceptions.SetFrameworkExceptions(&opap.PostureReport.FrameworkReports[f], opap.Exceptions, cautils.ClusterName)

	// 	// set counters
	// 	reporthandling.SetUniqueResourcesCounter(&opap.PostureReport.FrameworkReports[f])

	// 	// set default score
	// 	// reporthandling.SetDefaultScore(&opap.PostureReport.FrameworkReports[f])
	// }
}

func mapControlToInfo(mapResourceToControls map[string][]string, infoMap map[string]apis.StatusInfo, controlSummary reportsummary.ControlSummaries) map[string]apis.StatusInfo {
	controlToInfoMap := make(map[string]apis.StatusInfo)
	for resource, statusInfo := range infoMap {
		controlIDs := mapResourceToControls[resource]
		for _, controlID := range controlIDs {
			ctrl := controlSummary.GetControl(reportsummary.EControlCriteriaID, controlID)
			if ctrl != nil {
				resources := ctrl.NumberOfResources()
				// Check that there are no K8s resources too
				if isEmptyResources(resources) {
					controlToInfoMap[controlID] = statusInfo
				}
			}

		}
	}
	return controlToInfoMap
}

func isEmptyResources(counters reportsummary.ICounters) bool {
	return counters.Failed() == 0 && counters.Excluded() == 0 && counters.Passed() == 0
}

func getAllSupportedObjects(k8sResources *cautils.K8SResources, armoResources *cautils.ArmoResources, allResources map[string]workloadinterface.IMetadata, rule *reporthandling.PolicyRule) []workloadinterface.IMetadata {
	k8sObjects := []workloadinterface.IMetadata{}
	k8sObjects = append(k8sObjects, getKubernetesObjects(k8sResources, allResources, rule.Match)...)
	k8sObjects = append(k8sObjects, getArmoObjects(armoResources, allResources, rule.DynamicMatch)...)
	return k8sObjects
}

func getArmoObjects(k8sResources *cautils.ArmoResources, allResources map[string]workloadinterface.IMetadata, match []reporthandling.RuleMatchObjects) []workloadinterface.IMetadata {
	k8sObjects := []workloadinterface.IMetadata{}

	for m := range match {
		for _, groups := range match[m].APIGroups {
			for _, version := range match[m].APIVersions {
				for _, resource := range match[m].Resources {
					groupResources := k8sinterface.ResourceGroupToString(groups, version, resource)
					for _, groupResource := range groupResources {
						if k8sObj, ok := (*k8sResources)[groupResource]; ok {
							// if k8sObj == nil {
							// 	logger.L().Debug(fmt.Sprintf("resource '%s' is nil, probably failed to pull the resource", groupResource))
							// }
							for i := range k8sObj {
								k8sObjects = append(k8sObjects, allResources[k8sObj[i]])
							}
						}
					}
				}
			}
		}
	}

	return filterOutChildResources(k8sObjects, match)
}

func getKubernetesObjects(k8sResources *cautils.K8SResources, allResources map[string]workloadinterface.IMetadata, match []reporthandling.RuleMatchObjects) []workloadinterface.IMetadata {
	k8sObjects := []workloadinterface.IMetadata{}

	for m := range match {
		for _, groups := range match[m].APIGroups {
			for _, version := range match[m].APIVersions {
				for _, resource := range match[m].Resources {
					groupResources := k8sinterface.ResourceGroupToString(groups, version, resource)
					for _, groupResource := range groupResources {
						if k8sObj, ok := (*k8sResources)[groupResource]; ok {
							if k8sObj == nil {
								// logger.L().Debug("skipping", helpers.String("resource", groupResource))
							}
							for i := range k8sObj {
								k8sObjects = append(k8sObjects, allResources[k8sObj[i]])
							}
						}
					}
				}
			}
		}
	}

	return filterOutChildResources(k8sObjects, match)
}

// filterOutChildResources filter out child resources if the parent resource is in the list
func filterOutChildResources(objects []workloadinterface.IMetadata, match []reporthandling.RuleMatchObjects) []workloadinterface.IMetadata {
	response := []workloadinterface.IMetadata{}
	owners := []string{}
	for m := range match {
		for i := range match[m].Resources {
			owners = append(owners, match[m].Resources[i])
		}
	}
	for i := range objects {
		if !k8sinterface.IsTypeWorkload(objects[i].GetObject()) {
			response = append(response, objects[i])
			continue
		}
		w := workloadinterface.NewWorkloadObj(objects[i].GetObject())
		ownerReferences, err := w.GetOwnerReferences()
		if err != nil || len(ownerReferences) == 0 {
			response = append(response, w)
		} else if !k8sinterface.IsStringInSlice(owners, ownerReferences[0].Kind) {
			response = append(response, w)
		}
	}
	return response
}
func getRuleDependencies() (map[string]string, error) {
	modules := resources.LoadRegoModules()
	if len(modules) == 0 {
		logger.L().Warning("failed to load rule dependencies")
	}
	return modules, nil
}

func removeData(obj workloadinterface.IMetadata) {
	if !k8sinterface.IsTypeWorkload(obj.GetObject()) {
		return // remove data only from kubernetes objects
	}
	workload := workloadinterface.NewWorkloadObj(obj.GetObject())
	switch workload.GetKind() {
	case "Secret":
		removeSecretData(workload)
	case "ConfigMap":
		removeConfigMapData(workload)
	default:
		removePodData(workload)
	}
}

func removeConfigMapData(workload workloadinterface.IWorkload) {
	workload.RemoveAnnotation("kubectl.kubernetes.io/last-applied-configuration")
	workloadinterface.RemoveFromMap(workload.GetObject(), "metadata", "managedFields")
	overrideSensitiveData(workload)
}

func overrideSensitiveData(workload workloadinterface.IWorkload) {
	dataInterface, ok := workloadinterface.InspectMap(workload.GetObject(), "data")
	if ok {
		data, ok := dataInterface.(map[string]interface{})
		if ok {
			for key := range data {
				workloadinterface.SetInMap(workload.GetObject(), []string{"data"}, key, "XXXXXX")
			}
		}
	}
}

func removeSecretData(workload workloadinterface.IWorkload) {
	workload.RemoveAnnotation("kubectl.kubernetes.io/last-applied-configuration")
	workloadinterface.RemoveFromMap(workload.GetObject(), "metadata", "managedFields")
	overrideSensitiveData(workload)
}
func removePodData(workload workloadinterface.IWorkload) {
	workload.RemoveAnnotation("kubectl.kubernetes.io/last-applied-configuration")
	workloadinterface.RemoveFromMap(workload.GetObject(), "metadata", "managedFields")
	workloadinterface.RemoveFromMap(workload.GetObject(), "status")

	containers, err := workload.GetContainers()
	if err != nil || len(containers) == 0 {
		return
	}
	for i := range containers {
		for j := range containers[i].Env {
			containers[i].Env[j].Value = "XXXXXX"
		}
	}
	workloadinterface.SetInMap(workload.GetObject(), workloadinterface.PodSpec(workload.GetKind()), "containers", containers)
}

func ruleData(rule *reporthandling.PolicyRule) string {
	return rule.Rule
}

func ruleEnumeratorData(rule *reporthandling.PolicyRule) string {
	return rule.ResourceEnumerator
}
