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/config"
	"github.com/tidbcloud/cluster-service-ng/domain/entity"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow"
	bractionmocks "github.com/tidbcloud/cluster-service-ng/domain/workflow/action/br/mocks"
	"github.com/tidbcloud/cluster-service-ng/domain/workflow/dag"
	configmocks "github.com/tidbcloud/cluster-service-ng/pkg/clusterconfig/mocks"
	"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"
	repomocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// Mock transactioner for testing
type mockConfigVersionTransactioner struct{}

func (m *mockConfigVersionTransactioner) Transaction(ctx context.Context, fn func(context.Context) error) error {
	return fn(ctx)
}

func TestSetSharedPoolComponentConfigOrVersion_BasicValidation(t *testing.T) {
	testCases := []struct {
		name             string
		sharedPoolId     string
		targetComponents []*entity.TargetComponent
		scope            apiv1.SetSharedPoolComponentConfigVersionRequest_Scope
		expectedError    string
		expectedCode     codes.Code
	}{
		{
			name:             "empty shared pool ID",
			sharedPoolId:     "",
			targetComponents: []*entity.TargetComponent{{}},
			scope:            apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG,
			expectedError:    "sharedPool id is required",
			expectedCode:     codes.InvalidArgument,
		},
		{
			name:             "nil target component",
			sharedPoolId:     "testpool",
			targetComponents: nil,
			scope:            apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG,
			expectedError:    "target components is empty",
			expectedCode:     codes.InvalidArgument,
		},
		{
			name:         "unspecified component type",
			sharedPoolId: "testpool",
			targetComponents: []*entity.TargetComponent{
				{
					ComponentType: apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED,
				},
			},
			scope:         apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG,
			expectedError: "component type is required",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "data migration must use DM flow",
			sharedPoolId: "testpool",
			targetComponents: []*entity.TargetComponent{
				{
					ComponentType: apiv1.ComponentType_DATA_MIGRATION,
				},
			},
			scope:         apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG,
			expectedError: "DATA_MIGRATION must be handled by SetDMComponentConfigOrVersion",
			expectedCode:  codes.InvalidArgument,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Create server instance with minimal dependencies for validation tests
			server := &serverImpl{
				transactioner: &mockConfigVersionTransactioner{},
			}

			// Execute test
			err := server.SetSharedPoolComponentConfigOrVersion(context.Background(), tc.sharedPoolId, tc.targetComponents, tc.scope, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, false)
			// Validate results
			require.Error(t, err)
			assert.Contains(t, err.Error(), tc.expectedError)
			statusErr, ok := status.FromError(err)
			require.True(t, ok, "error should be a gRPC status error")
			assert.Equal(t, tc.expectedCode, statusErr.Code())
		})
	}
}

