package clusterconfig

import (
	"context"
	"strings"
	"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"
	configcentermocks "github.com/tidbcloud/cluster-service-ng/pkg/externalmocks/configcentermocks"
	schemav1 "github.com/tidbcloud/cluster-service-ng/pkg/storage/etcd/schema/v1"
	"github.com/tidbcloud/cluster-service-ng/pkg/utils"
	configcenter "github.com/tidbcloud/config-center/proto/config_center"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

// helper functions for creating test entities
func newTestTiDBGroup(tenantId, version string) *entity.TiDBGroup {
	return &entity.TiDBGroup{
		TiDBGroup: apiv1.TiDBGroup{
			Id:        "test-group-id",
			ClusterId: "test-cluster-id",
			TenantId:  tenantId,
			Version:   version,
		},
	}
}

func newTestSharedPool(version string) *entity.SharedPool {
	poolId := "test-pool-id"
	return &entity.SharedPool{
		ConfigCenterMetaProfileId: "",
		SharedPool: apiv1.SharedPool{
			Id:      poolId,
			Version: version,
			ComponentGroups: []*entity.Component{
				{
					Id:            utils.FnvHash(poolId),
					ComponentType: apiv1.ComponentType_TIKV,
				},
				{
					Id:            utils.FnvHash(poolId),
					ComponentType: apiv1.ComponentType_PD,
				},
			},
		},
	}
}

func setTestSharedPoolTenantTag(sharedPool *entity.SharedPool, tenantID string) {
	if sharedPool.Provider == nil {
		sharedPool.Provider = &entity.DataPlaneProvider{}
	}
	if sharedPool.Provider.Tags == nil {
		sharedPool.Provider.Tags = map[string]string{}
	}
	sharedPool.Provider.Tags[schemav1.LabelUserTenantKey] = tenantID
}

//nolint:unparam // test
func newTestTiDBCluster(tenantId, version string) *entity.TiDBCluster {
	return &entity.TiDBCluster{
		Id:           "test-cluster-id",
		TenantId:     tenantId,
		Version:      version,
		SharedPoolId: "test-pool-id",
	}
}

func TestParseTenantId(t *testing.T) {
	t.Run("empty string returns nil", func(t *testing.T) {
		result, err := ParseTenantId("", true)
		require.NoError(t, err)
		assert.Nil(t, result)
	})

	t.Run("multi-tenant tag returns nil", func(t *testing.T) {
		result, err := ParseTenantId(schemav1.MultiTenantTag, true)
		require.NoError(t, err)
		assert.Nil(t, result)
	})

	t.Run("valid tenant id string", func(t *testing.T) {
		result, err := ParseTenantId("12345", true)
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, uint64(12345), *result)
	})

	t.Run("valid tenant id with large number", func(t *testing.T) {
		result, err := ParseTenantId("18446744073709551615", true) // max uint64
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, uint64(18446744073709551615), *result)
	})

	t.Run("invalid tenant id returns error", func(t *testing.T) {
		result, err := ParseTenantId("invalid", true)
		require.Error(t, err)
		assert.Nil(t, result)
		assert.Contains(t, err.Error(), "parse tenantId")
	})

	t.Run("negative number returns error", func(t *testing.T) {
		result, err := ParseTenantId("-123", true)
		require.Error(t, err)
		assert.Nil(t, result)
	})

	t.Run("floating point number returns error", func(t *testing.T) {
		result, err := ParseTenantId("123.456", true)
		require.Error(t, err)
		assert.Nil(t, result)
	})

	t.Run("zero is valid", func(t *testing.T) {
		result, err := ParseTenantId("0", true)
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, uint64(0), *result)
	})

	t.Run("empty string returns error when multi-tenant is not allowed", func(t *testing.T) {
		result, err := ParseTenantId("", false)
		require.Error(t, err)
		assert.Nil(t, result)
		assert.Contains(t, err.Error(), "parse tenantId")
	})

	t.Run("multi-tenant tag returns error when multi-tenant is not allowed", func(t *testing.T) {
		result, err := ParseTenantId(schemav1.MultiTenantTag, false)
		require.Error(t, err)
		assert.Nil(t, result)
		assert.Contains(t, err.Error(), "parse tenantId")
	})

	t.Run("invalid tenant id returns error when multi-tenant is not allowed", func(t *testing.T) {
		result, err := ParseTenantId("invalid", false)
		require.Error(t, err)
		assert.Nil(t, result)
		assert.Contains(t, err.Error(), "parse tenantId")
	})

	t.Run("whitespace only string returns error when multi-tenant is not allowed", func(t *testing.T) {
		result, err := ParseTenantId("   ", false)
		require.Error(t, err)
		assert.Nil(t, result)
		assert.Contains(t, err.Error(), "parse tenantId")
	})

	t.Run("valid tenant id string works when multi-tenant is not allowed", func(t *testing.T) {
		result, err := ParseTenantId("12345", false)
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, uint64(12345), *result)
	})

	t.Run("zero is valid when multi-tenant is not allowed", func(t *testing.T) {
		result, err := ParseTenantId("0", false)
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, uint64(0), *result)
	})
}

func TestParseMetaProfileID(t *testing.T) {
	t.Run("empty string returns nil", func(t *testing.T) {
		result, err := ParseMetaProfileID("")
		require.NoError(t, err)
		assert.Nil(t, result)
	})

	t.Run("valid meta profile id string", func(t *testing.T) {
		result, err := ParseMetaProfileID("12345")
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, int64(12345), *result)
	})

	t.Run("valid meta profile id with large number", func(t *testing.T) {
		result, err := ParseMetaProfileID("9223372036854775807") // max int64
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, int64(9223372036854775807), *result)
	})

	t.Run("valid negative meta profile id", func(t *testing.T) {
		result, err := ParseMetaProfileID("-123")
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, int64(-123), *result)
	})

	t.Run("invalid meta profile id returns error", func(t *testing.T) {
		result, err := ParseMetaProfileID("invalid")
		require.Error(t, err)
		assert.Nil(t, result)
		assert.Contains(t, err.Error(), "parse meta profile id")
	})

	t.Run("floating point number returns error", func(t *testing.T) {
		result, err := ParseMetaProfileID("123.456")
		require.Error(t, err)
		assert.Nil(t, result)
	})

	t.Run("zero is valid", func(t *testing.T) {
		result, err := ParseMetaProfileID("0")
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, int64(0), *result)
	})

	t.Run("overflow int64 returns error", func(t *testing.T) {
		result, err := ParseMetaProfileID("9223372036854775808") // max int64 + 1
		require.Error(t, err)
		assert.Nil(t, result)
	})

	t.Run("whitespace only string returns error", func(t *testing.T) {
		result, err := ParseMetaProfileID("   ")
		require.Error(t, err)
		assert.Nil(t, result)
	})

	t.Run("string with leading zeros", func(t *testing.T) {
		result, err := ParseMetaProfileID("00123")
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, int64(123), *result)
	})
}

func TestGetTiDBGroupConfigFileName(t *testing.T) {
	t.Run("normal tidb group id", func(t *testing.T) {
		result := GetTiDBGroupConfigFileName("group-123")
		assert.Equal(t, "tidb-group-123", result)
	})

	t.Run("empty tidb group id", func(t *testing.T) {
		result := GetTiDBGroupConfigFileName("")
		assert.Equal(t, "tidb-", result)
	})

	t.Run("uuid style tidb group id", func(t *testing.T) {
		result := GetTiDBGroupConfigFileName("550e8400-e29b-41d4-a716-446655440000")
		assert.Equal(t, "tidb-550e8400-e29b-41d4-a716-446655440000", result)
	})

	t.Run("special characters in tidb group id", func(t *testing.T) {
		result := GetTiDBGroupConfigFileName("test_group-1")
		assert.Equal(t, "tidb-test_group-1", result)
	})
}

func TestGetNoExistBRComponents(t *testing.T) {
	t.Run("empty components list", func(t *testing.T) {
		result := getNoExistBRComponents([]*configcenter.ClusterComponent{})
		expected := []string{
			strings.ToLower(apiv1.ComponentType_TIBR_AUTOBACKUP.String()),
			strings.ToLower(apiv1.ComponentType_TIBR_RESTORE.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T2_DFSGC.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()),
			strings.ToLower(apiv1.ComponentType_CLASSIC_RESTORE.String()),
		}
		assert.ElementsMatch(t, expected, result)
	})

	t.Run("all BR components exist", func(t *testing.T) {
		components := []*configcenter.ClusterComponent{
			{ComponentName: apiv1.ComponentType_TIBR_AUTOBACKUP.String()},
			{ComponentName: apiv1.ComponentType_TIBR_RESTORE.String()},
			{ComponentName: apiv1.ComponentType_TIBRGC_T2_DFSGC.String()},
			{ComponentName: apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()},
			{ComponentName: apiv1.ComponentType_CLASSIC_RESTORE.String()},
		}
		result := getNoExistBRComponents(components)
		assert.Empty(t, result)
	})

	t.Run("some BR components exist", func(t *testing.T) {
		components := []*configcenter.ClusterComponent{
			{ComponentName: apiv1.ComponentType_TIBR_AUTOBACKUP.String()},
			{ComponentName: apiv1.ComponentType_TIBRGC_T2_DFSGC.String()},
			{ComponentName: "some-other-component"},
			{ComponentName: "another-component"},
		}
		result := getNoExistBRComponents(components)
		expected := []string{
			strings.ToLower(apiv1.ComponentType_TIBR_RESTORE.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()),
			strings.ToLower(apiv1.ComponentType_CLASSIC_RESTORE.String()),
		}
		assert.ElementsMatch(t, expected, result)
	})

	t.Run("mixed case component names", func(t *testing.T) {
		components := []*configcenter.ClusterComponent{
			{ComponentName: strings.ToUpper(apiv1.ComponentType_TIBR_AUTOBACKUP.String())},
			{ComponentName: strings.ToLower(apiv1.ComponentType_TIBR_RESTORE.String())},
			{ComponentName: "TiBrGc_T2_DfSgC"}, // mixed case
		}
		result := getNoExistBRComponents(components)
		expected := []string{
			strings.ToLower(apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()),
			strings.ToLower(apiv1.ComponentType_CLASSIC_RESTORE.String()),
		}
		assert.ElementsMatch(t, expected, result)
	})

	t.Run("duplicate components in input", func(t *testing.T) {
		components := []*configcenter.ClusterComponent{
			{ComponentName: apiv1.ComponentType_TIBR_AUTOBACKUP.String()},
			{ComponentName: apiv1.ComponentType_TIBR_AUTOBACKUP.String()}, // duplicate
			{ComponentName: apiv1.ComponentType_TIBR_RESTORE.String()},
		}
		result := getNoExistBRComponents(components)
		expected := []string{
			strings.ToLower(apiv1.ComponentType_TIBRGC_T2_DFSGC.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()),
			strings.ToLower(apiv1.ComponentType_CLASSIC_RESTORE.String()),
		}
		assert.ElementsMatch(t, expected, result)
	})

	t.Run("non-BR components only", func(t *testing.T) {
		components := []*configcenter.ClusterComponent{
			{ComponentName: "tikv"},
			{ComponentName: "tidb"},
			{ComponentName: "pd"},
			{ComponentName: "tiflash"},
		}
		result := getNoExistBRComponents(components)
		expected := []string{
			strings.ToLower(apiv1.ComponentType_TIBR_AUTOBACKUP.String()),
			strings.ToLower(apiv1.ComponentType_TIBR_RESTORE.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T2_DFSGC.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()),
			strings.ToLower(apiv1.ComponentType_CLASSIC_RESTORE.String()),
		}
		assert.ElementsMatch(t, expected, result)
	})

	t.Run("nil components slice", func(t *testing.T) {
		result := getNoExistBRComponents(nil)
		expected := []string{
			strings.ToLower(apiv1.ComponentType_TIBR_AUTOBACKUP.String()),
			strings.ToLower(apiv1.ComponentType_TIBR_RESTORE.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T2_DFSGC.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()),
			strings.ToLower(apiv1.ComponentType_CLASSIC_RESTORE.String()),
		}
		assert.ElementsMatch(t, expected, result)
	})
}

