package cert

import (
	"encoding/base64"
	"time"

	v1 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1"
	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/tidbcloud/eks-provider/api/v1alpha1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/namer"
)

// Historically, tidb cluster's all certificates(including both between tidb components and tidb client/server) are issued
// by AWSPCAIssuer in the same namespace as tidb cluster. And the PCA issuer is named as cluster-issuer.
//
// [[ About 'cluster-issuer' and 'pca-cluster-issuer' ]]
// In fact we can just use AWSClusterIssuer to avoid provisioning a namespaced AWSPCAIssuer per tidb cluster.
// So we introduce AWSClusterIssuerKind which is provisioned by network-controller or region-server per-dataplane.
// But we don't enable AWSClusterIssuerKind by default for backward compatibility. And only Azure is using it by default.
//
// [[ About self-signed issuers ]]
// To save AWS PCA cost, we introduce self-signed issuer to issue certificates for tidb cluster internal mTLS.
// And for testing environment, we can also use self-signed issuer to issue certificate for tidb server.
// Though both are self-signed issuers, they are used for different purposes and have different lifecycle management.
// So we create two self-signed issuers, for tidb server we use ClusterIssuer, for tidb components we use Issuer.
// For production environment, the tidb server still uses AWSPCAClusterIssuer.

const (
	AWSIssuerKind         = "AWSPCAIssuer"
	AWSIssuerGroup        = "awspca.cert-manager.io"
	PCAIssuerName         = "cluster-issuer" // PCA issuer, keep it as cluster-issuer for backward compatibility
	SelfSignedIssuerGroup = "cert-manager.io"

	TiDBServerSelfSignedClusterIssuerKind = v1.ClusterIssuerKind
	TiDBServerSelfSignedClusterIssuerName = "tidb-server-self-signed-cluster-issuer" // Self Signed Cluster Issuer for TiDB Server

	TiDBComponentsSelfSignedIssuerKind = v1.IssuerKind
	// TiDBComponentsSelfSignedIssuerName Security team recommend to use self-signed CA cert per cluster to maximize the isolation of cluster.
	// Therefore, we create namespaced, per-cluster self-signed issuer for tidb component certs.
	TiDBComponentsSelfSignedIssuerName = "tidb-components-self-signed-issuer" // Self Signed Issuer for TiDB components
	// TiDBComponentsSelfSignedCACertName is per cluster created
	TiDBComponentsSelfSignedCACertName = "tidb-components-self-signed-ca-cert" // Self Signed Cluster CA cert for TiDB inter-components
)

func ComponentCert(comp *namer.Namer, selfSigned bool) *v1.Certificate {
	var privateKey *v1.CertificatePrivateKey
	duration := consts.CertificateDuration
	if selfSigned {
		duration = consts.SelfSignedComponentCertDuration

		privateKey = &v1.CertificatePrivateKey{
			// Rotate both the private key and the certificate.
			// See https://cert-manager.io/docs/usage/certificate/#issuance-behavior-rotation-of-the-private-key to get more info.
			RotationPolicy: v1.RotationPolicyAlways,
		}
	}

	// Certificate spec cannot be changed once created, so we don't construct
	// mutate function here
	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      comp.CertSecret(),
			Namespace: comp.GetTiDBNamespace(),
		},
		Spec: v1.CertificateSpec{
			SecretName: comp.CertSecret(),
			Duration: &metav1.Duration{
				Duration: duration,
			},
			RenewBefore: &metav1.Duration{
				Duration: consts.RenewBefore,
			},
			CommonName: comp.GetClusterCommonName(),
			Subject: &v1.X509Subject{
				Organizations: []string{consts.CertOrganization},
			},
			Usages: []v1.KeyUsage{
				v1.UsageServerAuth,
				v1.UsageClientAuth,
			},
			DNSNames: comp.DNSNames(),
			IPAddresses: []string{
				"127.0.0.1",
				"::1",
			},
			PrivateKey: privateKey,
			IssuerRef:  getClusterComponentIssuerRef(selfSigned),
		},
	}
}

// componentCertValidity returns the certificate duration and renew-before window
// for a cluster certificate. When shortLived is true it returns values that stay
// under the 7-day validity cap of an AWS Private CA running in
// SHORT_LIVED_CERTIFICATE mode; the issuer is unchanged, so a MySQL client still
// verifies against the same CA.
func componentCertValidity(shortLived bool) (duration, renewBefore time.Duration) {
	if shortLived {
		return consts.ShortLivedComponentCertDuration, consts.ShortLivedComponentCertRenewBefore
	}
	return consts.CertificateDuration, consts.RenewBefore
}

func TiDBServerCert(comp *namer.Namer, useWildcardDNS bool, selfSigned bool, shortLived bool) *v1.Certificate {
	// In theory, we don't use selfSigned certificate for tidb-server. Therefore, we don't do changes like ComponentCert did.
	duration, renewBefore := componentCertValidity(shortLived)

	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      comp.ServerCertSecret(),
			Namespace: comp.GetTiDBNamespace(),
		},
		Spec: v1.CertificateSpec{
			SecretName: comp.ServerCertSecret(),
			DNSNames:   comp.GetClusterServerDNSNames(useWildcardDNS),
			Duration: &metav1.Duration{
				Duration: duration,
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			Subject: &v1.X509Subject{
				Organizations: []string{consts.CertOrganization},
			},
			IssuerRef: getTiDBServerIssuerRef(selfSigned),
		},
	}
}