func TestSetSharedPoolComponentConfigOrVersion_SuccessfulUpdateConfig(t *testing.T) {
	// Create mocks
	mockSharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	mockLockRepo := repomocks.NewLockRepo(t)
	mockDagExecutor := executormocks.NewRemoteExecutor(t)
	mockScaleManager := bractionmocks.NewScaleManager(t)
	mockClusterConfigClient := configmocks.NewClient(t)

	// Test data - use PD component for config update test (simpler than TIDB)
	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_PD,
		Configuration: &entity.ComponentConfiguration{
			ImageTag:                 "v6.5.0",
			ConfigurationFileContent: "[server]\ntest = \"value\"",
		},
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{
					Id:            "test-comp",
					ComponentType: apiv1.ComponentType_PD,
					Phase:         apiv1.Component_AVAILABLE,
					Configuration: &entity.ComponentConfiguration{
						ImageTag:                 "v6.4.0",
						ConfigurationFileContent: "[server]\nold = \"value\"",
					},
				},
			},
		},
	}

	// Setup mocks
	mockSharedPoolRepo.On("Load", mock.Anything, "testpool", false).Return(sharedPool, nil)
	mockLockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)
	mockDagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
	mockScaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)
	mockClusterConfigClient.On("GetComponentVersionAndConfig", mock.Anything, mock.Anything, mock.Anything).Return("v6.5.0", &entity.ComponentConfiguration{
		ImageTag:                 "v6.5.0",
		ConfigurationFileContent: "[server]\ntest = \"value\"",
	}, nil)
	mockDagExecutor.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)

	// Create server instance
	server := &serverImpl{
		sharedPoolRepo:      mockSharedPoolRepo,
		lockRepo:            mockLockRepo,
		dagExecutor:         mockDagExecutor,
		transactioner:       &mockConfigVersionTransactioner{},
		scaleManager:        mockScaleManager,
		clusterConfigClient: mockClusterConfigClient,
		meteringConfig:      &config.MeteringConfig{Enable: false},
	}

	// Execute test with force=true to bypass phase check
	ctx := context.Background()
	targetComponents := []*entity.TargetComponent{
		{
			Id:            targetComponent.Id,
			ComponentType: targetComponent.ComponentType,
		},
	}
	err := server.SetSharedPoolComponentConfigOrVersion(ctx, "testpool", targetComponents, apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, true)

	// Verify results
	require.NoError(t, err)

	// Verify all expectations were met
	mockSharedPoolRepo.AssertExpectations(t)
	mockLockRepo.AssertExpectations(t)
	mockDagExecutor.AssertExpectations(t)
}

