package server

import (
	"net/http"
	"net/http/httptest"
	"testing"

	pingcapv1alpha1 "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"
	"github.com/tidbcloud/config-service/api/v1alpha1"
	"github.com/tidbcloud/config-service/sdk"
	"github.com/tidbcloud/config-service/sdk/api"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake" // nolint
)

var testLog = ctrl.Log.WithName("unitest")

func makeTestData() []runtime.Object {
	var baseConfig = v1alpha1.ClusterConfig{
		TypeMeta: metav1.TypeMeta{
			APIVersion: "ops/v1alpha1",
			Kind:       "ClusterConfig",
		},
		ObjectMeta: metav1.ObjectMeta{
			Name: v1alpha1.BaseClusterConfig,
		},
		Spec: v1alpha1.ClusterConfigSpec{
			Version: "v4.0.9",
			ConfigMeta: &v1alpha1.ConfigMeta{
				GitCommitID:   "123456",
				ConfigVersion: 0,
			},
			TiDB: &v1alpha1.Configuration{
				BaseImage: "gcr.io/pingcap-public/dbaas/tidb",
				Config: api.NewTomlConfig(map[string]interface{}{
					"token-limit":           int64(1000),
					"max-merge-region-size": "{ max(TiDB.Memory.Request *0.008/1024/1024, 6400.5) }",
					"min-merge-region-size": "{ min(TiDB.Memory.Limit *0.008/1024/1024, 132) }",
				}),
			},
			TiFlash: &v1alpha1.Configuration{
				BaseImage: "gcr.io/pingcap-public/dbaas/tiflash",
				Config:    api.NewTomlConfig(map[string]interface{}{}),
			},
			TiFlashLearner: &v1alpha1.Configuration{
				BaseImage: "gcr.io/pingcap-public/dbaas/tiflash",
				Config: api.NewTomlConfig(map[string]interface{}{
					"redact-info-log": true,
				}),
			},
		},
	}
	tenantConfig := baseConfig.DeepCopy()
	tenantConfig.Name = baseConfig.Name + "-t123"
	tenantConfig.Labels = map[string]string{v1alpha1.TenentLabel: "123", v1alpha1.ClusterLabel: v1alpha1.AllCluster}

	tenantConfig.Spec.TiDB.Config = api.NewTomlConfig(map[string]interface{}{
		"token-limit":        int64(2000),
		"test-round":         map[string]interface{}{"max-bytes-per-sec": "{ round(TiDB.Memory.Limit * 0.8/102400 + TiDB.Memory.Request/TiDB.CPU.Limit) + 200 }"},
		"test-without-round": map[string]interface{}{"max-bytes-per-sec": "{ TiDB.Memory.Limit * 0.8/102400 + TiDB.Memory.Request/TiDB.CPU.Limit + 200 }"},
	})

	clusterConfig := tenantConfig.DeepCopy()
	clusterConfig.Name = tenantConfig.Name + "-c456"
	clusterConfig.Labels = map[string]string{v1alpha1.TenentLabel: "123", v1alpha1.ClusterLabel: "456"}
	clusterConfig.Spec.TiDB.Config = api.NewTomlConfig(map[string]interface{}{
		"token-limit": int64(3000),
	})
	instanceType := "aws.m5.2xlarge"
	overlay := v1alpha1.Overlay{
		InstanceType: instanceType,
		Config: api.NewTomlConfig(map[string]interface{}{
			"token-limit": int64(4000),
		}),
	}
	clusterConfig.Spec.TiDB.Overlays = append(clusterConfig.Spec.TiDB.Overlays, overlay)

	malformedConfig := tenantConfig.DeepCopy()
	malformedConfig.Name = tenantConfig.Name + "-c888"
	malformedConfig.Labels = map[string]string{v1alpha1.TenentLabel: "123", v1alpha1.ClusterLabel: "888"}
	malformedConfig.Spec.ConfigMeta = nil

	var objs []runtime.Object
	objs = append(objs, &baseConfig, tenantConfig, clusterConfig, malformedConfig)
	return objs
}

