package configeval

import (
	"testing"

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

	. "github.com/onsi/gomega"
	"github.com/pingcap/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
)

func TestEvalClusterConfig(t *testing.T) {
	scheme := runtime.NewScheme()
	_ = cloudv1.AddToScheme(scheme)
	infraApiClient := fake.NewClientBuilder().WithScheme(scheme).WithRuntimeObjects(makeInfraApiTestData()...).Build()
	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 := &cloudv1.ClusterConfig{
		Spec: cloudv1.ClusterConfigSpec{
			TiDB: &cloudv1.Configuration{
				Config: cloudv1.NewTomlConfig(map[string]interface{}{
					"token-limit": int64(1000),
				}),
			},
			TiKV: &cloudv1.Configuration{
				Config: cloudv1.NewTomlConfig(map[string]interface{}{
					"abort-on-panic": false,
				}),
			},
			PD: &cloudv1.Configuration{
				Config: cloudv1.NewTomlConfig(map[string]interface{}{}),
			},
			TiFlash: &cloudv1.Configuration{
				Config: cloudv1.NewTomlConfig(map[string]interface{}{}),
			},
		},
	}

	testRoundConfig := baseConfig.DeepCopy()
	testRoundConfig.Spec.TiKV.Config = cloudv1.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 = cloudv1.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) }",
		},
	})

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

	testInvalidConfig := baseConfig.DeepCopy()
	testInvalidConfig.Spec.TiFlash.Config = cloudv1.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 = cloudv1.NewTomlConfig(map[string]interface{}{
		"max-bytes-per-sec": "{TiDB.NonExist.Limit * 0.2}",
	})

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

				Spec: cloudv1.ClusterConfigSpec{
					TiDB: &cloudv1.Configuration{
						Config: cloudv1.NewTomlConfig(map[string]interface{}{
							"token-limit": int64(1000),
						}),
					},
					TiKV: &cloudv1.Configuration{
						Config: cloudv1.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: &cloudv1.Configuration{
						Config: cloudv1.NewTomlConfig(map[string]interface{}{}),
					},
					TiFlash: &cloudv1.Configuration{
						Config: cloudv1.NewTomlConfig(map[string]interface{}{}),
					},
				},
			},
			expectErr: nil,
		},
		"TestMinMaxFunc": {
			cc: testMinMaxConfig,
			expectValue: &cloudv1.ClusterConfig{

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

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

				Spec: cloudv1.ClusterConfigSpec{
					TiDB: &cloudv1.Configuration{
						Config: cloudv1.NewTomlConfig(map[string]interface{}{
							"token-limit": int64(1000),
						}),
					},
					TiKV: &cloudv1.Configuration{
						Config: cloudv1.NewTomlConfig(map[string]interface{}{
							"abort-on-panic": false,
						}),
					},
					PD: &cloudv1.Configuration{
						Config: cloudv1.NewTomlConfig(map[string]interface{}{}),
					},
					TiFlash: &cloudv1.Configuration{
						Config: cloudv1.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: &cloudv1.ClusterConfig{

				Spec: cloudv1.ClusterConfigSpec{
					TiDB: &cloudv1.Configuration{
						Config: cloudv1.NewTomlConfig(map[string]interface{}{
							"max-bytes-per-sec": "{TiDB.NonExist.Limit * 0.2}",
						}),
					},
					TiKV: &cloudv1.Configuration{
						Config: cloudv1.NewTomlConfig(map[string]interface{}{}),
					},
					PD: &cloudv1.Configuration{
						Config: cloudv1.NewTomlConfig(map[string]interface{}{}),
					},
					TiFlash: &cloudv1.Configuration{
						Config: cloudv1.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"),
					},
				},
			},
		},
	}
}
