/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package util

import (
	policyv1 "k8s.io/api/policy/v1"
	"reflect"
	"strconv"
	"strings"
	"time"

	"github.com/go-logr/logr"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	netv1 "k8s.io/api/networking/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
	"sigs.k8s.io/controller-runtime/pkg/reconcile"
)

var (
	SecretReadOnlyPermissions int32 = 440
	PublicReadOnlyPermissions int32 = 444
)

func Replicas(num int) *int32 {
	num32 := int32(num)
	return &num32
}

// Set the requeueAfter if it has not been set, or is greater than the new time to requeue at
func updateRequeueAfter(requeueOrNot *reconcile.Result, newWait time.Duration) {
	if requeueOrNot.RequeueAfter <= 0 || requeueOrNot.RequeueAfter > newWait {
		requeueOrNot.RequeueAfter = newWait
	}
}

// CopyLabelsAndAnnotations copies the labels and annotations from one object to another.
// Additional Labels and Annotations in the 'to' object will not be removed.
// Returns true if there are updates required to the object.
func CopyLabelsAndAnnotations(from, to *metav1.ObjectMeta, logger logr.Logger) (requireUpdate bool) {
	if len(to.Labels) == 0 && len(from.Labels) > 0 {
		to.Labels = make(map[string]string, len(from.Labels))
	}
	for k, v := range from.Labels {
		if to.Labels[k] != v {
			requireUpdate = true
			logger.Info("Update Label", "label", k, "newValue", v, "oldValue", to.Labels[k])
			to.Labels[k] = v
		}
	}

	if len(to.Annotations) == 0 && len(from.Annotations) > 0 {
		to.Annotations = make(map[string]string, len(from.Annotations))
	}
	for k, v := range from.Annotations {
		if to.Annotations[k] != v {
			requireUpdate = true
			logger.Info("Update Annotation", "annotation", k, "newValue", v, "oldValue", to.Annotations[k])
			to.Annotations[k] = v
		}
	}

	return requireUpdate
}

func DuplicateLabelsOrAnnotations(from map[string]string) map[string]string {
	to := make(map[string]string, len(from))
	for k, v := range from {
		to[k] = v
	}
	return to
}

func MergeLabelsOrAnnotations(base, additional map[string]string) map[string]string {
	merged := DuplicateLabelsOrAnnotations(base)
	for k, v := range additional {
		if _, alreadyExists := merged[k]; !alreadyExists {
			merged[k] = v
		}
	}
	return merged
}

// DeepEqualWithNils returns a deepEquals call that treats nil and zero-length maps, arrays and slices as the same.
func DeepEqualWithNils(x, y interface{}) bool {
	if (x == nil) != (y == nil) {
		// Make sure that x is not the nil value
		if x == nil {
			x = y
		}
		v := reflect.ValueOf(x)
		switch v.Kind() {
		case reflect.Array:
		case reflect.Map:
		case reflect.Slice:
			return v.Len() == 0
		}
	}
	return reflect.DeepEqual(x, y)
}

// ContainsString helper function to test string contains
func ContainsString(slice []string, s string) bool {
	for _, item := range slice {
		if item == s {
			return true
		}
	}
	return false
}

// RemoveString helper function to remove string
func RemoveString(slice []string, s string) (result []string) {
	for _, item := range slice {
		if item == s {
			continue
		}
		result = append(result, item)
	}
	return
}

// IsPVCOrphan determines whether the given name represents a PVC that is an orphan, or no longer has a pod associated with it.
func IsPVCOrphan(pvcName string, replicas int32) bool {
	index := strings.LastIndexAny(pvcName, "-")
	if index == -1 {
		return false
	}

	ordinal, err := strconv.Atoi(pvcName[index+1:])
	if err != nil {
		return false
	}

	return int32(ordinal) >= replicas
}

