package integration

import (
	"context"
	"fmt"
	"math/rand"
	"time"

	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
	corev1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/utils/pointer"
	"sigs.k8s.io/controller-runtime/pkg/client"

	"github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1"

	apierrors "k8s.io/apimachinery/pkg/api/errors"

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

var _ = Describe("Cluster config controller", func() {
	const (
		timeout  = time.Second * 120
		interval = time.Second * 1
	)

	Context("When create cluster", func() {
		It("Cluster config should be correct", func() {
			ctx := context.Background()
			nt, shootClient, _ := prepareNetwork("gitops-v2", ctx)

			By("Create a basic cluster")
			c := newCluster(nt, "", "", "test")
			Expect(k8sClient.Create(ctx, c)).To(Succeed())
			tcKey := types.NamespacedName{
				Namespace: c.PrefixedId(),
				Name:      consts.ClusterPrefix,
			}
			tc := &v1alpha1.TidbCluster{}
			Eventually(func() error {
				return shootClient.Get(ctx, tcKey, tc)
			}, timeout, interval).Should(Succeed(), "TiDB cluster should be created")
			Expect(tc.Spec.TLSCluster.Enabled).To(BeTrue())
			Expect(int(tc.Spec.TiDB.Replicas)).To(Equal(c.Spec.Cluster.TiDB.Replicas))
			Expect(tc.Spec.Version).To(Equal("v5.2.1"))
			Expect(tc.Spec.TiDB.BaseImage).To(Equal("v5.2.1-20210909"))
			Expect(tc.Spec.TiDB.Config.MP["enable-telemetry"]).To(Equal(false))
			Expect(tc.Spec.TiFlash.Config.Common.MP["delta_index_cache_size"]).To(Equal(int64(3221225472)))
			Expect(tc.Spec.PD.Config.Get("dashboard").Interface()).To(Equal(map[string]interface{}{
				"public-path-prefix": fmt.Sprintf("/proxy/cluster/%s/pd/dashboard", c.Name),
				"enable-telemetry":   false,
				"internal-proxy":     true,
			}))

			By("Create a cluster with tenantID")
			c2 := newCluster(nt, "1362657880846635008", "", "test")
			tc2 := &v1alpha1.TidbCluster{}
			Expect(k8sClient.Create(ctx, c2)).To(Succeed())
			tcKey = types.NamespacedName{
				Namespace: c2.PrefixedId(),
				Name:      consts.ClusterPrefix,
			}
			Eventually(func() error {
				return shootClient.Get(ctx, tcKey, tc2)
			}, timeout, interval).Should(Succeed(), "TiDB cluster should be created")
			Expect(tc2.Spec.Version).To(Equal("v5.2.1"))
			Expect(tc2.Spec.TiDB.BaseImage).To(Equal("v5.2.1-20210909"))
			Expect(tc2.Spec.TiDB.Config.MP["alter-primary-key"]).To(Equal(true))
			Expect(tc2.Spec.TiKV.Config.Get("raftstore").Interface()).To(Equal(map[string]interface{}{
				"hibernate-regions": true,
			}))
			Expect(tc2.Spec.PD.Config.Get("schedule").Interface()).To(Equal(map[string]interface{}{
				"enable-cross-table-merge": true,
			}))

			By("Create a cluster with instance type")
			c3 := newCluster(nt, "1362657880846635008", "", "i3.2xlarge")
			tc3 := &v1alpha1.TidbCluster{}
			Expect(k8sClient.Create(ctx, c3)).To(Succeed())
			tcKey = types.NamespacedName{
				Namespace: c3.PrefixedId(),
				Name:      consts.ClusterPrefix,
			}
			Eventually(func() error {
				return shootClient.Get(ctx, tcKey, tc3)
			}, timeout, interval).Should(Succeed(), "TiDB cluster should be created")
			Expect(tc3.Spec.Version).To(Equal("v5.2.1"))
			Expect(tc3.Spec.TiDB.BaseImage).To(Equal("v5.2.1-20210909"))
			Expect(tc3.Spec.TiDB.Config.MP["alter-primary-key"]).To(Equal(true))
			Expect(tc3.Spec.TiKV.Config.Get("raftstore").Interface()).To(Equal(map[string]interface{}{
				"apply-pool-size":   int64(1),
				"hibernate-regions": true,
				"store-pool-size":   int64(1),
			}))
			Expect(tc3.Spec.PD.Config.Get("schedule").Interface()).To(Equal(map[string]interface{}{
				"enable-cross-table-merge": true,
			}))
			writecf := tc3.Spec.TiKV.Config.Get("rocksdb.writecf").Interface()
			writecfMap := writecf.(map[string]interface{})
			Expect(writecfMap["hard-pending-compaction-bytes-limit"]).To(Equal("1024GB"))
			Expect(writecfMap["level0-slowdown-writes-trigger"]).To(Equal(int64(64)))
			Expect(writecfMap["level0-stop-writes-trigger"]).To(Equal(int64(128)))
			Expect(writecfMap["max-write-buffer-number"]).To(Equal(int64(10)))
			Expect(writecfMap["soft-pending-compaction-bytes-limit"]).To(Equal("1024GB"))

			By("Create a cluster not default version")
			c4 := newCluster(nt, "123", "", "test")
			tc4 := &v1alpha1.TidbCluster{}
			Expect(k8sClient.Create(ctx, c4)).To(Succeed())
			tcKey = types.NamespacedName{
				Namespace: c4.PrefixedId(),
				Name:      consts.ClusterPrefix,
			}
			Eventually(func() error {
				return shootClient.Get(ctx, tcKey, tc4)
			}, timeout, interval).Should(Succeed(), "TiDB cluster should be created")
			Expect(tc4.Spec.Version).To(Equal("v5.2.0"))
			Expect(tc4.Spec.TiDB.BaseImage).To(Equal("v5.2.0-20210827"))
			Expect(tc4.Spec.TiDB.Config.MP["log"]).To(Equal(map[string]interface{}{
				"level": "debug",
			}))
		})
	})
})

func prepareNetwork(namespace string, ctx context.Context) (*cloudv1.Network, client.Client, *cloudv1.GardenerNetworkStatus) {
	var (
		shootClient  client.Client
		gardenStatus *cloudv1.GardenerNetworkStatus
		err          error
	)

	ns := &corev1.Namespace{
		ObjectMeta: metav1.ObjectMeta{
			Name: namespace,
		},
	}
	err = k8sClient.Create(ctx, ns)
	if err != nil && !apierrors.IsAlreadyExists(err) {
		Expect(err).To(Succeed())
	}

	nt := &cloudv1.Network{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "network-" + cloudv1.RandomString(5),
			Namespace: ns.Name,
			Labels: map[string]string{
				"cloud.pingcap.com/project": cloudv1.RandomString(5),
			},
		},
		Spec: cloudv1.NetworkSpec{
			Provider: "aws",
			Region:   "us-east-2",
			CIDR:     "10.251.0.0/16",
		},
	}
	Expect(k8sClient.Create(ctx, nt)).To(Succeed())

	Eventually(func() error {
		gardenStatus, err = networkNormal(ctx, nt, true)
		if err != nil {
			return err
		}
		shootCtx, err := gardenerEnv.GetShootContext(ctx, gardenStatus.ShootName)
		if err != nil {
			return err
		}
		shootClient = shootCtx.Manager.GetClient()
		return nil
	}, time.Second*120, time.Second*1).Should(Succeed())

	return nt, shootClient, gardenStatus
}