// TiDBClientCert is just for compatibility only. In fact, this certificate is not required and should be removed in the future.
//
// When shortLived is true the certificate uses a short validity
// (consts.ShortLivedComponentCertDuration) so it can be issued by an AWS Private
// CA running in SHORT_LIVED_CERTIFICATE mode; the AWS Private CA issuer itself is
// unchanged.
func TiDBClientCert(comp *namer.Namer, shortLived bool) *v1.Certificate {
	duration, renewBefore := componentCertValidity(shortLived)

	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      comp.TidbClientCertSecret(),
			Namespace: comp.GetTiDBNamespace(),
		},
		Spec: v1.CertificateSpec{
			SecretName: comp.TidbClientCertSecret(),
			Duration: &metav1.Duration{
				Duration: duration,
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			CommonName: comp.GetClusterCommonName(),
			Subject: &v1.X509Subject{
				Organizations: []string{consts.CertOrganization},
			},
			Usages: []v1.KeyUsage{
				v1.UsageClientAuth,
			},
			IssuerRef: getTiDBServerIssuerRef(false),
		},
	}
}

// ClusterClientCert is used by some clients or one-time jobs such as tidb-operator, backup/restore jobs, etc.
func ClusterClientCert(cluster *namer.Namer, selfSigned bool) *v1.Certificate {
	var privateKey *v1.CertificatePrivateKey
	duration := consts.CertificateDuration
	if selfSigned {
		duration = consts.SelfSignedComponentCertDuration
		privateKey = &v1.CertificatePrivateKey{
			// Rotate both the private key and the certificate.
			// See https://cert-manager.io/docs/usage/certificate/#issuance-behavior-rotation-of-the-private-key to get more info.
			RotationPolicy: v1.RotationPolicyAlways,
		}
	}

	// Certificate spec cannot be changed once created, so we don't construct
	// mutate function here
	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      cluster.ClientCertSecret(),
			Namespace: cluster.GetTiDBNamespace(),
		},
		Spec: v1.CertificateSpec{
			SecretName: cluster.ClientCertSecret(),
			Duration: &metav1.Duration{
				Duration: duration,
			},
			RenewBefore: &metav1.Duration{
				Duration: consts.RenewBefore,
			},
			CommonName: cluster.GetClusterCommonName(),
			Subject: &v1.X509Subject{
				Organizations: []string{consts.CertOrganization},
			},
			Usages: []v1.KeyUsage{
				v1.UsageClientAuth,
			},
			PrivateKey: privateKey,
			IssuerRef:  getClusterComponentIssuerRef(selfSigned),
		},
	}
}

func ClusterServerSecretFromX509(n *namer.Namer, x509 *v1alpha1.X509) (*corev1.Secret, error) {
	secret := &corev1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:      n.ServerCertSecret(),
			Namespace: n.GetTiDBNamespace(),
			Labels:    n.Labels(),
		},
		Type: corev1.SecretTypeTLS,
		Data: make(map[string][]byte),
	}
	if bytes, err := base64.StdEncoding.DecodeString(x509.CACert); err != nil {
		return nil, err
	} else {
		secret.Data[corev1.ServiceAccountRootCAKey] = bytes
	}
	if bytes, err := base64.StdEncoding.DecodeString(x509.Cert); err != nil {
		return nil, err
	} else {
		secret.Data[corev1.TLSCertKey] = bytes
	}
	if bytes, err := base64.StdEncoding.DecodeString(x509.Key); err != nil {
		return nil, err
	} else {
		secret.Data[corev1.TLSPrivateKeyKey] = bytes
	}

	return secret, nil
}

func getTiDBServerIssuerRef(selfSigned bool) cmmeta.ObjectReference {
	if selfSigned {
		return cmmeta.ObjectReference{
			Name:  TiDBServerSelfSignedClusterIssuerName,
			Kind:  TiDBServerSelfSignedClusterIssuerKind,
			Group: SelfSignedIssuerGroup,
		}
	}

	return cmmeta.ObjectReference{
		Name:  PCAIssuerName,
		Kind:  AWSIssuerKind,
		Group: AWSIssuerGroup,
	}
}

func getClusterComponentIssuerRef(selfSigned bool) cmmeta.ObjectReference {
	if selfSigned {
		return cmmeta.ObjectReference{
			Name:  TiDBComponentsSelfSignedIssuerName,
			Kind:  TiDBComponentsSelfSignedIssuerKind,
			Group: SelfSignedIssuerGroup,
		}
	}

	return cmmeta.ObjectReference{
		Name:  PCAIssuerName,
		Kind:  AWSIssuerKind,
		Group: AWSIssuerGroup,
	}
}
