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"
	bractionmocks "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/executor"
	executormocks "github.com/tidbcloud/cluster-service-ng/pkg/runtime/executor/mocks"
	repomocks "github.com/tidbcloud/cluster-service-ng/pkg/storage/repo/mocks"
	"github.com/tidbcloud/cluster-service-ng/pkg/tioperator"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// Mock transactioner for testing
type mockLabelsTransactioner struct{}

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

func TestSetSharedPoolComponentLabels_BasicValidation(t *testing.T) {
	testCases := []struct {
		name            string
		sharedPoolId    string
		targetComponent *entity.Component
		expectedError   string
		expectedCode    codes.Code
	}{
		{
			name:            "empty shared pool ID",
			sharedPoolId:    "",
			targetComponent: &entity.Component{},
			expectedError:   "sharedPool id is required",
			expectedCode:    codes.InvalidArgument,
		},
		{
			name:            "nil target component",
			sharedPoolId:    "testpool",
			targetComponent: nil,
			expectedError:   "target component is empty",
			expectedCode:    codes.InvalidArgument,
		},
		{
			name:         "unspecified component type",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED,
			},
			expectedError: "target component type is required",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "unsupported component type - PD",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_PD,
				Labels:        map[string]string{"zone": "zone-a"},
			},
			expectedError: "component type 'PD' does not support labels",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "empty labels",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_TIKV,
				Labels:        map[string]string{},
			},
			expectedError: "labels cannot be empty",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "nil labels",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_TIKV,
				Labels:        nil,
			},
			expectedError: "labels cannot be empty",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "reserved label key - keyspace",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_TIKV,
				Labels: map[string]string{
					tioperator.LabelKeyKeyspace: "my-keyspace",
				},
			},
			expectedError: "label key 'keyspace' is reserved",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "reserved label key - cidr",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_TIDB,
				Labels: map[string]string{
					tioperator.LabelKeyCidr: "10.0.0.0/8",
				},
			},
			expectedError: "label key 'cidr' is reserved",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "too many labels",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_TIKV,
				Labels: func() map[string]string {
					labels := make(map[string]string)
					for i := 0; i <= MaxLabelsCount; i++ {
						labels[string(rune('a'+i%26))+string(rune('0'+i/26))] = "value"
					}
					return labels
				}(),
			},
			expectedError: "too many labels",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "label key too long",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_TIKV,
				Labels: map[string]string{
					"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa": "value", // 67 chars, exceeds 63
				},
			},
			expectedError: "exceeds maximum length",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "label key invalid characters",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_TIKV,
				Labels: map[string]string{
					"INVALID_KEY": "value", // uppercase not allowed
				},
			},
			expectedError: "contains invalid characters",
			expectedCode:  codes.InvalidArgument,
		},
		{
			name:         "label value too long",
			sharedPoolId: "testpool",
			targetComponent: &entity.Component{
				ComponentType: apiv1.ComponentType_TIKV,
				Labels: map[string]string{
					"valid-key": func() string {
						value := make([]byte, MaxLabelValueLength+1)
						for i := range value {
							value[i] = 'x'
						}
						return string(value)
					}(),
				},
			},
			expectedError: "exceeds maximum length",
			expectedCode:  codes.InvalidArgument,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			server := &serverImpl{
				transactioner: &mockLabelsTransactioner{},
			}

			result, err := server.SetSharedPoolComponentLabels(context.Background(), tc.sharedPoolId, tc.targetComponent)

			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())
			assert.Nil(t, result)
		})
	}
}