func TestGetNoExistExportComponents(t *testing.T) {
	t.Run("empty components list", func(t *testing.T) {
		result := getNoExistExportComponents([]*configcenter.ClusterComponent{})
		expected := []string{
			strings.ToLower(apiv1.ComponentType_DUMPLING.String()),
		}
		assert.ElementsMatch(t, expected, result)
	})

	t.Run("all export components exist", func(t *testing.T) {
		components := []*configcenter.ClusterComponent{
			{ComponentName: apiv1.ComponentType_DUMPLING.String()},
		}
		result := getNoExistExportComponents(components)
		assert.Empty(t, result)
	})

	t.Run("mixed case component names", func(t *testing.T) {
		components := []*configcenter.ClusterComponent{
			{ComponentName: "DuMpLiNg"},
		}
		result := getNoExistExportComponents(components)
		assert.Empty(t, result)
	})

	t.Run("non-export components only", func(t *testing.T) {
		components := []*configcenter.ClusterComponent{
			{ComponentName: "tikv"},
			{ComponentName: "tidb"},
			{ComponentName: "pd"},
		}
		result := getNoExistExportComponents(components)
		expected := []string{
			strings.ToLower(apiv1.ComponentType_DUMPLING.String()),
		}
		assert.ElementsMatch(t, expected, result)
	})
}

func TestConstants(t *testing.T) {
	t.Run("InitSql constant value", func(t *testing.T) {
		assert.Equal(t, "INIT_SQL", InitSql)
	})

	t.Run("TiDBSemConfig constant value", func(t *testing.T) {
		assert.Equal(t, "tidb_sem", TiDBSemConfig)
	})
}

func TestAllBRComponents(t *testing.T) {
	t.Run("should contain all expected BR components", func(t *testing.T) {
		expectedComponents := []string{
			strings.ToLower(apiv1.ComponentType_TIBR_AUTOBACKUP.String()),
			strings.ToLower(apiv1.ComponentType_TIBR_RESTORE.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T2_DFSGC.String()),
			strings.ToLower(apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()),
			strings.ToLower(apiv1.ComponentType_CLASSIC_RESTORE.String()),
		}

		assert.Len(t, AllBRComponents, len(expectedComponents))
		for _, comp := range expectedComponents {
			assert.True(t, AllBRComponents[comp], "AllBRComponents should contain %s", comp)
		}
	})

	t.Run("should have exactly 5 BR components", func(t *testing.T) {
		assert.Len(t, AllBRComponents, 5)
	})

	t.Run("should not contain non-BR components", func(t *testing.T) {
		nonBRComponents := []string{
			"tidb",
			"tikv",
			"pd",
			"tiflash",
			"tiproxy",
		}

		for _, comp := range nonBRComponents {
			assert.False(t, AllBRComponents[comp], "AllBRComponents should not contain %s", comp)
		}
	})

	t.Run("component names should be lowercase", func(t *testing.T) {
		for compName := range AllBRComponents {
			assert.Equal(t, strings.ToLower(compName), compName, "Component name %s should be lowercase", compName)
		}
	})
}

func TestAllExportComponents(t *testing.T) {
	t.Run("should contain dumpling", func(t *testing.T) {
		expectedComponents := []string{
			strings.ToLower(apiv1.ComponentType_DUMPLING.String()),
		}

		assert.Len(t, AllExportComponents, len(expectedComponents))
		for _, comp := range expectedComponents {
			assert.True(t, AllExportComponents[comp], "AllExportComponents should contain %s", comp)
		}
	})

	t.Run("should not contain non-export components", func(t *testing.T) {
		nonExportComponents := []string{
			"tidb",
			"tikv",
			"pd",
			"tibr_restore",
		}

		for _, comp := range nonExportComponents {
			assert.False(t, AllExportComponents[comp], "AllExportComponents should not contain %s", comp)
		}
	})
}

func TestGetTiDBGroupConfigFileName_Format(t *testing.T) {
	t.Run("should have tidb- prefix", func(t *testing.T) {
		testIds := []string{"123", "abc", "test-group", "uuid-1234-5678"}
		for _, id := range testIds {
			result := GetTiDBGroupConfigFileName(id)
			assert.True(t, strings.HasPrefix(result, "tidb-"), "Config filename should have 'tidb-' prefix")
		}
	})

	t.Run("should contain the original id", func(t *testing.T) {
		testIds := []string{"123", "abc", "test-group", "uuid-1234-5678"}
		for _, id := range testIds {
			result := GetTiDBGroupConfigFileName(id)
			assert.True(t, strings.HasSuffix(result, id), "Config filename should end with the original id")
		}
	})
}

func TestParseTenantId_EdgeCases(t *testing.T) {
	t.Run("whitespace only string returns error", func(t *testing.T) {
		result, err := ParseTenantId("   ", true)
		require.Error(t, err)
		assert.Nil(t, result)
	})

	t.Run("string with leading zeros", func(t *testing.T) {
		result, err := ParseTenantId("00123", true)
		require.NoError(t, err)
		assert.NotNil(t, result)
		assert.Equal(t, uint64(123), *result)
	})

	t.Run("number with spaces returns error", func(t *testing.T) {
		result, err := ParseTenantId("123 456", true)
		require.Error(t, err)
		assert.Nil(t, result)
	})

	t.Run("hex format returns error", func(t *testing.T) {
		result, err := ParseTenantId("0x123", true)
		require.Error(t, err)
		assert.Nil(t, result)
	})

	t.Run("scientific notation returns error", func(t *testing.T) {
		result, err := ParseTenantId("1e5", true)
		require.Error(t, err)
		assert.Nil(t, result)
	})

	t.Run("overflow uint64 returns error", func(t *testing.T) {
		result, err := ParseTenantId("18446744073709551616", true) // max uint64 + 1
		require.Error(t, err)
		assert.Nil(t, result)
	})
}

func TestBuildSystemSource(t *testing.T) {
	t.Run("with nil tenantId", func(t *testing.T) {
		result := buildSystemSource("v7.1.0", nil)
		require.NotNil(t, result)
		require.NotNil(t, result.FromSku)
		assert.Equal(t, "v7.1.0", result.FromSku.Version)
		assert.Equal(t, uint64(0), result.FromSku.TenantId)
	})

	t.Run("with valid tenantId", func(t *testing.T) {
		tenantId := uint64(12345)
		result := buildSystemSource("v7.1.0", &tenantId)
		require.NotNil(t, result)
		require.NotNil(t, result.FromSku)
		assert.Equal(t, "v7.1.0", result.FromSku.Version)
		assert.Equal(t, uint64(12345), result.FromSku.TenantId)
	})

	t.Run("with zero tenantId", func(t *testing.T) {
		tenantId := uint64(0)
		result := buildSystemSource("v7.1.0", &tenantId)
		require.NotNil(t, result)
		require.NotNil(t, result.FromSku)
		assert.Equal(t, "v7.1.0", result.FromSku.Version)
		assert.Equal(t, uint64(0), result.FromSku.TenantId)
	})

	t.Run("with empty version", func(t *testing.T) {
		tenantId := uint64(12345)
		result := buildSystemSource("", &tenantId)
		require.NotNil(t, result)
		require.NotNil(t, result.FromSku)
		assert.Empty(t, result.FromSku.Version)
		assert.Equal(t, uint64(12345), result.FromSku.TenantId)
	})

	t.Run("with large tenantId", func(t *testing.T) {
		tenantId := uint64(18446744073709551615) // max uint64
		result := buildSystemSource("v7.1.0", &tenantId)
		require.NotNil(t, result)
		require.NotNil(t, result.FromSku)
		assert.Equal(t, "v7.1.0", result.FromSku.Version)
		assert.Equal(t, uint64(18446744073709551615), result.FromSku.TenantId)
	})
}

func TestBuildCustomSourceFromTenant(t *testing.T) {
	t.Run("with nil tenantId returns nil", func(t *testing.T) {
		result := BuildCustomSourceFromTenant(nil)
		assert.Nil(t, result)
	})

	t.Run("with valid tenantId", func(t *testing.T) {
		tenantId := uint64(12345)
		result := BuildCustomSourceFromTenant(&tenantId)
		require.NotNil(t, result)
		require.NotNil(t, result.FromTenant)
		assert.Equal(t, uint64(12345), result.FromTenant.TenantId)
	})

	t.Run("with zero tenantId", func(t *testing.T) {
		tenantId := uint64(0)
		result := BuildCustomSourceFromTenant(&tenantId)
		require.NotNil(t, result)
		require.NotNil(t, result.FromTenant)
		assert.Equal(t, uint64(0), result.FromTenant.TenantId)
	})

	t.Run("with large tenantId", func(t *testing.T) {
		tenantId := uint64(18446744073709551615) // max uint64
		result := BuildCustomSourceFromTenant(&tenantId)
		require.NotNil(t, result)
		require.NotNil(t, result.FromTenant)
		assert.Equal(t, uint64(18446744073709551615), result.FromTenant.TenantId)
	})
}

