package server

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

	"github.com/modern-go/reflect2"
	. "github.com/onsi/gomega"
	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"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake" // nolint
)

func makeBrTestData() []runtime.Object {
	brVersion := "v4.0.10-hotfix"
	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,
			},
			Backup: &v1alpha1.Configuration{
				BaseImage: "gcr.io/pingcap-public/dbaas/br",
				Tag:       &brVersion,
				Config: api.NewTomlConfig(map[string]interface{}{
					"concurrency": int64(32),
				}),
			},
			Restore: &v1alpha1.Configuration{
				BaseImage: "gcr.io/pingcap-public/dbaas/br",
				Tag:       &brVersion,
				Config: api.NewTomlConfig(map[string]interface{}{
					"concurrency": int64(32),
				}),
			},
		},
	}
	tenantConfig := baseConfig.DeepCopy()
	tenantConfig.Name = baseConfig.Name + "-t123"
	tenantConfig.Labels = map[string]string{v1alpha1.TenentLabel: "123", v1alpha1.ClusterLabel: v1alpha1.AllCluster}

	tenantConfig.Spec.Backup.Config = api.NewTomlConfig(map[string]interface{}{
		"concurrency": int64(64),
	})
	tenantConfig.Spec.Restore.Config = api.NewTomlConfig(map[string]interface{}{
		"concurrency": int64(64),
	})

	clusterConfig := tenantConfig.DeepCopy()
	clusterConfig.Name = tenantConfig.Name + "-c456"
	clusterConfig.Labels = map[string]string{v1alpha1.TenentLabel: "123", v1alpha1.ClusterLabel: "456"}
	clusterConfig.Spec.Backup.Config = api.NewTomlConfig(map[string]interface{}{
		"concurrency": int64(128),
	})
	clusterConfig.Spec.Restore.Config = api.NewTomlConfig(map[string]interface{}{
		"concurrency": int64(128),
	})

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

func TestBRConfigHander(t *testing.T) {
	scheme := runtime.NewScheme()
	_ = v1alpha1.AddToScheme(scheme)
	_ = cloudv1.AddToScheme(scheme)
	fakeClient := fake.NewFakeClientWithScheme(scheme, makeBrTestData()...)
	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,
	}
	backupServer := httptest.NewServer(http.HandlerFunc(server.DescribeBackupConfiguration))
	restoreServer := httptest.NewServer(http.HandlerFunc(server.DescribeRestoreConfiguration))
	defer backupServer.Close()
	defer restoreServer.Close()

	tenantID := uint64(123)
	unknowTenant := uint64(3)
	clusterID := uint64(456)
	unknowCluster := uint64(999)
	brVersion := "v4.0.10-hotfix"
	cases := []struct {
		Description  string
		Input        api.DescribeBRConfigInput
		ExpectOutput *api.DescribeBRConfigOutput
		ExpectError  string
	}{
		{
			Description:  "empty request",
			Input:        api.DescribeBRConfigInput{},
			ExpectOutput: &api.DescribeBRConfigOutput{},
			ExpectError:  "must specipy tenant ID\n",
		},
		{
			Description: "match to base layer",
			Input: api.DescribeBRConfigInput{
				TenantID:  &unknowTenant,
				ClusterID: &unknowCluster,
			},
			ExpectOutput: &api.DescribeBRConfigOutput{
				Version: "v4.0.9",
				ConfigMeta: &api.ConfigMeta{
					ID:            v1alpha1.BaseClusterConfig,
					GitCommitID:   "123456",
					ConfigVersion: 0,
				},
				Config: map[string]interface{}{"concurrency": float64(32)},
				Image: api.Image{
					BaseImage: "gcr.io/pingcap-public/dbaas/br",
					Tag:       &brVersion,
				},
			},
		},
		{
			Description: "match to tenant layer",
			Input: api.DescribeBRConfigInput{
				TenantID:  &tenantID,
				ClusterID: &unknowCluster,
			},
			ExpectOutput: &api.DescribeBRConfigOutput{
				Version: "v4.0.9",
				ConfigMeta: &api.ConfigMeta{
					ID:            "base-t123",
					GitCommitID:   "123456",
					ConfigVersion: 0,
				},
				Config: map[string]interface{}{"concurrency": float64(64)},
				Image: api.Image{
					BaseImage: "gcr.io/pingcap-public/dbaas/br",
					Tag:       &brVersion,
				},
			},
		},
		{
			Description: "match to cluster layer",
			Input: api.DescribeBRConfigInput{
				TenantID:  &tenantID,
				ClusterID: &clusterID,
			},
			ExpectOutput: &api.DescribeBRConfigOutput{
				Version: "v4.0.9",
				ConfigMeta: &api.ConfigMeta{
					ID:            "base-t123-c456",
					GitCommitID:   "123456",
					ConfigVersion: 0,
				},
				Config: map[string]interface{}{"concurrency": float64(128)},
				Image: api.Image{
					BaseImage: "gcr.io/pingcap-public/dbaas/br",
					Tag:       &brVersion,
				},
			},
		},
	}

	backupClient := sdk.New(backupServer.URL)
	restoreClient := sdk.New(restoreServer.URL)
	for _, tt := range cases {
		t.Run(tt.Description, func(t *testing.T) {
			output, err := backupClient.DescribeBackupConfiguration(&tt.Input)
			validateResult(output, err, tt.ExpectError, tt.ExpectOutput, t)
			output, err = restoreClient.DescribeRestoreConfiguration(&tt.Input)
			validateResult(output, err, tt.ExpectError, tt.ExpectOutput, t)
		})
	}
}

func validateResult(output interface{}, err error, expectdErr string, expectOutput interface{}, t *testing.T) {
	g := NewGomegaWithT(t)
	if expectdErr != "" {
		g.Expect(err.Error()).To(Equal(expectdErr))
	} else {
		if err != nil {
			t.Fatal(err)
		}
	}
	// note: check nil of interface{} should be done via reflect
	// ref: https://stackoverflow.com/questions/13476349/check-for-nil-and-nil-interface-in-go
	if !reflect2.IsNil(expectOutput) {
		g.Expect(output).To(Equal(expectOutput))
	}
}