func makeInfraAPITestData() []runtime.Object {
	return []runtime.Object{
		&cloudv1.ProviderList{
			Items: []cloudv1.Provider{
				{
					Spec: cloudv1.ProviderSpec{
						Type: "aws",
						InstanceTypes: []cloudv1.InstanceType{
							{
								Name:   "compute",
								CPU:    resource.MustParse("8"),
								Memory: resource.MustParse("16Gi"),
							},
							{
								Name:   "storage",
								CPU:    resource.MustParse("8"),
								Memory: resource.MustParse("32Gi"),
							},
						},
					},
				},
			},
		},
		&cloudv1.Cluster{
			ObjectMeta: metav1.ObjectMeta{
				Name: "456",
				Labels: map[string]string{
					"cloud.pingcap.com/provider-type": "aws",
					"tags.tidbcloud.com/cluster":      "456",
					"tags.tidbcloud.com/tenant":       "123",
				},
			},
			Spec: cloudv1.ClusterSpec{
				Cluster: cloudv1.ClusterTopology{
					PD: cloudv1.PD{
						InstanceType: "compute",
						Replicas:     3,
					},
					TiDB: cloudv1.TiDB{
						InstanceType: "compute",
						Replicas:     2,
					},
					TiKV: cloudv1.TiKV{
						InstanceType: "storage",
						Replicas:     3,
					},
					TiFlash: cloudv1.TiFlash{
						InstanceType: "storage",
						Replicas:     1,
					},
				},
			},
		},
		&cloudv1.Cluster{
			ObjectMeta: metav1.ObjectMeta{
				Name: "888",
				Labels: map[string]string{
					"cloud.pingcap.com/provider-type": "aws",
					"tags.tidbcloud.com/cluster":      "888",
					"tags.tidbcloud.com/tenant":       "123",
				},
			},
			Spec: cloudv1.ClusterSpec{
				Cluster: cloudv1.ClusterTopology{
					PD: cloudv1.PD{
						InstanceType: "compute",
						Replicas:     3,
					},
					TiDB: cloudv1.TiDB{
						InstanceType: "compute",
						Replicas:     2,
					},
					TiKV: cloudv1.TiKV{
						InstanceType: "storage",
						Replicas:     3,
					},
					TiFlash: cloudv1.TiFlash{
						InstanceType: "storage",
						Replicas:     1,
					},
				},
			},
		},
		&cloudv1.Cluster{
			ObjectMeta: metav1.ObjectMeta{
				Name: "999",
				Labels: map[string]string{
					"cloud.pingcap.com/provider-type": "aws",
					"tags.tidbcloud.com/cluster":      "999",
					"tags.tidbcloud.com/tenant":       "3",
				},
			},
			Spec: cloudv1.ClusterSpec{
				Cluster: cloudv1.ClusterTopology{
					PD: cloudv1.PD{
						InstanceType: "compute",
						Replicas:     3,
					},
					TiDB: cloudv1.TiDB{
						InstanceType: "compute",
						Replicas:     2,
					},
					TiKV: cloudv1.TiKV{
						InstanceType: "storage",
						Replicas:     3,
					},
					TiFlash: cloudv1.TiFlash{
						InstanceType: "storage",
						Replicas:     1,
					},
				},
			},
		},
		&cloudv1.Cluster{
			ObjectMeta: metav1.ObjectMeta{
				Name:      "999",
				Namespace: "test-999",
				Labels: map[string]string{
					"cloud.pingcap.com/provider-type": "aws",
					"tags.tidbcloud.com/cluster":      "999",
					"tags.tidbcloud.com/tenant":       "123",
				},
			},
			Spec: cloudv1.ClusterSpec{
				Cluster: cloudv1.ClusterTopology{
					PD: cloudv1.PD{
						InstanceType: "compute",
						Replicas:     3,
					},
					TiDB: cloudv1.TiDB{
						InstanceType: "compute",
						Replicas:     2,
					},
					TiKV: cloudv1.TiKV{
						InstanceType: "storage",
						Replicas:     3,
					},
					TiFlash: cloudv1.TiFlash{
						InstanceType: "storage",
						Replicas:     1,
					},
				},
			},
		},
		&cloudv1.Cluster{
			ObjectMeta: metav1.ObjectMeta{
				Name: "654",
				Labels: map[string]string{
					"cloud.pingcap.com/provider-type": "aws",
					"tags.tidbcloud.com/cluster":      "654",
					"tags.tidbcloud.com/tenant":       "123",
				},
			},
			Spec: cloudv1.ClusterSpec{
				Cluster: cloudv1.ClusterTopology{
					PD: cloudv1.PD{
						InstanceType: "compute",
						Replicas:     3,
					},
					TiDB: cloudv1.TiDB{
						InstanceType: "compute",
						Replicas:     2,
					},
					TiKV: cloudv1.TiKV{
						InstanceType: "storage",
						Replicas:     3,
					},
					TiFlash: cloudv1.TiFlash{
						InstanceType: "storage",
						Replicas:     1,
					},
				},
			},
		},
	}
}

