// Copyright 2022 Clastix Labs
// SPDX-License-Identifier: Apache-2.0

package controlplane

import (
	"context"
	"crypto/md5"
	"fmt"
	"path"
	"sort"
	"strings"

	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/apimachinery/pkg/util/sets"
	"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
	pointer "k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"

	kamajiv1alpha1 "github.com/clastix/kamaji/api/v1alpha1"
	"github.com/clastix/kamaji/internal/utilities"
)

// Volume names.
const (
	kubernetesPKIVolumeName               = "etc-kubernetes-pki"
	caCertificatesVolumeName              = "etc-ca-certificates"
	usrShareCACertificatesVolumeName      = "usr-share-ca-certificates"
	usrLocalShareCaCertificateVolumeName  = "usr-local-share-ca-certificates"
	schedulerKubeconfigVolumeName         = "scheduler-kubeconfig"
	controllerManagerKubeconfigVolumeName = "controller-manager-kubeconfig"
	kineUDSVolume                         = "kine-uds"
	kineUDSFolder                         = "/uds"
	kineUDSPath                           = kineUDSFolder + "/kine"
	dataStoreCertsVolumeName              = "kine-config"
	kineVolumeCertName                    = "kine-certs"
)

const (
	apiServerFlagsAnnotation = "kube-apiserver.kamaji.clastix.io/args"
	// Kamaji container names.
	apiServerContainerName    = "kube-apiserver"
	controlPlaneContainerName = "kube-controller-manager"
	schedulerContainerName    = "kube-scheduler"
	kineContainerName         = "kine"
	kineInitContainerName     = "chmod"
)

func applyProbeOverrides(probe *corev1.Probe, spec *kamajiv1alpha1.ProbeSpec) {
	if probe == nil || spec == nil {
		return
	}

	probe.InitialDelaySeconds = pointer.Deref(spec.InitialDelaySeconds, probe.InitialDelaySeconds)
	probe.TimeoutSeconds = pointer.Deref(spec.TimeoutSeconds, probe.TimeoutSeconds)
	probe.PeriodSeconds = pointer.Deref(spec.PeriodSeconds, probe.PeriodSeconds)
	probe.SuccessThreshold = pointer.Deref(spec.SuccessThreshold, probe.SuccessThreshold)
	probe.FailureThreshold = pointer.Deref(spec.FailureThreshold, probe.FailureThreshold)
}

// defaultProbe builds the standard HTTPS probe shared by all control plane
// components; only the path and port differ between components and probe types.
func defaultProbe(path string, port int32) *corev1.Probe {
	return &corev1.Probe{
		ProbeHandler: corev1.ProbeHandler{
			HTTPGet: &corev1.HTTPGetAction{
				Path:   path,
				Port:   intstr.FromInt32(port),
				Scheme: corev1.URISchemeHTTPS,
			},
		},
		InitialDelaySeconds: 0,
		TimeoutSeconds:      1,
		PeriodSeconds:       10,
		SuccessThreshold:    1,
		FailureThreshold:    3,
	}
}

// applyProbeSetOverrides applies the global probe overrides first, then the
// per-component overrides, to every probe of the container. Probe pointers that
// are nil are skipped (applyProbeOverrides is nil-safe).
func applyProbeSetOverrides(c *corev1.Container, probes *kamajiv1alpha1.ControlPlaneProbes, set *kamajiv1alpha1.ProbeSet) {
	if probes == nil {
		return
	}

	applyProbeOverrides(c.LivenessProbe, probes.Liveness)
	applyProbeOverrides(c.ReadinessProbe, probes.Readiness)
	applyProbeOverrides(c.StartupProbe, probes.Startup)

	if set != nil {
		applyProbeOverrides(c.LivenessProbe, set.Liveness)
		applyProbeOverrides(c.ReadinessProbe, set.Readiness)
		applyProbeOverrides(c.StartupProbe, set.Startup)
	}
}

type DataStoreOverrides struct {
	Resource  string
	DataStore kamajiv1alpha1.DataStore
}

type Deployment struct {
	KineContainerImage string
	DataStore          kamajiv1alpha1.DataStore
	DataStoreOverrides []DataStoreOverrides
	Client             client.Client
}