// customizeProbe builds the probe logic used for pod liveness, readiness, startup checks
func customizeProbe(initialProbe *corev1.Probe, customProbe corev1.Probe) *corev1.Probe {
	if customProbe.InitialDelaySeconds != 0 {
		initialProbe.InitialDelaySeconds = customProbe.InitialDelaySeconds
	}

	if customProbe.TimeoutSeconds != 0 {
		initialProbe.TimeoutSeconds = customProbe.TimeoutSeconds
	}

	if customProbe.SuccessThreshold != 0 {
		initialProbe.SuccessThreshold = customProbe.SuccessThreshold
	}

	if customProbe.FailureThreshold != 0 {
		initialProbe.FailureThreshold = customProbe.FailureThreshold
	}

	if customProbe.PeriodSeconds != 0 {
		initialProbe.PeriodSeconds = customProbe.PeriodSeconds
	}

	if customProbe.ProbeHandler.Exec != nil || customProbe.ProbeHandler.HTTPGet != nil || customProbe.ProbeHandler.TCPSocket != nil {
		initialProbe.ProbeHandler = customProbe.ProbeHandler
	}

	return initialProbe
}

// CopyConfigMapFields copies the owned fields from one ConfigMap to another
func CopyConfigMapFields(from, to *corev1.ConfigMap, logger logr.Logger) bool {
	logger = logger.WithValues("kind", "configMap")
	requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger)

	// Don't copy the entire Spec, because we can't overwrite the clusterIp field

	if !DeepEqualWithNils(to.Data, from.Data) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Data", "from", to.Data, "to", from.Data)
	}
	to.Data = from.Data

	return requireUpdate
}

// CopyServiceFields copies the owned fields from one Service to another
func CopyServiceFields(from, to *corev1.Service, logger logr.Logger) bool {
	logger = logger.WithValues("kind", "service")
	requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger)

	// Don't copy the entire Spec, because we can't overwrite the clusterIp field

	if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.Selector", "from", to.Spec.Selector, "to", from.Spec.Selector)
	}
	to.Spec.Selector = from.Spec.Selector

	if !DeepEqualWithNils(to.Spec.Ports, from.Spec.Ports) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.Ports", "from", to.Spec.Ports, "to", from.Spec.Ports)
	}
	to.Spec.Ports = from.Spec.Ports

	if !DeepEqualWithNils(to.Spec.ExternalName, from.Spec.ExternalName) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.ExternalName", "from", to.Spec.ExternalName, "to", from.Spec.ExternalName)
	}
	to.Spec.ExternalName = from.Spec.ExternalName

	if !DeepEqualWithNils(to.Spec.PublishNotReadyAddresses, from.Spec.PublishNotReadyAddresses) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.PublishNotReadyAddresses", "from", to.Spec.PublishNotReadyAddresses, "to", from.Spec.PublishNotReadyAddresses)
	}
	to.Spec.PublishNotReadyAddresses = from.Spec.PublishNotReadyAddresses

	return requireUpdate
}

// CopyIngressFields copies the owned fields from one Ingress to another
func CopyIngressFields(from, to *netv1.Ingress, logger logr.Logger) bool {
	logger = logger.WithValues("kind", "ingress")
	requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger)

	if len(to.Spec.Rules) != len(from.Spec.Rules) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.Rules", "from", to.Spec.Rules, "to", from.Spec.Rules)
		to.Spec.Rules = from.Spec.Rules
	} else {
		for i := range from.Spec.Rules {
			ruleBase := "Spec.Rules[" + strconv.Itoa(i) + "]."
			fromRule := &from.Spec.Rules[i]
			toRule := &to.Spec.Rules[i]

			if !DeepEqualWithNils(toRule.Host, fromRule.Host) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", ruleBase+"Host", "from", toRule.Host, "to", fromRule.Host)
				toRule.Host = fromRule.Host
			}

			if fromRule.HTTP == nil || toRule.HTTP == nil {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", ruleBase+"HTTP", "from", toRule.HTTP, "to", fromRule.HTTP)
				toRule.HTTP = fromRule.HTTP
			} else if len(fromRule.HTTP.Paths) != len(toRule.HTTP.Paths) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", ruleBase+"HTTP.Paths", "from", toRule.HTTP.Paths, "to", fromRule.HTTP.Paths)
				toRule.HTTP.Paths = fromRule.HTTP.Paths
			} else {
				for j := range fromRule.HTTP.Paths {
					pathBase := ruleBase + "HTTP.Paths[" + strconv.Itoa(j) + "]."
					fromPath := &fromRule.HTTP.Paths[j]
					toPath := &toRule.HTTP.Paths[j]

					if toPath.PathType != nil && !DeepEqualWithNils(toPath.PathType, fromPath.PathType) {
						requireUpdate = true
						logger.Info("Update required because field changed", "field", pathBase+"PathType", "from", toPath.PathType, "to", fromPath.PathType)
						toPath.PathType = fromPath.PathType
					}

					if !DeepEqualWithNils(toPath.Path, fromPath.Path) {
						requireUpdate = true
						logger.Info("Update required because field changed", "field", pathBase+"Path", "from", toPath.Path, "to", fromPath.Path)
						toPath.Path = fromPath.Path
					}

					if !DeepEqualWithNils(toPath.Backend.Service, fromPath.Backend.Service) {
						requireUpdate = true
						logger.Info("Update required because field changed", "field", pathBase+"Backend.Service", "from", toPath.Backend.Service, "to", fromPath.Backend.Service)
						toPath.Backend.Service = fromPath.Backend.Service
					}

					if !DeepEqualWithNils(toPath.Backend.Resource, fromPath.Backend.Resource) {
						requireUpdate = true
						logger.Info("Update required because field changed", "field", pathBase+"Backend.Resource", "from", toPath.Backend.Resource, "to", fromPath.Backend.Resource)
						toPath.Backend.Resource = fromPath.Backend.Resource
					}
				}
			}
		}
	}

	if !DeepEqualWithNils(to.Spec.TLS, from.Spec.TLS) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.TLS", "from", to.Spec.TLS, "to", from.Spec.TLS)
		to.Spec.TLS = from.Spec.TLS
	}

	if from.Spec.IngressClassName != nil && !DeepEqualWithNils(to.Spec.IngressClassName, from.Spec.IngressClassName) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.IngressClassName", "from", to.Spec.IngressClassName, "to", from.Spec.IngressClassName)
		to.Spec.IngressClassName = from.Spec.IngressClassName
	}

	return requireUpdate
}