func TestSetSharedPoolComponentConfigOrVersion_BlocksTiKVRemoteWorkerRoutingSwitch(t *testing.T) {
	for _, runningPlan := range []string{
		dag.PlanEnableCoprocessorWorker,
		dag.PlanDisableCoprocessorWorker,
	} {
		t.Run(runningPlan, func(t *testing.T) {
			sharedPool := newTKRWSharedPool(tkrwEnabledBoth(t), 3)
			sharedPoolRepo, lockRepo, dagExecutor, scaleManager := newTKRWHandlerMocksWithRunningDag(
				t, sharedPool, runningPlan, runningDagStatus(tkrwKey(t, sharedPool.GetEnabledTikvRemoteWorkers())))

			server := &serverImpl{
				sharedPoolRepo: sharedPoolRepo,
				lockRepo:       lockRepo,
				dagExecutor:    dagExecutor,
				transactioner:  &mockConfigVersionTransactioner{},
				scaleManager:   scaleManager,
			}

			err := server.SetSharedPoolComponentConfigOrVersion(
				context.Background(),
				testTKRWSharedPoolID,
				[]*entity.TargetComponent{{ComponentType: apiv1.ComponentType_TIKV_WORKER}},
				apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG,
				apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE,
				true,
			)

			require.Error(t, err)
			assert.Equal(t, codes.FailedPrecondition, status.Code(err))
			assert.Equal(t, apiv1.Component_AVAILABLE,
				sharedPoolDefaultComponentMap(sharedPool)[apiv1.ComponentType_TIKV_WORKER].GetPhase())
			dagExecutor.AssertNotCalled(t, "Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
		})
	}
}

func TestSetSharedPoolComponentConfigOrVersion_BlocksTiKVRemoteWorkerTerminalDrift(t *testing.T) {
	sharedPool := newTKRWSharedPool(tkrwEnabledBoth(t), 3)
	sharedPoolRepo, lockRepo, dagExecutor, scaleManager := newTKRWHandlerMocks(t, sharedPool)

	server := &serverImpl{
		sharedPoolRepo:   sharedPoolRepo,
		lockRepo:         lockRepo,
		dagExecutor:      dagExecutor,
		transactioner:    &mockConfigVersionTransactioner{},
		scaleManager:     scaleManager,
		dataplaneManager: newTKRWDataplaneManager(t),
	}

	err := server.SetSharedPoolComponentConfigOrVersion(
		context.Background(),
		testTKRWSharedPoolID,
		[]*entity.TargetComponent{{ComponentType: apiv1.ComponentType_TIKV_WORKER}},
		apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG,
		apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE,
		true,
	)

	require.Error(t, err)
	assert.Equal(t, codes.FailedPrecondition, status.Code(err))
	assert.Equal(t, apiv1.Component_AVAILABLE,
		sharedPoolDefaultComponentMap(sharedPool)[apiv1.ComponentType_TIKV_WORKER].GetPhase())
	dagExecutor.AssertNotCalled(t, "Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
}

func TestSetSharedPoolComponentConfigOrVersion_SuccessfulUpdateVersion(t *testing.T) {
	// Create mocks
	mockSharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	mockLockRepo := repomocks.NewLockRepo(t)
	mockDagExecutor := executormocks.NewRemoteExecutor(t)
	mockScaleManager := bractionmocks.NewScaleManager(t)
	mockClusterConfigClient := configmocks.NewClient(t)

	// Test data - use PD for version update test
	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_PD,
		Configuration: &entity.ComponentConfiguration{
			ImageTag:                 "v6.5.0",
			ConfigurationFileContent: "[replication]\ntest = \"value\"",
		},
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{
					Id:            "test-comp",
					ComponentType: apiv1.ComponentType_PD,
					Phase:         apiv1.Component_AVAILABLE,
					Configuration: &entity.ComponentConfiguration{
						ImageTag:                 "v6.4.0",
						ConfigurationFileContent: "[replication]\nold = \"value\"",
					},
				},
			},
		},
	}

	// Setup mocks
	mockSharedPoolRepo.On("Load", mock.Anything, "testpool", false).Return(sharedPool, nil)
	mockLockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)
	mockDagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
	mockScaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)
	mockClusterConfigClient.On("GetComponentVersionAndConfig", mock.Anything, mock.Anything, mock.Anything).Return("v6.5.0", &entity.ComponentConfiguration{
		ImageTag:                 "v6.5.0",
		ConfigurationFileContent: "[replication]\ntest = \"value\"",
	}, nil)
	mockDagExecutor.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)

	// Create server instance
	server := &serverImpl{
		sharedPoolRepo:      mockSharedPoolRepo,
		lockRepo:            mockLockRepo,
		dagExecutor:         mockDagExecutor,
		transactioner:       &mockConfigVersionTransactioner{},
		scaleManager:        mockScaleManager,
		clusterConfigClient: mockClusterConfigClient,
		meteringConfig:      &config.MeteringConfig{Enable: false},
	}

	// Execute test with force=true to bypass phase check
	ctx := context.Background()
	targetComponents := []*entity.TargetComponent{
		{
			Id:            targetComponent.Id,
			ComponentType: targetComponent.ComponentType,
		},
	}
	err := server.SetSharedPoolComponentConfigOrVersion(ctx, "testpool", targetComponents, apiv1.SetSharedPoolComponentConfigVersionRequest_VERSION, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, true)

	// Verify results
	require.NoError(t, err)

	// Verify all expectations were met
	mockSharedPoolRepo.AssertExpectations(t)
	mockLockRepo.AssertExpectations(t)
	mockDagExecutor.AssertExpectations(t)
}