// Tests for configClientImpl methods using mockery-generated mocks

func TestGetClient(t *testing.T) {
	mockClient := configcentermocks.NewConfigCenterServiceClient(t)
	client := &configClientImpl{configCenterCli: mockClient}

	result := client.GetClient()
	assert.Equal(t, mockClient, result)
}

func TestGetDefaultVersion(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetDefaultVersion(mock.Anything, mock.MatchedBy(func(req *configcenter.GetDefaultVersionRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL
			})).
			Return(&configcenter.DefaultVersion{Version: "v7.1.0"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		version, err := client.GetDefaultVersion(context.Background(), "12345")
		require.NoError(t, err)
		assert.Equal(t, "v7.1.0", version)
	})

	t.Run("empty tenant id", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetDefaultVersion(mock.Anything, mock.MatchedBy(func(req *configcenter.GetDefaultVersionRequest) bool {
				return req.TenantId == nil
			})).
			Return(&configcenter.DefaultVersion{Version: "v7.1.0"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		version, err := client.GetDefaultVersion(context.Background(), "")
		require.NoError(t, err)
		assert.Equal(t, "v7.1.0", version)
	})

	t.Run("invalid tenant id", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		client := &configClientImpl{configCenterCli: mockClient}

		_, err := client.GetDefaultVersion(context.Background(), "invalid")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "parse tenantId")
	})

	t.Run("empty version returned", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetDefaultVersion(mock.Anything, mock.Anything).
			Return(&configcenter.DefaultVersion{Version: ""}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		_, err := client.GetDefaultVersion(context.Background(), "12345")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "cluster default version is empty")
	})

	t.Run("grpc error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetDefaultVersion(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}

		_, err := client.GetDefaultVersion(context.Background(), "12345")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "get cluster default version")
	})
}

func TestGetEffectiveComponentImageTag(t *testing.T) {
	t.Run("global image tag", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetComponentImage(mock.Anything, mock.MatchedBy(func(req *configcenter.GetComponentImageRequest) bool {
				return req.GetClusterType() == configcenter.ClusterType_SHARED_POOL &&
					req.GetComponentName() == "tikv" &&
					req.GetComponentVersion() == "v26.3.99" &&
					req.GetTenantId() == 12345
			})).
			Return(&configcenter.ComponentImage{Tag: "v26.3.6-nextgen"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("v26.3.99")
		sharedPool.TenantId = "12345"

		tag, err := client.GetEffectiveComponentImageTag(context.Background(), sharedPool, apiv1.ComponentType_TIKV)
		require.NoError(t, err)
		assert.Equal(t, "v26.3.6-nextgen", tag)
	})

	t.Run("meta profile overrides component version and image tag", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetMetaProfile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetMetaProfileRequest) bool {
				return req.GetClusterType() == configcenter.ClusterType_SHARED_POOL && req.GetProfileId() == 1001
			})).
			Return(&configcenter.MetaProfile{
				ComponentVersions: map[string]string{
					"tikv": "v26.4.99",
				},
				ComponentImageTags: map[string]string{
					"tikv": "v26.4.7-nextgen",
				},
			}, nil)
		mockClient.EXPECT().
			GetComponentImage(mock.Anything, mock.MatchedBy(func(req *configcenter.GetComponentImageRequest) bool {
				return req.GetComponentName() == "tikv" && req.GetComponentVersion() == "v26.4.99"
			})).
			Return(&configcenter.ComponentImage{Tag: "v26.4.1-nextgen"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("v26.3.99")
		sharedPool.TenantId = "12345"
		sharedPool.ConfigCenterMetaProfileId = "1001"

		tag, err := client.GetEffectiveComponentImageTag(context.Background(), sharedPool, apiv1.ComponentType_TIKV)
		require.NoError(t, err)
		assert.Equal(t, "v26.4.7-nextgen", tag)
	})

	t.Run("component image lookup failure", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetComponentImage(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "image not found"))

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("v26.3.99")

		_, err := client.GetEffectiveComponentImageTag(context.Background(), sharedPool, apiv1.ComponentType_TIKV)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "get effective image")
	})

	t.Run("empty effective image tag", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetComponentImage(mock.Anything, mock.Anything).
			Return(&configcenter.ComponentImage{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("v26.3.99")

		_, err := client.GetEffectiveComponentImageTag(context.Background(), sharedPool, apiv1.ComponentType_TIKV)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "effective image tag is empty")
	})
}

func TestCreateTiDBClusterMeta(t *testing.T) {
	t.Run("meta already exists", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterMetaRequest) bool {
				return req.ClusterType == configcenter.ClusterType_LOGICAL_CLUSTER && req.ClusterId == "test-cluster-id"
			})).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{
			Id:           "test-cluster-id",
			TenantId:     "12345",
			Version:      "v7.1.0",
			SharedPoolId: "test-pool-id",
		}

		err := client.CreateTiDBClusterMeta(context.Background(), newTestSharedPool(tidbCluster.GetVersion()), tidbCluster)
		require.NoError(t, err)
	})

	t.Run("tidbCluster version empty", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{
			Id:           "test-cluster-id",
			TenantId:     "12345",
			Version:      "",
			SharedPoolId: "test-pool-id",
		}

		err := client.CreateTiDBClusterMeta(context.Background(), newTestSharedPool(tidbCluster.GetVersion()), tidbCluster)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "tidbCluster version is empty")
	})

	t.Run("invalid tenant id", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()

		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{
			Id:           "test-cluster-id",
			TenantId:     "invalid",
			Version:      "v7.1.0",
			SharedPoolId: "test-pool-id",
		}

		err := client.CreateTiDBClusterMeta(context.Background(), newTestSharedPool(tidbCluster.GetVersion()), tidbCluster)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "parse tenantId")
	})

	t.Run("get tidb template config error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ConfigurationFilename == strings.ToLower(apiv1.ComponentType_TIDB.String())
			})).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{
			Id:           "test-cluster-id",
			TenantId:     "12345",
			Version:      "v7.1.0",
			SharedPoolId: "test-pool-id",
		}

		err := client.CreateTiDBClusterMeta(context.Background(), newTestSharedPool(tidbCluster.GetVersion()), tidbCluster)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "get tidb template config from sharedpool")
	})

	t.Run("init cluster meta error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))
		// tidb config
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == strings.ToLower(apiv1.ComponentType_TIDB.String())
			})).
			Return(&configcenter.ConfigurationFile{TemplateId: 1}, nil)
		// tiproxy config
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == strings.ToLower(apiv1.ComponentType_TIPROXY.String())
			})).
			Return(&configcenter.ConfigurationFile{TemplateId: 2}, nil)
		// tidb component from sharedpool (for OverwriteImageTag)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterComponentRequest) bool {
				return req.ComponentName == strings.ToLower(apiv1.ComponentType_TIDB.String())
			})).
			Return(&configcenter.ClusterComponent{Version: "v7.1.0", OverwriteImageTag: "v7.1.0-20240101"}, nil)
		// tiproxy component from sharedpool (for version and OverwriteImageTag)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterComponentRequest) bool {
				return req.ComponentName == strings.ToLower(apiv1.ComponentType_TIPROXY.String())
			})).
			Return(&configcenter.ClusterComponent{Version: "v1.0.0", OverwriteImageTag: "v1.0.0-20240101"}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{
			Id:           "test-cluster-id",
			TenantId:     "12345",
			Version:      "v7.1.0",
			SharedPoolId: "test-pool-id",
		}

		err := client.CreateTiDBClusterMeta(context.Background(), newTestSharedPool(tidbCluster.GetVersion()), tidbCluster)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "init tidb cluster meta")
	})

	t.Run("success with tidb-default and tiproxy configs", func(t *testing.T) {
		tidbVersion := "v7.1.0"
		tiproxyVersion := "v1.2.0"
		tidbTemplateId := int64(100)
		tiproxyTemplateId := int64(200)

		var capturedInitReq *configcenter.InitClusterMetaRequest

		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))
		// tidb config template from sharedpool
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ConfigurationFilename == strings.ToLower(apiv1.ComponentType_TIDB.String())
			})).
			Return(&configcenter.ConfigurationFile{TemplateId: tidbTemplateId}, nil)
		// tiproxy config template from sharedpool
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ConfigurationFilename == strings.ToLower(apiv1.ComponentType_TIPROXY.String())
			})).
			Return(&configcenter.ConfigurationFile{TemplateId: tiproxyTemplateId}, nil)
		// tidb component from sharedpool (for EffectiveImageTag)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterComponentRequest) bool {
				return req.ComponentName == strings.ToLower(apiv1.ComponentType_TIDB.String())
			})).
			Return(&configcenter.ClusterComponent{Version: tidbVersion, EffectiveImageTag: "v7.1.0-20240101"}, nil)
		// tiproxy component from sharedpool (for version and EffectiveImageTag)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterComponentRequest) bool {
				return req.ComponentName == strings.ToLower(apiv1.ComponentType_TIPROXY.String())
			})).
			Return(&configcenter.ClusterComponent{Version: tiproxyVersion, EffectiveImageTag: "v1.2.0-20240101"}, nil)
		// InitClusterMeta - now includes both components and config files
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedInitReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{
			Id:           "test-cluster-id",
			TenantId:     "12345",
			Version:      tidbVersion,
			SharedPoolId: "test-pool-id",
		}

		err := client.CreateTiDBClusterMeta(context.Background(), newTestSharedPool(tidbCluster.GetVersion()), tidbCluster)
		require.NoError(t, err)

		// Verify InitClusterMeta request
		require.NotNil(t, capturedInitReq)
		assert.Len(t, capturedInitReq.Components, 2)
		assert.Len(t, capturedInitReq.ConfigurationFiles, 2)

		// Verify tidb component - version comes from tidbCluster
		var tidbComponent *configcenter.CreateClusterComponentRequest
		for _, comp := range capturedInitReq.Components {
			if comp.ComponentName == strings.ToLower(apiv1.ComponentType_TIDB.String()) {
				tidbComponent = comp
				break
			}
		}
		require.NotNil(t, tidbComponent)
		assert.Equal(t, tidbVersion, tidbComponent.Version)
		require.NotNil(t, tidbComponent.OverwriteImageTag)
		assert.Equal(t, "v7.1.0-20240101", *tidbComponent.OverwriteImageTag) // from sharedpool tidb component

		// Verify tiproxy component
		var tiproxyComponent *configcenter.CreateClusterComponentRequest
		for _, comp := range capturedInitReq.Components {
			if comp.ComponentName == strings.ToLower(apiv1.ComponentType_TIPROXY.String()) {
				tiproxyComponent = comp
				break
			}
		}
		require.NotNil(t, tiproxyComponent)
		assert.Equal(t, tiproxyVersion, tiproxyComponent.Version) // tiproxy version comes from sharedpool component
		require.NotNil(t, tiproxyComponent.OverwriteImageTag)
		assert.Equal(t, "v1.2.0-20240101", *tiproxyComponent.OverwriteImageTag) // from sharedpool tiproxy component

		// Verify tiproxy config file uses sharedpool template
		var tiproxyConfigFile *configcenter.CreateClusterConfigFileRequest
		for _, cf := range capturedInitReq.ConfigurationFiles {
			if cf.ConfigurationFilename == strings.ToLower(apiv1.ComponentType_TIPROXY.String()) {
				tiproxyConfigFile = cf
				break
			}
		}
		require.NotNil(t, tiproxyConfigFile)
		assert.Equal(t, tiproxyTemplateId, tiproxyConfigFile.GetFromTemplate().GetTemplateId())

		// Verify tidb-default config file uses sharedpool template
		var tidbDefaultConfigFile *configcenter.CreateClusterConfigFileRequest
		for _, cf := range capturedInitReq.ConfigurationFiles {
			if cf.ConfigurationFilename == TiDBDefaultConfigFileName {
				tidbDefaultConfigFile = cf
				break
			}
		}
		require.NotNil(t, tidbDefaultConfigFile)
		assert.Equal(t, tidbTemplateId, tidbDefaultConfigFile.GetFromTemplate().GetTemplateId())
	})
}