// CopyStatefulSetFields copies the owned fields from one StatefulSet to another
// Returns true if the fields copied from don't match to.
func CopyStatefulSetFields(from, to *appsv1.StatefulSet, logger logr.Logger) bool {
	logger = logger.WithValues("kind", "statefulSet")
	requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger)

	if !DeepEqualWithNils(to.Spec.Replicas, from.Spec.Replicas) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.Replicas", "from", to.Spec.Replicas, "to", from.Spec.Replicas)
		to.Spec.Replicas = from.Spec.Replicas
	}

	if !DeepEqualWithNils(to.Spec.UpdateStrategy, from.Spec.UpdateStrategy) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.UpdateStrategy", "from", to.Spec.UpdateStrategy, "to", from.Spec.UpdateStrategy)
		to.Spec.UpdateStrategy = from.Spec.UpdateStrategy
	}

	/*
			Kubernetes does not currently support updates to these fields: Selector and PodManagementPolicy

		if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) {
			requireUpdate = true
			logger.Info("Update required because field changed", "field", "Spec.Selector", "from", to.Spec.Selector, "to", from.Spec.Selector)
			to.Spec.Selector = from.Spec.Selector
		}

		if !DeepEqualWithNils(to.Spec.PodManagementPolicy, from.Spec.PodManagementPolicy) {
			requireUpdate = true
			logger.Info("Update required because field changed", "field", "Spec.PodManagementPolicy", "from", to.Spec.PodManagementPolicy, "to", from.Spec.PodManagementPolicy)
			to.Spec.PodManagementPolicy = from.Spec.PodManagementPolicy
		}
	*/

	/*
			Kubernetes does not support modification of VolumeClaimTemplates currently. See:
		    https://github.com/kubernetes/enhancements/issues/661

		if len(from.Spec.VolumeClaimTemplates) > len(to.Spec.VolumeClaimTemplates) {
			requireUpdate = true
			logger.Info("Update required because field changed", "field", "Spec.VolumeClaimTemplates", "from", to.Spec.VolumeClaimTemplates, "to", from.Spec.VolumeClaimTemplates)
			to.Spec.VolumeClaimTemplates = from.Spec.VolumeClaimTemplates
		}
		for i := range from.Spec.VolumeClaimTemplates {
			vctBase := "Spec.VolumeClaimTemplates["+strconv.Itoa(i)+"]."
			fromVct := &from.Spec.VolumeClaimTemplates[i]
			toVct := &to.Spec.VolumeClaimTemplates[i]
			if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Name, fromVct.Name) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", vctBase+"Name", "from", toVct.Name, "to", fromVct.Name)
				toVct.Name = fromVct.Name
			}
			if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Labels, fromVct.Labels) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", vctBase+"Labels", "from", toVct.Labels, "to", fromVct.Labels)
				toVct.Labels = fromVct.Labels
			}
			if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Annotations, fromVct.Annotations) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", vctBase+"Annotations", "from", toVct.Annotations, "to", fromVct.Annotations)
				toVct.Annotations = fromVct.Annotations
			}
			if !DeepEqualWithNils(to.Spec.VolumeClaimTemplates[i].Spec, fromVct.Spec) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", vctBase+"Spec", "from", toVct.Spec, "to", fromVct.Spec)
				toVct.Spec = fromVct.Spec
			}
		}
	*/

	requireUpdate = CopyPodTemplates(&from.Spec.Template, &to.Spec.Template, "Spec.Template.", logger) || requireUpdate

	return requireUpdate
}