func TestServer(t *testing.T) {
	scheme := runtime.NewScheme()
	_ = v1alpha1.AddToScheme(scheme)
	_ = cloudv1.AddToScheme(scheme)
	fakeClient := fake.NewFakeClientWithScheme(scheme, makeTestData()...)
	infraAPIClient := fake.NewFakeClientWithScheme(scheme, makeInfraAPITestData()...)

	empty := pingcapv1alpha1.NewTiDBConfig()
	tidbConfig := empty.DeepCopy()
	tidbConfig.Set("token-limit", 1000)
	// pre config in tidb cluster
	tidbConfig.Set("security.cluster-verify-cn", []string{"TiDB"})
	server := Server{
		infraApiCli:             infraAPIClient,
		client:                  fakeClient,
		log:                     testLog,
		skipEvalNotFoundCluster: true,
	}

	ts := httptest.NewServer(http.HandlerFunc(server.DescribeClusterConfiguration))
	defer ts.Close()

	tenantID := uint64(123)
	unknowTenant := uint64(3)
	unfoundTenant := uint64(321)
	unfoundCluster := uint64(654)
	clusterID := uint64(456)
	unknowCluster := uint64(999)
	malformedCluster := uint64(888)
	cases := []struct {
		Description  string
		Input        api.DescribeClusterConfigInput
		ExpectOutput *api.DescribeClusterConfigOutput
		ExpectError  string
	}{
		{
			Description:  "empty request",
			Input:        api.DescribeClusterConfigInput{},
			ExpectOutput: &api.DescribeClusterConfigOutput{},
			ExpectError:  "must specipy tenant ID\n",
		},
		{
			Description: "match to base layer",
			Input: api.DescribeClusterConfigInput{
				TenantID:  &unknowTenant,
				ClusterID: &unknowCluster,
				Components: []api.ComponentInput{
					{
						Component: api.ComponentTiDB,
					},
				},
			},
			ExpectOutput: &api.DescribeClusterConfigOutput{
				Version: "v4.0.9",
				ConfigMeta: &api.ConfigMeta{
					ID:            v1alpha1.BaseClusterConfig,
					GitCommitID:   "123456",
					ConfigVersion: 0,
				},
				Components: []api.ComponentOutput{
					{
						Image: api.Image{
							BaseImage: "gcr.io/pingcap-public/dbaas/tidb",
						},
						Component: api.ComponentTiDB,
						Config: api.NewTomlConfig(map[string]interface{}{
							"token-limit":           int64(1000),
							"max-merge-region-size": float64(6400.5),
							"min-merge-region-size": float64(131.072),
						}),
					},
				},
			},
		},
		{
			Description: "test skip eval",
			Input: api.DescribeClusterConfigInput{
				TenantID:  &unfoundTenant,
				ClusterID: &unfoundCluster,
				Components: []api.ComponentInput{
					{
						Component: api.ComponentTiDB,
					},
				},
			},
			ExpectOutput: &api.DescribeClusterConfigOutput{
				Version: "v4.0.9",
				ConfigMeta: &api.ConfigMeta{
					ID:            v1alpha1.BaseClusterConfig,
					GitCommitID:   "123456",
					ConfigVersion: 0,
				},
				Components: []api.ComponentOutput{
					{
						Image: api.Image{
							BaseImage: "gcr.io/pingcap-public/dbaas/tidb",
						},
						Component: api.ComponentTiDB,
						Config: api.NewTomlConfig(map[string]interface{}{
							"token-limit": int64(1000),
						}),
					},
				},
			},
		},
		{
			Description: "match to tenant layer",
			Input: api.DescribeClusterConfigInput{
				TenantID:  &tenantID,
				ClusterID: &unknowCluster,
				Components: []api.ComponentInput{
					{
						Component: api.ComponentTiDB,
					},
				},
			},
			ExpectOutput: &api.DescribeClusterConfigOutput{
				Version: "v4.0.9",
				ConfigMeta: &api.ConfigMeta{
					ID:            "base-t123",
					GitCommitID:   "123456",
					ConfigVersion: 0,
				},
				Components: []api.ComponentOutput{
					{
						Image: api.Image{
							BaseImage: "gcr.io/pingcap-public/dbaas/tidb",
						},
						Component: api.ComponentTiDB,
						Config: api.NewTomlConfig(map[string]interface{}{
							"token-limit": int64(2000),
							"test-without-round": map[string]interface{}{
								"max-bytes-per-sec": float64(2.147618065728e+09),
							},
							"test-round": map[string]interface{}{
								"max-bytes-per-sec": int64(2147618066),
							},
						}),
					},
				},
			},
		},
		{
			Description: "match to cluster layer",
			Input: api.DescribeClusterConfigInput{
				TenantID:  &tenantID,
				ClusterID: &clusterID,
				Components: []api.ComponentInput{
					{
						Component: api.ComponentTiDB,
					},
				},
			},
			ExpectOutput: &api.DescribeClusterConfigOutput{
				Version: "v4.0.9",
				ConfigMeta: &api.ConfigMeta{
					ID:            "base-t123-c456",
					GitCommitID:   "123456",
					ConfigVersion: 0,
				},
				Components: []api.ComponentOutput{
					{
						Image: api.Image{
							BaseImage: "gcr.io/pingcap-public/dbaas/tidb",
						},
						Component: api.ComponentTiDB,
						Config:    api.NewTomlConfig(map[string]interface{}{"token-limit": int64(3000)}),
					},
				},
			},
		},
		{
			Description: "match to instance type overlayer",
			Input: api.DescribeClusterConfigInput{
				TenantID:  &tenantID,
				ClusterID: &clusterID,
				Components: []api.ComponentInput{
					{
						Component:    api.ComponentTiDB,
						InstanceType: "aws.m5.2xlarge",
					},
				},
			},
			ExpectOutput: &api.DescribeClusterConfigOutput{
				Version: "v4.0.9",
				ConfigMeta: &api.ConfigMeta{
					ID:            "base-t123-c456",
					GitCommitID:   "123456",
					ConfigVersion: 0,
				},
				Components: []api.ComponentOutput{
					{
						Image: api.Image{
							BaseImage: "gcr.io/pingcap-public/dbaas/tidb",
						},
						Component:    api.ComponentTiDB,
						Config:       api.NewTomlConfig(map[string]interface{}{"token-limit": int64(4000)}),
						InstanceType: "aws.m5.2xlarge",
					},
				},
			},
		},
		{
			Description: "match to tiflash and learner components",
			Input: api.DescribeClusterConfigInput{
				TenantID:  &tenantID,
				ClusterID: &clusterID,
				Components: []api.ComponentInput{
					{
						Component:    api.ComponentTiFlash,
						InstanceType: "aws.m5.2xlarge",
					},
					{
						Component:    api.ComponentTiFlashLearner,
						InstanceType: "aws.m5.2xlarge",
					},
				},
			},
			ExpectOutput: &api.DescribeClusterConfigOutput{
				Version: "v4.0.9",
				ConfigMeta: &api.ConfigMeta{
					ID:            "base-t123-c456",
					GitCommitID:   "123456",
					ConfigVersion: 0,
				},
				Components: []api.ComponentOutput{
					{
						Image: api.Image{
							BaseImage: "gcr.io/pingcap-public/dbaas/tiflash",
						},
						Component:    api.ComponentTiFlash,
						Config:       api.NewTomlConfig(map[string]interface{}{}),
						InstanceType: "aws.m5.2xlarge",
					},
					{
						Image: api.Image{
							BaseImage: "gcr.io/pingcap-public/dbaas/tiflash",
						},
						Component:    api.ComponentTiFlashLearner,
						Config:       api.NewTomlConfig(map[string]interface{}{"redact-info-log": true}),
						InstanceType: "aws.m5.2xlarge",
					},
				},
			},
		},
		{
			Description: "return internal server error on malformed config",
			Input: api.DescribeClusterConfigInput{
				TenantID:  &tenantID,
				ClusterID: &malformedCluster,
				Components: []api.ComponentInput{
					{
						Component:    api.ComponentTiDB,
						InstanceType: "aws.m5.2xlarge",
					},
				},
			},
			ExpectError: "malformed cluster config, config meta is nil\n",
		},
	}

	client := sdk.New(ts.URL)

	for _, tt := range cases {
		t.Run(tt.Description, func(t *testing.T) {
			output, err := client.DescribeClusterConfiguration(&tt.Input)
			validateResult(output, err, tt.ExpectError, tt.ExpectOutput, t)
		})
	}
}