func TestCreateTiDBGroupConfig(t *testing.T) {
	t.Run("config already exists", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(&configcenter.ConfigurationFile{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbGroup := newTestTiDBGroup("12345", "v7.1.0")

		err := client.CreateTiDBGroupConfig(context.Background(), tidbGroup)
		require.NoError(t, err)
	})

	t.Run("success from tidb-default", func(t *testing.T) {
		tidbDefaultTemplateId := int64(123)
		var capturedReq *configcenter.CreateClusterConfigurationFileRequest

		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		// First call: check if tidb group config exists
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == "tidb-test-group-id"
			})).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()
		// Second call: get tidb-default config
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == TiDBDefaultConfigFileName
			})).
			Return(&configcenter.ConfigurationFile{
				TemplateId:   tidbDefaultTemplateId,
				CustomParams: map[string]*configcenter.ParamItem{},
			}, nil)
		// Create tidb group config from tidb-default
		mockClient.EXPECT().
			CreateClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.CreateClusterConfigurationFileRequest) bool {
				return req.ConfigurationFile.ConfigurationFilename == "tidb-test-group-id"
			})).
			Run(func(ctx context.Context, req *configcenter.CreateClusterConfigurationFileRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ConfigurationFile{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbGroup := newTestTiDBGroup("12345", "v7.1.0")

		err := client.CreateTiDBGroupConfig(context.Background(), tidbGroup)
		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		assert.Equal(t, "tidb-test-group-id", capturedReq.ConfigurationFile.ConfigurationFilename)
		assert.Equal(t, tidbDefaultTemplateId, capturedReq.ConfigurationFile.TemplateId)
	})

	t.Run("error when tidb-default not found", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		// First call: check if tidb group config exists
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == "tidb-test-group-id"
			})).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()
		// Second call: tidb-default not found - should return error
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == TiDBDefaultConfigFileName
			})).
			Return(nil, status.Error(codes.NotFound, "not found"))

		client := &configClientImpl{configCenterCli: mockClient}

		tidbGroup := newTestTiDBGroup("12345", "v7.1.0")

		err := client.CreateTiDBGroupConfig(context.Background(), tidbGroup)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "get tidb-default config")
	})

	t.Run("invalid tenant id", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))

		client := &configClientImpl{configCenterCli: mockClient}

		tidbGroup := newTestTiDBGroup("invalid", "v7.1.0")

		err := client.CreateTiDBGroupConfig(context.Background(), tidbGroup)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "parse tenantId")
	})
}

func TestDeleteTiDBGroupConfig(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.DeleteClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_LOGICAL_CLUSTER &&
					req.ClusterId == "test-cluster-id" &&
					req.ConfigurationFilename == "tidb-test-group-id"
			})).
			Return(nil, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteTiDBGroupConfig(context.Background(), "test-cluster-id", "test-group-id")
		require.NoError(t, err)
	})

	t.Run("not found is idempotent", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteTiDBGroupConfig(context.Background(), "test-cluster-id", "test-group-id")
		require.NoError(t, err)
	})

	t.Run("grpc error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteTiDBGroupConfig(context.Background(), "test-cluster-id", "test-group-id")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "delete tidb group config")
	})
}

func TestDeleteTiKVComponentConfig(t *testing.T) {
	component := &entity.Component{
		Id:            "test-id",
		ComponentType: apiv1.ComponentType_TIKV,
	}

	t.Run("success", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.DeleteClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-pool-id" &&
					req.ConfigurationFilename == "tikv-test-id"
			})).
			Return(nil, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteTiKVComponentConfig(context.Background(), newTestSharedPool(""), component)
		require.NoError(t, err)
	})

	t.Run("not found is idempotent", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteTiKVComponentConfig(context.Background(), newTestSharedPool(""), component)
		require.NoError(t, err)
	})

	t.Run("grpc status is preserved", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteTiKVComponentConfig(context.Background(), newTestSharedPool(""), component)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "delete tikv group config")
		assert.Equal(t, codes.Internal, status.Code(err))
	})
}

func TestDeleteClusterMeta(t *testing.T) {
	t.Run("delete tidb cluster meta", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterMeta(mock.Anything, mock.MatchedBy(func(req *configcenter.DeleteClusterMetaRequest) bool {
				return req.ClusterType == configcenter.ClusterType_LOGICAL_CLUSTER &&
					req.ClusterId == "test-cluster-id"
			})).
			Return(nil, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteTiDBClusterMeta(context.Background(), "test-cluster-id")
		require.NoError(t, err)
	})

	t.Run("delete shared pool meta", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterMeta(mock.Anything, mock.MatchedBy(func(req *configcenter.DeleteClusterMetaRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-shared-pool-id"
			})).
			Return(nil, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteClusterMeta(context.Background(), "test-shared-pool-id")
		require.NoError(t, err)
	})

	t.Run("not found is idempotent", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteTiDBClusterMeta(context.Background(), "test-cluster-id")
		require.NoError(t, err)
	})

	t.Run("grpc error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			DeleteClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}
		err := client.DeleteTiDBClusterMeta(context.Background(), "test-cluster-id")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "delete cluster meta")
	})
}

func TestGetClusterInitSqlStmts(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == InitSql
			})).
			Return(&configcenter.ConfigurationFile{Text: "CREATE DATABASE test;"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		stmts, err := client.GetClusterInitSqlStmts(context.Background(), "test-pool-id")
		require.NoError(t, err)
		assert.Equal(t, "CREATE DATABASE test;", stmts)
	})

	t.Run("empty text", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(&configcenter.ConfigurationFile{Text: ""}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		_, err := client.GetClusterInitSqlStmts(context.Background(), "test-pool-id")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "init sql stmts not found")
	})

	t.Run("grpc error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))

		client := &configClientImpl{configCenterCli: mockClient}

		_, err := client.GetClusterInitSqlStmts(context.Background(), "test-pool-id")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "get cluster init sql stmts")
	})
}

func TestGetClusterTiDBSemConfig(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == TiDBSemConfig
			})).
			Return(&configcenter.ConfigurationFile{Text: `{"enabled": true}`}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		config, err := client.GetClusterTiDBSemConfig(context.Background(), "test-pool-id")
		require.NoError(t, err)
		assert.Equal(t, `{"enabled": true}`, config)
	})

	t.Run("empty config", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(&configcenter.ConfigurationFile{Text: ""}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		_, err := client.GetClusterTiDBSemConfig(context.Background(), "test-pool-id")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "tidb sem config is empty")
	})
}

func TestGetTiDBClusterVersion(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterMetaRequest) bool {
				return req.ClusterType == configcenter.ClusterType_LOGICAL_CLUSTER && req.ClusterId == "test-cluster-id"
			})).
			Return(&configcenter.ClusterMeta{Version: "v7.1.0"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{Id: "test-cluster-id"}
		version, err := client.GetTiDBClusterVersion(context.Background(), tidbCluster)
		require.NoError(t, err)
		assert.Equal(t, "v7.1.0", version)
	})

	t.Run("empty cluster id", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{Id: ""}
		_, err := client.GetTiDBClusterVersion(context.Background(), tidbCluster)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "tidb cluster id is empty")
	})

	t.Run("empty version returned", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{Version: ""}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{Id: "test-cluster-id"}
		_, err := client.GetTiDBClusterVersion(context.Background(), tidbCluster)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "tidb cluster version not found")
	})

	t.Run("grpc error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))

		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := &entity.TiDBCluster{Id: "test-cluster-id"}
		_, err := client.GetTiDBClusterVersion(context.Background(), tidbCluster)
		require.Error(t, err)
	})
}

