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

import (
	"context"

	solrv1beta1 "github.com/apache/solr-operator/api/v1beta1"
	certmanagerv1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
	certmanagermetav1 "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/rand"
	"k8s.io/utils/pointer"
)

const (
	solrIssuerName = "solr-issuer"

	secretTlsPasswordKey = "password"

	clientAuthPasswordSecret = "client-auth-password"
	clientAuthSecret         = "client-auth"
)

func generateBaseSolrCloudWithSecretTLS(ctx context.Context, replicas int, includeClientTLS bool, createKeyStore bool) (solrCloud *solrv1beta1.SolrCloud) {
	solrCloud = generateBaseSolrCloud(replicas)

	solrCertSecret, tlsPasswordSecret, clientCertSecret, clientTlsPasswordSecret := generateSolrCert(ctx, solrCloud, includeClientTLS, createKeyStore)

	solrCloud.Spec.SolrTLS = &solrv1beta1.SolrTLSOptions{
		PKCS12Secret: &corev1.SecretKeySelector{
			LocalObjectReference: corev1.LocalObjectReference{
				Name: solrCertSecret,
			},
			Key: "keystore.p12",
		},
		KeyStorePasswordSecret: &corev1.SecretKeySelector{
			LocalObjectReference: corev1.LocalObjectReference{
				Name: tlsPasswordSecret,
			},
			Key: secretTlsPasswordKey,
		},
		TrustStoreSecret: &corev1.SecretKeySelector{
			LocalObjectReference: corev1.LocalObjectReference{
				Name: solrCertSecret,
			},
			Key: "truststore.p12",
		},
		TrustStorePasswordSecret: &corev1.SecretKeySelector{
			LocalObjectReference: corev1.LocalObjectReference{
				Name: tlsPasswordSecret,
			},
			Key: secretTlsPasswordKey,
		},
	}

	if includeClientTLS {
		solrCloud.Spec.SolrClientTLS = &solrv1beta1.SolrTLSOptions{
			PKCS12Secret: &corev1.SecretKeySelector{
				LocalObjectReference: corev1.LocalObjectReference{
					Name: clientCertSecret,
				},
				Key: "keystore.p12",
			},
			KeyStorePasswordSecret: &corev1.SecretKeySelector{
				LocalObjectReference: corev1.LocalObjectReference{
					Name: clientTlsPasswordSecret,
				},
				Key: secretTlsPasswordKey,
			},
			TrustStoreSecret: &corev1.SecretKeySelector{
				LocalObjectReference: corev1.LocalObjectReference{
					Name: clientCertSecret,
				},
				Key: "truststore.p12",
			},
			TrustStorePasswordSecret: &corev1.SecretKeySelector{
				LocalObjectReference: corev1.LocalObjectReference{
					Name: clientTlsPasswordSecret,
				},
				Key: secretTlsPasswordKey,
			},
		}
	}
	return
}