func newCluster(network *cloudv1.Network, tenantID, clusterID, instanceType string) *cloudv1.Cluster {
	c := &cloudv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      fmt.Sprintf("%d", rand.Int()),
			Namespace: network.Namespace,
			Labels: map[string]string{
				cloudv1.ReconcilerLabelKey: "enable-gitops",
			},
			Annotations: map[string]string{},
		},
		Spec: cloudv1.ClusterSpec{
			NetworkName: network.Name,
			Cluster: cloudv1.ClusterTopology{
				TiDB: cloudv1.TiDB{
					Replicas:     2,
					InstanceType: instanceType,
				},
				TiKV: cloudv1.TiKV{
					Replicas:     3,
					InstanceType: instanceType,
					StorageSize:  resource.MustParse("1000Gi"),
				},
				PD: cloudv1.PD{
					Replicas:     3,
					InstanceType: instanceType,
					StorageSize:  resource.MustParse("10Gi"),
				},
				TiFlash: cloudv1.TiFlash{
					Replicas:     2,
					InstanceType: instanceType,
					StorageSize:  resource.MustParse("1000Gi"),
				},
			},
			SqlEndpoints: cloudv1.SqlEndpoints{
				Public: cloudv1.Service{
					Enabled: pointer.Bool(true),
				},
			},
		},
	}
	if tenantID != "" {
		c.Labels[consts.TenantTagKey] = tenantID
	}
	if clusterID != "" {
		c.Labels[consts.ClusterTagKey] = clusterID
	}
	return c
}