func TestSetSharedPoolComponentConfigOrVersion_UpgradePolicyFlag(t *testing.T) {
	testCases := []struct {
		name           string
		scope          apiv1.SetSharedPoolComponentConfigVersionRequest_Scope
		expectedEnsure bool
	}{
		{
			name:           "config update does not require policy enforcement",
			scope:          apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG,
			expectedEnsure: false,
		},
		{
			name:           "version update requires policy enforcement",
			scope:          apiv1.SetSharedPoolComponentConfigVersionRequest_VERSION,
			expectedEnsure: true,
		},
		{
			name:           "combined update requires policy enforcement",
			scope:          apiv1.SetSharedPoolComponentConfigVersionRequest_ALL,
			expectedEnsure: true,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			sharedPoolRepo := repomocks.NewSharedPoolRepo(t)
			lockRepo := repomocks.NewLockRepo(t)
			dagExecutor := executormocks.NewRemoteExecutor(t)
			scaleManager := bractionmocks.NewScaleManager(t)
			clusterConfigClient := configmocks.NewClient(t)

			sharedPool := &entity.SharedPool{
				SharedPool: apiv1.SharedPool{
					Id:    "testpool",
					Phase: apiv1.SharedPool_AVAILABLE,
					ComponentGroups: []*entity.Component{
						{
							Id:            "test-comp",
							ComponentType: apiv1.ComponentType_PD,
							Phase:         apiv1.Component_AVAILABLE,
							Configuration: &entity.ComponentConfiguration{
								ImageTag: "v6.4.0",
							},
						},
					},
				},
			}

			sharedPoolRepo.On("Load", mock.Anything, "testpool", false).Return(sharedPool, nil)
			lockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)
			dagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).
				Return(executor.NewExecTaskIdStatus(), nil)
			scaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)
			clusterConfigClient.On("GetComponentVersionAndConfig", mock.Anything, mock.Anything, mock.Anything).
				Return("v6.5.0", &entity.ComponentConfiguration{
					ImageTag:                 "v6.5.0",
					ConfigurationFileContent: "[replication]\nmax-replicas = 3",
				}, nil)
			dagExecutor.On(
				"Submit",
				mock.Anything,
				dag.PlanUpdateComponent,
				mock.MatchedBy(func(args actions.ActionArgs) bool {
					_, exists := args[workflow.EnsureNoConstraintsUpgradePolicyKey]
					if !tc.expectedEnsure {
						return !exists
					}
					if !exists {
						return false
					}
					ensure, err := args.GetBool(workflow.EnsureNoConstraintsUpgradePolicyKey)
					return err == nil && ensure
				}),
				mock.Anything,
			).Return(nil)

			server := &serverImpl{
				sharedPoolRepo:      sharedPoolRepo,
				lockRepo:            lockRepo,
				dagExecutor:         dagExecutor,
				transactioner:       &mockConfigVersionTransactioner{},
				scaleManager:        scaleManager,
				clusterConfigClient: clusterConfigClient,
				meteringConfig:      &config.MeteringConfig{Enable: false},
			}

			err := server.SetSharedPoolComponentConfigOrVersion(
				context.Background(),
				"testpool",
				[]*entity.TargetComponent{
					{
						Id:            "test-comp",
						ComponentType: apiv1.ComponentType_PD,
					},
				},
				tc.scope,
				apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE,
				true,
			)
			require.NoError(t, err)
		})
	}
}

func TestSetSharedPoolComponentConfigOrVersion_InvalidScope(t *testing.T) {
	// Test data
	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_TIDB,
	}

	server := &serverImpl{}

	targetComponents := []*entity.TargetComponent{
		{
			Id:            targetComponent.Id,
			ComponentType: targetComponent.ComponentType,
		},
	}

	// Execute test with invalid scope (SCOPE_UNSPECIFIED)
	err := server.SetSharedPoolComponentConfigOrVersion(context.Background(), "testpool", targetComponents, apiv1.SetSharedPoolComponentConfigVersionRequest_SCOPE_UNSPECIFIED, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, true)

	// Verify results - scope validation happens early, before any other operations
	require.Error(t, err)
	assert.Contains(t, err.Error(), "invalid scope")
}

func TestSetSharedPoolComponentConfigOrVersion_ComponentNotFound(t *testing.T) {
	// Create mocks
	mockSharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	mockLockRepo := repomocks.NewLockRepo(t)
	mockDagExecutor := executormocks.NewRemoteExecutor(t)
	mockScaleManager := bractionmocks.NewScaleManager(t)

	// Test data - component not in shared pool
	targetComponent := &entity.Component{
		Id:            "non-existent-comp",
		ComponentType: apiv1.ComponentType_TIDB,
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{
					Id:            "different-comp",
					ComponentType: apiv1.ComponentType_TIDB,
					Phase:         apiv1.Component_AVAILABLE,
				},
			},
		},
	}

	// Setup mocks
	mockSharedPoolRepo.On("Load", mock.Anything, "testpool", false).Return(sharedPool, nil)
	mockLockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)
	mockDagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
	mockScaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)

	server := &serverImpl{
		sharedPoolRepo: mockSharedPoolRepo,
		lockRepo:       mockLockRepo,
		dagExecutor:    mockDagExecutor,
		transactioner:  &mockConfigVersionTransactioner{},
		scaleManager:   mockScaleManager,
	}

	targetComponents := []*entity.TargetComponent{
		{
			Id:            targetComponent.Id,
			ComponentType: targetComponent.ComponentType,
		},
	}

	// Execute test
	err := server.SetSharedPoolComponentConfigOrVersion(context.Background(), "testpool", targetComponents, apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, false)

	// Verify results
	require.Error(t, err)
	assert.Contains(t, err.Error(), "not found")

	// Verify expectations
	mockSharedPoolRepo.AssertExpectations(t)
	mockLockRepo.AssertExpectations(t)
	mockDagExecutor.AssertExpectations(t)
}

