package cert

import (
	"encoding/base64"

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

	"github.com/tidbcloud/ticdc-service-ng/pkg/consts"
	"github.com/tidbcloud/ticdc-service-ng/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),
		},
	}
}

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

	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: consts.CertificateDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: consts.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.
func TiDBClientCert(comp *namer.Namer) *v1.Certificate {
	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      comp.TidbClientCertSecret(),
			Namespace: comp.GetTiDBNamespace(),
		},
		Spec: v1.CertificateSpec{
			SecretName: comp.TidbClientCertSecret(),
			Duration: &metav1.Duration{
				Duration: consts.CertificateDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: consts.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),
	}
	bytes, err := base64.StdEncoding.DecodeString(x509.CACert)
	if err != nil {
		return nil, err
	}
	secret.Data[corev1.ServiceAccountRootCAKey] = bytes
	bytes, err = base64.StdEncoding.DecodeString(x509.Cert)
	if err != nil {
		return nil, err
	}
	secret.Data[corev1.TLSCertKey] = bytes
	bytes, err = base64.StdEncoding.DecodeString(x509.Key)
	if err != nil {
		return nil, err
	}
	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,
	}
}