func (d Deployment) Build(ctx context.Context, deployment *appsv1.Deployment, tenantControlPlane kamajiv1alpha1.TenantControlPlane) {
	address, _, _ := tenantControlPlane.AssignedControlPlaneAddress()

	d.setLabels(deployment, utilities.MergeMaps(
		deployment.GetLabels(),
		utilities.KamajiLabels(tenantControlPlane.GetName(), "deployment"),
		tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalMetadata.Labels,
	))
	d.setAnnotations(deployment, utilities.MergeMaps(deployment.Annotations, tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalMetadata.Annotations))
	d.setTemplateLabels(&deployment.Spec.Template, utilities.MergeMaps(d.templateLabels(ctx, &tenantControlPlane), tenantControlPlane.Spec.ControlPlane.Deployment.PodAdditionalMetadata.Labels))
	d.setTemplateAnnotations(&deployment.Spec.Template, utilities.MergeMaps(tenantControlPlane.Spec.ControlPlane.Deployment.PodAdditionalMetadata.Annotations, map[string]string{
		// The following annotations are used to trigger a rollout in case of any change:
		// - configuration changes
		// - certificate changes, or CA rotation
		"storage.kamaji.clastix.io/config":      tenantControlPlane.Status.Storage.Config.Checksum,
		"storage.kamaji.clastix.io/certificate": tenantControlPlane.Status.Storage.Certificate.Checksum,
	}))
	d.setNodeSelector(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.setToleration(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.setAffinity(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.setStrategy(&deployment.Spec, tenantControlPlane)
	d.setSelector(&deployment.Spec, tenantControlPlane)
	d.setTopologySpreadConstraints(&deployment.Spec, tenantControlPlane.Spec.ControlPlane.Deployment.TopologySpreadConstraints)
	d.setRuntimeClass(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.setReplicas(&deployment.Spec, tenantControlPlane)
	d.resetKubeAPIServerFlags(deployment, tenantControlPlane)
	d.setInitContainers(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.setAdditionalContainers(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.setContainers(&deployment.Spec.Template.Spec, tenantControlPlane, address)
	d.setSecurityContext(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.setAdditionalVolumes(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.setVolumes(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.setServiceAccount(&deployment.Spec.Template.Spec, tenantControlPlane)
	d.Client.Scheme().Default(deployment)
}

func (d Deployment) setContainers(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane, address string) {
	d.buildKubeAPIServer(podSpec, tcp, address)
	d.buildScheduler(podSpec, tcp)
	d.buildControllerManager(podSpec, tcp)
	d.buildKine(podSpec, tcp)
}

// setInitContainers allows adding extra init containers from the user-space:
// this function must be called priorit the setContainers to ensure the idempotency of podSpec building.
func (d Deployment) setInitContainers(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	initContainers := tcp.Spec.ControlPlane.Deployment.AdditionalInitContainers

	if d.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
		podSpec.InitContainers = initContainers
	}

	found, index := utilities.HasNamedContainer(podSpec.InitContainers, kineInitContainerName)
	if found {
		initContainers = append(initContainers, podSpec.InitContainers[index:]...)
	}

	podSpec.InitContainers = initContainers
}

// setAdditionalContainers must be called before setContainers: the user-space ones are going to be prepended
// to simplify the management of the Kamaji ones during the create or update action.
func (d Deployment) setAdditionalContainers(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	containers := tcp.Spec.ControlPlane.Deployment.AdditionalContainers

	found, index := utilities.HasNamedContainer(podSpec.Containers, apiServerContainerName)
	if found {
		containers = append(containers, podSpec.Containers[index:]...)
	}

	podSpec.Containers = containers
}

func (d Deployment) setStrategy(deployment *appsv1.DeploymentSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	deployment.Strategy = appsv1.DeploymentStrategy{
		Type: tcp.Spec.ControlPlane.Deployment.Strategy.Type,
	}
	// If it's recreate strategy, we don't need any RollingUpdate params
	if tcp.Spec.ControlPlane.Deployment.Strategy.Type == appsv1.RecreateDeploymentStrategyType {
		tcp.Spec.ControlPlane.Deployment.Strategy.RollingUpdate = nil

		return
	}
	// In case of no RollingUpdate options, Kamaji will perform a blue/green rollout:
	// this will ensure to avoid round-robin between old and new version,
	// useful especially during the Kubernetes version upgrade phase.
	if tcp.Spec.ControlPlane.Deployment.Strategy.RollingUpdate == nil {
		maxSurge := intstr.FromString("100%")

		maxUnavailable := intstr.FromInt32(0)

		deployment.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{
			MaxUnavailable: &maxUnavailable,
			MaxSurge:       &maxSurge,
		}

		return
	}

	deployment.Strategy.RollingUpdate = &appsv1.RollingUpdateDeployment{}

	if tcp.Spec.ControlPlane.Deployment.Strategy.RollingUpdate.MaxUnavailable != nil {
		deployment.Strategy.RollingUpdate.MaxUnavailable = tcp.Spec.ControlPlane.Deployment.Strategy.RollingUpdate.MaxUnavailable
	}

	if tcp.Spec.ControlPlane.Deployment.Strategy.RollingUpdate.MaxSurge != nil {
		deployment.Strategy.RollingUpdate.MaxSurge = tcp.Spec.ControlPlane.Deployment.Strategy.RollingUpdate.MaxSurge
	}
}

func (d Deployment) setVolumes(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	for _, fn := range []func(*corev1.PodSpec, kamajiv1alpha1.TenantControlPlane){
		d.buildPKIVolume,
		d.buildCAVolume,
		d.buildShareCAVolume,
		d.buildLocalShareCAVolume,
		d.buildSchedulerVolume,
		d.buildControllerManagerVolume,
		d.buildKineVolume,
	} {
		fn(podSpec, tcp)
	}
}

func (d Deployment) buildPKIVolume(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	found, index := utilities.HasNamedVolume(podSpec.Volumes, kubernetesPKIVolumeName)
	if !found {
		index = len(podSpec.Volumes)
		podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
	}

	sources := []corev1.VolumeProjection{
		{
			Secret: d.secretProjection(tcp.Status.Certificates.APIServer.SecretName, constants.APIServerCertName, constants.APIServerKeyName),
		},
		{
			Secret: d.secretProjection(tcp.Status.Certificates.CA.SecretName, constants.CACertName, constants.CAKeyName),
		},
		{
			Secret: d.secretProjection(tcp.Status.Certificates.APIServerKubeletClient.SecretName, constants.APIServerKubeletClientCertName, constants.APIServerKubeletClientKeyName),
		},
		{
			Secret: d.secretProjection(tcp.Status.Certificates.FrontProxyCA.SecretName, constants.FrontProxyCACertName, constants.FrontProxyCAKeyName),
		},
		{
			Secret: d.secretProjection(tcp.Status.Certificates.FrontProxyClient.SecretName, constants.FrontProxyClientCertName, constants.FrontProxyClientKeyName),
		},
		{
			Secret: d.secretProjection(tcp.Status.Certificates.SA.SecretName, constants.ServiceAccountPublicKeyName, constants.ServiceAccountPrivateKeyName),
		},
	}

	if d.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
		sources = append(sources, corev1.VolumeProjection{
			Secret: &corev1.SecretProjection{
				LocalObjectReference: corev1.LocalObjectReference{
					Name: tcp.Status.Storage.Certificate.SecretName,
				},
				Items: []corev1.KeyToPath{
					{
						Key:  "ca.crt",
						Path: "etcd/ca.crt",
					},
					{
						Key:  "server.crt",
						Path: "etcd/server.crt",
					},
					{
						Key:  "server.key",
						Path: "etcd/server.key",
					},
				},
			},
		})
	}

	podSpec.Volumes[index].Name = kubernetesPKIVolumeName
	podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
		Projected: &corev1.ProjectedVolumeSource{
			Sources:     sources,
			DefaultMode: pointer.To(int32(420)),
		},
	}
}

func (d Deployment) buildCAVolume(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	found, index := utilities.HasNamedVolume(podSpec.Volumes, caCertificatesVolumeName)
	if !found {
		index = len(podSpec.Volumes)
		podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
	}

	podSpec.Volumes[index].Name = caCertificatesVolumeName
	podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
		Secret: &corev1.SecretVolumeSource{
			SecretName:  tcp.Status.Certificates.CA.SecretName,
			DefaultMode: pointer.To(int32(420)),
		},
	}
}

func (d Deployment) buildShareCAVolume(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	found, index := utilities.HasNamedVolume(podSpec.Volumes, usrShareCACertificatesVolumeName)
	if !found {
		index = len(podSpec.Volumes)
		podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
	}

	podSpec.Volumes[index].Name = usrShareCACertificatesVolumeName
	podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
		Secret: &corev1.SecretVolumeSource{
			SecretName:  tcp.Status.Certificates.CA.SecretName,
			DefaultMode: pointer.To(int32(420)),
		},
	}
}

func (d Deployment) buildLocalShareCAVolume(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	found, index := utilities.HasNamedVolume(podSpec.Volumes, usrLocalShareCaCertificateVolumeName)
	if !found {
		index = len(podSpec.Volumes)
		podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
	}

	podSpec.Volumes[index].Name = usrLocalShareCaCertificateVolumeName
	podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
		Secret: &corev1.SecretVolumeSource{
			SecretName:  tcp.Status.Certificates.CA.SecretName,
			DefaultMode: pointer.To(int32(420)),
		},
	}
}

func (d Deployment) buildSchedulerVolume(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	found, index := utilities.HasNamedVolume(podSpec.Volumes, schedulerKubeconfigVolumeName)
	if !found {
		index = len(podSpec.Volumes)
		podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
	}

	podSpec.Volumes[index].Name = schedulerKubeconfigVolumeName
	podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
		Secret: &corev1.SecretVolumeSource{
			SecretName:  tcp.Status.KubeConfig.Scheduler.SecretName,
			DefaultMode: pointer.To(int32(420)),
		},
	}
}

func (d Deployment) buildControllerManagerVolume(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	found, index := utilities.HasNamedVolume(podSpec.Volumes, controllerManagerKubeconfigVolumeName)
	if !found {
		index = len(podSpec.Volumes)
		podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
	}

	podSpec.Volumes[index].Name = controllerManagerKubeconfigVolumeName
	podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
		Secret: &corev1.SecretVolumeSource{
			SecretName:  tcp.Status.KubeConfig.ControllerManager.SecretName,
			DefaultMode: pointer.To(int32(420)),
		},
	}
}

// setAdditionalVolumes must be called before setVolumes: the user-space ones are going to be prepended
// to simplify the management of the Kamaji ones during the create or update action.
func (d Deployment) setAdditionalVolumes(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	volumes := tcp.Spec.ControlPlane.Deployment.AdditionalVolumes

	found, index := utilities.HasNamedVolume(podSpec.Volumes, kubernetesPKIVolumeName)
	if found {
		volumes = append(volumes, podSpec.Volumes[index:]...)
	}

	podSpec.Volumes = volumes
}

func (d Deployment) buildScheduler(podSpec *corev1.PodSpec, tenantControlPlane kamajiv1alpha1.TenantControlPlane) {
	found, index := utilities.HasNamedContainer(podSpec.Containers, schedulerContainerName)
	if !found {
		index = len(podSpec.Containers)
		podSpec.Containers = append(podSpec.Containers, corev1.Container{})
	}

	args := map[string]string{}

	if tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs != nil {
		args = utilities.ArgsFromSliceToMap(tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs.Scheduler)
	}

	kubeconfig := "/etc/kubernetes/scheduler.conf"

	args["--authentication-kubeconfig"] = kubeconfig
	args["--authorization-kubeconfig"] = kubeconfig
	args["--bind-address"] = "0.0.0.0"
	args["--kubeconfig"] = kubeconfig
	args["--leader-elect"] = "true"

	podSpec.Containers[index].Name = schedulerContainerName
	podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeSchedulerImage(tenantControlPlane.Spec.Kubernetes.Version)
	podSpec.Containers[index].Command = []string{"kube-scheduler"}
	podSpec.Containers[index].Args = utilities.ArgsFromMapToSlice(args)
	podSpec.Containers[index].LivenessProbe = defaultProbe("/healthz", 10259)
	podSpec.Containers[index].ReadinessProbe = defaultProbe("/healthz", 10259)
	podSpec.Containers[index].StartupProbe = defaultProbe("/healthz", 10259)

	if probes := tenantControlPlane.Spec.ControlPlane.Deployment.Probes; probes != nil {
		applyProbeSetOverrides(&podSpec.Containers[index], probes, probes.Scheduler)
	}

	if containerSecurityContexts := tenantControlPlane.Spec.ControlPlane.Deployment.ContainerSecurityContexts; containerSecurityContexts != nil {
		podSpec.Containers[index].SecurityContext = containerSecurityContexts.Scheduler
	} else {
		podSpec.Containers[index].SecurityContext = nil
	}

	switch {
	case tenantControlPlane.Spec.ControlPlane.Deployment.Resources == nil:
		podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
	case tenantControlPlane.Spec.ControlPlane.Deployment.Resources.Scheduler != nil:
		podSpec.Containers[index].Resources = *tenantControlPlane.Spec.ControlPlane.Deployment.Resources.Scheduler
	default:
		podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
	}
	// Volume mounts
	var extraVolumeMounts []corev1.VolumeMount

	if additionalVolumeMounts := tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalVolumeMounts; additionalVolumeMounts != nil {
		extraVolumeMounts = append(extraVolumeMounts, additionalVolumeMounts.Scheduler...)
	}

	volumeMounts := d.initVolumeMounts(schedulerKubeconfigVolumeName, podSpec.Containers[index].VolumeMounts, extraVolumeMounts...)

	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      schedulerKubeconfigVolumeName,
		ReadOnly:  true,
		MountPath: "/etc/kubernetes",
	})

	podSpec.Containers[index].VolumeMounts = volumeMounts
}