func TestGetComponentVersionAndConfig(t *testing.T) {
	t.Run("not support tidb", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("")
		component := &entity.Component{
			Id:            "test-id",
			ComponentType: apiv1.ComponentType_TIDB,
		}
		_, _, err := client.GetComponentVersionAndConfig(context.Background(), sharedPool, component)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "not support")
	})

	t.Run("success for tiproxy", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "v7.1.0",
				BaseImage:         "pingcap/tiproxy",
				EffectiveImageTag: "v7.1.0",
			}, nil)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(&configcenter.ConfigurationFile{Text: "tiproxy-config"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("")
		component := &entity.Component{
			Id:            "test-id",
			ComponentType: apiv1.ComponentType_TIPROXY,
		}
		version, config, err := client.GetComponentVersionAndConfig(context.Background(), sharedPool, component)
		require.NoError(t, err)
		assert.Equal(t, "v7.1.0", version)
		assert.Equal(t, "tiproxy-config", config.ConfigurationFileContent)
		assert.Equal(t, "pingcap/tiproxy", config.ImageRepo)
		assert.Equal(t, "v7.1.0", config.ImageTag)
	})

	t.Run("success for tikv", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "v7.1.0",
				BaseImage:         "pingcap/tikv",
				EffectiveImageTag: "v7.1.0",
			}, nil)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(&configcenter.ConfigurationFile{Text: "tikv-config"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("")
		component := &entity.Component{
			Id:            "test-id",
			ComponentType: apiv1.ComponentType_TIKV,
		}
		version, config, err := client.GetComponentVersionAndConfig(context.Background(), sharedPool, component)
		require.NoError(t, err)
		assert.Equal(t, "v7.1.0", version)
		assert.Equal(t, "tikv-config", config.ConfigurationFileContent)
		assert.Equal(t, "pingcap/tikv", config.ImageRepo)
		assert.Equal(t, "v7.1.0", config.ImageTag)
	})

	t.Run("schema manager uses its own config-center component and config", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		// config-center exposes a dedicated schema_manager component, but since
		// schema-manager is a tikv-worker binary variant its registered image
		// still points at the tikv-worker image. We only assert that csng asks
		// config-center for "schema_manager" and faithfully returns whatever
		// image/config it gets back.
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterComponentRequest) bool {
				return req.ComponentName == strings.ToLower(apiv1.ComponentType_SCHEMA_MANAGER.String())
			})).
			Return(&configcenter.ClusterComponent{
				Version:           "v7.1.0",
				BaseImage:         "pingcap/tikv-worker",
				EffectiveImageTag: "v7.1.0",
			}, nil)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == strings.ToLower(apiv1.ComponentType_SCHEMA_MANAGER.String())
			})).
			Return(&configcenter.ConfigurationFile{Text: "schema-manager-config"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("")
		component := &entity.Component{
			Id:            "test-id",
			ComponentType: apiv1.ComponentType_SCHEMA_MANAGER,
		}
		version, config, err := client.GetComponentVersionAndConfig(context.Background(), sharedPool, component)
		require.NoError(t, err)
		assert.Equal(t, "v7.1.0", version)
		assert.Equal(t, "schema-manager-config", config.ConfigurationFileContent)
		assert.Equal(t, "pingcap/tikv-worker", config.ImageRepo)
		assert.Equal(t, "v7.1.0", config.ImageTag)
	})

	t.Run("tiflash learner skips image check", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(&configcenter.ConfigurationFile{Text: "tiflash-learner-config"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("")
		component := &entity.Component{
			Id:            "test-id",
			ComponentType: apiv1.ComponentType_TIFLASH_LEARNER,
		}
		version, config, err := client.GetComponentVersionAndConfig(context.Background(), sharedPool, component)
		require.NoError(t, err)
		assert.Empty(t, version)
		assert.Equal(t, "tiflash-learner-config", config.ConfigurationFileContent)
	})

	t.Run("empty version error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "",
				BaseImage:         "pingcap/tikv",
				EffectiveImageTag: "v7.1.0",
			}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("")
		component := &entity.Component{
			Id:            "test-id",
			ComponentType: apiv1.ComponentType_TIKV,
		}
		_, _, err := client.GetComponentVersionAndConfig(context.Background(), sharedPool, component)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "component version not found")
	})

	t.Run("empty image error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "v7.1.0",
				BaseImage:         "",
				EffectiveImageTag: "",
			}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("")
		component := &entity.Component{
			Id:            "test-id",
			ComponentType: apiv1.ComponentType_TIKV,
		}
		_, _, err := client.GetComponentVersionAndConfig(context.Background(), sharedPool, component)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "component image not found")
	})
}

func TestGetDMComponentVersionAndConfig(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterComponentRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-pool-id" &&
					req.ComponentName == getDMComponentName()
			})).
			Return(&configcenter.ClusterComponent{
				Version:           "v8.5.0",
				BaseImage:         "docker.io/pingcap/data-migration",
				EffectiveImageTag: "dm-hotfix-001",
			}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		version, cfg, err := client.GetDMComponentVersionAndConfig(context.Background(), newTestSharedPool("v8.5.0"))
		require.NoError(t, err)
		assert.Equal(t, "v8.5.0", version)
		assert.Equal(t, "docker.io/pingcap/data-migration", cfg.ImageRepo)
		assert.Equal(t, "dm-hotfix-001", cfg.ImageTag)
	})

	t.Run("backfills missing component", func(t *testing.T) {
		sharedPool := newTestSharedPool("v8.5.0")
		sharedPool.TenantId = "12345"

		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()
		mockClient.EXPECT().
			GetDefaultVersion(mock.Anything, mock.MatchedBy(func(req *configcenter.GetDefaultVersionRequest) bool {
				return req.Component == getDMComponentName() && req.GetTenantId() == 12345
			})).
			Return(&configcenter.DefaultVersion{Version: "v8.5.1"}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.MatchedBy(func(req *configcenter.InitClusterMetaRequest) bool {
				if req.ClusterType != configcenter.ClusterType_SHARED_POOL || req.ClusterId != sharedPool.GetId() || len(req.Components) != 1 {
					return false
				}
				component := req.Components[0]
				return component.ComponentName == getDMComponentName() &&
					component.GetVersion() == "v8.5.1"
			})).
			Return(&configcenter.ClusterMeta{}, nil)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "v8.5.1",
				BaseImage:         "docker.io/pingcap/data-migration",
				EffectiveImageTag: "dm-hotfix-002",
			}, nil).
			Once()

		client := &configClientImpl{configCenterCli: mockClient}
		version, cfg, err := client.GetDMComponentVersionAndConfig(context.Background(), sharedPool)
		require.NoError(t, err)
		assert.Equal(t, "v8.5.1", version)
		assert.Equal(t, "dm-hotfix-002", cfg.ImageTag)
	})

	t.Run("empty image error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "v8.5.0",
				BaseImage:         "",
				EffectiveImageTag: "",
			}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		version, cfg, err := client.GetDMComponentVersionAndConfig(context.Background(), newTestSharedPool("v8.5.0"))
		require.Error(t, err)
		assert.Contains(t, err.Error(), "component image not found")
		assert.Equal(t, "v8.5.0", version)
		require.NotNil(t, cfg)
		assert.Empty(t, cfg.ImageRepo)
		assert.Empty(t, cfg.ImageTag)
	})
}

func TestGetTiDBClusterVersionAndConfig(t *testing.T) {
	t.Run("not support pd", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		client := &configClientImpl{configCenterCli: mockClient}

		tidbGroup := newTestTiDBGroup("", "")
		_, _, err := client.GetTiDBClusterVersionAndConfig(context.Background(), tidbGroup, apiv1.ComponentType_PD)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "not support")
	})

	t.Run("success for tidb", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "v7.1.0",
				BaseImage:         "pingcap/tidb",
				EffectiveImageTag: "v7.1.0",
			}, nil)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == "tidb-test-group-id"
			})).
			Return(&configcenter.ConfigurationFile{Text: "tidb-config"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbGroup := newTestTiDBGroup("", "")
		version, config, err := client.GetTiDBClusterVersionAndConfig(context.Background(), tidbGroup, apiv1.ComponentType_TIDB)
		require.NoError(t, err)
		assert.Equal(t, "v7.1.0", version)
		assert.Equal(t, "tidb-config", config.ConfigurationFileContent)
	})

	t.Run("success for tiproxy", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "v1.0.0",
				BaseImage:         "pingcap/tiproxy",
				EffectiveImageTag: "v1.0.0",
			}, nil)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == "TIPROXY"
			})).
			Return(&configcenter.ConfigurationFile{Text: "tiproxy-config"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbGroup := newTestTiDBGroup("", "")
		version, config, err := client.GetTiDBClusterVersionAndConfig(context.Background(), tidbGroup, apiv1.ComponentType_TIPROXY)
		require.NoError(t, err)
		assert.Equal(t, "v1.0.0", version)
		assert.Equal(t, "tiproxy-config", config.ConfigurationFileContent)
	})

	t.Run("empty version error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{Version: ""}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbGroup := newTestTiDBGroup("", "")
		_, _, err := client.GetTiDBClusterVersionAndConfig(context.Background(), tidbGroup, apiv1.ComponentType_TIDB)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "component version not found")
	})

	t.Run("empty image error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "v7.1.0",
				BaseImage:         "",
				EffectiveImageTag: "",
			}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		tidbGroup := newTestTiDBGroup("", "")
		_, _, err := client.GetTiDBClusterVersionAndConfig(context.Background(), tidbGroup, apiv1.ComponentType_TIDB)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "component image not found")
	})
}

