/*
 * 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 controllers

import (
	"context"
	"crypto/md5"
	"fmt"
	"github.com/apache/solr-operator/controllers/util"
	appsv1 "k8s.io/api/apps/v1"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"strings"
	"time"

	solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

var _ = FDescribe("SolrPrometheusExporter controller - TLS", func() {

	// Define utility constants for object names and testing timeouts/durations and intervals.
	const (
		timeout  = time.Second * 5
		duration = time.Second * 1
		interval = time.Millisecond * 250
	)
	SetDefaultConsistentlyDuration(duration)
	SetDefaultConsistentlyPollingInterval(interval)
	SetDefaultEventuallyTimeout(timeout)
	SetDefaultEventuallyPollingInterval(interval)

	var (
		solrPrometheusExporter *solrv1beta1.SolrPrometheusExporter
	)

	BeforeEach(func(ctx context.Context) {
		ctx = context.Background()

		solrPrometheusExporter = &solrv1beta1.SolrPrometheusExporter{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "foo",
				Namespace: "default",
			},
			Spec: solrv1beta1.SolrPrometheusExporterSpec{},
		}
	})

	JustBeforeEach(func(ctx context.Context) {
		By("creating the SolrCloud")
		Expect(k8sClient.Create(ctx, solrPrometheusExporter)).To(Succeed())

		By("defaulting the missing SolrCloud values")
		expectSolrPrometheusExporterWithChecks(ctx, solrPrometheusExporter, func(g Gomega, found *solrv1beta1.SolrPrometheusExporter) {
			g.Expect(found.WithDefaults()).To(BeFalse(), "The SolrPrometheusExporter spec should not need to be defaulted eventually")
		})
	})

	AfterEach(func(ctx context.Context) {
		cleanupTest(ctx, solrPrometheusExporter)
	})

	FContext("TLS Secret", func() {
		tlsSecretName := "tls-cert-secret-from-user"
		BeforeEach(func() {
			solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{
				SolrReference: solrv1beta1.SolrReference{
					Cloud: &solrv1beta1.SolrCloudReference{
						ZookeeperConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
							InternalConnectionString: "host:2181",
							ChRoot:                   "/this/path",
						},
					},
					SolrTLS: createTLSOptions(tlsSecretName, "keystore-passwords-are-important", false),
				},
			}
		})
		FIt("has the correct resources", func(ctx context.Context) {
			By("testing the SolrPrometheusExporter Deployment")
			testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, false)
		})
	})

	FContext("TLS Secret - PKCS12 Conversion", func() {
		tlsSecretName := "tls-cert-secret-from-user-no-pkcs12"
		BeforeEach(func() {
			solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{
				SolrReference: solrv1beta1.SolrReference{
					Cloud: &solrv1beta1.SolrCloudReference{
						ZookeeperConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
							InternalConnectionString: "host:2181",
							ChRoot:                   "/this/path",
						},
					},
					SolrTLS: createTLSOptions(tlsSecretName, "keystore-passwords-are-important", false),
				},
			}
		})
		FIt("has the correct resources", func(ctx context.Context) {
			By("testing the SolrPrometheusExporter Deployment")
			testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, true)
		})
	})

	FContext("TLS Secret - Restart on Secret Update", func() {
		tlsSecretName := "tls-cert-secret-update"
		BeforeEach(func() {
			solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{
				SolrReference: solrv1beta1.SolrReference{
					Cloud: &solrv1beta1.SolrCloudReference{
						ZookeeperConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
							InternalConnectionString: "host:2181",
							ChRoot:                   "/this/path",
						},
					},
					SolrTLS: createTLSOptions(tlsSecretName, "keystore-passwords-are-important", true),
				},
			}
		})
		FIt("has the correct resources", func(ctx context.Context) {
			By("testing the SolrPrometheusExporter Deployment")
			testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, false)
		})
	})

	FContext("TLS Secret - With BasicAuth", func() {
		tlsSecretName := "tls-cert-secret-update"
		basicAuthSecretName := tlsSecretName + "-basic-auth"
		BeforeEach(func(ctx context.Context) {
			solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{
				SolrReference: solrv1beta1.SolrReference{
					Cloud: &solrv1beta1.SolrCloudReference{
						ZookeeperConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
							InternalConnectionString: "host:2181",
							ChRoot:                   "/this/path",
						},
					},
					SolrTLS:         createTLSOptions(tlsSecretName, "keystore-passwords-are-important", true),
					BasicAuthSecret: basicAuthSecretName,
				},
			}

			By("Creating secret to use with BasicAuth")
			Expect(k8sClient.Create(ctx, createBasicAuthSecret(basicAuthSecretName, solrv1beta1.DefaultBasicAuthUsername, solrPrometheusExporter.Namespace))).To(Succeed(), "Could not create basic auth secret")
		})
		FIt("has the correct resources", func(ctx context.Context) {
			By("testing the SolrPrometheusExporter Deployment")
			testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, false)
		})
	})

	FContext("TLS Secret - With BasicAuth - Restart on Secret Update", func() {
		tlsSecretName := "tls-cert-secret-update"
		basicAuthSecretName := tlsSecretName + "-basic-auth"
		BeforeEach(func(ctx context.Context) {
			solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{
				SolrReference: solrv1beta1.SolrReference{
					Cloud: &solrv1beta1.SolrCloudReference{
						ZookeeperConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
							InternalConnectionString: "host:2181",
							ChRoot:                   "/this/path",
						},
					},
					SolrTLS:         createTLSOptions(tlsSecretName, "keystore-passwords-are-important", true),
					BasicAuthSecret: basicAuthSecretName,
				},
			}

			By("Creating secret to use with BasicAuth")
			Expect(k8sClient.Create(ctx, createBasicAuthSecret(basicAuthSecretName, solrv1beta1.DefaultBasicAuthUsername, solrPrometheusExporter.Namespace))).To(Succeed(), "Could not create basic auth secret")
		})
		FIt("has the correct resources", func(ctx context.Context) {
			By("testing the SolrPrometheusExporter Deployment")
			testReconcileWithTLS(ctx, solrPrometheusExporter, tlsSecretName, false)

			By("expect Deployment to respond when changing the basicAuthKey")
			testChangingBasicAuthSecret(ctx, solrPrometheusExporter)
		})
	})

	FContext("TLS Secret - TrustStore Only", func() {
		tlsSecretName := "tls-cert-secret-from-user"
		BeforeEach(func() {
			solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{
				SolrReference: solrv1beta1.SolrReference{
					Cloud: &solrv1beta1.SolrCloudReference{
						ZookeeperConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
							InternalConnectionString: "host:2181",
							ChRoot:                   "/this/path",
						},
					},
					SolrTLS: &solrv1beta1.SolrTLSOptions{
						TrustStorePasswordSecret: &corev1.SecretKeySelector{
							LocalObjectReference: corev1.LocalObjectReference{Name: tlsSecretName},
							Key:                  "keystore-passwords-are-important",
						},
						TrustStoreSecret: &corev1.SecretKeySelector{
							LocalObjectReference: corev1.LocalObjectReference{Name: tlsSecretName},
							Key:                  util.DefaultPkcs12TruststoreFile,
						},
						RestartOnTLSSecretUpdate: false,
					},
				},
			}
		})
		FIt("has the correct resources", func(ctx context.Context) {
			By("testing the SolrPrometheusExporter Deployment")
			testReconcileWithTruststoreOnly(ctx, solrPrometheusExporter, tlsSecretName)
		})
	})

	FContext("TLS Secret - TrustStore Only - Restart on Secret Update", func() {
		tlsSecretName := "tls-cert-secret-from-user"
		BeforeEach(func() {
			solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{
				SolrReference: solrv1beta1.SolrReference{
					Cloud: &solrv1beta1.SolrCloudReference{
						ZookeeperConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
							InternalConnectionString: "host:2181",
							ChRoot:                   "/this/path",
						},
					},
					SolrTLS: &solrv1beta1.SolrTLSOptions{
						TrustStorePasswordSecret: &corev1.SecretKeySelector{
							LocalObjectReference: corev1.LocalObjectReference{Name: tlsSecretName},
							Key:                  "keystore-passwords-are-important",
						},
						TrustStoreSecret: &corev1.SecretKeySelector{
							LocalObjectReference: corev1.LocalObjectReference{Name: tlsSecretName},
							Key:                  util.DefaultPkcs12TruststoreFile,
						},
						RestartOnTLSSecretUpdate: true,
					},
				},
			}
		})
		FIt("has the correct resources", func(ctx context.Context) {
			By("testing the SolrPrometheusExporter Deployment")
			testReconcileWithTruststoreOnly(ctx, solrPrometheusExporter, tlsSecretName)
		})
	})

	FContext("Mounted TLS Certs", func() {
		BeforeEach(func() {
			solrPrometheusExporter.Spec = solrv1beta1.SolrPrometheusExporterSpec{
				SolrReference: solrv1beta1.SolrReference{
					Cloud: &solrv1beta1.SolrCloudReference{
						ZookeeperConnectionInfo: &solrv1beta1.ZookeeperConnectionInfo{
							InternalConnectionString: "host:2181",
							ChRoot:                   "/this/path",
						},
					},
					SolrTLS: &solrv1beta1.SolrTLSOptions{
						MountedTLSDir: &solrv1beta1.MountedTLSDirectory{
							Path:                 "/mounted-tls-dir",
							KeystoreFile:         util.DefaultPkcs12KeystoreFile,
							TruststoreFile:       util.DefaultPkcs12TruststoreFile,
							KeystorePasswordFile: util.DefaultKeystorePasswordFile,
						},
						CheckPeerName:        true,
						ClientAuth:           "Need",
						VerifyClientHostname: false,
					},
				},
				BusyBoxImage: &solrv1beta1.ContainerImage{
					Repository: "myBBImage",
					Tag:        "test",
					PullPolicy: "",
				},
			}
		})
		FIt("has the correct resources", func(ctx context.Context) {
			By("testing the SolrPrometheusExporter Deployment")
			deployment := expectDeployment(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName())

			podTemplate := deployment.Spec.Template

			// verify the mountedTLSDir config on the deployment
			Expect(podTemplate.Spec.Containers).To(Not(BeEmpty()), "The SolrPrometheusExporter requires containers")
			mainContainer := podTemplate.Spec.Containers[0]
			Expect(mainContainer.Env).To(Not(BeEmpty()), "No Env vars for main exporter container in the deployment!")
			// make sure JAVA_OPTS is set correctly with the TLS related sys props
			envVars := filterVarsByName(mainContainer.Env, func(n string) bool {
				return strings.HasPrefix(n, "JAVA_OPTS")
			})
			Expect(envVars).To(HaveLen(1), "Wrong number of Env vars with JAVA_OPTS")
			Expect(envVars[0].Value).To(And(
				ContainSubstring("-Djavax.net.ssl.trustStore=$(SOLR_SSL_CLIENT_TRUST_STORE)"),
				ContainSubstring("-Djavax.net.ssl.trustStoreType=PKCS12"),
				ContainSubstring("-Dsolr.ssl.checkPeerName=$(SOLR_SSL_CHECK_PEER_NAME)"),
				Not(ContainSubstring("-Djavax.net.ssl.keyStorePassword")),
				Not(ContainSubstring("-Djavax.net.ssl.trustStorePassword")),
			), "JAVA_OPTS does not contain correct SSL properties")

			// verify initContainer to create a wrapper script around the solr-exporter script
			initContainerName := "create-tls-wrapper-script"
			expInitContainer := expectInitContainer(&podTemplate, initContainerName, "tls-wrapper-script", "/usr/local/solr-exporter-tls")
			Expect(expInitContainer.Command).To(HaveLen(3), "Wrong command length for %s init container", initContainerName)

			Expect(expInitContainer.Command[2]).To(And(
				ContainSubstring("-Djavax.net.ssl.keyStorePassword"),
				ContainSubstring("-Djavax.net.ssl.trustStorePassword"),
				ContainSubstring("/opt/solr/contrib/prometheus-exporter/bin/solr-exporter"),
			), "Init container command does not have necessary arguments")

			Expect(expInitContainer.Image).To(Equal("myBBImage:test"), "Wrong image for BusyBox initContainer")

			By("testing the SolrPrometheusExporter Service")
			service := expectService(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsServiceName(), deployment.Spec.Selector.MatchLabels, false)
			Expect(service.Annotations).To(HaveKeyWithValue("prometheus.io/scrape", "true"), "Metrics Service Prometheus scraping is not enabled.")
			Expect(service.Spec.Ports[0].TargetPort).To(Equal(intstr.FromInt(util.SolrMetricsPort)), "Wrong target port on metrics Service")
			Expect(service.Spec.Ports[0].Name).To(Equal(util.SolrMetricsPortName), "Wrong port name on metrics Service")
			Expect(service.Spec.Ports[0].Port).To(Equal(int32(80)), "Wrong port number on metrics Service")
			Expect(service.Spec.Ports[0].Protocol).To(Equal(corev1.ProtocolTCP), "Wrong protocol on metrics Service")
			Expect(service.Spec.Ports[0].AppProtocol).ToNot(BeNil(), "AppProtocol on metrics Service should not be nil")
			Expect(*service.Spec.Ports[0].AppProtocol).To(Equal("http"), "Wrong appProtocol on metrics Service")
		})
	})
})

func testReconcileWithTLS(ctx context.Context, solrPrometheusExporter *solrv1beta1.SolrPrometheusExporter, tlsSecretName string, needsPkcs12InitContainer bool) {
	keystorePassKey := "keystore-passwords-are-important"

	// create the TLS and keystore secrets needed for reconciling TLS options
	tlsKey := "keystore.p12"
	if needsPkcs12InitContainer {
		tlsKey = "tls.key" // to trigger the initContainer creation, don't want keystore.p12 in the secret
	}
	mockSecret := createMockTLSSecret(ctx, solrPrometheusExporter, tlsSecretName, tlsKey, keystorePassKey, "")

	deployment := expectDeploymentWithChecks(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName(), func(g Gomega, found *appsv1.Deployment) {
		expectTLSConfigOnPodTemplateWithGomega(g, nil, solrPrometheusExporter.Spec.SolrReference.SolrTLS, &found.Spec.Template, needsPkcs12InitContainer, true, nil)
	})
	mainContainer := &deployment.Spec.Template.Spec.Containers[0]

	// make sure JAVA_OPTS is set correctly with the TLS related sys props
	envVars := filterVarsByName(mainContainer.Env, func(n string) bool {
		return strings.HasPrefix(n, "JAVA_OPTS")
	})
	Expect(envVars).To(HaveLen(1), "Wrong number of Env vars with JAVA_OPTS")
	Expect(envVars[0].Value).To(ContainSubstring("-Dsolr.ssl.checkPeerName=$(SOLR_SSL_CHECK_PEER_NAME)"), "JAVA_OPTS does not contain SSL property")

	if !solrPrometheusExporter.Spec.SolrReference.SolrTLS.RestartOnTLSSecretUpdate && solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret == "" {
		// shouldn't be any annotations on the podTemplateSpec if we're not tracking updates to the TLS secret
		Expect(deployment.Spec.Template.ObjectMeta.Annotations).To(BeEmpty(), "there shouldn't be any annotations on the podTemplateSpec if we're not tracking updates to the TLS secret")
		return
	}

	// Check that the hashshum annotations are correct on the pod
	expectedAnnotations := map[string]string{
		util.SolrClientTlsCertMd5Annotation: fmt.Sprintf("%x", md5.Sum(mockSecret.Data[util.TLSCertKey])),
	}

	if solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret != "" {
		lookupAuthSecret := expectSecret(ctx, solrPrometheusExporter, solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret)
		expectBasicAuthEnvVars(mainContainer.Env, lookupAuthSecret)

		// if auth enabled, then annotations also include an md5 for the password so exporters get restarted when it changes
		creds := fmt.Sprintf("%s:%s", lookupAuthSecret.Data[corev1.BasicAuthUsernameKey], lookupAuthSecret.Data[corev1.BasicAuthPasswordKey])
		expectedAnnotations[util.BasicAuthMd5Annotation] = fmt.Sprintf("%x", md5.Sum([]byte(creds)))
	}

	Expect(deployment.Spec.Template.ObjectMeta.Annotations).To(Equal(expectedAnnotations), "wrong pod annotations")

	foundTLSSecret := expectSecret(ctx, solrPrometheusExporter, solrPrometheusExporter.Spec.SolrReference.SolrTLS.PKCS12Secret.Name)

	// change the tls.crt which should trigger a rolling restart
	updatedTlsCertData := "certificate renewed"
	foundTLSSecret.Data[util.TLSCertKey] = []byte(updatedTlsCertData)
	Expect(k8sClient.Update(ctx, foundTLSSecret)).To(Succeed(), "Cannot renew certificate for TLS Cert Secret")

	expectedAnnotations[util.SolrClientTlsCertMd5Annotation] = fmt.Sprintf("%x", md5.Sum(foundTLSSecret.Data[util.TLSCertKey]))
	expectDeploymentWithChecks(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName(), func(g Gomega, found *appsv1.Deployment) {
		g.Expect(found.Spec.Template.ObjectMeta.Annotations).To(Equal(expectedAnnotations), "wrong pod annotations after updating TLS Secret")
	})
}

func testChangingBasicAuthSecret(ctx context.Context, solrPrometheusExporter *solrv1beta1.SolrPrometheusExporter) {
	initialDeployment := expectDeployment(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName())

	// verify the pods would get restarted if the basic auth secret changes
	lookupBasicAuthSecret := expectSecret(ctx, solrPrometheusExporter, solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret)
	// change the tls.crt which should trigger a rolling restart
	updatedPassword := "updated-password"
	lookupBasicAuthSecret.Data[corev1.BasicAuthUsernameKey] = []byte(updatedPassword)
	Expect(k8sClient.Update(ctx, lookupBasicAuthSecret)).To(Succeed(), "Cannot update basicAuth secret")

	creds := string(lookupBasicAuthSecret.Data[corev1.BasicAuthUsernameKey]) + ":" + string(lookupBasicAuthSecret.Data[corev1.BasicAuthPasswordKey])
	expectedAnnotations := initialDeployment.Spec.Template.ObjectMeta.Annotations
	// Update the expected BasicAuthMd5 for new password
	expectedAnnotations[util.BasicAuthMd5Annotation] = fmt.Sprintf("%x", md5.Sum([]byte(creds)))
	// Check the annotation on the pod template to make sure a rolling restart will take place
	expectDeploymentWithChecks(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName(), func(g Gomega, found *appsv1.Deployment) {
		g.Expect(found.Spec.Template.ObjectMeta.Annotations).To(Equal(expectedAnnotations), "wrong pod annotations after updating TLS Secret")
	})
}

func testReconcileWithTruststoreOnly(ctx context.Context, solrPrometheusExporter *solrv1beta1.SolrPrometheusExporter, tlsSecretName string) {
	keystorePassKey := "keystore-passwords-are-important"
	// create the TLS and keystore secrets needed for reconciling TLS options
	tlsKey := util.DefaultPkcs12TruststoreFile
	mockSecret := createMockTLSSecret(ctx, solrPrometheusExporter, tlsSecretName, tlsKey, keystorePassKey, "")

	deployment := expectDeploymentWithChecks(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName(), func(g Gomega, found *appsv1.Deployment) {
		expectTLSConfigOnPodTemplateWithGomega(g, nil, solrPrometheusExporter.Spec.SolrReference.SolrTLS, &found.Spec.Template, false, true, nil)
	})
	mainContainer := &deployment.Spec.Template.Spec.Containers[0]

	// make sure JAVA_OPTS is set correctly with the TLS related sys props
	envVars := filterVarsByName(mainContainer.Env, func(n string) bool {
		return strings.HasPrefix(n, "JAVA_OPTS")
	})
	Expect(envVars).To(HaveLen(1), "Wrong number of Env vars with JAVA_OPTS")
	Expect(envVars[0].Value).To(ContainSubstring("-Dsolr.ssl.checkPeerName=$(SOLR_SSL_CHECK_PEER_NAME)"), "JAVA_OPTS does not contain SSL property")

	if !solrPrometheusExporter.Spec.SolrReference.SolrTLS.RestartOnTLSSecretUpdate && solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret == "" {
		// shouldn't be any annotations on the podTemplateSpec if we're not tracking updates to the TLS secret
		Expect(deployment.Spec.Template.ObjectMeta.Annotations).To(BeEmpty(), "there shouldn't be any annotations on the podTemplateSpec if we're not tracking updates to the TLS secret")
		return
	}

	// Check that the hashshum annotations are correct on the pod
	expectedAnnotations := map[string]string{
		util.SolrClientTlsCertMd5Annotation: fmt.Sprintf("%x", md5.Sum(mockSecret.Data[tlsKey])),
	}

	if solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret != "" {
		lookupAuthSecret := expectSecret(ctx, solrPrometheusExporter, solrPrometheusExporter.Spec.SolrReference.BasicAuthSecret)
		expectBasicAuthEnvVars(mainContainer.Env, lookupAuthSecret)

		// if auth enabled, then annotations also include an md5 for the password so exporters get restarted when it changes
		creds := fmt.Sprintf("%s:%s", lookupAuthSecret.Data[corev1.BasicAuthUsernameKey], lookupAuthSecret.Data[corev1.BasicAuthPasswordKey])
		expectedAnnotations[util.BasicAuthMd5Annotation] = fmt.Sprintf("%x", md5.Sum([]byte(creds)))
	}

	Expect(deployment.Spec.Template.ObjectMeta.Annotations).To(Equal(expectedAnnotations), "wrong pod annotations")

	foundTLSSecret := expectSecret(ctx, solrPrometheusExporter, solrPrometheusExporter.Spec.SolrReference.SolrTLS.TrustStoreSecret.Name)

	// change the truststore.p12 which should trigger a rolling restart
	updatedTlsCertData := "certificate renewed"
	foundTLSSecret.Data[tlsKey] = []byte(updatedTlsCertData)
	Expect(k8sClient.Update(ctx, foundTLSSecret)).To(Succeed(), "Cannot update secret for TLS Truststore")

	expectedAnnotations[util.SolrClientTlsCertMd5Annotation] = fmt.Sprintf("%x", md5.Sum(foundTLSSecret.Data[tlsKey]))
	expectDeploymentWithChecks(ctx, solrPrometheusExporter, solrPrometheusExporter.MetricsDeploymentName(), func(g Gomega, found *appsv1.Deployment) {
		g.Expect(found.Spec.Template.ObjectMeta.Annotations).To(Equal(expectedAnnotations), "wrong pod annotations after updating TLS Secret")
	})
}

func expectBasicAuthEnvVars(envVars []corev1.EnvVar, basicAuthSecret *corev1.Secret) {
	Expect(envVars).To(Not(BeEmpty()), "Env vars should not be empty when using basic auth")
	envVars = filterVarsByName(envVars, func(n string) bool {
		return n == "BASIC_AUTH_PASS" || n == "BASIC_AUTH_USER" || n == "JAVA_OPTS"
	})
	Expect(envVars).To(HaveLen(3), "Env vars should have values for each: BASIC_AUTH_PASS, BASIC_AUTH_USER, JAVA_OPTS")

	for _, envVar := range envVars {
		if envVar.Name == "JAVA_OPTS" {
			Expect(envVar.Value).To(ContainSubstring("-Dbasicauth=$(BASIC_AUTH_USER):$(BASIC_AUTH_PASS)"), "Expected basic auth creds in JAVA_OPTS")
		}

		if envVar.Name == "BASIC_AUTH_PASS" {
			Expect(envVar.Value).To(BeEmpty(), "Env var %s comes from a secret, so should not have an explicit value", envVar.Name)
			Expect(envVar.ValueFrom).To(Not(BeNil()), "Env var %s comes from a secret, so valueFrom should be used", envVar.Name)
			Expect(envVar.ValueFrom.SecretKeyRef).To(Not(BeNil()), "Env var %s comes from a secret, so valueFrom.secretKeyRef should be used", envVar.Name)
			Expect(envVar.ValueFrom.SecretKeyRef.Name).To(Equal(basicAuthSecret.Name), "Wrong secret name for basicAuth envVar %s", envVar.Name)
			Expect(envVar.ValueFrom.SecretKeyRef.Key).To(Equal(corev1.BasicAuthPasswordKey), "Wrong secret key for basicAuth envVar %s", envVar.Name)
		}

		if envVar.Name == "BASIC_AUTH_USER" {
			Expect(envVar.Value).To(BeEmpty(), "Env var %s comes from a secret, so should not have an explicit value", envVar.Name)
			Expect(envVar.ValueFrom).To(Not(BeNil()), "Env var %s comes from a secret, so valueFrom should be used", envVar.Name)
			Expect(envVar.ValueFrom.SecretKeyRef).To(Not(BeNil()), "Env var %s comes from a secret, so valueFrom.secretKeyRef should be used", envVar.Name)
			Expect(envVar.ValueFrom.SecretKeyRef.Name).To(Equal(basicAuthSecret.Name), "Wrong secret name for basicAuth envVar %s", envVar.Name)
			Expect(envVar.ValueFrom.SecretKeyRef.Key).To(Equal(corev1.BasicAuthUsernameKey), "Wrong secret key for basicAuth envVar %s", envVar.Name)
		}
	}
}