func TestSetSharedPoolComponentConfigOrVersion_SharedPoolNotAvailable(t *testing.T) {
	// Create mocks
	mockSharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	mockLockRepo := repomocks.NewLockRepo(t)
	mockDagExecutor := executormocks.NewRemoteExecutor(t)
	mockScaleManager := bractionmocks.NewScaleManager(t)

	// Test data - shared pool is MODIFYING
	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_TIDB,
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_CREATING, // Not available
			ComponentGroups: []*entity.Component{
				{
					Id:            "test-comp",
					ComponentType: apiv1.ComponentType_TIDB,
					Phase:         apiv1.Component_AVAILABLE,
				},
			},
		},
	}

	// Setup mocks
	mockSharedPoolRepo.On("Load", mock.Anything, "testpool", false).Return(sharedPool, nil)
	mockLockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)
	mockDagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
	mockScaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)

	server := &serverImpl{
		sharedPoolRepo: mockSharedPoolRepo,
		lockRepo:       mockLockRepo,
		dagExecutor:    mockDagExecutor,
		transactioner:  &mockConfigVersionTransactioner{},
		scaleManager:   mockScaleManager,
	}

	targetComponents := []*entity.TargetComponent{
		{
			Id:            targetComponent.Id,
			ComponentType: targetComponent.ComponentType,
		},
	}

	// Execute test - should fail because pool is not available
	err := server.SetSharedPoolComponentConfigOrVersion(context.Background(), "testpool", targetComponents, apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, false)

	// Verify results - should fail validation
	require.Error(t, err)

	// Verify expectations
	mockSharedPoolRepo.AssertExpectations(t)
	mockLockRepo.AssertExpectations(t)
	mockDagExecutor.AssertExpectations(t)
}

func TestSetSharedPoolComponentConfigOrVersion_ComponentNotAvailable(t *testing.T) {
	// Create mocks
	mockSharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	mockLockRepo := repomocks.NewLockRepo(t)
	mockDagExecutor := executormocks.NewRemoteExecutor(t)
	mockScaleManager := bractionmocks.NewScaleManager(t)

	// Test data - component is CREATING
	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_TIDB,
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{
					Id:            "test-comp",
					ComponentType: apiv1.ComponentType_TIDB,
					Phase:         apiv1.Component_CREATING, // Not available
				},
			},
		},
	}

	// Setup mocks
	mockSharedPoolRepo.On("Load", mock.Anything, "testpool", false).Return(sharedPool, nil)
	mockLockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)
	mockDagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
	mockScaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)

	server := &serverImpl{
		sharedPoolRepo: mockSharedPoolRepo,
		lockRepo:       mockLockRepo,
		dagExecutor:    mockDagExecutor,
		transactioner:  &mockConfigVersionTransactioner{},
		scaleManager:   mockScaleManager,
	}

	targetComponents := []*entity.TargetComponent{
		{
			Id:            targetComponent.Id,
			ComponentType: targetComponent.ComponentType,
		},
	}

	// Execute test - should fail because component is not available
	err := server.SetSharedPoolComponentConfigOrVersion(context.Background(), "testpool", targetComponents, apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, false)

	// Verify results - should fail validation
	require.Error(t, err)

	// Verify expectations
	mockSharedPoolRepo.AssertExpectations(t)
	mockLockRepo.AssertExpectations(t)
	mockDagExecutor.AssertExpectations(t)
}

