package cert

import (
	"fmt"

	v1 "github.com/cert-manager/cert-manager/pkg/apis/certmanager/v1"
	cmmeta "github.com/cert-manager/cert-manager/pkg/apis/meta/v1"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func certSecret(compType, compId string) string {
	if compId == "" {
		return fmt.Sprintf("db-%s-cluster-secret", compType)
	}

	return fmt.Sprintf("db-%s-%s-cluster-secret", compId, compType)
}

func ServerCertSecret(compType, compId string) string {
	if compId == "" {
		return fmt.Sprintf("db-%s-server-secret", compType)
	}
	return fmt.Sprintf("db-%s-%s-server-secret", compId, compType)
}

func TiProxyTiDBCertSecret() string {
	return "db-tiproxy-tidb-secret"
}

// func tidbClientCertSecret(compId string) string {
// 	return fmt.Sprintf("db-tidb-%s-client-secret", compId)
// }

func clientCertSecret() string {
	return "db-cluster-client-secret"
}

func clusterAgentServerCertSecret() string {
	return "cluster-agent-server-secret"
}

func clusterAgentDNSNames(namespace string) []string {
	// Kong may route to cluster-agent with a non-standard service DNS name that embeds the port,
	// e.g. "cluster-agent.<ns>.8080.svc". This value can become the upstream Host/SNI during TLS
	// verification, so it must be included in the server certificate SANs.
	return []string{
		"cluster-agent",
		fmt.Sprintf("cluster-agent.%s", namespace),
		fmt.Sprintf("cluster-agent.%s.svc", namespace),
		fmt.Sprintf("cluster-agent.%s.8080.svc", namespace),
		fmt.Sprintf("cluster-agent.%s.svc.cluster.local", namespace),
		fmt.Sprintf("cluster-agent.%s.8080.svc.cluster.local", namespace),
	}
}

func tidbDNSNameWildcard(namespace string) []string {
	names := []string{
		"*.db-cluster",
		fmt.Sprintf("*.db-cluster.%s", namespace),
		fmt.Sprintf("*.db-cluster.%s.svc", namespace),
		fmt.Sprintf("*.db-cluster.%s.svc.cluster.local", namespace),
	}

	return names
}

func dnsNameWildcard(compType, compId, namespace string) []string {

	names := []string{
		"*.db-cluster",
		fmt.Sprintf("*.db-cluster.%s", namespace),
		fmt.Sprintf("*.db-cluster.%s.svc", namespace),
		fmt.Sprintf("*.db-cluster.%s.svc.cluster.local", namespace),
	}

	if compId != "" {
		names = append(names,
			fmt.Sprintf("db-%s-%s", compId, compType),
			fmt.Sprintf("db-%s-%s.%s", compId, compType, namespace),
			fmt.Sprintf("db-%s-%s.%s.svc", compId, compType, namespace),
			fmt.Sprintf("db-%s-%s.%s.svc.cluster.local", compId, compType, namespace),
			fmt.Sprintf("db-%s-%s-peer", compId, compType),
			fmt.Sprintf("db-%s-%s-peer.%s", compId, compType, namespace),
			fmt.Sprintf("db-%s-%s-peer.%s.svc", compId, compType, namespace),
			fmt.Sprintf("db-%s-%s-peer.%s.svc.cluster.local", compId, compType, namespace),
			fmt.Sprintf("*.db-%s-%s-peer", compId, compType),
			fmt.Sprintf("*.db-%s-%s-peer.%s", compId, compType, namespace),
			fmt.Sprintf("*.db-%s-%s-peer.%s.svc", compId, compType, namespace),
			fmt.Sprintf("*.db-%s-%s-peer.%s.svc.cluster.local", compId, compType, namespace),
		)
	}

	if compType == "pd" {
		names = append(names,
			"db-pd",
			fmt.Sprintf("db-pd.%s", namespace),
			fmt.Sprintf("db-pd.%s.svc", namespace),
			fmt.Sprintf("db-pd.%s.svc.cluster.local", namespace),
		)
	}

	return names
}

func getTiProxyClusterServerDNSWildcard(compId, namespace, identity, domainSuffix string) []string {

	names := []string{
		fmt.Sprintf("db-%s-tiproxy", compId),
		fmt.Sprintf("db-%s-tiproxy.%s", compId, namespace),
		fmt.Sprintf("db-%s-tiproxy.%s.svc", compId, namespace),
		fmt.Sprintf("db-%s-tiproxy.%s.svc.cluster.local", compId, namespace),
	}
	if identity != "" && domainSuffix != "" {
		// for example: *.abcd1234.tidbcloud.com
		names = append(names, fmt.Sprintf("*.%s.%s", identity, domainSuffix))
	}

	return names

}

func getTiDBClusterServerDNSWildcard(namespace string) []string {

	return []string{
		fmt.Sprintf("*.%s", namespace),
		fmt.Sprintf("*.%s.svc", namespace),
		"*.db-cluster",
		fmt.Sprintf("*.db-cluster.%s", namespace),
		fmt.Sprintf("*.db-cluster.%s.svc", namespace),
		fmt.Sprintf("*.db-cluster.%s.svc.cluster.local", namespace),
	}
}

func TiDBComponentCert(namespace string) *v1.Certificate {
	return componentCert("tidb", "", namespace, tidbDNSNameWildcard(namespace))
}

func ComponentCert(compType, compId, namespace string) *v1.Certificate {
	return componentCert(compType, compId, namespace, dnsNameWildcard(compType, compId, namespace))
}

func componentCert(compType, compId, namespace string, dnsNames []string) *v1.Certificate {
	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,
	}
	duration := selfSignedComponentCACertDuration

	// Certificate spec cannot be changed once created, so we don't construct
	// mutate function here
	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      certSecret(compType, compId),
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: certSecret(compType, compId),
			Duration: &metav1.Duration{
				Duration: duration,
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			CommonName: utils.FnvHash(namespace),
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
			Usages: []v1.KeyUsage{
				v1.UsageServerAuth,
				v1.UsageClientAuth,
			},
			DNSNames: dnsNames,
			IPAddresses: []string{
				"127.0.0.1",
				"::1",
			},
			PrivateKey: privateKey,
			IssuerRef: cmmeta.ObjectReference{
				Name:  tidbComponentsSelfSignedIssuerName,
				Kind:  tidbComponentsSelfSignedIssuerKind,
				Group: selfSignedIssuerGroup,
			},
		},
	}
}