func (d Deployment) buildControllerManager(podSpec *corev1.PodSpec, tenantControlPlane kamajiv1alpha1.TenantControlPlane) {
	found, index := utilities.HasNamedContainer(podSpec.Containers, controlPlaneContainerName)
	if !found {
		index = len(podSpec.Containers)
		podSpec.Containers = append(podSpec.Containers, corev1.Container{})
	}

	// backward compatibility for deprecated CIDR fields
	serviceCIDRs := utilities.GetEffectiveCIDRs(tenantControlPlane.Spec.NetworkProfile.ServiceCIDR, tenantControlPlane.Spec.NetworkProfile.ServiceCIDRs)
	podCIDRs := utilities.GetEffectiveCIDRs(tenantControlPlane.Spec.NetworkProfile.PodCIDR, tenantControlPlane.Spec.NetworkProfile.PodCIDRs)

	kubeconfig := "/etc/kubernetes/controller-manager.conf"

	args := map[string]string{
		"--allocate-node-cidrs":              "true",
		"--authentication-kubeconfig":        kubeconfig,
		"--authorization-kubeconfig":         kubeconfig,
		"--bind-address":                     "0.0.0.0",
		"--client-ca-file":                   path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
		"--cluster-name":                     tenantControlPlane.GetName(),
		"--cluster-signing-cert-file":        path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
		"--cluster-signing-key-file":         path.Join(v1beta3.DefaultCertificatesDir, constants.CAKeyName),
		"--controllers":                      "*,bootstrapsigner,tokencleaner",
		"--kubeconfig":                       kubeconfig,
		"--leader-elect":                     "true",
		"--service-cluster-ip-range":         strings.Join(serviceCIDRs, ","),
		"--cluster-cidr":                     strings.Join(podCIDRs, ","),
		"--requestheader-client-ca-file":     path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyCACertName),
		"--root-ca-file":                     path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
		"--service-account-private-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPrivateKeyName),
		"--use-service-account-credentials":  "true",
	}

	if extraArgs := tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs; extraArgs != nil && len(extraArgs.ControllerManager) > 0 {
		args = utilities.MergeMaps(args, utilities.ArgsFromSliceToMap(extraArgs.ControllerManager))
	}

	podSpec.Containers[index].Name = "kube-controller-manager"
	podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeControllerManagerImage(tenantControlPlane.Spec.Kubernetes.Version)
	podSpec.Containers[index].Command = []string{"kube-controller-manager"}
	podSpec.Containers[index].Args = utilities.ArgsFromMapToSlice(args)
	podSpec.Containers[index].LivenessProbe = defaultProbe("/healthz", 10257)
	podSpec.Containers[index].ReadinessProbe = defaultProbe("/healthz", 10257)
	podSpec.Containers[index].StartupProbe = defaultProbe("/healthz", 10257)

	if probes := tenantControlPlane.Spec.ControlPlane.Deployment.Probes; probes != nil {
		applyProbeSetOverrides(&podSpec.Containers[index], probes, probes.ControllerManager)
	}

	if containerSecurityContexts := tenantControlPlane.Spec.ControlPlane.Deployment.ContainerSecurityContexts; containerSecurityContexts != nil {
		podSpec.Containers[index].SecurityContext = containerSecurityContexts.ControllerManager
	} else {
		podSpec.Containers[index].SecurityContext = nil
	}

	switch {
	case tenantControlPlane.Spec.ControlPlane.Deployment.Resources == nil:
		podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
	case tenantControlPlane.Spec.ControlPlane.Deployment.Resources.ControllerManager != nil:
		podSpec.Containers[index].Resources = *tenantControlPlane.Spec.ControlPlane.Deployment.Resources.ControllerManager
	default:
		podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
	}
	// Volume mounts
	var extraVolumeMounts []corev1.VolumeMount

	if additionalVolumeMounts := tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalVolumeMounts; additionalVolumeMounts != nil {
		extraVolumeMounts = append(extraVolumeMounts, additionalVolumeMounts.ControllerManager...)
	}

	volumeMounts := d.initVolumeMounts(controllerManagerKubeconfigVolumeName, podSpec.Containers[index].VolumeMounts, extraVolumeMounts...)

	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      controllerManagerKubeconfigVolumeName,
		ReadOnly:  true,
		MountPath: "/etc/kubernetes",
	})
	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      kubernetesPKIVolumeName,
		ReadOnly:  true,
		MountPath: v1beta3.DefaultCertificatesDir,
	})
	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      caCertificatesVolumeName,
		ReadOnly:  true,
		MountPath: "/etc/ca-certificates",
	})
	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      usrShareCACertificatesVolumeName,
		ReadOnly:  true,
		MountPath: "/usr/share/ca-certificates",
	})
	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      usrLocalShareCaCertificateVolumeName,
		ReadOnly:  true,
		MountPath: "/usr/local/share/ca-certificates",
	})

	podSpec.Containers[index].VolumeMounts = volumeMounts
}