// CopyDeploymentFields copies the owned fields from one Deployment to another
// Returns true if the fields copied from don't match to.
func CopyDeploymentFields(from, to *appsv1.Deployment, logger logr.Logger) bool {
	logger = logger.WithValues("kind", "deployment")
	requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger)

	if !DeepEqualWithNils(to.Spec.Replicas, from.Spec.Replicas) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.Replicas", "from", to.Spec.Replicas, "to", from.Spec.Replicas)
		to.Spec.Replicas = from.Spec.Replicas
	}

	if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.Selector", "from", to.Spec.Selector, "to", from.Spec.Selector)
		to.Spec.Selector = from.Spec.Selector
	}

	requireUpdate = CopyPodTemplates(&from.Spec.Template, &to.Spec.Template, "Spec.Template.", logger) || requireUpdate

	return requireUpdate
}

func CopyPodTemplates(from, to *corev1.PodTemplateSpec, basePath string, logger logr.Logger) (requireUpdate bool) {
	if basePath == "" {
		logger = logger.WithValues("kind", "pod")
	}
	if !DeepEqualWithNils(to.Labels, from.Labels) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Labels", "from", to.Labels, "to", from.Labels)
		to.Labels = from.Labels
	}

	if !DeepEqualWithNils(to.Annotations, from.Annotations) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Annotations", "from", to.Annotations, "to", from.Annotations)
		to.Annotations = from.Annotations
	}

	requireUpdate = CopyPodContainers(&from.Spec.Containers, &to.Spec.Containers, basePath+"Spec.Containers", logger) || requireUpdate

	requireUpdate = CopyPodContainers(&from.Spec.InitContainers, &to.Spec.InitContainers, basePath+"Spec.InitContainers", logger) || requireUpdate

	requireUpdate = CopyPodVolumes(&from.Spec.Volumes, &to.Spec.Volumes, basePath+"Spec.Volumes", logger) || requireUpdate

	if !DeepEqualWithNils(to.Spec.HostAliases, from.Spec.HostAliases) {
		hostAliasUpdate := false
		if to.Spec.HostAliases == nil {
			hostAliasUpdate = true
			to.Spec.HostAliases = from.Spec.HostAliases
		} else {
			// Do not remove aliases that are no longer used.
			// This is in case Solr is scaling down and we want to keep the old addresses for future use.
			for _, fromAlias := range from.Spec.HostAliases {
				found := false
				for i, toAlias := range to.Spec.HostAliases {
					if fromAlias.Hostnames[0] == toAlias.Hostnames[0] {
						found = true
						if !DeepEqualWithNils(toAlias, fromAlias) {
							hostAliasUpdate = true
							to.Spec.HostAliases[i] = fromAlias
							break
						}
					}
				}
				if !found {
					hostAliasUpdate = true
					to.Spec.HostAliases = append(to.Spec.HostAliases, fromAlias)
				}
			}
		}
		if hostAliasUpdate {
			requireUpdate = true
			logger.Info("Update required because field changed", "field", basePath+"Spec.HostAliases", "from", to.Spec.HostAliases, "to", from.Spec.HostAliases)
		}
	}

	if !DeepEqualWithNils(to.Spec.ImagePullSecrets, from.Spec.ImagePullSecrets) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.ImagePullSecrets", "from", to.Spec.ImagePullSecrets, "to", from.Spec.ImagePullSecrets)
		to.Spec.ImagePullSecrets = from.Spec.ImagePullSecrets
	}

	if !DeepEqualWithNils(to.Spec.Affinity, from.Spec.Affinity) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.Affinity", "from", to.Spec.Affinity, "to", from.Spec.Affinity)
		to.Spec.Affinity = from.Spec.Affinity
	}

	if !DeepEqualWithNils(to.Spec.SecurityContext, from.Spec.SecurityContext) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.SecurityContext", "from", to.Spec.SecurityContext, "to", from.Spec.SecurityContext)
		to.Spec.SecurityContext = from.Spec.SecurityContext
	}

	if !DeepEqualWithNils(to.Spec.NodeSelector, from.Spec.NodeSelector) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.NodeSelector", "from", to.Spec.NodeSelector, "to", from.Spec.NodeSelector)
		to.Spec.NodeSelector = from.Spec.NodeSelector
	}

	if !DeepEqualWithNils(to.Spec.Tolerations, from.Spec.Tolerations) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.Tolerations", "from", to.Spec.Tolerations, "to", from.Spec.Tolerations)
		to.Spec.Tolerations = from.Spec.Tolerations
	}

	if !DeepEqualWithNils(to.Spec.PriorityClassName, from.Spec.PriorityClassName) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.PriorityClassName", "from", to.Spec.PriorityClassName, "to", from.Spec.PriorityClassName)
		to.Spec.PriorityClassName = from.Spec.PriorityClassName
	}

	if !DeepEqualWithNils(to.Spec.TerminationGracePeriodSeconds, from.Spec.TerminationGracePeriodSeconds) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.TerminationGracePeriodSeconds", "from", to.Spec.TerminationGracePeriodSeconds, "to", from.Spec.TerminationGracePeriodSeconds)
		to.Spec.TerminationGracePeriodSeconds = from.Spec.TerminationGracePeriodSeconds
	}

	if !DeepEqualWithNils(to.Spec.ServiceAccountName, from.Spec.ServiceAccountName) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.ServiceAccountName", "from", to.Spec.ServiceAccountName, "to", from.Spec.ServiceAccountName)
		to.Spec.ServiceAccountName = from.Spec.ServiceAccountName
	}

	if !DeepEqualWithNils(to.Spec.TopologySpreadConstraints, from.Spec.TopologySpreadConstraints) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.TopologySpreadConstraints", "from", to.Spec.TopologySpreadConstraints, "to", from.Spec.TopologySpreadConstraints)
		to.Spec.TopologySpreadConstraints = from.Spec.TopologySpreadConstraints
	}

	if !DeepEqualWithNils(to.Spec.ReadinessGates, from.Spec.ReadinessGates) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+"Spec.ReadinessGates", "from", to.Spec.ReadinessGates, "to", from.Spec.ReadinessGates)
		to.Spec.ReadinessGates = from.Spec.ReadinessGates
	}

	return requireUpdate
}