func TiProxyTiDBCert(namespace string) *v1.Certificate {
	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,
	}
	duration := selfSignedComponentCACertDuration

	// Certificate spec cannot be changed once created, so we don't construct
	// mutate function here
	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      TiProxyTiDBCertSecret(),
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: TiProxyTiDBCertSecret(),
			Duration: &metav1.Duration{
				Duration: duration,
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			CommonName: utils.FnvHash(namespace),
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
			Usages: []v1.KeyUsage{
				v1.UsageServerAuth,
				v1.UsageClientAuth,
			},
			DNSNames: dnsNameWildcard("tiproxy", "", namespace),
			IPAddresses: []string{
				"127.0.0.1",
				"::1",
			},
			PrivateKey: privateKey,
			IssuerRef: cmmeta.ObjectReference{
				Name:  tidbComponentsSelfSignedIssuerName,
				Kind:  tidbComponentsSelfSignedIssuerKind,
				Group: selfSignedIssuerGroup,
			},
		},
	}
}

func TiDBServerCerts(namespace string, opts ...Option) *v1.Certificate {
	// In theory, we don't use selfSigned certificate for tidb-server. Therefore, we don't do changes like ComponentCert did.
	resolvedOpts := resolveOptions(opts...)
	tidbServerCert := &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ServerCertSecret("tidb", ""),
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: ServerCertSecret("tidb", ""),
			DNSNames:   getTiDBClusterServerDNSWildcard(namespace),
			Duration: &metav1.Duration{
				Duration: resolvedOpts.duration.TiDBServerDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: resolvedOpts.duration.RenewBefore,
			},
			CommonName: utils.FnvHash(namespace),
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
		},
	}

	tidbServerCert.Spec.IssuerRef = cmmeta.ObjectReference{
		Name:  tidbComponentsSelfSignedIssuerName,
		Kind:  tidbComponentsSelfSignedIssuerKind,
		Group: selfSignedIssuerGroup,
	}

	return tidbServerCert
}