func TestCreateClusterMeta(t *testing.T) {
	t.Run("meta already exists", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterMetaRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL && req.ClusterId == "test-pool-id"
			})).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		err := client.CreateClusterMeta(context.Background(), sharedPool, "12345", "")
		require.NoError(t, err)
	})

	t.Run("invalid tenant id", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		err := client.CreateClusterMeta(context.Background(), sharedPool, "invalid", "")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "parse tenantId")
	})

	t.Run("invalid meta profile id", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		err := client.CreateClusterMeta(context.Background(), sharedPool, "12345", "invalid")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "parse meta profile id")
	})

	t.Run("init cluster meta error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))
		// Mock GetDefaultVersion for tiproxy
		mockClient.EXPECT().
			GetDefaultVersion(mock.Anything, mock.MatchedBy(func(req *configcenter.GetDefaultVersionRequest) bool {
				return req.Component == "tiproxy"
			})).
			Return(&configcenter.DefaultVersion{Version: "v1.0.0"}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		err := client.CreateClusterMeta(context.Background(), sharedPool, "12345", "")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "create cluster meta")
	})

	t.Run("success", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))
		// Mock GetDefaultVersion for tiproxy
		mockClient.EXPECT().
			GetDefaultVersion(mock.Anything, mock.MatchedBy(func(req *configcenter.GetDefaultVersionRequest) bool {
				return req.Component == "tiproxy"
			})).
			Return(&configcenter.DefaultVersion{Version: "v1.2.0"}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := &entity.SharedPool{
			SharedPool: apiv1.SharedPool{
				Id:      "test-pool-id",
				Version: "v7.1.0",
			},
		}

		err := client.CreateClusterMeta(context.Background(), sharedPool, "12345", "1")
		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		assert.Equal(t, configcenter.ClusterType_SHARED_POOL, capturedReq.ClusterType)
		assert.Equal(t, "test-pool-id", capturedReq.ClusterId)
		componentNames := make([]string, 0, len(capturedReq.Components))
		for _, component := range capturedReq.Components {
			componentNames = append(componentNames, component.GetComponentName())
		}
		assert.NotContains(t, componentNames, getDMComponentName())
	})

	t.Run("create cluster meta does not fetch or create dm", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))
		mockClient.EXPECT().
			GetDefaultVersion(mock.Anything, mock.MatchedBy(func(req *configcenter.GetDefaultVersionRequest) bool {
				return req.Component == "tiproxy"
			})).
			Return(&configcenter.DefaultVersion{Version: "v1.2.0"}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("v7.1.0")

		err := client.CreateClusterMeta(context.Background(), sharedPool, "12345", "")
		require.NoError(t, err)
		require.NotNil(t, capturedReq)

		componentNames := make([]string, 0, len(capturedReq.Components))
		for _, component := range capturedReq.Components {
			componentNames = append(componentNames, component.GetComponentName())
		}
		assert.NotContains(t, componentNames, getDMComponentName())
	})

	t.Run("registers schema manager component and config", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found"))
		mockClient.EXPECT().
			GetDefaultVersion(mock.Anything, mock.MatchedBy(func(req *configcenter.GetDefaultVersionRequest) bool {
				return req.Component == "tiproxy"
			})).
			Return(&configcenter.DefaultVersion{Version: "v1.2.0"}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := &entity.SharedPool{
			SharedPool: apiv1.SharedPool{
				Id:      "test-pool-id",
				Version: "v7.1.0",
				ComponentGroups: []*entity.Component{
					{Id: "sm-id", ComponentType: apiv1.ComponentType_SCHEMA_MANAGER},
				},
			},
		}

		err := client.CreateClusterMeta(context.Background(), sharedPool, "12345", "1")
		require.NoError(t, err)
		require.NotNil(t, capturedReq)

		smName := strings.ToLower(apiv1.ComponentType_SCHEMA_MANAGER.String())
		hasComponent := false
		for _, c := range capturedReq.Components {
			if c.ComponentName == smName {
				hasComponent = true
			}
		}
		assert.True(t, hasComponent, "schema_manager component should be registered")

		hasConfig := false
		for _, f := range capturedReq.ConfigurationFiles {
			if f.ConfigurationFilename == smName {
				hasConfig = true
				assert.Equal(t, smName, f.ConfigurationType)
				assert.Equal(t, smName, f.GetBelongToComponent())
			}
		}
		assert.True(t, hasConfig, "schema_manager config file should be registered")
	})

}

func TestAddBRComponentsToClusterMeta(t *testing.T) {
	t.Run("get cluster meta error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		err := client.AddBRComponentsToClusterMeta(context.Background(), sharedPool, "", "")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "get cluster meta")
	})

	t.Run("all BR components exist", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{
				Components: []*configcenter.ClusterComponent{
					{ComponentName: apiv1.ComponentType_TIBR_AUTOBACKUP.String()},
					{ComponentName: apiv1.ComponentType_TIBR_RESTORE.String()},
					{ComponentName: apiv1.ComponentType_TIBRGC_T2_DFSGC.String()},
					{ComponentName: apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()},
					{ComponentName: apiv1.ComponentType_CLASSIC_RESTORE.String()},
				},
				ConfigurationFiles: []*configcenter.ConfigurationFile{
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_TIBR_AUTOBACKUP.String())},
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_TIBR_RESTORE.String())},
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_TIBRGC_T2_DFSGC.String())},
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String())},
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_CLASSIC_RESTORE.String())},
				},
			}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		err := client.AddBRComponentsToClusterMeta(context.Background(), sharedPool, "", "")
		require.NoError(t, err)
	})

	t.Run("init cluster meta error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{Components: []*configcenter.ClusterComponent{}}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		err := client.AddBRComponentsToClusterMeta(context.Background(), sharedPool, "", "")
		require.Error(t, err)
		assert.Contains(t, err.Error(), "add br components to cluster meta")
	})

	t.Run("success adding missing BR components with empty meta profile", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{
				Components: []*configcenter.ClusterComponent{
					{ComponentName: apiv1.ComponentType_TIBR_AUTOBACKUP.String()},
				},
				ConfigurationFiles: []*configcenter.ConfigurationFile{
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_TIBR_AUTOBACKUP.String())},
				},
			}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")
		sharedPool.TenantId = "12345"

		err := client.AddBRComponentsToClusterMeta(context.Background(), sharedPool, "12345", "")
		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		require.Nil(t, capturedReq.MetaProfileId)
		assert.Len(t, capturedReq.Components, 4)
		assert.Len(t, capturedReq.ConfigurationFiles, 4)
		for _, req := range capturedReq.Components {
			require.NotNil(t, req.TenantId)
			assert.Equal(t, uint64(12345), req.GetTenantId())
		}
		for _, req := range capturedReq.ConfigurationFiles {
			require.NotNil(t, req.GetFromTenant())
			assert.Equal(t, uint64(12345), req.GetFromTenant().GetTenantId())
		}
	})

	t.Run("success adding missing BR components with meta profile", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		err := client.AddBRComponentsToClusterMeta(context.Background(), sharedPool, "", "1001")
		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		require.NotNil(t, capturedReq.MetaProfileId)
		assert.Equal(t, int64(1001), capturedReq.GetMetaProfileId())
		assert.Len(t, capturedReq.Components, len(AllBRComponents))
		assert.Len(t, capturedReq.ConfigurationFiles, len(AllBRComponents))
	})
}