func CopyPodContainers(fromPtr, toPtr *[]corev1.Container, basePath string, logger logr.Logger) (requireUpdate bool) {
	to := *toPtr
	from := *fromPtr
	if len(to) != len(from) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+".Length", "from", len(to), "to", len(from))
		*toPtr = from
	} else {
		for i := 0; i < len(from); i++ {
			containerBasePath := basePath + "[" + strconv.Itoa(i) + "]."
			if !DeepEqualWithNils(to[i].Name, from[i].Name) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"Name", "from", to[i].Name, "to", from[i].Name)
				to[i].Name = from[i].Name
			}

			if !DeepEqualWithNils(to[i].Image, from[i].Image) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"Image", "from", to[i].Image, "to", from[i].Image)
				to[i].Image = from[i].Image
			}

			if from[i].ImagePullPolicy != "" && !DeepEqualWithNils(to[i].ImagePullPolicy, from[i].ImagePullPolicy) {
				// Only request an update if the requestedPullPolicy is not empty
				// Otherwise kubernetes will specify a defaultPollPolicy and the operator will endlessly recurse, trying to unset the default policy.
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"ImagePullPolicy", "from", to[i].ImagePullPolicy, "to", from[i].ImagePullPolicy)
			}
			to[i].ImagePullPolicy = from[i].ImagePullPolicy

			if !DeepEqualWithNils(to[i].Command, from[i].Command) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"Command", "from", to[i].Command, "to", from[i].Command)
				to[i].Command = from[i].Command
			}

			if !DeepEqualWithNils(to[i].Args, from[i].Args) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"Args", "from", to[i].Args, "to", from[i].Args)
				to[i].Args = from[i].Args
			}

			if !DeepEqualWithNils(to[i].Env, from[i].Env) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"Env", "from", to[i].Env, "to", from[i].Env)
				to[i].Env = from[i].Env
			}

			requireUpdate = CopyResources(&from[i].Resources, &to[i].Resources, containerBasePath+"Resources.", logger) || requireUpdate

			if !DeepEqualWithNils(to[i].VolumeMounts, from[i].VolumeMounts) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"VolumeMounts", "from", to[i].VolumeMounts, "to", from[i].VolumeMounts)
				to[i].VolumeMounts = from[i].VolumeMounts
			}

			if !DeepEqualWithNils(to[i].Ports, from[i].Ports) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"Ports", "from", to[i].Ports, "to", from[i].Ports)
				to[i].Ports = from[i].Ports
			}

			if !DeepEqualWithNils(to[i].Lifecycle, from[i].Lifecycle) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"Lifecycle", "from", to[i].Lifecycle, "to", from[i].Lifecycle)
				to[i].Lifecycle = from[i].Lifecycle
			}

			if !DeepEqualWithNils(to[i].LivenessProbe, from[i].LivenessProbe) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"LivenessProbe", "from", to[i].LivenessProbe, "to", from[i].LivenessProbe)
				to[i].LivenessProbe = from[i].LivenessProbe
			}

			if !DeepEqualWithNils(to[i].ReadinessProbe, from[i].ReadinessProbe) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"ReadinessProbe", "from", to[i].ReadinessProbe, "to", from[i].ReadinessProbe)
				to[i].ReadinessProbe = from[i].ReadinessProbe
			}

			if !DeepEqualWithNils(to[i].StartupProbe, from[i].StartupProbe) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"StartupProbe", "from", to[i].StartupProbe, "to", from[i].StartupProbe)
				to[i].StartupProbe = from[i].StartupProbe
			}

			if !DeepEqualWithNils(to[i].Lifecycle, from[i].Lifecycle) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"Lifecycle", "from", to[i].Lifecycle, "to", from[i].Lifecycle)
				to[i].Lifecycle = from[i].Lifecycle
			}

			if from[i].TerminationMessagePath != "" && !DeepEqualWithNils(to[i].TerminationMessagePath, from[i].TerminationMessagePath) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"TerminationMessagePath", "from", to[i].TerminationMessagePath, "to", from[i].TerminationMessagePath)
				to[i].TerminationMessagePath = from[i].TerminationMessagePath
			}

			if from[i].TerminationMessagePolicy != "" && !DeepEqualWithNils(to[i].TerminationMessagePolicy, from[i].TerminationMessagePolicy) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"TerminationMessagePolicy", "from", to[i].TerminationMessagePolicy, "to", from[i].TerminationMessagePolicy)
				to[i].TerminationMessagePolicy = from[i].TerminationMessagePolicy
			}

			if !DeepEqualWithNils(to[i].SecurityContext, from[i].SecurityContext) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"SecurityContext", "from", to[i].SecurityContext, "to", from[i].SecurityContext)
				to[i].SecurityContext = from[i].SecurityContext
			}
		}
	}
	return requireUpdate
}