func TiProxyServerCerts(compId, namespace, gatewayIdentity, domainSuffix string, enablePCA bool, opts ...Option) *v1.Certificate {
	// In theory, we don't use selfSigned certificate for tiproxy. Therefore, we don't do changes like ComponentCert did.
	resolvedOpts := resolveOptions(opts...)
	tiproxyServerCert := &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ServerCertSecret("tiproxy", compId),
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: ServerCertSecret("tiproxy", compId),
			DNSNames:   getTiProxyClusterServerDNSWildcard(compId, namespace, gatewayIdentity, domainSuffix),
			Duration: &metav1.Duration{
				Duration: resolvedOpts.duration.TiProxyServerDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: resolvedOpts.duration.RenewBefore,
			},
			CommonName: "TiDB Cloud CA X1",
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
		},
	}

	if enablePCA {
		tiproxyServerCert.Spec.IssuerRef = cmmeta.ObjectReference{
			Name:  PCAIssuerName,
			Kind:  awsIssuerKind,
			Group: awsIssuerGroup,
		}
	} else {
		tiproxyServerCert.Spec.IssuerRef = cmmeta.ObjectReference{
			Name:  tidbComponentsSelfSignedIssuerName,
			Kind:  tidbComponentsSelfSignedIssuerKind,
			Group: selfSignedIssuerGroup,
		}
	}

	return tiproxyServerCert
}

func TiDBPrivilegedServerCert(compId, namespace string) *v1.Certificate {
	tidbPrivilegedCert := &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ServerCertSecret(entity.TiDBPrivilegedType, compId),
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: ServerCertSecret(entity.TiDBPrivilegedType, compId),
			DNSNames: []string{
				fmt.Sprintf("db-%s-privileged-tidb.%s", compId, namespace),
				fmt.Sprintf("db-%s-privileged-tidb.%s.svc", compId, namespace),
			},
			Duration: &metav1.Duration{
				Duration: tidbServerCertificateDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			CommonName: utils.FnvHash(namespace),
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
			IssuerRef: cmmeta.ObjectReference{
				Name:  tidbComponentsSelfSignedIssuerName,
				Kind:  tidbComponentsSelfSignedIssuerKind,
				Group: selfSignedIssuerGroup,
			},
		},
	}

	return tidbPrivilegedCert
}

func TiDBDiagnoseServerCert(compId, namespace string) *v1.Certificate {
	tidbDiagnoseCert := &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      ServerCertSecret(entity.TiDBDiagnosisType, compId),
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: ServerCertSecret(entity.TiDBDiagnosisType, compId),
			DNSNames: []string{
				fmt.Sprintf("db-%s-diagnosis-tidb.%s", compId, namespace),
				fmt.Sprintf("db-%s-diagnosis-tidb.%s.svc", compId, namespace),
			},
			Duration: &metav1.Duration{
				Duration: tidbServerCertificateDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
			IssuerRef: cmmeta.ObjectReference{
				Name:  tidbComponentsSelfSignedIssuerName,
				Kind:  tidbComponentsSelfSignedIssuerKind,
				Group: selfSignedIssuerGroup,
			},
		},
	}

	return tidbDiagnoseCert
}

// ClusterClientCert is used by some clients or one-time jobs such as tidb-operator, backup/restore jobs, etc.
func ClusterClientCert(namespace string) *v1.Certificate {
	duration := 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:      clientCertSecret(),
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: clientCertSecret(),
			Duration: &metav1.Duration{
				Duration: duration,
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			CommonName: utils.FnvHash(namespace),
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
			Usages: []v1.KeyUsage{
				v1.UsageClientAuth,
			},
			PrivateKey: privateKey,
			IssuerRef: cmmeta.ObjectReference{
				Name:  tidbComponentsSelfSignedIssuerName,
				Kind:  tidbComponentsSelfSignedIssuerKind,
				Group: selfSignedIssuerGroup,
			},
		},
	}
}