func generateBaseSolrCloudWithCSITLS(replicas int, csiClientTLS bool, secretClientTLS bool) (solrCloud *solrv1beta1.SolrCloud) {
	solrCloud = generateBaseSolrCloud(replicas)
	solrCloud.Spec.CustomSolrKubeOptions.PodOptions.Volumes = []solrv1beta1.AdditionalVolume{
		{
			Name: "server-tls",
			Source: corev1.VolumeSource{
				CSI: &corev1.CSIVolumeSource{
					Driver:   "csi.cert-manager.io",
					ReadOnly: pointer.Bool(true),
					VolumeAttributes: map[string]string{
						"csi.cert-manager.io/issuer-name": solrIssuerName,
						"csi.cert-manager.io/common-name": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}",
						"csi.cert-manager.io/dns-names": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}.svc.cluster.local," +
							solrCloud.Name + "-solrcloud-common.${POD_NAMESPACE}," +
							solrCloud.Name + "-solrcloud-common.${POD_NAMESPACE}.svc.cluster.local," +
							"${POD_NAME}," +
							"${POD_NAME}.${POD_NAMESPACE}," +
							"${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local",
						"csi.cert-manager.io/key-usages":      "client auth,server auth,digital signature",
						"csi.cert-manager.io/pkcs12-enable":   "true",
						"csi.cert-manager.io/pkcs12-password": "pass",
						"csi.cert-manager.io/fs-group":        "8983",
					},
				},
			},
			DefaultContainerMount: &corev1.VolumeMount{
				ReadOnly:  true,
				MountPath: "/opt/server-tls",
			},
		},
	}

	solrCloud.Spec.SolrTLS = &solrv1beta1.SolrTLSOptions{
		MountedTLSDir: &solrv1beta1.MountedTLSDirectory{
			Path:             "/opt/server-tls",
			KeystoreFile:     "keystore.p12",
			KeystorePassword: "pass",
		},
	}

	if csiClientTLS {
		solrCloud.Spec.CustomSolrKubeOptions.PodOptions.Volumes = append(
			solrCloud.Spec.CustomSolrKubeOptions.PodOptions.Volumes,
			solrv1beta1.AdditionalVolume{
				Name: "client-tls",
				Source: corev1.VolumeSource{
					CSI: &corev1.CSIVolumeSource{
						Driver:   "csi.cert-manager.io",
						ReadOnly: pointer.Bool(true),
						VolumeAttributes: map[string]string{
							"csi.cert-manager.io/issuer-name": solrIssuerName,
							"csi.cert-manager.io/common-name": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}",
							"csi.cert-manager.io/dns-names": "${POD_NAME}." + solrCloud.Name + "-solrcloud-headless.${POD_NAMESPACE}.svc.cluster.local," +
								"${POD_NAME}," +
								"${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local",
							"csi.cert-manager.io/key-usages":      "client auth,digital signature",
							"csi.cert-manager.io/pkcs12-enable":   "true",
							"csi.cert-manager.io/pkcs12-password": "pass",
							"csi.cert-manager.io/fs-group":        "8983",
						},
					},
				},
				DefaultContainerMount: &corev1.VolumeMount{
					ReadOnly:  true,
					MountPath: "/opt/client-tls",
				},
			})

		solrCloud.Spec.SolrClientTLS = &solrv1beta1.SolrTLSOptions{
			MountedTLSDir: &solrv1beta1.MountedTLSDirectory{
				Path:             "/opt/client-tls",
				KeystoreFile:     "keystore.p12",
				KeystorePassword: "pass",
			},
		}
	} else if secretClientTLS {
		// TODO: It is not currently supported to mix secret and mountedDir TLS.
		// This will not work until that support is added.
		solrCloud.Spec.SolrClientTLS = &solrv1beta1.SolrTLSOptions{
			PKCS12Secret: &corev1.SecretKeySelector{
				LocalObjectReference: corev1.LocalObjectReference{
					Name: clientAuthSecret,
				},
				Key: "keystore.p12",
			},
			TrustStoreSecret: &corev1.SecretKeySelector{
				LocalObjectReference: corev1.LocalObjectReference{
					Name: clientAuthSecret,
				},
				Key: "truststore.p12",
			},
			TrustStorePasswordSecret: &corev1.SecretKeySelector{
				LocalObjectReference: corev1.LocalObjectReference{
					Name: clientAuthPasswordSecret,
				},
				Key: "password",
			},
		}
	}
	return
}

func addCSITLSToPrometheusExporter(prometheusExporter *solrv1beta1.SolrPrometheusExporter) {
	if prometheusExporter.Spec.CustomKubeOptions.PodOptions == nil {
		prometheusExporter.Spec.CustomKubeOptions.PodOptions = &solrv1beta1.PodOptions{}
	}
	prometheusExporter.Spec.CustomKubeOptions.PodOptions.Volumes = []solrv1beta1.AdditionalVolume{
		{
			Name: "server-tls",
			Source: corev1.VolumeSource{
				CSI: &corev1.CSIVolumeSource{
					Driver:   "csi.cert-manager.io",
					ReadOnly: pointer.Bool(true),
					VolumeAttributes: map[string]string{
						"csi.cert-manager.io/issuer-name": solrIssuerName,
						"csi.cert-manager.io/common-name": "${POD_NAME}",
						"csi.cert-manager.io/dns-names": "${POD_NAME}.${POD_NAMESPACE}," +
							"${POD_NAME}.${POD_NAMESPACE}.svc.cluster.local",
						"csi.cert-manager.io/key-usages":      "client auth,digital signature",
						"csi.cert-manager.io/pkcs12-enable":   "true",
						"csi.cert-manager.io/pkcs12-password": "pass",
						"csi.cert-manager.io/fs-group":        "8983",
					},
				},
			},
			DefaultContainerMount: &corev1.VolumeMount{
				ReadOnly:  true,
				MountPath: "/opt/client-tls",
			},
		},
	}

	prometheusExporter.Spec.SolrReference.SolrTLS = &solrv1beta1.SolrTLSOptions{
		MountedTLSDir: &solrv1beta1.MountedTLSDirectory{
			Path:             "/opt/client-tls",
			KeystoreFile:     "keystore.p12",
			KeystorePassword: "pass",
		},
	}
	return
}