func CopyPodVolumes(fromPtr, toPtr *[]corev1.Volume, basePath string, logger logr.Logger) (requireUpdate bool) {
	to := *toPtr
	from := *fromPtr
	if len(to) != len(from) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+".Length", "from", len(to), "to", len(from))
		*toPtr = from
	} else {
		for i := 0; i < len(from); i++ {
			containerBasePath := basePath + "[" + strconv.Itoa(i) + "]."
			if !DeepEqualWithNils(to[i].Name, from[i].Name) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"Name", "from", to[i].Name, "to", from[i].Name)
				to[i].Name = from[i].Name
			}

			if !DeepEqualWithNils(to[i].VolumeSource, from[i].VolumeSource) {
				requireUpdate = true
				logger.Info("Update required because field changed", "field", containerBasePath+"VolumeSource", "from", to[i].VolumeSource, "to", from[i].VolumeSource)
				to[i].VolumeSource = from[i].VolumeSource
			}
		}
	}
	return requireUpdate
}

func CopyResources(from, to *corev1.ResourceRequirements, basePath string, logger logr.Logger) (requireUpdate bool) {

	requireUpdate = CopyContainerResourceList(&from.Requests, &to.Requests, basePath+"Requests", logger) || requireUpdate

	requireUpdate = CopyContainerResourceList(&from.Limits, &to.Limits, basePath+"Limits", logger) || requireUpdate

	return requireUpdate
}