func TestSetSharedPoolComponentConfigOrVersion_ForceUpdateWhenNotAvailable(t *testing.T) {
	// Create mocks
	mockSharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	mockTiDBGroupRepo := repomocks.NewTiDBGroupRepo(t)
	mockLockRepo := repomocks.NewLockRepo(t)
	mockDagExecutor := executormocks.NewRemoteExecutor(t)
	mockScaleManager := bractionmocks.NewScaleManager(t)
	mockClusterConfigClient := configmocks.NewClient(t)

	// Test data - component is MODIFYING but force=true
	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_TIDB,
		Configuration: &entity.ComponentConfiguration{
			ImageTag:                 "v6.5.0",
			ConfigurationFileContent: "[server]\ntest = \"value\"",
		},
	}

	tidbGroup := &entity.TiDBGroup{
		TiDBGroup: apiv1.TiDBGroup{
			Id:        "test-comp",
			ClusterId: "test-cluster",
		},
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{
					Id:            "test-comp",
					ComponentType: apiv1.ComponentType_TIDB,
					Phase:         apiv1.Component_MODIFYING, // Not available
					Configuration: &entity.ComponentConfiguration{
						ImageTag:                 "v6.4.0",
						ConfigurationFileContent: "[server]\nold = \"value\"",
					},
				},
			},
		},
	}

	// Setup mocks
	mockSharedPoolRepo.On("Load", mock.Anything, "testpool", false).Return(sharedPool, nil)
	mockTiDBGroupRepo.On("Load", mock.Anything, "test-comp", false).Return(tidbGroup, nil)
	mockLockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)
	mockDagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
	mockScaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)
	mockClusterConfigClient.On("GetTiDBClusterVersionAndConfig", mock.Anything, mock.Anything, mock.Anything).Return("v6.5.0", &entity.ComponentConfiguration{
		ImageTag:                 "v6.5.0",
		ConfigurationFileContent: "[server]\ntest = \"value\"",
	}, nil)
	mockDagExecutor.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)

	server := &serverImpl{
		sharedPoolRepo:      mockSharedPoolRepo,
		tidbGroupRepo:       mockTiDBGroupRepo,
		lockRepo:            mockLockRepo,
		dagExecutor:         mockDagExecutor,
		transactioner:       &mockConfigVersionTransactioner{},
		scaleManager:        mockScaleManager,
		clusterConfigClient: mockClusterConfigClient,
		meteringConfig:      &config.MeteringConfig{Enable: false},
	}

	targetComponents := []*entity.TargetComponent{
		{
			Id:            targetComponent.Id,
			ComponentType: targetComponent.ComponentType,
		},
	}

	// Execute test with force=true
	err := server.SetSharedPoolComponentConfigOrVersion(context.Background(), "testpool", targetComponents, apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, true)

	// Verify results - should succeed with force
	require.NoError(t, err)

	// Verify expectations
	mockSharedPoolRepo.AssertExpectations(t)
	mockLockRepo.AssertExpectations(t)
	mockDagExecutor.AssertExpectations(t)
}

func TestSetSharedPoolComponentConfigOrVersion_LockFailure(t *testing.T) {
	// Create mocks
	mockSharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	mockLockRepo := repomocks.NewLockRepo(t)

	// Setup mocks - lock fails
	mockLockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, status.Error(codes.Internal, "lock failed"))

	server := &serverImpl{
		sharedPoolRepo: mockSharedPoolRepo,
		lockRepo:       mockLockRepo,
		transactioner:  &mockConfigVersionTransactioner{},
	}

	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_TIDB,
	}

	targetComponents := []*entity.TargetComponent{
		{
			Id:            targetComponent.Id,
			ComponentType: targetComponent.ComponentType,
		},
	}

	// Execute test
	err := server.SetSharedPoolComponentConfigOrVersion(context.Background(), "testpool", targetComponents, apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, false)

	// Verify results
	require.Error(t, err)
	assert.Contains(t, err.Error(), "lock failed")

	// Verify expectations
	mockLockRepo.AssertExpectations(t)
}