// ensureVolumeMount retrieve the index for the named volumeMount, in case of missing it's going to be appended.
func (d Deployment) ensureVolumeMount(in *[]corev1.VolumeMount, desired corev1.VolumeMount) {
	list := *in

	found, index := utilities.HasNamedVolumeMount(*in, desired.Name)
	if !found {
		index = len(list)
		list = append(list, corev1.VolumeMount{})
	}

	list[index] = desired

	*in = list
}

// initVolumeMounts is responsible to create the idempotent slice of corev1.VolumeMount:
// firstSystemVolumeMountName must refer to the first Kamaji-space volume mount to detect properly user-space ones.
func (d Deployment) initVolumeMounts(firstSystemVolumeMountName string, actual []corev1.VolumeMount, extra ...corev1.VolumeMount) []corev1.VolumeMount {
	var volumeMounts []corev1.VolumeMount

	volumeMounts = append(volumeMounts, extra...)
	// Retrieve the first safe volume mount to pick up from:
	// this is required to be sure to delete all the extra containers from the user space.
	if vmFound, vmIndex := utilities.HasNamedVolumeMount(actual, firstSystemVolumeMountName); vmFound {
		volumeMounts = append(volumeMounts, actual[vmIndex:]...)
	}

	return volumeMounts
}

func (d Deployment) buildKubeAPIServer(podSpec *corev1.PodSpec, tenantControlPlane kamajiv1alpha1.TenantControlPlane, address string) {
	found, index := utilities.HasNamedContainer(podSpec.Containers, apiServerContainerName)
	if !found {
		index = len(podSpec.Containers)
		podSpec.Containers = append(podSpec.Containers, corev1.Container{})
	}

	args := d.buildKubeAPIServerCommand(tenantControlPlane, address, podSpec.Containers[index].Args)

	podSpec.Containers[index].Name = apiServerContainerName
	podSpec.Containers[index].Args = args
	podSpec.Containers[index].Image = tenantControlPlane.Spec.ControlPlane.Deployment.RegistrySettings.KubeAPIServerImage(tenantControlPlane.Spec.Kubernetes.Version)
	podSpec.Containers[index].Command = []string{"kube-apiserver"}
	podSpec.Containers[index].LivenessProbe = defaultProbe("/livez", tenantControlPlane.Spec.NetworkProfile.Port)
	podSpec.Containers[index].ReadinessProbe = defaultProbe("/readyz", tenantControlPlane.Spec.NetworkProfile.Port)
	podSpec.Containers[index].StartupProbe = defaultProbe("/livez", tenantControlPlane.Spec.NetworkProfile.Port)

	if probes := tenantControlPlane.Spec.ControlPlane.Deployment.Probes; probes != nil {
		applyProbeSetOverrides(&podSpec.Containers[index], probes, probes.APIServer)
	}

	if containerSecurityContexts := tenantControlPlane.Spec.ControlPlane.Deployment.ContainerSecurityContexts; containerSecurityContexts != nil {
		podSpec.Containers[index].SecurityContext = containerSecurityContexts.APIServer
	} else {
		podSpec.Containers[index].SecurityContext = nil
	}

	podSpec.Containers[index].ImagePullPolicy = corev1.PullAlways
	// Volume mounts
	var extraVolumeMounts []corev1.VolumeMount

	if additionalVolumeMounts := tenantControlPlane.Spec.ControlPlane.Deployment.AdditionalVolumeMounts; additionalVolumeMounts != nil {
		extraVolumeMounts = append(extraVolumeMounts, additionalVolumeMounts.APIServer...)
	}

	volumeMounts := d.initVolumeMounts(kubernetesPKIVolumeName, podSpec.Containers[index].VolumeMounts, extraVolumeMounts...)

	if d.DataStore.Spec.Driver == kamajiv1alpha1.KineMySQLDriver ||
		d.DataStore.Spec.Driver == kamajiv1alpha1.KinePostgreSQLDriver ||
		d.DataStore.Spec.Driver == kamajiv1alpha1.KineNatsDriver {
		d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
			Name:      kineUDSVolume,
			ReadOnly:  false,
			MountPath: kineUDSFolder,
		})
	}

	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      kubernetesPKIVolumeName,
		ReadOnly:  true,
		MountPath: v1beta3.DefaultCertificatesDir,
	})
	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      caCertificatesVolumeName,
		ReadOnly:  true,
		MountPath: "/etc/ca-certificates",
	})
	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      usrShareCACertificatesVolumeName,
		ReadOnly:  true,
		MountPath: "/usr/share/ca-certificates",
	})
	d.ensureVolumeMount(&volumeMounts, corev1.VolumeMount{
		Name:      usrLocalShareCaCertificateVolumeName,
		ReadOnly:  true,
		MountPath: "/usr/local/share/ca-certificates",
	})

	podSpec.Containers[index].VolumeMounts = volumeMounts

	switch {
	case tenantControlPlane.Spec.ControlPlane.Deployment.Resources == nil:
		podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
	case tenantControlPlane.Spec.ControlPlane.Deployment.Resources.APIServer != nil:
		podSpec.Containers[index].Resources = *tenantControlPlane.Spec.ControlPlane.Deployment.Resources.APIServer
	default:
		podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
	}
}

