package cluster

import (
	"context"
	"testing"

	"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
	cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
	. "github.com/onsi/gomega"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"github.com/tidbcloud/infra-api/controllers/test"
	"github.com/tidbcloud/infra-api/pkg/consts"
)

func TestCreateTiDBServerCert(t *testing.T) {
	cases := map[string]struct {
		namespace string
		address   string
		expect    *v1alpha2.Certificate
	}{
		"AWSServerCert": {
			namespace: "test",
			address:   "test.aws.pingcap.com",
			expect: &v1alpha2.Certificate{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "db-tidb-server-secret",
					Namespace: "test",
				},
				Spec: v1alpha2.CertificateSpec{
					SecretName: "db-tidb-server-secret",
					DNSNames: []string{
						"db-tidb.test",
						"db-tidb.test.svc",
						"test.aws.pingcap.com",
						"private-test.aws.pingcap.com",
						"privatelink-test.aws.pingcap.com",
					},
					Duration: &metav1.Duration{
						Duration: consts.CertificateDuration,
					},
					RenewBefore: &metav1.Duration{
						Duration: consts.RenewBefore,
					},
					Organization: []string{consts.CertificateOrg},
					IssuerRef: cmmeta.ObjectReference{
						Name:  consts.AwsPcaIssuer,
						Kind:  consts.AwsPcaIssuerKind,
						Group: consts.CertManagerGroup,
					},
				},
			},
		},
		"GCPPublicAddress": {
			namespace: "test",
			address:   "test.gcp.pingcap.com",
			expect: &v1alpha2.Certificate{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "db-tidb-server-secret",
					Namespace: "test",
				},
				Spec: v1alpha2.CertificateSpec{
					SecretName: "db-tidb-server-secret",
					DNSNames: []string{
						"db-tidb.test",
						"db-tidb.test.svc",
						"test.gcp.pingcap.com",
						"private-test.gcp.pingcap.com",
						"privatelink-test.gcp.pingcap.com",
					},
					Duration: &metav1.Duration{
						Duration: consts.CertificateDuration,
					},
					RenewBefore: &metav1.Duration{
						Duration: consts.RenewBefore,
					},
					Organization: []string{consts.CertificateOrg},
					IssuerRef: cmmeta.ObjectReference{
						Name:  consts.AwsPcaIssuer,
						Kind:  consts.AwsPcaIssuerKind,
						Group: consts.CertManagerGroup,
					},
				},
			},
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			var created []client.Object
			shootCli := &test.MockClient{
				MockCreate: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
					created = append(created, obj)
					return nil
				},
			}
			r := newFakeReconcileContext(&cloudv1.Cluster{},
				&ClusterReconciler{ProviderType: "mock"}, shootCli)
			err := r.createTiDBServerCert(tc.namespace, "db", tc.address)
			g.Expect(err).To(Succeed())
			g.Expect(created).To(ConsistOf(tc.expect))
		})
	}
}

