package server

import (
	"context"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	brmocks "github.com/tidbcloud/cluster-service-ng/domain/workflow/action/br/mocks"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow/dag"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/actions"
	"github.com/tidbcloud/cluster-service-ng/pkg/runtime/executor"
	executormocks "github.com/tidbcloud/cluster-service-ng/pkg/runtime/executor/mocks"
	repomnocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/encoding/protojson"
)

func TestListTiDBGroups(t *testing.T) {
	tests := []struct {
		name          string
		clusterId     string
		setupMocks    func(*repomnocks.TiDBClusterRepo, *repomnocks.TiDBGroupRepo, *repomnocks.SharedPoolRepo, *executormocks.RemoteExecutor)
		expectedError string
		expectedCode  codes.Code
		validate      func(*testing.T, []*entity.TiDBGroup, error)
	}{
		{
			name:      "successful list tidb groups",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				tidbGroups := []*entity.TiDBGroup{
					{
						TiDBGroup: apiv1.TiDBGroup{
							Id:        "group-1",
							ClusterId: "testclusterid",
							Phase:     apiv1.TiDBGroup_AVAILABLE,
						},
					},
					{
						TiDBGroup: apiv1.TiDBGroup{
							Id:        "group-2",
							ClusterId: "testclusterid",
							Phase:     apiv1.TiDBGroup_AVAILABLE,
						},
					},
				}

				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(tidbGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanCreateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanUpdateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanDeleteTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
			},
			validate: func(t *testing.T, result []*entity.TiDBGroup, err error) {
				t.Helper()
				require.NoError(t, err)
				require.NotNil(t, result)
				assert.Len(t, result, 2)
				groupIds := make([]string, len(result))
				for i, group := range result {
					groupIds[i] = group.GetId()
				}
				assert.Contains(t, groupIds, "group-1")
				assert.Contains(t, groupIds, "group-2")
			},
		},
		{
			name:      "empty cluster id",
			clusterId: "",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				// No mocks needed for this test case
			},
			expectedError: "clusterId is required",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:      "cluster not found",
			clusterId: "non-existingcluster",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbClusterRepo.On("Load", mock.Anything, "non-existingcluster", false).Return(nil, status.Error(codes.NotFound, "cluster not found"))
			},
			expectedError: "failed to load tidb cluster:",
			expectedCode:  codes.Internal,
		},
		{
			name:      "tidb group repo list error",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(nil, status.Error(codes.Internal, "database error"))
			},
			expectedError: "failed to list tidb groups:",
			expectedCode:  codes.Internal,
		},
		{
			name:      "groups from create/update dag",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				existingGroups := []*entity.TiDBGroup{
					{
						TiDBGroup: apiv1.TiDBGroup{
							Id:        "existing-group",
							ClusterId: "testclusterid",
							Phase:     apiv1.TiDBGroup_AVAILABLE,
						},
					},
				}

				// Group from DAG
				creatingGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:        "creating-group",
						ClusterId: "testclusterid",
						Phase:     apiv1.TiDBGroup_CREATING,
					},
				}

				tidbGroupStr, _ := protojson.Marshal(creatingGroup)
				mockArgs := make(actions.ActionArgs)
				mockArgs[workflow.TiDBGroupIdKey] = actions.ActionArgString("creating-group")
				mockArgs[workflow.TiDBGroupKey] = actions.ActionArgString(string(tidbGroupStr))

				createIns := executor.ExecTaskIdStatus{
					"exec-1": &executor.ExecTaskStatusEx{
						ExecTaskStatus: executor.ExecTaskStatus{
							ExecTask: &executor.ExecutorTask{
								Args: mockArgs,
							},
						},
					},
				}

				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(existingGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanCreateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(createIns, nil)
				// Add missing mocks for UpdateTiDBGroup and DeleteTiDBGroup
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanUpdateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanDeleteTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
			},
			validate: func(t *testing.T, result []*entity.TiDBGroup, err error) {
				t.Helper()
				require.NoError(t, err)
				require.NotNil(t, result)
				assert.Len(t, result, 2)
				groupIds := make([]string, len(result))
				for i, group := range result {
					groupIds[i] = group.GetId()
				}
				assert.Contains(t, groupIds, "existing-group")
				assert.Contains(t, groupIds, "creating-group")

				// Find the creating group and verify its phase
				for _, group := range result {
					if group.GetId() == "creating-group" {
						assert.Equal(t, apiv1.TiDBGroup_CREATING, group.GetPhase())
					}
				}
			},
		},
		{
			name:      "group excluded due to delete dag",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				existingGroups := []*entity.TiDBGroup{
					{
						TiDBGroup: apiv1.TiDBGroup{
							Id:        "group-1",
							ClusterId: "testclusterid",
							Phase:     apiv1.TiDBGroup_AVAILABLE,
						},
					},
					{
						TiDBGroup: apiv1.TiDBGroup{
							Id:        "deleting-group",
							ClusterId: "testclusterid",
							Phase:     apiv1.TiDBGroup_AVAILABLE,
						},
					},
				}

				mockArgs := make(actions.ActionArgs)
				mockArgs[workflow.TiDBGroupIdKey] = actions.ActionArgString("deleting-group")

				deleteIns := executor.ExecTaskIdStatus{
					"exec-1": &executor.ExecTaskStatusEx{
						ExecTaskStatus: executor.ExecTaskStatus{
							ExecTask: &executor.ExecutorTask{
								Args: mockArgs,
							},
						},
					},
				}

				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(existingGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanCreateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanUpdateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanDeleteTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(deleteIns, nil)
			},
			validate: func(t *testing.T, result []*entity.TiDBGroup, err error) {
				t.Helper()
				require.NoError(t, err)
				require.NotNil(t, result)
				assert.Len(t, result, 1)
				assert.Equal(t, "group-1", result[0].GetId())
				// Ensure deleting-group is not in the result
				for _, group := range result {
					assert.NotEqual(t, "deleting-group", group.GetId())
				}
			},
		},
		{
			name:      "error getting create/update dag instances",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				tidbGroups := []*entity.TiDBGroup{}

				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(tidbGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanCreateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(nil, status.Error(codes.Internal, "dag executor error"))
			},
			expectedError: "failed to get running create/update tidb group dag instance:",
			expectedCode:  codes.Internal,
		},
		{
			name:      "error getting delete dag instances",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				tidbGroups := []*entity.TiDBGroup{}

				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(tidbGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanCreateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanUpdateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanDeleteTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(nil, status.Error(codes.Internal, "dag executor error"))
			},
			expectedError: "failed to get running delete tidb group dag instance:",
			expectedCode:  codes.Internal,
		},
		{
			name:      "error getting tidb group id from dag args",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				tidbGroups := []*entity.TiDBGroup{}

				// Create DAG instance without TiDBGroupIdKey for delete operation
				mockArgs := make(actions.ActionArgs)
				// Missing TiDBGroupIdKey

				deleteIns := executor.ExecTaskIdStatus{
					"exec-1": &executor.ExecTaskStatusEx{
						ExecTaskStatus: executor.ExecTaskStatus{
							ExecTask: &executor.ExecutorTask{
								Args: mockArgs,
							},
						},
					},
				}

				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(tidbGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanCreateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanUpdateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanDeleteTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(deleteIns, nil)
			},
			expectedError: "failed to get tidb group id from exec task args:",
			expectedCode:  codes.Internal,
		},
		{
			name:      "error getting tidb group from dag args",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				tidbGroups := []*entity.TiDBGroup{}

				// Create DAG instance with TiDBGroupIdKey but without TiDBGroupKey
				mockArgs := make(actions.ActionArgs)
				mockArgs[workflow.TiDBGroupIdKey] = actions.ActionArgString("test-group")
				// Missing TiDBGroupKey

				createIns := executor.ExecTaskIdStatus{
					"exec-1": &executor.ExecTaskStatusEx{
						ExecTaskStatus: executor.ExecTaskStatus{
							ExecTask: &executor.ExecutorTask{
								Args: mockArgs,
							},
						},
					},
				}

				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(tidbGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanCreateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(createIns, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanUpdateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
			},
			expectedError: "failed to get tidb group:",
			expectedCode:  codes.Internal,
		},
		{
			name:      "group with mismatched cluster id in dag is skipped",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				existingGroups := []*entity.TiDBGroup{}

				// Group from DAG with different cluster ID
				mismatchedGroup := &entity.TiDBGroup{
					TiDBGroup: apiv1.TiDBGroup{
						Id:        "mismatched-group",
						ClusterId: "other-cluster-id", // Different cluster ID
						Phase:     apiv1.TiDBGroup_CREATING,
					},
				}

				tidbGroupStr, _ := protojson.Marshal(mismatchedGroup)
				mockArgs := make(actions.ActionArgs)
				mockArgs[workflow.TiDBGroupIdKey] = actions.ActionArgString("mismatched-group")
				mockArgs[workflow.TiDBGroupKey] = actions.ActionArgString(string(tidbGroupStr))

				createIns := executor.ExecTaskIdStatus{
					"exec-1": &executor.ExecTaskStatusEx{
						ExecTaskStatus: executor.ExecTaskStatus{
							ExecTask: &executor.ExecutorTask{
								Args: mockArgs,
							},
						},
					},
				}

				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(existingGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanCreateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(createIns, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanUpdateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanDeleteTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
			},
			validate: func(t *testing.T, result []*entity.TiDBGroup, err error) {
				t.Helper()
				require.NoError(t, err)
				require.NotNil(t, result)
				assert.Empty(t, result) // Should be empty because mismatched group is skipped
			},
		},
		{
			name:      "error unmarshaling tidb group from dag - group skipped",
			clusterId: "testclusterid",
			setupMocks: func(tidbClusterRepo *repomnocks.TiDBClusterRepo, tidbGroupRepo *repomnocks.TiDBGroupRepo, sharedPoolRepo *repomnocks.SharedPoolRepo, dagExecutor *executormocks.RemoteExecutor) {
				tidbCluster := &entity.TiDBCluster{
					Id:           "testclusterid",
					SharedPoolId: "testsharedpool",
				}
				existingGroups := []*entity.TiDBGroup{
					{
						TiDBGroup: apiv1.TiDBGroup{
							Id:        "existing-group",
							ClusterId: "testclusterid",
							Phase:     apiv1.TiDBGroup_AVAILABLE,
						},
					},
				}

				// Invalid JSON for unmarshaling
				mockArgs := make(actions.ActionArgs)
				mockArgs[workflow.TiDBGroupIdKey] = actions.ActionArgString("invalid-group")
				mockArgs[workflow.TiDBGroupKey] = actions.ActionArgString("invalid json")

				createIns := executor.ExecTaskIdStatus{
					"exec-1": &executor.ExecTaskStatusEx{
						ExecTaskStatus: executor.ExecTaskStatus{
							ExecTask: &executor.ExecutorTask{
								Args: mockArgs,
							},
						},
					},
				}

				tidbClusterRepo.On("Load", mock.Anything, "testclusterid", false).Return(tidbCluster, nil)
				tidbGroupRepo.On("List", mock.Anything, []string(nil), []string(nil), []string{"testclusterid"}, []string(nil), false).Return(existingGroups, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanCreateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(createIns, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanUpdateTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
				dagExecutor.On("QueryRunnings", mock.Anything, dag.PlanDeleteTiDBGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil)
			},
			validate: func(t *testing.T, result []*entity.TiDBGroup, err error) {
				t.Helper()
				require.NoError(t, err)
				require.NotNil(t, result)
				// Should still return existing groups, invalid group from DAG is skipped after unmarshal error
				assert.Len(t, result, 1) // Only existing group, invalid group is skipped
				groupIds := make([]string, len(result))
				for i, group := range result {
					groupIds[i] = group.GetId()
				}
				assert.Contains(t, groupIds, "existing-group")
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Create mocks
			tidbClusterRepo := repomnocks.NewTiDBClusterRepo(t)
			tidbGroupRepo := repomnocks.NewTiDBGroupRepo(t)
			sharedPoolRepo := repomnocks.NewSharedPoolRepo(t)
			dagExecutor := executormocks.NewRemoteExecutor(t)
			scaleManager := brmocks.NewScaleManager(t)

			// Setup default mocks for sharedPool and scaleManager (may not be called in some test cases)
			sharedPoolRepo.EXPECT().Load(mock.Anything, mock.Anything, mock.Anything).Return(&entity.SharedPool{}, nil).Maybe()
			scaleManager.EXPECT().AnyFullfillingDecision(mock.Anything, mock.Anything).Return(false, nil).Maybe()

			// Setup default mocks for SharedPool DAG queries (always needed when sharedPool is loaded)
			// These use Maybe() to allow early-return test cases to skip them
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanCreateSharedPool, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanDeleteSharedPool, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanPauseSharedPool, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanResumeSharedPool, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanUpdateComponent, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanApplyComponent, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanCreateComponent, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanDeleteTiKVGroup, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanSetSharedPoolPDDeploymentModeToMonolithic, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanSetSharedPoolPDDeploymentModeToMicroservice, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanCreateTiDBCluster, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanUpdateTiDBCluster, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()
			dagExecutor.EXPECT().QueryRunnings(mock.Anything, dag.PlanDeleteTiDBCluster, mock.AnythingOfType("executor.MatchFilter")).Return(executor.ExecTaskIdStatus{}, nil).Maybe()

			// Setup mocks
			tt.setupMocks(tidbClusterRepo, tidbGroupRepo, sharedPoolRepo, dagExecutor)

			// Create server implementation
			server := &serverImpl{
				tidbClusterRepo: tidbClusterRepo,
				tidbGroupRepo:   tidbGroupRepo,
				sharedPoolRepo:  sharedPoolRepo,
				dagExecutor:     dagExecutor,
				scaleManager:    scaleManager,
			}

			// Execute test
			result, err := server.ListTiDBGroups(context.Background(), tt.clusterId)

			switch {
			case tt.expectedError != "":
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
				if tt.expectedCode != codes.OK {
					assert.Equal(t, tt.expectedCode, status.Code(err))
				}
				assert.Nil(t, result)

			case tt.validate != nil:
				tt.validate(t, result, err)

			default:
				require.NoError(t, err)
				require.NotNil(t, result)
			}
		})
	}
}
