package v1

import (
	"context"
	"testing"

	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	storagev1 "k8s.io/api/storage/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
)

func TestProjectId(t *testing.T) {
	cases := map[string]struct {
		namespace *corev1.Namespace
		project   string
	}{
		"Basic": {
			namespace: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: "dev-123456",
				},
			},
			project: "dev-123456",
		},
		"Override": {
			namespace: &corev1.Namespace{
				ObjectMeta: metav1.ObjectMeta{
					Name: "dev-123456",
					Labels: map[string]string{
						"cloud.pingcap.com/project": "123456",
					},
				},
			},
			project: "123456",
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			g.Expect(ProjectId(tc.namespace)).To(Equal(tc.project))
		})
	}
}

func TestValidateProjectId(t *testing.T) {
	cases := map[string]struct {
		meta  *metav1.ObjectMeta
		valid bool
	}{
		"Basic": {
			meta: &metav1.ObjectMeta{
				Labels: map[string]string{
					"cloud.pingcap.com/project": "123456",
				},
			},
			valid: true,
		},
		"AllowDash": {
			meta: &metav1.ObjectMeta{
				Labels: map[string]string{
					"cloud.pingcap.com/project": "dev-123456",
				},
			},
			valid: true,
		},
		"BadFormat": {
			meta: &metav1.ObjectMeta{
				Labels: map[string]string{
					"cloud.pingcap.com/project": "dev_123456",
				},
			},
			valid: false,
		},
		"NilLabels": {
			meta: &metav1.ObjectMeta{
				Labels: nil,
			},
			valid: false,
		},
		"NoProjectLabel": {
			meta: &metav1.ObjectMeta{
				Labels: map[string]string{
					"cloud.pingcap.com/other-key": "123456",
				},
			},
			valid: false,
		},
	}
	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			if tc.valid {
				g.Expect(ValidateProjectId(tc.meta)).To(Succeed())
			} else {
				g.Expect(ValidateProjectId(tc.meta)).NotTo(Succeed())
			}
		})
	}
}

func TestTypeOfStorageFromName(t *testing.T) {
	type test struct {
		name          string
		cloudProvider CloudProvider
		tp            string
	}

	tests := []test{
		// aws
		{name: "tikv-gp2", cloudProvider: CloudProviderAWS, tp: "gp2"},
		{name: "tikv-gp3", cloudProvider: CloudProviderAWS, tp: "gp3"},
		{name: "tikv-gp3-100-100", cloudProvider: CloudProviderAWS, tp: "gp3"},
		// gcp
		{name: "tikv-pd-ssd", cloudProvider: CloudProviderGCP, tp: "pd-ssd"},
		{name: "tikv-pd-standard", cloudProvider: CloudProviderGCP, tp: "pd-standard"},
		// false cases
		{name: "tikv", cloudProvider: "", tp: ""},
		{name: "tikv-anUnknownType", cloudProvider: "", tp: ""},
	}

	g := NewGomegaWithT(t)
	for _, tc := range tests {
		cp, tp, err := TypeOfStorageFromName(tc.name)
		if tc.cloudProvider == "" {
			g.Expect(err).ShouldNot(Succeed())
		} else {
			g.Expect(err).Should(Succeed())
			g.Expect(cp).Should(Equal(tc.cloudProvider))
			g.Expect(tp).Should(Equal(tc.tp))
		}
	}
}

func TestEnsureAWSStorageClass(t *testing.T) {
	g := NewGomegaWithT(t)
	client := fake.NewClientBuilder().Build()
	ctx := context.Background()

	param := map[string]string{
		"type": "gp3",
		"iops": "1000",
	}
	err := EnsureAWSStorageClass(ctx, client, "test", param)
	g.Expect(err).Should(Succeed())
	sc := &storagev1.StorageClass{}
	err = client.Get(ctx, types.NamespacedName{Name: "test"}, sc)
	g.Expect(err).Should(Succeed())
	g.Expect(sc.Provisioner).Should(Equal("ebs.csi.aws.com"))
	g.Expect(sc.Parameters).Should(Equal(map[string]string{
		"type":                      "gp3",
		"iops":                      "1000",
		"csi.storage.k8s.io/fstype": "ext4",
		"encrypted":                 "true",
	}))
}

func TestEnsureGCPStorageClass(t *testing.T) {
	g := NewGomegaWithT(t)
	client := fake.NewClientBuilder().Build()
	ctx := context.Background()

	param := map[string]string{
		"k": "v",
	}
	err := EnsureGCPStorageClass(ctx, client, "test", param)
	g.Expect(err).Should(Succeed())
	sc := &storagev1.StorageClass{}
	err = client.Get(ctx, types.NamespacedName{Name: "test"}, sc)
	g.Expect(err).Should(Succeed())
	g.Expect(sc.Provisioner).Should(Equal("kubernetes.io/gce-pd"))
	g.Expect(sc.Parameters).Should(Equal(map[string]string{
		"k": "v",
	}))
}

func TestEnsureClusterStorageClass(t *testing.T) {
	provider := &Provider{
		Spec: ProviderSpec{
			StorageClasses: []StorageClass{
				{Name: "tikv-gp3"},
				{Name: "pd-gp3"},
				{Name: "tiflash-gp3"},
			},
		},
	}

	cluster := &Cluster{}
	cluster.Spec.Cluster.PD.StorageClassName = "pd-gp3"
	cluster.Spec.Cluster.TiKV.StorageClassName = "tikv-gp3"
	cluster.Spec.Cluster.TiFlash.StorageClassName = "tiflash-gp3"

	g := NewGomegaWithT(t)
	client := fake.NewClientBuilder().Build()
	ctx := context.Background()

	err := EnsureClusterStorageClass(ctx, client, provider, cluster)
	g.Expect(err).Should(Succeed())

	sc := &storagev1.StorageClass{}
	err = client.Get(ctx, types.NamespacedName{Name: "pd-gp3"}, sc)
	g.Expect(err).Should(Succeed())
	err = client.Get(ctx, types.NamespacedName{Name: "tikv-gp3"}, sc)
	g.Expect(err).Should(Succeed())
	err = client.Get(ctx, types.NamespacedName{Name: "tiflash-gp3"}, sc)
	g.Expect(err).Should(Succeed())
}