func (d Deployment) buildKubeAPIServerCommand(tenantControlPlane kamajiv1alpha1.TenantControlPlane, address string, current []string) []string {
	var userExtras []string

	if tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs != nil {
		userExtras = tenantControlPlane.Spec.ControlPlane.Deployment.ExtraArgs.APIServer
	}

	kubeletPreferredAddressTypes := make([]string, 0, len(tenantControlPlane.Spec.Kubernetes.Kubelet.PreferredAddressTypes))

	for _, addressType := range tenantControlPlane.Spec.Kubernetes.Kubelet.PreferredAddressTypes {
		kubeletPreferredAddressTypes = append(kubeletPreferredAddressTypes, string(addressType))
	}
	// Use the advertiseAddress (tenant-facing VIP) for --advertise-address when set.
	// This ensures the kubernetes endpoint in the tenant cluster points to the VIP,
	// allowing pods to reach the API server via the OVN localport path.
	apiAdvertiseAddress := address
	if adv := tenantControlPlane.Spec.NetworkProfile.AdvertiseAddress; adv != "" {
		apiAdvertiseAddress = adv
	}
	// Opinionated defaults: applied only when the user didn't provide the same flag in ExtraArgs.
	safeDefaults := map[string]string{
		"--allow-privileged":                   "true",
		"--enable-bootstrap-token-auth":        "true",
		"--requestheader-extra-headers-prefix": "X-Remote-Extra-",
		"--requestheader-group-headers":        "X-Remote-Group",
		"--requestheader-username-headers":     "X-Remote-User",
		"--service-account-issuer":             "https://kubernetes.default.svc.cluster.local",
	}

	// https://github.com/kubernetes/kubernetes/blob/6720f0f96000abb82ad51d1177489b04188819d8/cmd/kubeadm/app/phases/controlplane/manifests.go#L253-L255
	// Mirror kubeadm's mutually exclusive configuration
	if _, ok := utilities.ArgsFromSliceToMap(userExtras)["--authorization-config"]; !ok {
		safeDefaults["--authorization-mode"] = "Node,RBAC"
	}

	// backward compatibility for deprecated CIDR fields
	serviceCIDRs := utilities.GetEffectiveCIDRs(tenantControlPlane.Spec.NetworkProfile.ServiceCIDR, tenantControlPlane.Spec.NetworkProfile.ServiceCIDRs)

	// Managed flags: derived from the TCP spec, always applied, override any user duplicate.
	managed := map[string]string{
		"--advertise-address":                apiAdvertiseAddress,
		"--client-ca-file":                   path.Join(v1beta3.DefaultCertificatesDir, constants.CACertName),
		"--enable-admission-plugins":         strings.Join(tenantControlPlane.Spec.Kubernetes.AdmissionControllers.ToSlice(), ","),
		"--service-cluster-ip-range":         strings.Join(serviceCIDRs, ","),
		"--kubelet-client-certificate":       path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerKubeletClientCertName),
		"--kubelet-client-key":               path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerKubeletClientKeyName),
		"--kubelet-preferred-address-types":  strings.Join(kubeletPreferredAddressTypes, ","),
		"--proxy-client-cert-file":           path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyClientCertName),
		"--proxy-client-key-file":            path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyClientKeyName),
		"--requestheader-allowed-names":      constants.FrontProxyClientCertCommonName,
		"--requestheader-client-ca-file":     path.Join(v1beta3.DefaultCertificatesDir, constants.FrontProxyCACertName),
		"--secure-port":                      fmt.Sprintf("%d", tenantControlPlane.Spec.NetworkProfile.Port),
		"--service-account-key-file":         path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPublicKeyName),
		"--service-account-signing-key-file": path.Join(v1beta3.DefaultCertificatesDir, constants.ServiceAccountPrivateKeyName),
		"--tls-cert-file":                    path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerCertName),
		"--tls-private-key-file":             path.Join(v1beta3.DefaultCertificatesDir, constants.APIServerKeyName),
	}

	switch d.DataStore.Spec.Driver {
	case kamajiv1alpha1.KineMySQLDriver, kamajiv1alpha1.KinePostgreSQLDriver, kamajiv1alpha1.KineNatsDriver:
		managed["--etcd-servers"] = "unix://" + kineUDSPath
	case kamajiv1alpha1.EtcdDriver:
		httpsEndpoints := make([]string, 0, len(d.DataStore.Spec.Endpoints))

		for _, ep := range d.DataStore.Spec.Endpoints {
			httpsEndpoints = append(httpsEndpoints, fmt.Sprintf("https://%s", ep))
		}

		managed["--etcd-compaction-interval"] = "0"
		managed["--etcd-prefix"] = fmt.Sprintf("/%s", tenantControlPlane.Status.Storage.Setup.Schema)
		managed["--etcd-servers"] = strings.Join(httpsEndpoints, ",")
		managed["--etcd-cafile"] = "/etc/kubernetes/pki/etcd/ca.crt"
		managed["--etcd-certfile"] = "/etc/kubernetes/pki/etcd/server.crt"
		managed["--etcd-keyfile"] = "/etc/kubernetes/pki/etcd/server.key"
	}

	if len(d.DataStoreOverrides) != 0 {
		managed["--etcd-servers-overrides"] = d.etcdServersOverrides()
	}

	return mergeAPIServerArgs(current, userExtras, safeDefaults, managed)
}