func TestEnsurePDMicroserviceComponents(t *testing.T) {
	t.Run("adds missing components and configuration files", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{
				Components: []*configcenter.ClusterComponent{
					{ComponentName: "pd"},
				},
				ConfigurationFiles: []*configcenter.ConfigurationFile{
					{ConfigurationFilename: "pd"},
				},
			}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("v8.5.0")
		setTestSharedPoolTenantTag(sharedPool, "12345")
		sharedPool.ConfigCenterMetaProfileId = "1001"
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups,
			&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
			&entity.Component{ComponentType: apiv1.ComponentType_PD_SCHEDULING},
			&entity.Component{ComponentType: apiv1.ComponentType_RESOURCE_MANAGER},
		)

		err := client.EnsurePDMicroserviceComponents(context.Background(), sharedPool)

		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		assert.Equal(t, configcenter.ClusterType_SHARED_POOL, capturedReq.GetClusterType())
		assert.Equal(t, sharedPool.GetId(), capturedReq.GetClusterId())
		assert.Equal(t, int64(1001), capturedReq.GetMetaProfileId())
		require.Len(t, capturedReq.GetComponents(), 3)
		require.Len(t, capturedReq.GetConfigurationFiles(), 3)

		componentNames := make([]string, 0, len(capturedReq.GetComponents()))
		for _, component := range capturedReq.GetComponents() {
			componentNames = append(componentNames, component.GetComponentName())
			assert.Equal(t, "v8.5.0", component.GetVersion())
			assert.Equal(t, uint64(12345), component.GetTenantId())
		}
		assert.ElementsMatch(t, []string{"pd_tso", "pd_scheduling", "resource_manager"}, componentNames)

		configNames := make([]string, 0, len(capturedReq.GetConfigurationFiles()))
		for _, configuration := range capturedReq.GetConfigurationFiles() {
			configNames = append(configNames, configuration.GetConfigurationFilename())
			assert.Equal(t, configuration.GetConfigurationFilename(), configuration.GetConfigurationType())
			assert.Equal(t, configuration.GetConfigurationFilename(), configuration.GetBelongToComponent())
			assert.Equal(t, "v8.5.0", configuration.GetFromSku().GetVersion())
			assert.Equal(t, uint64(12345), configuration.GetFromTenant().GetTenantId())
		}
		assert.ElementsMatch(t, []string{"pd_tso", "pd_scheduling", "resource_manager"}, configNames)
	})

	t.Run("uses cluster meta version when persisted shared pool version is empty", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{Version: "v8.5.0"}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("")
		setTestSharedPoolTenantTag(sharedPool, schemav1.MultiTenantTag)
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups,
			&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
		)

		err := client.EnsurePDMicroserviceComponents(context.Background(), sharedPool)

		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		require.Len(t, capturedReq.GetComponents(), 1)
		require.Len(t, capturedReq.GetConfigurationFiles(), 1)
		assert.Equal(t, "v8.5.0", capturedReq.GetComponents()[0].GetVersion())
		assert.Equal(t, "v8.5.0", capturedReq.GetConfigurationFiles()[0].GetFromSku().GetVersion())
	})

	t.Run("uses provider tenant tag when entity tenant is empty", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("v8.5.0")
		sharedPool.Provider = &entity.DataPlaneProvider{Tags: map[string]string{
			schemav1.LabelUserTenantKey: "12345",
		}}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups,
			&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
		)

		err := client.EnsurePDMicroserviceComponents(context.Background(), sharedPool)

		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		require.Len(t, capturedReq.GetComponents(), 1)
		require.Len(t, capturedReq.GetConfigurationFiles(), 1)
		assert.Equal(t, uint64(12345), capturedReq.GetComponents()[0].GetTenantId())
		assert.Equal(t, uint64(12345), capturedReq.GetConfigurationFiles()[0].GetFromSku().GetTenantId())
		require.NotNil(t, capturedReq.GetConfigurationFiles()[0].GetFromTenant())
		assert.Equal(t, uint64(12345), capturedReq.GetConfigurationFiles()[0].GetFromTenant().GetTenantId())
	})

	t.Run("supports explicit multi tenant provider tag", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("v8.5.0")
		sharedPool.Provider = &entity.DataPlaneProvider{Tags: map[string]string{
			schemav1.LabelUserTenantKey: schemav1.MultiTenantTag,
		}}
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups,
			&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
		)

		err := client.EnsurePDMicroserviceComponents(context.Background(), sharedPool)

		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		require.Len(t, capturedReq.GetComponents(), 1)
		require.Len(t, capturedReq.GetConfigurationFiles(), 1)
		assert.Nil(t, capturedReq.GetComponents()[0].TenantId)
		assert.Zero(t, capturedReq.GetConfigurationFiles()[0].GetFromSku().GetTenantId())
		assert.Nil(t, capturedReq.GetConfigurationFiles()[0].GetFromTenant())
		assert.Nil(t, capturedReq.MetaProfileId)
	})

	t.Run("handles missing component and configuration independently", func(t *testing.T) {
		tests := []struct {
			name                     string
			sharedPoolVersion        string
			clusterMeta              *configcenter.ClusterMeta
			wantComponents           int
			wantConfigurationFiles   int
			wantConfigurationVersion string
		}{
			{
				name:              "configuration uses existing component version",
				sharedPoolVersion: "v8.5.0",
				clusterMeta: &configcenter.ClusterMeta{Components: []*configcenter.ClusterComponent{
					{ComponentName: "pd_tso", Version: "v8.4.0"},
				}},
				wantConfigurationFiles:   1,
				wantConfigurationVersion: "v8.4.0",
			},
			{
				name:              "configuration falls back when existing component version is empty",
				sharedPoolVersion: "v8.5.0",
				clusterMeta: &configcenter.ClusterMeta{Components: []*configcenter.ClusterComponent{
					{ComponentName: "pd_tso"},
				}},
				wantConfigurationFiles:   1,
				wantConfigurationVersion: "v8.5.0",
			},
			{
				name: "existing component version does not require a global fallback",
				clusterMeta: &configcenter.ClusterMeta{Components: []*configcenter.ClusterComponent{
					{ComponentName: "pd_tso", Version: "v8.4.0"},
				}},
				wantConfigurationFiles:   1,
				wantConfigurationVersion: "v8.4.0",
			},
			{
				name:              "configuration exists but component is missing",
				sharedPoolVersion: "v8.5.0",
				clusterMeta: &configcenter.ClusterMeta{ConfigurationFiles: []*configcenter.ConfigurationFile{
					{ConfigurationFilename: "pd_tso"},
				}},
				wantComponents: 1,
			},
		}

		for _, tt := range tests {
			t.Run(tt.name, func(t *testing.T) {
				var capturedReq *configcenter.InitClusterMetaRequest
				mockClient := configcentermocks.NewConfigCenterServiceClient(t)
				mockClient.EXPECT().GetClusterMeta(mock.Anything, mock.Anything).Return(tt.clusterMeta, nil)
				mockClient.EXPECT().
					InitClusterMeta(mock.Anything, mock.Anything).
					Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
						capturedReq = req
					}).
					Return(&configcenter.ClusterMeta{}, nil)

				client := &configClientImpl{configCenterCli: mockClient}
				sharedPool := newTestSharedPool(tt.sharedPoolVersion)
				setTestSharedPoolTenantTag(sharedPool, "12345")
				sharedPool.ComponentGroups = append(sharedPool.ComponentGroups,
					&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
				)

				err := client.EnsurePDMicroserviceComponents(context.Background(), sharedPool)

				require.NoError(t, err)
				require.NotNil(t, capturedReq)
				assert.Len(t, capturedReq.GetComponents(), tt.wantComponents)
				require.Len(t, capturedReq.GetConfigurationFiles(), tt.wantConfigurationFiles)
				if tt.wantConfigurationFiles > 0 {
					assert.Equal(t, tt.wantConfigurationVersion, capturedReq.GetConfigurationFiles()[0].GetFromSku().GetVersion())
				}
			})
		}
	})

	t.Run("uses existing pd component version as final fallback", func(t *testing.T) {
		var capturedReq *configcenter.InitClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{Components: []*configcenter.ClusterComponent{
				{ComponentName: "pd", Version: "v8.4.0"},
			}}, nil)
		mockClient.EXPECT().
			InitClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.InitClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.ClusterMeta{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("")
		setTestSharedPoolTenantTag(sharedPool, "12345")
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups,
			&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
		)

		err := client.EnsurePDMicroserviceComponents(context.Background(), sharedPool)

		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		assert.Equal(t, "v8.4.0", capturedReq.GetComponents()[0].GetVersion())
	})

	t.Run("fails closed for invalid metadata", func(t *testing.T) {
		tests := []struct {
			name       string
			sharedPool *entity.SharedPool
			wantError  string
		}{
			{
				name:       "missing tenant",
				sharedPool: newTestSharedPool("v8.5.0"),
				wantError:  "shared pool tenant id is unavailable",
			},
			{
				name: "invalid profile",
				sharedPool: func() *entity.SharedPool {
					sharedPool := newTestSharedPool("v8.5.0")
					setTestSharedPoolTenantTag(sharedPool, "12345")
					sharedPool.ConfigCenterMetaProfileId = "not-an-id"
					return sharedPool
				}(),
				wantError: "parse meta profile id",
			},
		}

		for _, tt := range tests {
			t.Run(tt.name, func(t *testing.T) {
				tt.sharedPool.ComponentGroups = append(tt.sharedPool.ComponentGroups,
					&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
				)
				mockClient := configcentermocks.NewConfigCenterServiceClient(t)
				mockClient.EXPECT().GetClusterMeta(mock.Anything, mock.Anything).Return(&configcenter.ClusterMeta{}, nil)
				client := &configClientImpl{configCenterCli: mockClient}

				err := client.EnsurePDMicroserviceComponents(context.Background(), tt.sharedPool)

				require.ErrorContains(t, err, tt.wantError)
			})
		}
	})

	t.Run("returns config center read and initialization errors", func(t *testing.T) {
		t.Run("read", func(t *testing.T) {
			mockClient := configcentermocks.NewConfigCenterServiceClient(t)
			mockClient.EXPECT().
				GetClusterMeta(mock.Anything, mock.Anything).
				Return(nil, status.Error(codes.Unavailable, "config center unavailable"))
			client := &configClientImpl{configCenterCli: mockClient}

			err := client.EnsurePDMicroserviceComponents(context.Background(), newTestSharedPool("v8.5.0"))

			require.ErrorContains(t, err, "get cluster meta")
		})

		t.Run("initialization", func(t *testing.T) {
			mockClient := configcentermocks.NewConfigCenterServiceClient(t)
			mockClient.EXPECT().GetClusterMeta(mock.Anything, mock.Anything).Return(&configcenter.ClusterMeta{}, nil)
			mockClient.EXPECT().
				InitClusterMeta(mock.Anything, mock.Anything).
				Return(nil, status.Error(codes.Unavailable, "config center unavailable"))
			client := &configClientImpl{configCenterCli: mockClient}
			sharedPool := newTestSharedPool("v8.5.0")
			setTestSharedPoolTenantTag(sharedPool, "12345")
			sharedPool.ComponentGroups = append(sharedPool.ComponentGroups,
				&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
			)

			err := client.EnsurePDMicroserviceComponents(context.Background(), sharedPool)

			require.ErrorContains(t, err, "ensure pd microservice components")
		})
	})

	t.Run("skips initialization when target metadata already exists", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{
				Components: []*configcenter.ClusterComponent{
					{ComponentName: "pd_tso"},
					{ComponentName: "pd_scheduling"},
				},
				ConfigurationFiles: []*configcenter.ConfigurationFile{
					{ConfigurationFilename: "pd_tso"},
					{ConfigurationFilename: "pd_scheduling"},
				},
			}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		sharedPool := newTestSharedPool("")
		setTestSharedPoolTenantTag(sharedPool, "12345")
		sharedPool.ConfigCenterMetaProfileId = "1001"
		sharedPool.ComponentGroups = append(sharedPool.ComponentGroups,
			&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
			&entity.Component{ComponentType: apiv1.ComponentType_PD_SCHEDULING},
		)

		err := client.EnsurePDMicroserviceComponents(context.Background(), sharedPool)

		require.NoError(t, err)
	})

	t.Run("validates metadata before the existing-target fast path", func(t *testing.T) {
		tests := []struct {
			name       string
			sharedPool *entity.SharedPool
			wantError  string
		}{
			{
				name:       "missing tenant",
				sharedPool: newTestSharedPool(""),
				wantError:  "shared pool tenant id is unavailable",
			},
			{
				name: "invalid tenant",
				sharedPool: func() *entity.SharedPool {
					sharedPool := newTestSharedPool("")
					setTestSharedPoolTenantTag(sharedPool, "not-a-tenant-id")
					return sharedPool
				}(),
				wantError: "parse shared pool tenant id",
			},
			{
				name: "invalid profile",
				sharedPool: func() *entity.SharedPool {
					sharedPool := newTestSharedPool("")
					setTestSharedPoolTenantTag(sharedPool, "12345")
					sharedPool.ConfigCenterMetaProfileId = "not-a-profile-id"
					return sharedPool
				}(),
				wantError: "parse meta profile id",
			},
		}

		for _, tt := range tests {
			t.Run(tt.name, func(t *testing.T) {
				mockClient := configcentermocks.NewConfigCenterServiceClient(t)
				mockClient.EXPECT().
					GetClusterMeta(mock.Anything, mock.Anything).
					Return(&configcenter.ClusterMeta{
						Components: []*configcenter.ClusterComponent{
							{ComponentName: "pd_tso"},
						},
						ConfigurationFiles: []*configcenter.ConfigurationFile{
							{ConfigurationFilename: "pd_tso"},
						},
					}, nil)
				client := &configClientImpl{configCenterCli: mockClient}
				tt.sharedPool.ComponentGroups = append(tt.sharedPool.ComponentGroups,
					&entity.Component{ComponentType: apiv1.ComponentType_PD_TSO},
				)

				err := client.EnsurePDMicroserviceComponents(context.Background(), tt.sharedPool)

				require.ErrorContains(t, err, tt.wantError)
			})
		}
	})
}

func TestAddExportComponentsToClusterMeta(t *testing.T) {
	t.Run("create tidb cluster meta error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}
		tidbCluster := newTestTiDBCluster("12345", "v7.1.0")

		err := client.AddExportComponentsToClusterMeta(context.Background(), tidbCluster)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "get export logical cluster meta")
	})

	t.Run("all export components exist", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{
				Components: []*configcenter.ClusterComponent{
					{ComponentName: apiv1.ComponentType_DUMPLING.String()},
				},
			}, nil).
			Once()

		client := &configClientImpl{configCenterCli: mockClient}
		tidbCluster := newTestTiDBCluster("12345", "v7.1.0")

		err := client.AddExportComponentsToClusterMeta(context.Background(), tidbCluster)
		require.NoError(t, err)
	})

	t.Run("auto update logical cluster meta error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{}, nil).
			Once()
		mockClient.EXPECT().
			AutoUpdateClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}
		tidbCluster := newTestTiDBCluster("12345", "v7.1.0")

		err := client.AddExportComponentsToClusterMeta(context.Background(), tidbCluster)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "add export components to logical cluster meta")
	})

	t.Run("success adding export components", func(t *testing.T) {
		var capturedReq *configcenter.AutoUpdateClusterMetaRequest
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{Components: []*configcenter.ClusterComponent{}}, nil)
		mockClient.EXPECT().
			AutoUpdateClusterMeta(mock.Anything, mock.Anything).
			Run(func(ctx context.Context, req *configcenter.AutoUpdateClusterMetaRequest, opts ...grpc.CallOption) {
				capturedReq = req
			}).
			Return(&configcenter.AutoUpdateClusterMetaResponse{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		tidbCluster := newTestTiDBCluster("12345", "v7.1.0")

		err := client.AddExportComponentsToClusterMeta(context.Background(), tidbCluster)
		require.NoError(t, err)
		require.NotNil(t, capturedReq)
		assert.Equal(t, configcenter.ClusterType_LOGICAL_CLUSTER, capturedReq.ClusterType)
		assert.Equal(t, "test-cluster-id", capturedReq.ClusterId)
		assert.Len(t, capturedReq.ComponentRequests, 1)
		assert.Len(t, capturedReq.ConfigurationFileRequests, 1)
		assert.Equal(t, strings.ToLower(apiv1.ComponentType_DUMPLING.String()), capturedReq.ComponentRequests[0].GetComponentName())
		require.NotNil(t, capturedReq.ComponentRequests[0].TenantId)
		assert.Equal(t, uint64(12345), capturedReq.ComponentRequests[0].GetTenantId())
		require.NotNil(t, capturedReq.ConfigurationFileRequests[0].TenantId)
		assert.Equal(t, uint64(12345), capturedReq.ConfigurationFileRequests[0].GetTenantId())
	})
}