func CopyVolumeResources(from, to *corev1.VolumeResourceRequirements, basePath string, logger logr.Logger) (requireUpdate bool) {

	requireUpdate = CopyContainerResourceList(&from.Requests, &to.Requests, basePath+"Requests", logger) || requireUpdate

	requireUpdate = CopyContainerResourceList(&from.Limits, &to.Limits, basePath+"Limits", logger) || requireUpdate

	return requireUpdate
}

func CopyContainerResourceList(fromPtr, toPtr *corev1.ResourceList, basePath string, logger logr.Logger) (requireUpdate bool) {
	to := *toPtr
	from := *fromPtr
	copyEntireMap := false
	if len(to) != len(from) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", basePath+".Length", "from", len(to), "to", len(from))
		copyEntireMap = true
	} else {
		for resourceName, newQuantity := range from {
			resourceBasePath := basePath + "[" + resourceName.String() + "]"
			if oldQuantity, isThere := to[resourceName]; isThere {
				if !oldQuantity.Equal(newQuantity) {
					requireUpdate = true
					logger.Info("Update required because field changed", "field", resourceBasePath, "from", oldQuantity, "to", newQuantity)
					to[resourceName] = newQuantity
				}
			} else {
				// This means the keys of the resource maps (from & to) are not the same, therefore update the entire map.
				// We will still loop through the rest of the resources to print out any additional updates required,
				// otherwise the logging could miss resource quantity changes.
				requireUpdate = true
				logger.Info("Update required because field changed", "field", resourceBasePath, "from", nil, "to", newQuantity)
				copyEntireMap = true
			}
		}
	}
	if copyEntireMap {
		*toPtr = from
	}
	return requireUpdate
}

// CopyPodDisruptionBudgetFields copies the owned fields from one PodDisruptionBudget to another
func CopyPodDisruptionBudgetFields(from, to *policyv1.PodDisruptionBudget, logger logr.Logger) bool {
	logger = logger.WithValues("kind", "PodDisruptionBudget")
	requireUpdate := CopyLabelsAndAnnotations(&from.ObjectMeta, &to.ObjectMeta, logger)

	if !DeepEqualWithNils(to.Spec.MinAvailable, from.Spec.MinAvailable) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.MinAvailable", "from", to.Spec.MinAvailable, "to", from.Spec.MinAvailable)
		to.Spec.MinAvailable = from.Spec.MinAvailable
	}
	if !DeepEqualWithNils(to.Spec.MaxUnavailable, from.Spec.MaxUnavailable) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.MaxUnavailable", "from", to.Spec.MaxUnavailable, "to", from.Spec.MaxUnavailable)
		to.Spec.MaxUnavailable = from.Spec.MaxUnavailable
	}
	if !DeepEqualWithNils(to.Spec.Selector, from.Spec.Selector) {
		requireUpdate = true
		logger.Info("Update required because field changed", "field", "Spec.Selector", "from", to.Spec.Selector, "to", from.Spec.Selector)
		to.Spec.Selector = from.Spec.Selector
	}

	return requireUpdate
}

// OvertakeControllerRef makes sure that the controlled object has the owner as the controller ref.
// If the object has a different controller, then that ref will be downgraded to an "owner" and the new controller ref will be added
func OvertakeControllerRef(owner metav1.Object, controlled metav1.Object, scheme *runtime.Scheme) (needsUpdate bool, err error) {
	if !metav1.IsControlledBy(controlled, owner) {
		if otherController := metav1.GetControllerOfNoCopy(controlled); otherController != nil {
			otherController.Controller = pointer.BoolPtr(false)
			otherController.BlockOwnerDeletion = pointer.BoolPtr(false)
		}
		err = controllerutil.SetControllerReference(owner, controlled, scheme)
		needsUpdate = true
	}
	return needsUpdate, err
}