// ClusterAgentServerCert creates a server certificate for the cluster-agent HTTPS endpoint.
func ClusterAgentServerCert(namespace string) *v1.Certificate {
	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      clusterAgentServerCertSecret(),
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: clusterAgentServerCertSecret(),
			DNSNames:   clusterAgentDNSNames(namespace),
			Duration: &metav1.Duration{
				Duration: selfSignedComponentCertDuration, // 13 months
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			CommonName: utils.FnvHash(namespace),
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
			Usages: []v1.KeyUsage{
				v1.UsageServerAuth,
			},
			IPAddresses: []string{
				"127.0.0.1",
				"::1",
			},
			PrivateKey: &v1.CertificatePrivateKey{
				RotationPolicy: v1.RotationPolicyAlways,
			},
			IssuerRef: cmmeta.ObjectReference{
				Name:  tidbComponentsSelfSignedIssuerName,
				Kind:  tidbComponentsSelfSignedIssuerKind,
				Group: selfSignedIssuerGroup,
			},
		},
	}
}

const (
	TiBRCertificateSuffix    = "tibr-cluster-secret"
	TiBRGCRCertificateSuffix = "tibrgc-cluster-secret"
)

func TiBRCertName(tibrName string) string {
	return fmt.Sprintf("%s-%s", tibrName, TiBRCertificateSuffix)
}
func TiBRCert(tibrName, namespace string) *v1.Certificate {
	secretName := TiBRCertName(tibrName)

	// Certificate spec cannot be changed once created, so we don't construct
	// mutate function here
	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      secretName,
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: secretName,
			Duration: &metav1.Duration{
				Duration: selfSignedComponentCACertDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			CommonName: utils.FnvHash(namespace),
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
			Usages: []v1.KeyUsage{
				v1.UsageServerAuth,
				v1.UsageClientAuth,
			},
			DNSNames: tibrDNSNames(tibrName, namespace),
			IPAddresses: []string{
				"127.0.0.1",
				"::1",
			},
			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,
			},
			IssuerRef: cmmeta.ObjectReference{
				Name:  tidbComponentsSelfSignedIssuerName,
				Kind:  tidbComponentsSelfSignedIssuerKind,
				Group: selfSignedIssuerGroup,
			},
		},
	}
}

func tibrDNSNames(tibrName, namespace string) []string {
	return []string{
		fmt.Sprintf("%s-tibr-headless", tibrName),
		fmt.Sprintf("%s-tibr-headless.%s", tibrName, namespace),
		fmt.Sprintf("%s-tibr-headless.%s.svc", tibrName, namespace),
	}
}

func TiBRGCRCertName(tibrGCRName string) string {
	return fmt.Sprintf("%s-%s", tibrGCRName, TiBRGCRCertificateSuffix)
}
func TiBRGCRCert(tibrGCRName, namespace string) *v1.Certificate {
	secretName := TiBRGCRCertName(tibrGCRName)

	return &v1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      secretName,
			Namespace: namespace,
		},
		Spec: v1.CertificateSpec{
			SecretName: secretName,
			Duration: &metav1.Duration{
				Duration: selfSignedComponentCACertDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: renewBefore,
			},
			CommonName: utils.FnvHash(namespace),
			Subject: &v1.X509Subject{
				Organizations: []string{certOrganization},
			},
			Usages: []v1.KeyUsage{
				v1.UsageClientAuth,
			},
			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,
			},
			IssuerRef: cmmeta.ObjectReference{
				Name:  tidbComponentsSelfSignedIssuerName,
				Kind:  tidbComponentsSelfSignedIssuerKind,
				Group: selfSignedIssuerGroup,
			},
		},
	}
}