// mergeAPIServerArgs composes the final kube-apiserver argument slice from three layers:
// - Kamaji-managed flags always win and drop any user duplicate sharing the same name;
// - user ExtraArgs are preserved verbatim, duplicates included for repeatable flags;
// - safe defaults fill in only for flag names the user didn't provide.
//
// Flags already on the container that Kamaji doesn't own and the user didn't set are kept
// (e.g. --egress-selector-config-file injected by the Konnectivity addon).
func mergeAPIServerArgs(current, userExtras []string, safeDefaults, managed map[string]string) []string {
	userFlags := sets.New[string]()
	// sanitizedExtras will contain the userExtras arguments,
	// only if not trying to overwrite the managed ones.
	sanitizedExtras := make([]string, 0, len(userExtras))

	for _, arg := range userExtras {
		flag, _, _ := strings.Cut(arg, "=")
		// Managed flags always win: user duplicates are dropped silently.
		if _, ok := managed[flag]; ok {
			continue
		}

		userFlags.Insert(flag)
		sanitizedExtras = append(sanitizedExtras, arg)
	}

	formatArg := func(flag, value string) string {
		if value == "" {
			return flag
		}

		return fmt.Sprintf("%s=%s", flag, value)
	}
	// Kamaji-owned segment: preserved foreign flags from current
	// safe defaults the user didn't set, and managed flags.
	// Sorted for idempotency.
	//nolint:prealloc
	var kamajiOwned []string

	for _, arg := range current {
		flag, _, _ := strings.Cut(arg, "=")

		if _, ok := managed[flag]; ok {
			continue
		}

		if _, ok := safeDefaults[flag]; ok {
			continue
		}

		if userFlags.Has(flag) {
			continue
		}

		kamajiOwned = append(kamajiOwned, arg)
	}

	for flag, value := range safeDefaults {
		if userFlags.Has(flag) {
			continue
		}

		kamajiOwned = append(kamajiOwned, formatArg(flag, value))
	}

	for flag, value := range managed {
		kamajiOwned = append(kamajiOwned, formatArg(flag, value))
	}

	sort.Strings(kamajiOwned)
	// sanitizedExtras must not be sorted due to some kube-apiserver ordering issues.
	// Taking for granted user is aware of it given the high level of customisation.
	return append(kamajiOwned, sanitizedExtras...)
}

func (d Deployment) etcdServersOverrides() string {
	dataStoreOverridesEndpoints := make([]string, 0, len(d.DataStoreOverrides))
	for _, dso := range d.DataStoreOverrides {
		httpsEndpoints := make([]string, 0, len(dso.DataStore.Spec.Endpoints))

		for _, ep := range dso.DataStore.Spec.Endpoints {
			httpsEndpoints = append(httpsEndpoints, fmt.Sprintf("https://%s", ep))
		}
		dataStoreOverridesEndpoints = append(dataStoreOverridesEndpoints, fmt.Sprintf("%s#%s", dso.Resource, strings.Join(httpsEndpoints, ";")))
	}

	return strings.Join(dataStoreOverridesEndpoints, ",")
}

func (d Deployment) secretProjection(secretName, certKeyName, keyName string) *corev1.SecretProjection {
	return &corev1.SecretProjection{
		LocalObjectReference: corev1.LocalObjectReference{
			Name: secretName,
		},
		Items: []corev1.KeyToPath{
			{
				Key:  certKeyName,
				Path: certKeyName,
			},
			{
				Key:  keyName,
				Path: keyName,
			},
		},
	}
}

func (d Deployment) removeKineVolumes(podSpec *corev1.PodSpec) {
	for _, volumeName := range []string{kineVolumeCertName, dataStoreCertsVolumeName, kineUDSVolume} {
		if found, index := utilities.HasNamedVolume(podSpec.Volumes, volumeName); found {
			var volumes []corev1.Volume

			volumes = append(volumes, podSpec.Volumes[:index]...)
			volumes = append(volumes, podSpec.Volumes[index+1:]...)

			podSpec.Volumes = volumes
		}
	}
}

func (d Deployment) buildKineVolume(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	if d.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
		return
	}

	found, index := utilities.HasNamedVolume(podSpec.Volumes, kineUDSVolume)
	if !found {
		index = len(podSpec.Volumes)
		podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
	}

	podSpec.Volumes[index].Name = kineUDSVolume
	podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
		EmptyDir: &corev1.EmptyDirVolumeSource{
			Medium: "Memory",
		},
	}

	found, index = utilities.HasNamedVolume(podSpec.Volumes, dataStoreCertsVolumeName)
	if !found {
		index = len(podSpec.Volumes)
		podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
	}

	podSpec.Volumes[index].Name = dataStoreCertsVolumeName
	podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
		Secret: &corev1.SecretVolumeSource{
			SecretName:  tcp.Status.Storage.Certificate.SecretName,
			DefaultMode: pointer.To(int32(420)),
		},
	}
	// Adding the volume to read Kine certificates:
	// these must be subsequently fixed with a chmod due to pg issues with private key.
	found, index = utilities.HasNamedVolume(podSpec.Volumes, kineVolumeCertName)
	if !found {
		index = len(podSpec.Volumes)
		podSpec.Volumes = append(podSpec.Volumes, corev1.Volume{})
	}

	podSpec.Volumes[index].Name = kineVolumeCertName
	podSpec.Volumes[index].VolumeSource = corev1.VolumeSource{
		EmptyDir: &corev1.EmptyDirVolumeSource{},
	}
}