func TestSetSharedPoolComponentLabels_SupportedComponentTypes(t *testing.T) {
	// Test that only TiDB, TiKV, TiFlash (write/compute), TiProxy support labels
	supportedTypes := []apiv1.ComponentType{
		apiv1.ComponentType_TIDB,
		apiv1.ComponentType_TIKV,
		apiv1.ComponentType_TIFLASH_WRITE,
		apiv1.ComponentType_TIFLASH_COMPUTE,
		apiv1.ComponentType_TIPROXY,
	}

	for _, compType := range supportedTypes {
		t.Run(compType.String()+"_supported", func(t *testing.T) {
			assert.True(t, isSupportedLabelComponentType(compType), "%s should support labels", compType.String())
		})
	}

	unsupportedTypes := []apiv1.ComponentType{
		apiv1.ComponentType_PD,
		apiv1.ComponentType_PD_SCHEDULING,
		apiv1.ComponentType_PD_TSO,
		apiv1.ComponentType_RESOURCE_MANAGER,
		apiv1.ComponentType_TIFLASH_LEARNER,
		apiv1.ComponentType_TIFLASH_COMPUTE_LEARNER,
		apiv1.ComponentType_COMPONENT_TYPE_UNSPECIFIED,
	}

	for _, compType := range unsupportedTypes {
		t.Run(compType.String()+"_unsupported", func(t *testing.T) {
			assert.False(t, isSupportedLabelComponentType(compType), "%s should not support labels", compType.String())
		})
	}
}

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

	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_TIKV,
		Labels: map[string]string{
			"app": "myapp",
			"env": "prod",
		},
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{
					Id:            "test-comp",
					ComponentType: apiv1.ComponentType_TIKV,
					Phase:         apiv1.Component_AVAILABLE,
					Labels:        map[string]string{"existing": "label"},
				},
			},
		},
	}

	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)
	mockDagExecutor.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)

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

	ctx := context.Background()
	result, err := server.SetSharedPoolComponentLabels(ctx, "testpool", targetComponent)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, targetComponent.Id, result.Id)
	assert.Equal(t, targetComponent.ComponentType, result.ComponentType)
	// Verify labels are merged (existing + new)
	assert.Equal(t, "label", result.Labels["existing"])
	assert.Equal(t, "myapp", result.Labels["app"])
	assert.Equal(t, "prod", result.Labels["env"])

	mockSharedPoolRepo.AssertExpectations(t)
	mockLockRepo.AssertExpectations(t)
	mockDagExecutor.AssertExpectations(t)
}

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

	// Target component with new labels
	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_TIKV,
		Labels: map[string]string{
			"app":     "newapp", // override existing
			"new-key": "new-value",
		},
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{
					Id:            "test-comp",
					ComponentType: apiv1.ComponentType_TIKV,
					Phase:         apiv1.Component_AVAILABLE,
					Labels: map[string]string{
						"app":      "oldapp", // will be overridden
						"existing": "value",  // will be preserved
					},
				},
			},
		},
	}

	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)
	mockDagExecutor.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)

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

	ctx := context.Background()
	result, err := server.SetSharedPoolComponentLabels(ctx, "testpool", targetComponent)

	require.NoError(t, err)
	assert.NotNil(t, result)
	// Verify merge semantics: upsert, not delete
	assert.Equal(t, "newapp", result.Labels["app"])        // overridden
	assert.Equal(t, "value", result.Labels["existing"])    // preserved
	assert.Equal(t, "new-value", result.Labels["new-key"]) // added
}

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

	targetComponent := &entity.Component{
		Id:            "non-existent-comp",
		ComponentType: apiv1.ComponentType_TIKV,
		Labels: map[string]string{
			"app": "myapp",
		},
	}

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

	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:  &mockLabelsTransactioner{},
		scaleManager:   mockScaleManager,
	}

	ctx := context.Background()
	result, err := server.SetSharedPoolComponentLabels(ctx, "testpool", targetComponent)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "not found")
	statusErr, ok := status.FromError(err)
	require.True(t, ok)
	assert.Equal(t, codes.NotFound, statusErr.Code())
	assert.Nil(t, result)
}

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

	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_TIKV,
		Labels: map[string]string{
			"app": "myapp",
		},
	}

	mockSharedPoolRepo.On("Load", mock.Anything, "testpool", false).Return(nil, status.Errorf(codes.NotFound, "not found"))
	mockLockRepo.On("Lock", mock.Anything, mock.Anything).Return(func() error { return nil }, nil)

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

	ctx := context.Background()
	result, err := server.SetSharedPoolComponentLabels(ctx, "testpool", targetComponent)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "not found")
	statusErr, ok := status.FromError(err)
	require.True(t, ok)
	assert.Equal(t, codes.NotFound, statusErr.Code())
	assert.Nil(t, result)
}

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

	targetComponent := &entity.Component{
		Id:            "test-comp",
		ComponentType: apiv1.ComponentType_TIKV,
		Labels: map[string]string{
			"app": "myapp",
		},
	}

	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_CREATING,
		},
	}

	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:  &mockLabelsTransactioner{},
		scaleManager:   mockScaleManager,
	}

	ctx := context.Background()
	result, err := server.SetSharedPoolComponentLabels(ctx, "testpool", targetComponent)

	require.Error(t, err)
	assert.Contains(t, err.Error(), "creating")
	statusErr, ok := status.FromError(err)
	require.True(t, ok)
	assert.Equal(t, codes.FailedPrecondition, statusErr.Code())
	assert.Nil(t, result)
}

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

	// Target component without ID (should use default)
	targetComponent := &entity.Component{
		Id:            "", // empty, should use default
		ComponentType: apiv1.ComponentType_TIKV,
		Labels: map[string]string{
			"app": "myapp",
		},
	}

	// GetDefaultComponenId returns the ID of PD component
	sharedPool := &entity.SharedPool{
		SharedPool: apiv1.SharedPool{
			Id:    "testpool",
			Phase: apiv1.SharedPool_AVAILABLE,
			ComponentGroups: []*entity.Component{
				{
					Id:            "default-comp",
					ComponentType: apiv1.ComponentType_PD, // PD is used to get default component ID
					Phase:         apiv1.Component_AVAILABLE,
				},
				{
					Id:            "default-comp",
					ComponentType: apiv1.ComponentType_TIKV,
					Phase:         apiv1.Component_AVAILABLE,
				},
			},
		},
	}

	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)
	mockDagExecutor.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)

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

	ctx := context.Background()
	result, err := server.SetSharedPoolComponentLabels(ctx, "testpool", targetComponent)

	require.NoError(t, err)
	assert.NotNil(t, result)
	assert.Equal(t, "default-comp", result.Id)
}