func installBootstrapIssuer(ctx context.Context) {
	bootstrapIssuer := &certmanagerv1.ClusterIssuer{
		ObjectMeta: metav1.ObjectMeta{
			Name: "bootstrap-issuer",
		},
		Spec: certmanagerv1.IssuerSpec{
			IssuerConfig: certmanagerv1.IssuerConfig{
				SelfSigned: &certmanagerv1.SelfSignedIssuer{},
			},
		},
	}
	Expect(k8sClient.Create(ctx, bootstrapIssuer)).To(Succeed(), "Failed to install SelfSigned ClusterIssuer for bootstrapping CA")
	DeferCleanup(func(ctx context.Context) {
		Expect(k8sClient.Delete(ctx, bootstrapIssuer)).To(Succeed(), "Failed to delete SelfSigned bootstrapping ClusterIssuer")
	})
}

func installSolrIssuer(ctx context.Context, namespace string) {
	secretName := "solr-ca-key-pair"
	clusterCA := &certmanagerv1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "solr-ca",
			Namespace: namespace,
		},
		Spec: certmanagerv1.CertificateSpec{
			IsCA:       true,
			CommonName: "solr-ca",
			SecretName: secretName,
			PrivateKey: &certmanagerv1.CertificatePrivateKey{
				RotationPolicy: certmanagerv1.RotationPolicyNever,
				Algorithm:      "RSA",
			},
			IssuerRef: certmanagermetav1.ObjectReference{
				Name:  "bootstrap-issuer",
				Kind:  "ClusterIssuer",
				Group: "cert-manager.io",
			},
		},
	}
	Expect(k8sClient.Create(ctx, clusterCA)).To(Succeed(), "Failed to install Solr CA for tests")

	namespaceIssuer := &certmanagerv1.Issuer{
		ObjectMeta: metav1.ObjectMeta{
			Name:      solrIssuerName,
			Namespace: namespace,
		},
		Spec: certmanagerv1.IssuerSpec{
			IssuerConfig: certmanagerv1.IssuerConfig{
				CA: &certmanagerv1.CAIssuer{
					SecretName: secretName,
				},
			},
		},
	}
	Expect(k8sClient.Create(ctx, namespaceIssuer)).To(Succeed(), "Failed to install CA Issuer for issuing test certs in namespace "+namespace)

	expectSecret(ctx, clusterCA, secretName)
}