func (d Deployment) removeKineContainers(podSpec *corev1.PodSpec) {
	// Removing the kine container, if present
	if found, index := utilities.HasNamedContainer(podSpec.Containers, kineContainerName); found {
		var containers []corev1.Container

		containers = append(containers, podSpec.Containers[:index]...)
		containers = append(containers, podSpec.Containers[index+1:]...)

		podSpec.Containers = containers
	}
	d.removeKineInitContainers(podSpec)
}

func (d Deployment) removeKineInitContainers(podSpec *corev1.PodSpec) {
	if found, index := utilities.HasNamedContainer(podSpec.InitContainers, kineInitContainerName); found {
		var initContainers []corev1.Container

		initContainers = append(initContainers, podSpec.InitContainers[:index]...)
		initContainers = append(initContainers, podSpec.InitContainers[index+1:]...)

		podSpec.InitContainers = initContainers
	}
}

func (d Deployment) buildKine(podSpec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	if d.DataStore.Spec.Driver == kamajiv1alpha1.EtcdDriver {
		d.removeKineContainers(podSpec)
		d.removeKineVolumes(podSpec)

		return
	}

	// Building kine arguments, taking in consideration the user-space ones if provided.
	args := map[string]string{}

	args["--listen-address"] = "unix://" + kineUDSPath

	if d.DataStore.Spec.TLSConfig != nil {
		// Ensuring the init container required for kine is present:
		// a chmod is required for kine in order to read the certificates to connect to the secured datastore.
		found, index := utilities.HasNamedContainer(podSpec.InitContainers, kineInitContainerName)
		if !found {
			index = len(podSpec.InitContainers)
			podSpec.InitContainers = append(podSpec.InitContainers, corev1.Container{})
		}

		podSpec.InitContainers[index].Name = kineInitContainerName
		if podSpec.InitContainers[index].Image == "" {
			podSpec.InitContainers[index].Image = d.KineContainerImage
		}
		podSpec.InitContainers[index].Command = []string{"sh"}

		podSpec.InitContainers[index].Args = []string{
			"-c",
			"cp /kine/*.* /certs && chmod -R 600 /certs/*.*",
		}

		podSpec.InitContainers[index].VolumeMounts = []corev1.VolumeMount{
			{
				Name:      dataStoreCertsVolumeName,
				ReadOnly:  true,
				MountPath: "/kine",
			},
			{
				Name:      kineVolumeCertName,
				MountPath: "/certs",
				ReadOnly:  false,
			},
		}

		args["--ca-file"] = "/certs/ca.crt"

		if d.DataStore.Spec.TLSConfig.ClientCertificate != nil {
			args["--cert-file"] = "/certs/server.crt"
			args["--key-file"] = "/certs/server.key"
		}
	} else {
		// if no TLS configuration is provided, the kine initContainer must be removed.
		d.removeKineInitContainers(podSpec)
	}

	// Kine is expecting an additional container, and it must be removed before proceeding with the additional one
	// in order to make this function idempotent.
	found, index := utilities.HasNamedContainer(podSpec.Containers, kineContainerName)
	if !found {
		index = len(podSpec.Containers)
		podSpec.Containers = append(podSpec.Containers, corev1.Container{})
	}

	if tcp.Spec.ControlPlane.Deployment.ExtraArgs != nil {
		utilArgs := utilities.ArgsFromSliceToMap(tcp.Spec.ControlPlane.Deployment.ExtraArgs.Kine)

		// Merging the user-space arguments with the Kamaji ones.
		for k, v := range utilArgs {
			args[k] = v
		}
	}

	switch d.DataStore.Spec.Driver {
	case kamajiv1alpha1.KineMySQLDriver:
		args["--endpoint"] = "mysql://$(DB_USER):$(DB_PASSWORD)@tcp($(DB_CONNECTION_STRING))/$(DB_SCHEMA)"
	case kamajiv1alpha1.KinePostgreSQLDriver:
		args["--endpoint"] = "postgres://$(DB_USER):$(DB_PASSWORD)@$(DB_CONNECTION_STRING)/$(DB_SCHEMA)"
	case kamajiv1alpha1.KineNatsDriver:
		args["--endpoint"] = "nats://$(DB_USER):$(DB_PASSWORD)@$(DB_CONNECTION_STRING)?bucket=$(DB_SCHEMA)&noEmbed"
	}

	podSpec.Containers[index].Name = kineContainerName
	if podSpec.Containers[index].Image == "" {
		podSpec.Containers[index].Image = d.KineContainerImage
	}
	podSpec.Containers[index].Command = []string{"/bin/kine"}
	podSpec.Containers[index].Args = utilities.ArgsFromMapToSlice(args)
	podSpec.Containers[index].VolumeMounts = []corev1.VolumeMount{
		{
			Name:      kineVolumeCertName,
			MountPath: "/certs",
			ReadOnly:  false,
		},
		{
			Name:      kineUDSVolume,
			MountPath: kineUDSFolder,
			ReadOnly:  false,
		},
	}
	podSpec.Containers[index].Env = []corev1.EnvVar{
		{
			Name:  "GODEBUG",
			Value: "x509ignoreCN=0",
		},
	}
	podSpec.Containers[index].EnvFrom = []corev1.EnvFromSource{
		{
			SecretRef: &corev1.SecretEnvSource{
				LocalObjectReference: corev1.LocalObjectReference{
					Name: tcp.Status.Storage.Config.SecretName,
				},
			},
		},
	}
	podSpec.Containers[index].Ports = []corev1.ContainerPort{
		{
			ContainerPort: 2379,
			Name:          "server",
			Protocol:      corev1.ProtocolTCP,
		},
	}

	podSpec.Containers[index].ImagePullPolicy = corev1.PullAlways

	switch {
	case tcp.Spec.ControlPlane.Deployment.Resources == nil:
		podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
	case tcp.Spec.ControlPlane.Deployment.Resources.Kine != nil:
		podSpec.Containers[index].Resources = *tcp.Spec.ControlPlane.Deployment.Resources.Kine
	default:
		podSpec.Containers[index].Resources = corev1.ResourceRequirements{}
	}
}

func (d Deployment) setSelector(deploymentSpec *appsv1.DeploymentSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	deploymentSpec.Selector = &metav1.LabelSelector{
		MatchLabels: map[string]string{
			"kamaji.clastix.io/name": tcp.GetName(),
		},
	}
}

func (d Deployment) setReplicas(deploymentSpec *appsv1.DeploymentSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	deploymentSpec.Replicas = tcp.Spec.ControlPlane.Deployment.Replicas
}

