package cluster

import (
	"fmt"
	"time"

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

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/shared-provider/pkg/utils"
)

const (
	DefaultCertificateDuration = 10 * 365 * 24 * time.Hour // 10 year
	DefaultRenewBefore         = 90 * 24 * time.Hour       // 90 days
	DefaultOrganization        = "PingCAP"
	CertManagerGroup           = "awspca.cert-manager.io"
	AwsPcaIssuer               = "aws-pca-issuer"
	AwsPcaIssuerKind           = "AWSPCAClusterIssuer"
)

func tidbServerSecretName(clusterName string) string {
	return fmt.Sprintf("%s-tidb-server-secret", clusterName)
}

func tidbClusterComponentSecretName(clusterName string, comp cloudv1.ComponentType) string {
	return fmt.Sprintf("%s-%s-cluster-secret", clusterName, comp)
}

func clusterClientSecretName(clusterName string) string {
	return fmt.Sprintf("%s-cluster-client-secret", clusterName)
}

func (r *reconcileContext) createTLSCertForMySQLClient() error {
	return r.createTiDBServerCert(
		r.tcKey.Namespace,
		r.tcKey.Name,
		r.endpointDNS(EndpointTypePublic),
		r.endpointDNS(EndpointTypePrivateLink),
	)
}

func (r *reconcileContext) createTiDBServerCert(ns, clusterName string, addr ...string) error {
	componentDNSNames := r.componentSvcNamespaceNames(ns, clusterName, cloudv1.ComponentTiDB)
	serverCertificate := &cmv1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tidbServerSecretName(clusterName),
			Namespace: ns,
		},
		Spec: cmv1.CertificateSpec{
			SecretName: tidbServerSecretName(clusterName),
			DNSNames:   append(componentDNSNames, addr...),
			Duration: &metav1.Duration{
				Duration: DefaultCertificateDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: DefaultRenewBefore,
			},
			Subject: &cmv1.X509Subject{
				Organizations: []string{DefaultOrganization},
			},
			IssuerRef: cmmeta.ObjectReference{
				Name:  AwsPcaIssuer,
				Kind:  AwsPcaIssuerKind,
				Group: CertManagerGroup,
			},
		},
	}

	return r.createOrGetWithOwnerLabel(serverCertificate)
}

func CommonName(name string) string {
	return utils.FnvHash(name)
}

func (r *reconcileContext) createTLSCertBetweenComponents() error {
	for _, comp := range r.cluster.GetComponents() {
		err := r.createTiDBComponentCert(comp.GetComponentType())
		if err != nil {
			return err
		}
	}

	return r.createClusterClientCert()
}

func (r *reconcileContext) componentSvcNamespaceNames(ns, clusterName string, comp cloudv1.ComponentType) []string {
	return []string{
		fmt.Sprintf("%s-%s.%s", clusterName, comp, ns),
		fmt.Sprintf("%s-%s.%s.svc", clusterName, comp, ns),
	}
}

func (r *reconcileContext) createTiDBComponentCert(comp cloudv1.ComponentType) error {
	ns, clusterName := r.tcKey.Namespace, r.tcKey.Name
	componentDNSNames := append([]string{fmt.Sprintf("%s-%s", clusterName, comp)},
		r.componentSvcNamespaceNames(ns, clusterName, comp)...,
	)
	componentDNSNames = append(componentDNSNames,
		fmt.Sprintf("%s-%s-peer", clusterName, comp),
		fmt.Sprintf("%s-%s-peer.%s", clusterName, comp, ns),
		fmt.Sprintf("%s-%s-peer.%s.svc", clusterName, comp, ns),
		fmt.Sprintf("*.%s-%s-peer", clusterName, comp),
		fmt.Sprintf("*.%s-%s-peer.%s", clusterName, comp, ns),
		fmt.Sprintf("*.%s-%s-peer.%s.svc", clusterName, comp, ns),
	)
	componentCertificate := &cmv1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      tidbClusterComponentSecretName(clusterName, comp),
			Namespace: ns,
		},
		Spec: cmv1.CertificateSpec{
			SecretName: tidbClusterComponentSecretName(clusterName, comp),
			Duration: &metav1.Duration{
				Duration: DefaultCertificateDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: DefaultRenewBefore,
			},
			CommonName: CommonName(clusterName),
			Subject: &cmv1.X509Subject{
				Organizations: []string{DefaultOrganization},
			},
			Usages: []cmv1.KeyUsage{
				cmv1.UsageServerAuth,
				cmv1.UsageClientAuth,
			},
			DNSNames: componentDNSNames,
			IPAddresses: []string{
				"127.0.0.1",
				"::1",
			},
			IssuerRef: cmmeta.ObjectReference{
				Name:  AwsPcaIssuer,
				Kind:  AwsPcaIssuerKind,
				Group: CertManagerGroup,
			},
		},
	}

	return r.createOrGetWithOwnerLabel(componentCertificate)
}

func (r *reconcileContext) createClusterClientCert() error {
	clientCertificate := &cmv1.Certificate{
		ObjectMeta: metav1.ObjectMeta{
			Name:      clusterClientSecretName(r.tcKey.Name),
			Namespace: r.tcKey.Namespace,
		},
		Spec: cmv1.CertificateSpec{
			SecretName: clusterClientSecretName(r.tcKey.Name),
			Duration: &metav1.Duration{
				Duration: DefaultCertificateDuration,
			},
			RenewBefore: &metav1.Duration{
				Duration: DefaultRenewBefore,
			},
			CommonName: CommonName(r.tcKey.Name),
			Subject: &cmv1.X509Subject{
				Organizations: []string{DefaultOrganization},
			},
			Usages: []cmv1.KeyUsage{
				cmv1.UsageClientAuth,
			},
			IssuerRef: cmmeta.ObjectReference{
				Name:  AwsPcaIssuer,
				Kind:  AwsPcaIssuerKind,
				Group: CertManagerGroup,
			},
		},
	}

	return r.createOrGetWithOwnerLabel(clientCertificate)
}