func TestGetBRComponentVersionAndConfig(t *testing.T) {
	t.Run("not a BR component", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		_, _, err := client.GetBRComponentVersionAndConfig(context.Background(), sharedPool, sharedPool.TenantId, apiv1.ComponentType_TIKV)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "not a br component")
	})

	t.Run("add BR components error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		_, _, err := client.GetBRComponentVersionAndConfig(context.Background(), sharedPool, sharedPool.TenantId, apiv1.ComponentType_TIBR_AUTOBACKUP)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "add br components to cluster meta")
	})

	t.Run("success with BR component image", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		// First call for AddBRComponentsToClusterMeta
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterMeta{
				Components: []*configcenter.ClusterComponent{
					{ComponentName: apiv1.ComponentType_TIBR_AUTOBACKUP.String()},
					{ComponentName: apiv1.ComponentType_TIBR_RESTORE.String()},
					{ComponentName: apiv1.ComponentType_TIBRGC_T2_DFSGC.String()},
					{ComponentName: apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String()},
					{ComponentName: apiv1.ComponentType_CLASSIC_RESTORE.String()},
				},
				ConfigurationFiles: []*configcenter.ConfigurationFile{
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_TIBR_AUTOBACKUP.String())},
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_TIBR_RESTORE.String())},
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_TIBRGC_T2_DFSGC.String())},
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_TIBRGC_T3_ARCHIVE.String())},
					{ConfigurationFilename: strings.ToLower(apiv1.ComponentType_CLASSIC_RESTORE.String())},
				},
			}, nil)
		// GetClusterComponent for BR component
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.Anything).
			Return(&configcenter.ClusterComponent{
				Version:           "v7.1.0",
				BaseImage:         "pingcap/br",
				EffectiveImageTag: "v7.1.0",
			}, nil)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(&configcenter.ConfigurationFile{Text: "br-config"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")

		version, config, err := client.GetBRComponentVersionAndConfig(context.Background(), sharedPool, sharedPool.TenantId, apiv1.ComponentType_TIBR_AUTOBACKUP)
		require.NoError(t, err)
		assert.Equal(t, "v7.1.0", version)
		assert.Equal(t, "br-config", config.ConfigurationFileContent)
		assert.Equal(t, "pingcap/br", config.ImageRepo)
	})
}

func TestGetExportComponentVersionAndConfig(t *testing.T) {
	t.Run("not an export component", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		client := &configClientImpl{configCenterCli: mockClient}

		tidbCluster := newTestTiDBCluster("12345", "v7.1.0")

		_, _, err := client.GetExportComponentVersionAndConfig(context.Background(), tidbCluster, apiv1.ComponentType_TIKV)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "not an export component")
	})

	t.Run("add export components error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}
		tidbCluster := newTestTiDBCluster("12345", "v7.1.0")

		_, _, err := client.GetExportComponentVersionAndConfig(context.Background(), tidbCluster, apiv1.ComponentType_DUMPLING)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "add export components to cluster meta")
	})

	t.Run("success with export component image", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		mockClient.EXPECT().
			GetClusterMeta(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterMetaRequest) bool {
				return req.ClusterType == configcenter.ClusterType_LOGICAL_CLUSTER && req.ClusterId == "test-cluster-id"
			})).
			Return(&configcenter.ClusterMeta{
				Components: []*configcenter.ClusterComponent{
					{ComponentName: apiv1.ComponentType_DUMPLING.String()},
				},
			}, nil).
			Once()
		mockClient.EXPECT().
			GetClusterComponent(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterComponentRequest) bool {
				return req.ClusterType == configcenter.ClusterType_LOGICAL_CLUSTER &&
					req.ClusterId == "test-cluster-id" &&
					req.ComponentName == strings.ToLower(apiv1.ComponentType_DUMPLING.String())
			})).
			Return(&configcenter.ClusterComponent{
				Version:           "v7.1.0",
				BaseImage:         "pingcap/dumpling",
				EffectiveImageTag: "v7.1.0",
			}, nil)
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_LOGICAL_CLUSTER &&
					req.ClusterId == "test-cluster-id" &&
					req.ConfigurationFilename == strings.ToLower(apiv1.ComponentType_DUMPLING.String())
			})).
			Return(&configcenter.ConfigurationFile{Text: "dumpling-config"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}
		tidbCluster := newTestTiDBCluster("12345", "v7.1.0")

		version, config, err := client.GetExportComponentVersionAndConfig(context.Background(), tidbCluster, apiv1.ComponentType_DUMPLING)
		require.NoError(t, err)
		assert.Equal(t, "v7.1.0", version)
		assert.Equal(t, "dumpling-config", config.ConfigurationFileContent)
		assert.Equal(t, "pingcap/dumpling", config.ImageRepo)
	})
}

func TestCreateNewComponentConfig(t *testing.T) {
	t.Run("success - create new tikv group config", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)

		// First call to check if config exists - returns NotFound
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-pool-id" &&
					strings.HasPrefix(req.ConfigurationFilename, "tikv-")
			})).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()

		// Second call to get default tikv config - try with component group id first, returns NotFound
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-pool-id" &&
					strings.HasPrefix(req.ConfigurationFilename, "tikv-")
			})).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()

		// Third call to get default tikv config - fallback to old style without group id
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-pool-id" &&
					req.ConfigurationFilename == "tikv"
			})).
			Return(&configcenter.ConfigurationFile{
				TemplateId:   123,
				CustomParams: map[string]*configcenter.ParamItem{},
			}, nil)

		// Fourth call to create the new config using CreateClusterConfigurationFile
		mockClient.EXPECT().
			CreateClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.CreateClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-pool-id" &&
					req.ConfigurationFile != nil &&
					strings.HasPrefix(req.ConfigurationFile.ConfigurationFilename, "tikv-")
			})).
			Return(&configcenter.ConfigurationFile{}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")
		componentGroup := &entity.Component{
			Id:            "test-component-id",
			ComponentType: apiv1.ComponentType_TIKV,
		}

		err := client.CreateNewComponentConfig(context.Background(), sharedPool, componentGroup)
		require.NoError(t, err)
	})

	t.Run("config already exists - skip creation", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)

		// Config already exists - returns successfully
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(&configcenter.ConfigurationFile{Text: "existing-config"}, nil)

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")
		componentGroup := &entity.Component{
			Id:            "test-component-id",
			ComponentType: apiv1.ComponentType_TIKV,
		}

		err := client.CreateNewComponentConfig(context.Background(), sharedPool, componentGroup)
		require.NoError(t, err)
	})

	t.Run("unsupported component type - returns error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)
		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")
		componentGroup := &entity.Component{
			Id:            "test-component-id",
			ComponentType: apiv1.ComponentType_TIDB,
		}

		err := client.CreateNewComponentConfig(context.Background(), sharedPool, componentGroup)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "create component config not support for")
	})

	t.Run("get config error - non-NotFound error", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)

		// Returns internal error instead of NotFound
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "internal error"))

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")
		componentGroup := &entity.Component{
			Id:            "test-component-id",
			ComponentType: apiv1.ComponentType_TIKV,
		}

		err := client.CreateNewComponentConfig(context.Background(), sharedPool, componentGroup)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "get tikv group config")
	})

	t.Run("get default config fails", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)

		// First call to check if config exists - returns NotFound
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-pool-id" &&
					strings.HasPrefix(req.ConfigurationFilename, "tikv-")
			})).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()

		// Second call to get default tikv config fails
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.Internal, "config-center unavailable"))

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")
		componentGroup := &entity.Component{
			Id:            "test-component-id",
			ComponentType: apiv1.ComponentType_TIKV,
		}

		err := client.CreateNewComponentConfig(context.Background(), sharedPool, componentGroup)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "get config from default tikv group")
	})

	t.Run("create config fails", func(t *testing.T) {
		mockClient := configcentermocks.NewConfigCenterServiceClient(t)

		// First call to check if config exists - returns NotFound
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-pool-id" &&
					strings.HasPrefix(req.ConfigurationFilename, "tikv-")
			})).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()

		// Second and third calls to get default tikv config
		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.Anything).
			Return(nil, status.Error(codes.NotFound, "not found")).
			Once()

		mockClient.EXPECT().
			GetClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.GetClusterConfigurationFileRequest) bool {
				return req.ConfigurationFilename == "tikv"
			})).
			Return(&configcenter.ConfigurationFile{
				TemplateId:   123,
				CustomParams: map[string]*configcenter.ParamItem{},
			}, nil)

		// Create config fails
		mockClient.EXPECT().
			CreateClusterConfigurationFile(mock.Anything, mock.MatchedBy(func(req *configcenter.CreateClusterConfigurationFileRequest) bool {
				return req.ClusterType == configcenter.ClusterType_SHARED_POOL &&
					req.ClusterId == "test-pool-id"
			})).
			Return(nil, status.Error(codes.Internal, "creation failed"))

		client := &configClientImpl{configCenterCli: mockClient}

		sharedPool := newTestSharedPool("v7.1.0")
		componentGroup := &entity.Component{
			Id:            "test-component-id",
			ComponentType: apiv1.ComponentType_TIKV,
		}

		err := client.CreateNewComponentConfig(context.Background(), sharedPool, componentGroup)
		require.Error(t, err)
		assert.Contains(t, err.Error(), "create tikv group config")
	})
}
