package configeval

import (
	"testing"

	"k8s.io/apimachinery/pkg/api/resource"
	"k8s.io/apimachinery/pkg/runtime"
	"sigs.k8s.io/controller-runtime/pkg/client/fake" // nolint

	. "github.com/onsi/gomega"
	"github.com/pkg/errors"
	"github.com/tidbcloud/config-service/api/v1alpha1"
	"github.com/tidbcloud/config-service/sdk/api"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestEvalClusterConfig(t *testing.T) {
	scheme := runtime.NewScheme()
	_ = cloudv1.AddToScheme(scheme)
	infraApiClient := fake.NewFakeClientWithScheme(scheme, makeInfraApiTestData()...)
	cluster := &cloudv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "123",
			Namespace: "nightly-1",
			Labels: map[string]string{
				"cloud.pingcap.com/provider-type": "aws",
				"tags.tidbcloud.com/cluster":      "123",
				"tags.tidbcloud.com/tenant":       "1",
			},
		},
		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,
				},
			},
		},
	}

	baseConfig := &v1alpha1.ClusterConfig{
		Spec: v1alpha1.ClusterConfigSpec{
			TiDB: &v1alpha1.Configuration{
				Config: api.NewTomlConfig(map[string]interface{}{
					"token-limit": int64(1000),
				}),
			},
			TiKV: &v1alpha1.Configuration{
				Config: api.NewTomlConfig(map[string]interface{}{
					"abort-on-panic": false,
				}),
			},
			PD: &v1alpha1.Configuration{
				Config: api.NewTomlConfig(map[string]interface{}{}),
			},
			TiFlash: &v1alpha1.Configuration{
				Config: api.NewTomlConfig(map[string]interface{}{}),
			},
		},
	}

	testRoundConfig := baseConfig.DeepCopy()
	testRoundConfig.Spec.TiKV.Config = api.NewTomlConfig(map[string]interface{}{
		"test-round":         map[string]interface{}{"max-bytes-per-sec": "{ round(PD.CPU.Limit*0.8 + TiFlash.IOPS + TiFlash.CPU.Request)}"},
		"test-without-round": map[string]interface{}{"max-bytes-per-sec": "{ TiKV.Memory.Limit * 0.8/102400 + TiKV.Memory.Request/TiKV.CPU.Limit + 200 }"},
	})

	testMinMaxConfig := baseConfig.DeepCopy()
	testMinMaxConfig.Spec.TiDB.Config = api.NewTomlConfig(map[string]interface{}{
		"storage": map[string]interface{}{
			"max-bytes-per-sec": "{ max(TiKV.Memory.Request *0.008/1024/1024, 6400.5)}",
			"min-bytes-per-sec": "{ min(PD.Memory.Limit *0.008/1024/1024, 132) }",
		},
	})

	testOverlayConfig := baseConfig.DeepCopy()
	computeOverlay := v1alpha1.Overlay{
		InstanceType: "aws.compute",
		Config: api.NewTomlConfig(map[string]interface{}{
			"mem-quota-query": "{TiDB.Memory.Limit}",
			"token-limit":     "{string(TiDB.Memory.Limit * 0.8/1024/1024) +\"MB\"}",
		}),
	}
	testOverlayConfig.Spec.TiDB.Overlays = append(testOverlayConfig.Spec.TiDB.Overlays, computeOverlay)

	testPlainValuesConfig := baseConfig.DeepCopy()
	testPlainValuesConfig.Spec.PD.Config = api.NewTomlConfig(map[string]interface{}{
		"storage": map[string]interface{}{"max-bytes-per-sec": "{3.0*2+1/23}"},
	})

	testInvalidConfig := baseConfig.DeepCopy()
	testInvalidConfig.Spec.TiFlash.Config = api.NewTomlConfig(map[string]interface{}{
		"storage": map[string]interface{}{
			"illegal-string": "{TiDB.CPU.Limit * 0.2} MB"},
		"missing-brackets": "TiDB.CPU.Limit * 0.2",
	})

	testWrongKeysConfig := baseConfig.DeepCopy()
	testWrongKeysConfig.Spec.TiDB.Config = api.NewTomlConfig(map[string]interface{}{
		"max-bytes-per-sec": "{TiDB.NonExist.Limit * 0.2}",
	})

	cases := map[string]struct {
		cc          *v1alpha1.ClusterConfig
		expectValue *v1alpha1.ClusterConfig
		expectErr   error
	}{
		"TestRoundFunc": {
			cc: testRoundConfig,
			expectValue: &v1alpha1.ClusterConfig{

				Spec: v1alpha1.ClusterConfigSpec{
					TiDB: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"token-limit": int64(1000),
						}),
					},
					TiKV: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"test-round": map[string]interface{}{
								"max-bytes-per-sec": (int64)(6014),
							},
							"test-without-round": map[string]interface{}{
								"max-bytes-per-sec": (4.295235931456e+09),
							},
						}),
					},
					PD: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
					TiFlash: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
				},
			},
			expectErr: nil,
		},
		"TestMinMaxFunc": {
			cc: testMinMaxConfig,
			expectValue: &v1alpha1.ClusterConfig{

				Spec: v1alpha1.ClusterConfigSpec{
					TiDB: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"storage": map[string]interface{}{
								"max-bytes-per-sec": (float64)(6400.5),
								"min-bytes-per-sec": (float64)(131.072),
							},
						}),
					},
					TiKV: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"abort-on-panic": false,
						}),
					},
					PD: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
					TiFlash: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
				},
			},
			expectErr: nil,
		},
		"TestOverlayWithStringFunc": {
			cc: testOverlayConfig,
			expectValue: &v1alpha1.ClusterConfig{

				Spec: v1alpha1.ClusterConfigSpec{
					TiDB: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"token-limit": int64(1000),
						}),
						Overlays: []v1alpha1.Overlay{
							{
								Config: api.NewTomlConfig(map[string]interface{}{
									"mem-quota-query": (int64)(17179869184),
									"token-limit":     "13107.20MB",
								}),
								InstanceType: "aws.compute",
							},
						},
					},
					TiKV: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"abort-on-panic": false,
						}),
					},
					PD: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
					TiFlash: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
				},
			},
			expectErr: nil,
		},
		"TestPlainValues": {
			cc: testPlainValuesConfig,
			expectValue: &v1alpha1.ClusterConfig{

				Spec: v1alpha1.ClusterConfigSpec{
					TiDB: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"token-limit": int64(1000),
						}),
					},
					TiKV: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"abort-on-panic": false,
						}),
					},
					PD: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"storage": map[string]interface{}{
								"max-bytes-per-sec": float64(6),
							},
						}),
					},
					TiFlash: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
				},
			},
			expectErr: nil,
		},
		"TestInvalidValues": {
			cc: testInvalidConfig,
			expectValue: &v1alpha1.ClusterConfig{

				Spec: v1alpha1.ClusterConfigSpec{
					TiDB: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"token-limit": int64(1000),
						}),
					},
					TiKV: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"abort-on-panic": false,
						}),
					},
					PD: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
					TiFlash: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"storage": map[string]interface{}{
								"illegal-string": "{TiDB.CPU.Limit * 0.2} MB"},
							"missing-brackets": "TiDB.CPU.Limit * 0.2",
						}),
					},
				},
			},
			expectErr: nil,
		},
		"TestWrongKeys": {
			cc: testWrongKeysConfig,
			expectValue: &v1alpha1.ClusterConfig{

				Spec: v1alpha1.ClusterConfigSpec{
					TiDB: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{
							"max-bytes-per-sec": "{TiDB.NonExist.Limit * 0.2}",
						}),
					},
					TiKV: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
					PD: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
					TiFlash: &v1alpha1.Configuration{
						Config: api.NewTomlConfig(map[string]interface{}{}),
					},
				},
			},
			expectErr: errors.New("cannot fetch Limit from"),
		},
	}

	for name, tc := range cases {
		t.Run(name, func(t *testing.T) {
			g := NewGomegaWithT(t)
			err := EvalClusterConfig(infraApiClient, tc.cc, cluster)
			if tc.expectErr == nil {
				g.Expect(err).To(BeNil())
				g.Expect(tc.cc).Should(Equal(tc.expectValue))
			} else {
				g.Expect(err).Should(HaveOccurred())
				g.Expect(err.Error()).Should(ContainSubstring(tc.expectErr.Error()))
			}
		})
	}
}

func makeInfraApiTestData() []runtime.Object {
	return []runtime.Object{
		&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"),
					},
				},
			},
		},
	}
}