func TestIsReservedLabelKey(t *testing.T) {
	tests := []struct {
		name     string
		key      string
		expected bool
	}{
		{
			name:     "keyspace is reserved",
			key:      tioperator.LabelKeyKeyspace,
			expected: true,
		},
		{
			name:     "cidr is reserved",
			key:      tioperator.LabelKeyCidr,
			expected: true,
		},
		{
			name:     "zone is reserved",
			key:      "zone",
			expected: true,
		},
		{
			name:     "region is reserved",
			key:      "region",
			expected: true,
		},
		{
			name:     "host is reserved",
			key:      "host",
			expected: true,
		},
		{
			name:     "rack is not reserved",
			key:      "rack",
			expected: false,
		},
		{
			name:     "custom is not reserved",
			key:      "custom-label",
			expected: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := isReservedLabelKey(tt.key)
			assert.Equal(t, tt.expected, result)
		})
	}
}

// TestSetSharedPoolComponentLabels_GuardBlocksOnRunningTiKVRemoteWorkerDAG
// verifies a TIKV label update is rejected while a tikv-remote-worker switch
// DAG is running: both the label DAG and the switch DAG render the same
// TiKVGroup from frozen snapshots, so they must be serialized.
func TestSetSharedPoolComponentLabels_GuardBlocksOnRunningTiKVRemoteWorkerDAG(t *testing.T) {
	sharedPool := newTKRWSharedPool(tkrwEnabledBoth(t), 3)
	sharedPoolRepo, lockRepo, dagExecutor, scaleManager := newTKRWHandlerMocksWithRunningDag(
		t, sharedPool, dag.PlanEnableCoprocessorWorker, runningDagStatus(tkrwKey(t, tkrwEnabledBoth(t))))

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

	targetComponent := &entity.Component{
		Id:            testTKRWComponentID,
		ComponentType: apiv1.ComponentType_TIKV,
		Labels:        map[string]string{"app": "myapp"},
	}

	result, err := server.SetSharedPoolComponentLabels(context.Background(), testTKRWSharedPoolID, targetComponent)
	require.Error(t, err)
	assert.Equal(t, codes.FailedPrecondition, status.Code(err))
	assert.Nil(t, result)
	dagExecutor.AssertNotCalled(t, "Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
}

// TestSetSharedPoolComponentLabels_GuardBlocksOnUnconvergedTiKVRemoteWorkerTopology
// verifies a TIKV label update is rejected when the persisted remote-worker
// target has not converged in the live topology (no running DAG, but the live
// TiKVGroup does not match the persisted desired routing).
func TestSetSharedPoolComponentLabels_GuardBlocksOnUnconvergedTiKVRemoteWorkerTopology(t *testing.T) {
	sharedPool := newTKRWSharedPool(tkrwEnabledBoth(t), 3)
	sharedPoolRepo, lockRepo, dagExecutor, scaleManager := newTKRWHandlerMocks(t, sharedPool)

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

	targetComponent := &entity.Component{
		Id:            testTKRWComponentID,
		ComponentType: apiv1.ComponentType_TIKV,
		Labels:        map[string]string{"app": "myapp"},
	}

	result, err := server.SetSharedPoolComponentLabels(context.Background(), testTKRWSharedPoolID, targetComponent)
	require.Error(t, err)
	assert.Equal(t, codes.FailedPrecondition, status.Code(err))
	assert.Nil(t, result)
	dagExecutor.AssertNotCalled(t, "Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything)
}

// TestSetSharedPoolComponentLabels_TiDBLabelsAllowedDuringTiKVRemoteWorkerSwitch
// verifies the guard stays scoped: a TIDB label update is unaffected by a
// running tikv-remote-worker switch because TIDB is not a guarded component
// type.
func TestSetSharedPoolComponentLabels_TiDBLabelsAllowedDuringTiKVRemoteWorkerSwitch(t *testing.T) {
	sharedPool := newTKRWSharedPool(tkrwEnabledBoth(t), 3)
	sharedPool.ComponentGroups = append(sharedPool.ComponentGroups, &apiv1.Component{
		Id:            testTKRWComponentID,
		ComponentType: apiv1.ComponentType_TIDB,
		Phase:         apiv1.Component_AVAILABLE,
	})
	sharedPoolRepo, lockRepo, dagExecutor, scaleManager := newTKRWHandlerMocksWithRunningDag(
		t, sharedPool, dag.PlanEnableCoprocessorWorker, runningDagStatus(tkrwKey(t, tkrwEnabledBoth(t))))
	dagExecutor.On("Submit", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)

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

	targetComponent := &entity.Component{
		Id:            testTKRWComponentID,
		ComponentType: apiv1.ComponentType_TIDB,
		Labels:        map[string]string{"app": "myapp"},
	}

	result, err := server.SetSharedPoolComponentLabels(context.Background(), testTKRWSharedPoolID, targetComponent)
	require.NoError(t, err)
	require.NotNil(t, result)
	assert.Equal(t, "myapp", result.GetLabels()["app"])
}