func TestSetSharedPoolComponentConfigOrVersion_DefaultComponentId(t *testing.T) {
	// Create mocks
	mockSharedPoolRepo := repomocks.NewSharedPoolRepo(t)
	mockTiDBGroupRepo := repomocks.NewTiDBGroupRepo(t)
	mockLockRepo := repomocks.NewLockRepo(t)
	mockDagExecutor := executormocks.NewRemoteExecutor(t)
	mockScaleManager := bractionmocks.NewScaleManager(t)
	mockClusterConfigClient := configmocks.NewClient(t)

	// Test data - component ID is empty, should use default
	targetComponent := &entity.Component{
		Id:            "", // Empty ID
		ComponentType: apiv1.ComponentType_TIDB,
		Configuration: &entity.ComponentConfiguration{
			ImageTag:                 "v6.5.0",
			ConfigurationFileContent: "[server]\ntest = \"value\"",
		},
	}

	tidbGroup := &entity.TiDBGroup{
		TiDBGroup: apiv1.TiDBGroup{
			Id:        "pd-default",
			ClusterId: "test-cluster",
		},
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{
					Id:            "pd-default", // Default component (PD)
					ComponentType: apiv1.ComponentType_PD,
					Phase:         apiv1.Component_AVAILABLE,
					Configuration: &entity.ComponentConfiguration{
						ImageTag: "v6.4.0",
					},
				},
				{
					Id:            "pd-default", // Same ID as PD for TIDB component
					ComponentType: apiv1.ComponentType_TIDB,
					Phase:         apiv1.Component_AVAILABLE,
					Configuration: &entity.ComponentConfiguration{
						ImageTag:                 "v6.4.0",
						ConfigurationFileContent: "[server]\nold = \"value\"",
					},
				},
			},
		},
	}

	// Setup mocks
	mockSharedPoolRepo.On("Load", mock.Anything, "testpool", false).Return(sharedPool, nil)
	mockTiDBGroupRepo.On("Load", mock.Anything, "pd-default", false).Return(tidbGroup, nil)
	mockLockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)
	mockDagExecutor.On("QueryRunnings", mock.Anything, mock.Anything, mock.Anything).Return(executor.NewExecTaskIdStatus(), nil)
	mockScaleManager.On("AnyFullfillingDecision", mock.Anything, mock.Anything).Return(false, nil)
	mockClusterConfigClient.On("GetTiDBClusterVersionAndConfig", mock.Anything, mock.Anything, mock.Anything).Return("v6.5.0", &entity.ComponentConfiguration{
		ImageTag:                 "v6.5.0",
		ConfigurationFileContent: "[server]\ntest = \"value\"",
	}, nil)
	mockDagExecutor.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)

	server := &serverImpl{
		sharedPoolRepo:      mockSharedPoolRepo,
		tidbGroupRepo:       mockTiDBGroupRepo,
		lockRepo:            mockLockRepo,
		dagExecutor:         mockDagExecutor,
		transactioner:       &mockConfigVersionTransactioner{},
		scaleManager:        mockScaleManager,
		clusterConfigClient: mockClusterConfigClient,
		meteringConfig:      &config.MeteringConfig{Enable: false},
	}

	targetComponents := []*entity.TargetComponent{
		{
			Id:            targetComponent.Id,
			ComponentType: targetComponent.ComponentType,
		},
	}

	// Execute test with force=true to bypass phase check
	err := server.SetSharedPoolComponentConfigOrVersion(context.Background(), "testpool", targetComponents, apiv1.SetSharedPoolComponentConfigVersionRequest_CONFIG, apiv1.SetSharedPoolComponentConfigVersionRequest_ROLLING_UPDATE, true)

	// Verify results
	require.NoError(t, err)

	// Verify expectations
	mockSharedPoolRepo.AssertExpectations(t)
	mockLockRepo.AssertExpectations(t)
	mockDagExecutor.AssertExpectations(t)
}