func (d Deployment) setRuntimeClass(spec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	if len(tcp.Spec.ControlPlane.Deployment.RuntimeClassName) > 0 {
		spec.RuntimeClassName = pointer.To(tcp.Spec.ControlPlane.Deployment.RuntimeClassName)

		return
	}

	spec.RuntimeClassName = nil
}

func (d Deployment) templateLabels(ctx context.Context, tenantControlPlane *kamajiv1alpha1.TenantControlPlane) (labels map[string]string) {
	hash := func(ctx context.Context, namespace, secretName string) string {
		h, _ := d.secretHashValue(ctx, d.Client, namespace, secretName)

		return h
	}

	labels = map[string]string{
		"kamaji.clastix.io/name":                                            tenantControlPlane.GetName(),
		"kamaji.clastix.io/component":                                       "deployment",
		"component.kamaji.clastix.io/api-server-certificate":                hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.APIServer.SecretName),
		"component.kamaji.clastix.io/api-server-kubelet-client-certificate": hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.APIServerKubeletClient.SecretName),
		"component.kamaji.clastix.io/ca":                                    hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.CA.SecretName),
		"component.kamaji.clastix.io/controller-manager-kubeconfig":         hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.KubeConfig.ControllerManager.SecretName),
		"component.kamaji.clastix.io/front-proxy-ca-certificate":            hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.FrontProxyCA.SecretName),
		"component.kamaji.clastix.io/front-proxy-client-certificate":        hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.FrontProxyClient.SecretName),
		"component.kamaji.clastix.io/service-account":                       hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.Certificates.SA.SecretName),
		"component.kamaji.clastix.io/scheduler-kubeconfig":                  hash(ctx, tenantControlPlane.GetNamespace(), tenantControlPlane.Status.KubeConfig.Scheduler.SecretName),
		"component.kamaji.clastix.io/datastore":                             tenantControlPlane.Status.Storage.DataStoreName,
	}

	return labels
}

// secretHashValue function returns the md5 value for the secret of the given name and namespace.
func (d Deployment) secretHashValue(ctx context.Context, client client.Client, namespace, name string) (string, error) {
	secret := &corev1.Secret{}
	if err := client.Get(ctx, types.NamespacedName{Namespace: namespace, Name: name}, secret); err != nil {
		return "", fmt.Errorf("cannot retrieve *corev1.Secret for resource version retrieval: %w", err)
	}

	return d.hashValue(*secret), nil
}

// hashValue function returns the md5 value for the given secret.
func (d Deployment) hashValue(secret corev1.Secret) string {
	// Go access map values in random way, it means we have to sort them.
	keys := make([]string, 0, len(secret.Data))

	for k := range secret.Data {
		keys = append(keys, k)
	}

	sort.Strings(keys)
	// Generating MD5 of Secret values, sorted by key
	h := md5.New()

	for _, key := range keys {
		h.Write(secret.Data[key])
	}

	return fmt.Sprintf("%x", h.Sum(nil))
}

func (d Deployment) setTemplateLabels(template *corev1.PodTemplateSpec, labels map[string]string) {
	template.SetLabels(labels)
}

func (d Deployment) setTemplateAnnotations(template *corev1.PodTemplateSpec, annotations map[string]string) {
	template.SetAnnotations(annotations)
}

func (d Deployment) setLabels(resource *appsv1.Deployment, labels map[string]string) {
	resource.SetLabels(labels)
}

func (d Deployment) setAnnotations(resource *appsv1.Deployment, annotations map[string]string) {
	resource.SetAnnotations(annotations)
}

func (d Deployment) setTopologySpreadConstraints(spec *appsv1.DeploymentSpec, topologies []corev1.TopologySpreadConstraint) {
	defaultSelector := spec.Selector

	for index, topology := range topologies {
		if topology.LabelSelector == nil {
			topologies[index].LabelSelector = defaultSelector
		}
	}

	spec.Template.Spec.TopologySpreadConstraints = topologies
}

// resetKubeAPIServerFlags ensures that upon a change of the kube-apiserver extra flags the desired ones are properly
// applied, also considering that the container could be lately patched by the konnectivity addon resources.
func (d Deployment) resetKubeAPIServerFlags(resource *appsv1.Deployment, tcp kamajiv1alpha1.TenantControlPlane) {
	if tcp.Spec.ControlPlane.Deployment.ExtraArgs == nil {
		return
	}
	// kube-apiserver container is not still there, we can skip the hashing
	if found, _ := utilities.HasNamedContainer(resource.Spec.Template.Spec.Containers, apiServerContainerName); !found {
		return
	}
	// setting up annotation to avoid assignment to a nil one
	if resource.GetAnnotations() == nil {
		resource.SetAnnotations(map[string]string{})
	}
	// retrieving the previous hash for the apiserver args:
	// in case of non-matching values, removing all the args in order to perform a full reconciliation from a clean start.
	previousHash := resource.GetAnnotations()[apiServerFlagsAnnotation]
	currentHash, err := utilities.CalculateStringSliceChecksum(tcp.Spec.ControlPlane.Deployment.ExtraArgs.APIServer)
	if err != nil {
		return
	}

	// there's a mismatch between current and previous hash: let's reset and store the new hash.
	if previousHash != currentHash {
		_, index := utilities.HasNamedContainer(resource.Spec.Template.Spec.Containers, apiServerContainerName)
		resource.Spec.Template.Spec.Containers[index].Args = []string{}
	}

	resource.GetAnnotations()[apiServerFlagsAnnotation] = currentHash
}

func (d Deployment) setNodeSelector(spec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	spec.NodeSelector = tcp.Spec.ControlPlane.Deployment.NodeSelector
}

func (d Deployment) setToleration(spec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	spec.Tolerations = tcp.Spec.ControlPlane.Deployment.Tolerations
}

func (d Deployment) setAffinity(spec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	spec.Affinity = tcp.Spec.ControlPlane.Deployment.Affinity
}

func (d Deployment) setServiceAccount(spec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	spec.AutomountServiceAccountToken = tcp.Spec.ControlPlane.Deployment.AutomountServiceAccountToken

	if len(tcp.Spec.ControlPlane.Deployment.ServiceAccountName) > 0 {
		spec.ServiceAccountName = tcp.Spec.ControlPlane.Deployment.ServiceAccountName

		return
	}

	spec.ServiceAccountName = "default"
}

func (d Deployment) setSecurityContext(spec *corev1.PodSpec, tcp kamajiv1alpha1.TenantControlPlane) {
	spec.SecurityContext = tcp.Spec.ControlPlane.Deployment.PodSecurityContext
}
