package orchestrator

import (
	"fmt"
	"testing"

	"github.com/onsi/gomega"
	"github.com/samber/lo"
	"github.com/tidbcloud/fastflow/pkg/entity"
	"google.golang.org/protobuf/proto"

	openapidedicatedv1beta1 "github.com/tidbcloud/tidb-management-service/api/spec/tidb_cloud_open_api/dedicated/v1beta1"
)

func TestAdvancedModifySetValidateOnly(t *testing.T) {
	g := gomega.NewWithT(t)

	t.Run("do not contain validateOnly", func(t *testing.T) {
		var request proto.Message = &openapidedicatedv1beta1.GetClusterRequest{}
		validateOnly := true

		err := setValidateOnlyField(t.Context(), request, validateOnly)
		g.Expect(err).Should(gomega.HaveOccurred())
	})

	t.Run("update cluster request", func(t *testing.T) {
		var request proto.Message = &openapidedicatedv1beta1.UpdateClusterRequest{}
		validateOnly := true

		err := setValidateOnlyField(t.Context(), request, validateOnly)
		g.Expect(err).ShouldNot(gomega.HaveOccurred())
		req, ok := request.(*openapidedicatedv1beta1.UpdateClusterRequest)
		g.Expect(ok).Should(gomega.BeTrue())
		g.Expect(req.ValidateOnly).Should(gomega.BeTrue())
	})

	t.Run("create tidb group request", func(t *testing.T) {
		var request proto.Message = &openapidedicatedv1beta1.CreateTidbNodeGroupRequest{}
		validateOnly := false

		err := setValidateOnlyField(t.Context(), request, validateOnly)
		g.Expect(err).ShouldNot(gomega.HaveOccurred())
		req, ok := request.(*openapidedicatedv1beta1.CreateTidbNodeGroupRequest)
		g.Expect(ok).Should(gomega.BeTrue())
		g.Expect(req.ValidateOnly).Should(gomega.BeFalse())
	})

	t.Run("delete tidb group request", func(t *testing.T) {
		var request proto.Message = &openapidedicatedv1beta1.DeleteTidbNodeGroupRequest{}
		validateOnly := false

		err := setValidateOnlyField(t.Context(), request, validateOnly)
		g.Expect(err).ShouldNot(gomega.HaveOccurred())
		req, ok := request.(*openapidedicatedv1beta1.DeleteTidbNodeGroupRequest)
		g.Expect(ok).Should(gomega.BeTrue())
		g.Expect(req.ValidateOnly).Should(gomega.BeFalse())
	})

	t.Run("update tidb group request", func(t *testing.T) {
		var request proto.Message = &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest{}
		validateOnly := true

		err := setValidateOnlyField(t.Context(), request, validateOnly)
		g.Expect(err).ShouldNot(gomega.HaveOccurred())
		req, ok := request.(*openapidedicatedv1beta1.UpdateTidbNodeGroupRequest)
		g.Expect(ok).Should(gomega.BeTrue())
		g.Expect(req.ValidateOnly).Should(gomega.BeTrue())
	})
}

func TestAdvancedModifyTransformTasks(t *testing.T) {
	g := gomega.NewWithT(t)

	t.Run("only 1 task", func(t *testing.T) {
		requests := []*OpenAPIRequest{
			{
				Message: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest{
					TidbNodeGroup: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest_TidbNodeGroup{
						TidbNodeGroupId: "1",
						NodeCount:       lo.ToPtr(int32(1)),
					},
				},
				ActionName: "update_tidb_group",
			},
		}

		tasks, err := transformTasks(t.Context(), requests)
		g.Expect(err).ShouldNot(gomega.HaveOccurred())
		g.Expect(len(tasks)).Should(gomega.Equal(3))
		g.Expect(checkTaskDependOn(tasks)).ShouldNot(gomega.HaveOccurred())
		for i := 1; i < len(tasks)-1; i += 2 {
			g.Expect(tasks[i].ID).Should(gomega.Equal(fmt.Sprintf("%s-%d", actionWaitClusterActive, (i-1)/2)))
		}
		g.Expect(tasks[len(tasks)-1].ID).Should(gomega.Equal(actionDeleteCustomStatus))
	})

	t.Run("only 2 task", func(t *testing.T) {
		requests := []*OpenAPIRequest{
			{
				Message: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest{
					TidbNodeGroup: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest_TidbNodeGroup{
						TidbNodeGroupId: "1",
						NodeCount:       lo.ToPtr(int32(1)),
					},
				},
				ActionName: "update_tidb_group",
			},
			{
				Message: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest{
					TidbNodeGroup: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest_TidbNodeGroup{
						TidbNodeGroupId: "1",
						NodeCount:       lo.ToPtr(int32(1)),
					},
				},
				ActionName: "update_tidb_group",
			},
		}

		tasks, err := transformTasks(t.Context(), requests)
		g.Expect(err).ShouldNot(gomega.HaveOccurred())
		g.Expect(len(tasks)).Should(gomega.Equal(5))
		g.Expect(checkTaskDependOn(tasks)).ShouldNot(gomega.HaveOccurred())
		for i := 1; i < len(tasks)-1; i += 2 {
			g.Expect(tasks[i].ID).Should(gomega.Equal(fmt.Sprintf("%s-%d", actionWaitClusterActive, (i-1)/2)))
		}
		g.Expect(tasks[len(tasks)-1].ID).Should(gomega.Equal(actionDeleteCustomStatus))
	})

	t.Run("only 3 tasks", func(t *testing.T) {
		requests := []*OpenAPIRequest{
			{
				Message: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest{
					TidbNodeGroup: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest_TidbNodeGroup{
						TidbNodeGroupId: "1",
						NodeCount:       lo.ToPtr(int32(1)),
					},
				},
				ActionName: "update_tidb_group",
			},
			{
				Message: &openapidedicatedv1beta1.CreateTidbNodeGroupRequest{
					TidbNodeGroup: &openapidedicatedv1beta1.TidbNodeGroup{
						TidbNodeGroupId: "1",
						NodeCount:       1,
					},
				},
				ActionName: "create_tidb_group",
			},
			{
				Message: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest{
					TidbNodeGroup: &openapidedicatedv1beta1.UpdateTidbNodeGroupRequest_TidbNodeGroup{
						TidbNodeGroupId: "2",
						NodeCount:       lo.ToPtr(int32(2)),
					},
				},
				ActionName: "update_tidb_group",
			},
		}

		tasks, err := transformTasks(t.Context(), requests)
		g.Expect(err).ShouldNot(gomega.HaveOccurred())
		g.Expect(len(tasks)).Should(gomega.Equal(7))
		g.Expect(checkTaskDependOn(tasks)).ShouldNot(gomega.HaveOccurred())
		for i := 1; i < len(tasks)-1; i += 2 {
			g.Expect(tasks[i].ID).Should(gomega.Equal(fmt.Sprintf("%s-%d", actionWaitClusterActive, (i-1)/2)))
		}
		g.Expect(tasks[len(tasks)-1].ID).Should(gomega.Equal(actionDeleteCustomStatus))
	})
}

func checkTaskDependOn(tasks []entity.Task) error {
	parent := tasks[0]
	for i := 1; i < len(tasks); i++ {
		if tasks[i].DependOn[0] != parent.Name {
			return fmt.Errorf("expect paraent %s, actual pararent %s", tasks[i].DependOn[0], parent.Name)
		}

		parent = tasks[i]
	}

	return nil
}