func TestTiDBComponentCert(t *testing.T) {
	cases := map[string]struct {
		namespace string
		comp      cloudv1.ComponentType
		expect    *v1alpha2.Certificate
	}{
		"TiDBCert": {
			namespace: "test",
			comp:      cloudv1.ComponentTiDB,
			expect: &v1alpha2.Certificate{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "db-tidb-cluster-secret",
					Namespace: "test",
				},
				Spec: v1alpha2.CertificateSpec{
					SecretName: "db-tidb-cluster-secret",
					DNSNames: []string{
						"db-tidb",
						"db-tidb.test",
						"db-tidb.test.svc",
						"db-tidb-peer",
						"db-tidb-peer.test",
						"db-tidb-peer.test.svc",
						"*.db-tidb-peer",
						"*.db-tidb-peer.test",
						"*.db-tidb-peer.test.svc",
						"db-tidb-extra-service.test",
						"db-tidb-extra-service.test.svc",
						"*.db-tidb-extra-service.test",
						"*.db-tidb-extra-service.test.svc",
					},
					IPAddresses: []string{
						"127.0.0.1",
						"::1",
					},
					Duration: &metav1.Duration{
						Duration: consts.CertificateDuration,
					},
					RenewBefore: &metav1.Duration{
						Duration: consts.RenewBefore,
					},
					CommonName:   CommonName("test"),
					Organization: []string{consts.CertificateOrg},
					Usages: []v1alpha2.KeyUsage{
						v1alpha2.UsageServerAuth,
						v1alpha2.UsageClientAuth,
					},
					IssuerRef: cmmeta.ObjectReference{
						Name:  consts.AwsPcaIssuer,
						Kind:  consts.AwsPcaIssuerKind,
						Group: consts.CertManagerGroup,
					},
				},
			},
		},
		"TiKVCert": {
			namespace: "test",
			comp:      cloudv1.ComponentTiKV,
			expect: &v1alpha2.Certificate{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "db-tikv-cluster-secret",
					Namespace: "test",
				},
				Spec: v1alpha2.CertificateSpec{
					SecretName: "db-tikv-cluster-secret",
					DNSNames: []string{
						"db-tikv",
						"db-tikv.test",
						"db-tikv.test.svc",
						"db-tikv-peer",
						"db-tikv-peer.test",
						"db-tikv-peer.test.svc",
						"*.db-tikv-peer",
						"*.db-tikv-peer.test",
						"*.db-tikv-peer.test.svc",
					},
					IPAddresses: []string{
						"127.0.0.1",
						"::1",
					},
					Duration: &metav1.Duration{
						Duration: consts.CertificateDuration,
					},
					RenewBefore: &metav1.Duration{
						Duration: consts.RenewBefore,
					},
					CommonName:   CommonName("test"),
					Organization: []string{consts.CertificateOrg},
					Usages: []v1alpha2.KeyUsage{
						v1alpha2.UsageServerAuth,
						v1alpha2.UsageClientAuth,
					},
					IssuerRef: cmmeta.ObjectReference{
						Name:  consts.AwsPcaIssuer,
						Kind:  consts.AwsPcaIssuerKind,
						Group: consts.CertManagerGroup,
					},
				},
			},
		},
		"PDCert": {
			namespace: "test",
			comp:      cloudv1.ComponentPD,
			expect: &v1alpha2.Certificate{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "db-pd-cluster-secret",
					Namespace: "test",
				},
				Spec: v1alpha2.CertificateSpec{
					SecretName: "db-pd-cluster-secret",
					DNSNames: []string{
						"db-pd",
						"db-pd.test",
						"db-pd.test.svc",
						"db-pd-peer",
						"db-pd-peer.test",
						"db-pd-peer.test.svc",
						"*.db-pd-peer",
						"*.db-pd-peer.test",
						"*.db-pd-peer.test.svc",
					},
					IPAddresses: []string{
						"127.0.0.1",
						"::1",
					},
					Duration: &metav1.Duration{
						Duration: consts.CertificateDuration,
					},
					RenewBefore: &metav1.Duration{
						Duration: consts.RenewBefore,
					},
					CommonName:   CommonName("test"),
					Organization: []string{consts.CertificateOrg},
					Usages: []v1alpha2.KeyUsage{
						v1alpha2.UsageServerAuth,
						v1alpha2.UsageClientAuth,
					},
					IssuerRef: cmmeta.ObjectReference{
						Name:  consts.AwsPcaIssuer,
						Kind:  consts.AwsPcaIssuerKind,
						Group: consts.CertManagerGroup,
					},
				},
			},
		},
		"TiFlashCert": {
			namespace: "test",
			comp:      cloudv1.ComponentTiFlash,
			expect: &v1alpha2.Certificate{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "db-tiflash-cluster-secret",
					Namespace: "test",
				},
				Spec: v1alpha2.CertificateSpec{
					SecretName: "db-tiflash-cluster-secret",
					DNSNames: []string{
						"db-tiflash",
						"db-tiflash.test",
						"db-tiflash.test.svc",
						"db-tiflash-peer",
						"db-tiflash-peer.test",
						"db-tiflash-peer.test.svc",
						"*.db-tiflash-peer",
						"*.db-tiflash-peer.test",
						"*.db-tiflash-peer.test.svc",
					},
					IPAddresses: []string{
						"127.0.0.1",
						"::1",
					},
					Duration: &metav1.Duration{
						Duration: consts.CertificateDuration,
					},
					RenewBefore: &metav1.Duration{
						Duration: consts.RenewBefore,
					},
					CommonName:   CommonName("test"),
					Organization: []string{consts.CertificateOrg},
					Usages: []v1alpha2.KeyUsage{
						v1alpha2.UsageServerAuth,
						v1alpha2.UsageClientAuth,
					},
					IssuerRef: cmmeta.ObjectReference{
						Name:  consts.AwsPcaIssuer,
						Kind:  consts.AwsPcaIssuerKind,
						Group: consts.CertManagerGroup,
					},
				},
			},
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			var created []client.Object
			shootCli := &test.MockClient{
				MockCreate: func(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
					created = append(created, obj)
					return nil
				},
			}
			r := newFakeReconcileContext(&cloudv1.Cluster{},
				&ClusterReconciler{ProviderType: "mock"}, shootCli)
			err := r.createTiDBComponentCert(tc.namespace, "db", tc.comp)
			g.Expect(err).To(Succeed())
			g.Expect(created).To(ConsistOf(tc.expect))
		})
	}
}