func generateSolrCert(ctx context.Context, solrCloud *solrv1beta1.SolrCloud, includeClientTLS bool, createKeyStore bool) (certSecretName string, tlsPasswordSecretName string, clientTLSCertSecretName string, clientTLSPasswordSecretName string) {
	// First create a secret to use as a password for the keystore/truststore
	tlsPasswordSecret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      solrCloud.Name + "-keystore-password",
			Namespace: solrCloud.Namespace,
		},
		StringData: map[string]string{
			secretTlsPasswordKey: rand.String(10),
		},
		Type: corev1.SecretTypeOpaque,
	}
	Expect(k8sClient.Create(ctx, tlsPasswordSecret)).To(Succeed(), "Failed to create secret for tls password in namespace "+solrCloud.Namespace)

	expectSecret(ctx, solrCloud, tlsPasswordSecret.Name)
	tlsPasswordSecretName = tlsPasswordSecret.Name

	allDNSNames := make([]string, *solrCloud.Spec.Replicas*2+1)
	for _, pod := range solrCloud.GetAllSolrPodNames() {
		allDNSNames = append(allDNSNames, pod, solrCloud.InternalNodeUrl(pod, false))
	}

	certSecretName = solrCloud.Name + "-secret-auth"

	solrCert := &certmanagerv1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      solrCloud.Name + "-secret-auth",
			Namespace: solrCloud.Namespace,
		},
		Spec: certmanagerv1.CertificateSpec{
			CommonName: solrCloud.InternalCommonUrl(false),
			DNSNames:   allDNSNames,
			SecretName: certSecretName,
			Keystores: &certmanagerv1.CertificateKeystores{
				PKCS12: &certmanagerv1.PKCS12Keystore{
					Create: createKeyStore,
					PasswordSecretRef: certmanagermetav1.SecretKeySelector{
						LocalObjectReference: certmanagermetav1.LocalObjectReference{
							Name: tlsPasswordSecret.Name,
						},
						Key: secretTlsPasswordKey,
					},
				},
			},
			IssuerRef: certmanagermetav1.ObjectReference{
				Name:  solrIssuerName,
				Kind:  "Issuer",
				Group: "cert-manager.io",
			},
			IsCA:   false,
			Usages: []certmanagerv1.KeyUsage{certmanagerv1.UsageServerAuth, certmanagerv1.UsageDigitalSignature},
			PrivateKey: &certmanagerv1.CertificatePrivateKey{
				RotationPolicy: certmanagerv1.RotationPolicyNever,
				Algorithm:      "RSA",
			},
		},
	}
	Expect(k8sClient.Create(ctx, solrCert)).To(Succeed(), "Failed to install Solr secret cert for tests")

	expectSecret(ctx, solrCert, certSecretName)

	if includeClientTLS {
		// First create a secret to use as a password for the keystore/truststore
		clientTlsPasswordSecret := &corev1.Secret{
			ObjectMeta: metav1.ObjectMeta{
				Name:      solrCloud.Name + "-client-tls-password",
				Namespace: solrCloud.Namespace,
			},
			StringData: map[string]string{
				secretTlsPasswordKey: rand.String(10),
			},
			Type: corev1.SecretTypeOpaque,
		}
		Expect(k8sClient.Create(ctx, clientTlsPasswordSecret)).To(Succeed(), "Failed to create secret for client tls password in namespace "+solrCloud.Namespace)

		expectSecret(ctx, solrCloud, clientTlsPasswordSecret.Name)
		clientTLSPasswordSecretName = clientTlsPasswordSecret.Name

		clientTLSCertSecretName = solrCloud.Name + "-client-tls-secret-auth"

		solrClientCert := &certmanagerv1.Certificate{
			ObjectMeta: metav1.ObjectMeta{
				Name:      solrCloud.Name + "-client-secret-auth",
				Namespace: solrCloud.Namespace,
			},
			Spec: certmanagerv1.CertificateSpec{
				CommonName: solrCloud.InternalCommonUrl(false),
				DNSNames:   allDNSNames,
				SecretName: clientTLSCertSecretName,
				Keystores: &certmanagerv1.CertificateKeystores{
					PKCS12: &certmanagerv1.PKCS12Keystore{
						Create: true,
						PasswordSecretRef: certmanagermetav1.SecretKeySelector{
							LocalObjectReference: certmanagermetav1.LocalObjectReference{
								Name: clientTlsPasswordSecret.Name,
							},
							Key: secretTlsPasswordKey,
						},
					},
				},
				IssuerRef: certmanagermetav1.ObjectReference{
					Name:  solrIssuerName,
					Kind:  "Issuer",
					Group: "cert-manager.io",
				},
				IsCA:   false,
				Usages: []certmanagerv1.KeyUsage{certmanagerv1.UsageClientAuth, certmanagerv1.UsageDigitalSignature},
				PrivateKey: &certmanagerv1.CertificatePrivateKey{
					RotationPolicy: certmanagerv1.RotationPolicyNever,
					Algorithm:      "RSA",
				},
			},
		}
		Expect(k8sClient.Create(ctx, solrClientCert)).To(Succeed(), "Failed to install Solr clientTLS secret cert for tests")

		expectSecret(ctx, solrClientCert, clientTLSCertSecretName)
	}

	return
}
