package server

import (
	"context"
	"fmt"
	"os"
	"testing"

	"github.com/stretchr/testify/assert"
	corev1 "k8s.io/api/core/v1"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/utils/ptr"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/client/fake"
	"sigs.k8s.io/yaml"

	"github.com/tidbcloud/cluster-controller/server/internal/util"
	cloudv1 "github.com/tidbcloud/infra-api/api/v1"
	apiv1 "github.com/tidbcloud/infra-provider/api/tidbcloud/cluster-server/v1"
	"github.com/tidbcloud/infra-provider/pkg/consts"
	"github.com/tidbcloud/infra-provider/pkg/gitops"
	"github.com/tidbcloud/infra-provider/pkg/scheme"
)

// testInstanceTypeProvider provides a simple mock for common instance types
type testInstanceTypeProvider struct {
	instanceTypes map[string]cloudv1.InstanceType
}

func newTestInstanceTypeProvider() gitops.InstanceTypeProvider {
	return &testInstanceTypeProvider{
		instanceTypes: map[string]cloudv1.InstanceType{
			"c5.xlarge": {
				Name:   "c5.xlarge",
				Arch:   "amd64",
				CPU:    *resource.NewQuantity(4, resource.DecimalSI),
				Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI),
			},
			"n1-standard-4": {
				Name:   "n1-standard-4",
				Arch:   "amd64",
				CPU:    *resource.NewQuantity(4, resource.DecimalSI),
				Memory: *resource.NewQuantity(15*1024*1024*1024, resource.BinarySI),
			},
			"Standard_D2ps_v5": {
				Name:   "Standard_D2ps_v5",
				Arch:   "amd64",
				CPU:    *resource.NewQuantity(2, resource.DecimalSI),
				Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI),
			},
		},
	}
}

func (p *testInstanceTypeProvider) GetInstanceTypes() ([]cloudv1.InstanceType, error) {
	result := make([]cloudv1.InstanceType, 0, len(p.instanceTypes))
	for _, it := range p.instanceTypes {
		result = append(result, it)
	}
	return result, nil
}

func (p *testInstanceTypeProvider) FindInstanceType(name string) (cloudv1.InstanceType, error) {
	it, ok := p.instanceTypes[name]
	if !ok {
		return cloudv1.InstanceType{}, fmt.Errorf("instance type %s not found", name)
	}
	return it, nil
}

func TestBRServer_CreateSnapshotBackup(t *testing.T) {
	type args struct {
		ctx  context.Context
		objs []client.Object
		req  *apiv1.CreateSnapshotBackupRequest
	}

	tests := []struct {
		name     string
		args     args
		wantYAML string
		wantErr  bool
	}{
		{
			name: "aws-snapshot-backup",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10467160699038821215",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "eks",
								"cloud.pingcap.com/region":        "us-west-2",
								"tags.tidbcloud.com/cluster":      "10467160699038821215",
								"tags.tidbcloud.com/environment":  "dev",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									AWS: &cloudv1.CloudAWS{
										AdminNodeRole: "arn:aws:iam::123456789012:role/ng-role-nightly-ms-3199728-10467160699038821215-62b9274a",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateSnapshotBackupRequest{
					Backup: &apiv1.SnapshotBackup{
						Id:            "test3-11595019",
						ClusterId:     "10467160699038821215",
						InstanceType:  "c5.xlarge",
						Bucket:        "s3://backup-test-bucket/cluster-10467160699038821215-sr(us-west-2)",
						ImageRepo:     "gcr.io/pingcap-public/dbaas/br",
						ImageTag:      "v8.1.1",
						Configuration: "{\"checksum\":false,\"concurrency\":32}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/aws-snapshot-backup-infra.yaml",
			wantErr:  false,
		},
		{
			name: "gcp-snapshot-backup",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10602997610413447074",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "gke",
								"cloud.pingcap.com/region":        "us-west1",
								"tags.tidbcloud.com/cluster":      "10602997610413447074",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									GCP: &cloudv1.CloudGCP{
										AdminNodeSa: "np-sa-test-cluster-noderole@test-project.iam.gserviceaccount.com",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateSnapshotBackupRequest{
					Backup: &apiv1.SnapshotBackup{
						Id:            "test2-11595020",
						ClusterId:     "10602997610413447074",
						InstanceType:  "c5.xlarge",
						Bucket:        "gs://backup-test-bucket/10602997610413447074",
						ImageRepo:     "gcr.io/pingcap-public/dbaas/br",
						ImageTag:      "v8.1.1",
						Configuration: "{\"checksum\":false,\"concurrency\":32}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/gcp-snapshot-backup-infra.yaml",
			wantErr:  false,
		},
		{
			name: "azure-snapshot-backup",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10305758427116646972",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "aks",
								"cloud.pingcap.com/region":        "eastus",
								"creator":                         "cluster-controller-server",
								"tags.tidbcloud.com/cluster":      "10305758427116646972",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
							},
						},
					},
				},
				req: &apiv1.CreateSnapshotBackupRequest{
					Backup: &apiv1.SnapshotBackup{
						Id:            "test-11594973",
						ClusterId:     "10305758427116646972",
						InstanceType:  "Standard_D2ps_v5",
						Bucket:        "https://teststorageaccount.blob.core.windows.net/test-storage-container/10305758427116646972?se=2034-11-06T15%3A03%3A32Z&sig=test%2Ftesttesttesttest%3D&sp=rwdlac&spr=https&srt=co&ss=b&st=2024-11-08T15%3A03%3A32Z&sv=2020-02-10",
						ImageRepo:     "gcr.io/pingcap-public/dbaas/br",
						ImageTag:      "v8.1.1",
						Configuration: "{\"checksum\":false,\"concurrency\":32}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/azure-snapshot-backup-infra.yaml",
			wantErr:  false,
		},
		{
			name: "aws-snapshot-backup-with-env",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10467160699038821215",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "eks",
								"cloud.pingcap.com/region":        "us-west-2",
								"tags.tidbcloud.com/cluster":      "10467160699038821215",
								"tags.tidbcloud.com/environment":  "dev",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									AWS: &cloudv1.CloudAWS{
										AdminNodeRole: "arn:aws:iam::123456789012:role/ng-role-nightly-ms-3199728-10467160699038821215-62b9274a",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateSnapshotBackupRequest{
					Backup: &apiv1.SnapshotBackup{
						Id:            "test-env-11595019",
						ClusterId:     "10467160699038821215",
						InstanceType:  "c5.xlarge",
						Bucket:        "s3://backup-test-bucket/cluster-10467160699038821215-sr(us-west-2)",
						ImageRepo:     "gcr.io/pingcap-public/dbaas/br",
						ImageTag:      "v8.1.1",
						Configuration: "{\"checksum\":false,\"concurrency\":32}",
						Env: map[string]string{
							"LOG_LEVEL":    "debug",
							"TEST_ENV_KEY": "test_env_value",
						},
					},
				},
			},
			wantYAML: "./ut_test_data/br/aws-snapshot-backup-with-env-infra.yaml",
			wantErr:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			kubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.args.objs...).Build()
			s := &brServer{
				kubeClient:           kubeClient,
				clusterReader:        &util.LabelSelectorReader{},
				instanceTypeProvider: newTestInstanceTypeProvider(),
			}
			_, err := s.CreateSnapshotBackup(tt.args.ctx, tt.args.req)
			if (err != nil) != tt.wantErr {
				t.Errorf("brServer.CreateSnapshotBackup() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			gotBackup := &cloudv1.Backup{}

			if err = s.kubeClient.Get(tt.args.ctx, client.ObjectKey{Name: tt.args.req.Backup.Id, Namespace: tt.args.objs[0].GetNamespace()}, gotBackup); err != nil {
				t.Errorf("Failed to get Backup object: %v", err)
				return
			}
			gotYAML, err := yaml.Marshal(gotBackup)
			fmt.Printf("%s", gotYAML)
			if err != nil {
				t.Errorf("Failed to marshal got object to YAML: %v", err)
				return
			}

			// // generate the expected YAML file
			// err = os.WriteFile(tt.wantYAML, gotYAML, 0644)
			// assert.NoError(t, err, "Failed to write generated YAML to file")
			// t.Logf("Generated YAML written to %s", tt.wantYAML)

			wantYAML, err := os.ReadFile(tt.wantYAML)
			if err != nil {
				t.Errorf("Failed to read want YAML file: %v", err)
				return
			}
			assert.Equal(t, string(wantYAML), string(gotYAML))
		})
	}
}

func TestBRServer_CreateLogBackup(t *testing.T) {
	type args struct {
		ctx  context.Context
		objs []client.Object
		req  *apiv1.CreateLogBackupRequest
	}

	tests := []struct {
		name     string
		args     args
		wantYAML string
		wantErr  bool
	}{
		{
			name: "aws-log-backup",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10467160699038821215",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "eks",
								"cloud.pingcap.com/region":        "us-west-2",
								"tags.tidbcloud.com/cluster":      "10467160699038821215",
								"tags.tidbcloud.com/environment":  "dev",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									AWS: &cloudv1.CloudAWS{
										AdminNodeRole: "arn:aws:iam::1234556789123:role/ng-role-nightly-ms-3199728-10467160699038821215-62b9274a",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateLogBackupRequest{
					Backup: &apiv1.LogBackup{
						Id:            "log-test-420336",
						StartTs:       "456389151948800000",
						ClusterId:     "10467160699038821215",
						InstanceType:  "c5.xlarge",
						Bucket:        "s3://backup-test-bucket/cluster-10467160699038821215-sr(us-west-2)",
						ImageRepo:     "gcr.io/pingcap-public/dbaas/br",
						ImageTag:      "v8.1.1",
						Configuration: "{\"checksum\":false,\"concurrency\":32}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/aws-log-backup-infra.yaml",
			wantErr:  false,
		},
		{
			name: "gcp-log-backup",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10602997610413447074",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "gke",
								"cloud.pingcap.com/region":        "us-west1",
								"tags.tidbcloud.com/cluster":      "10602997610413447074",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									GCP: &cloudv1.CloudGCP{
										AdminNodeSa: "np-test-cluster-role@test-gcp-projects.iam.gserviceaccount.com",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateLogBackupRequest{
					Backup: &apiv1.LogBackup{
						Id:            "log-test-420338",
						StartTs:       "456390137610240000",
						ClusterId:     "10602997610413447074",
						InstanceType:  "c5.xlarge",
						Bucket:        "gs://backup-test-bucket/10602997610413447074",
						ImageRepo:     "gcr.io/pingcap-public/dbaas/br",
						ImageTag:      "v8.1.1",
						Configuration: "{\"checksum\":false,\"concurrency\":32}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/gcp-log-backup-infra.yaml",
			wantErr:  false,
		},
		{
			name: "azure-log-backup",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10305758427116646972",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "aks",
								"cloud.pingcap.com/region":        "eastus",
								"creator":                         "cluster-controller-server",
								"tags.tidbcloud.com/cluster":      "10305758427116646972",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
							},
						},
					},
				},
				req: &apiv1.CreateLogBackupRequest{
					Backup: &apiv1.LogBackup{
						Id:            "log-test-420340",
						ClusterId:     "10305758427116646972",
						InstanceType:  "Standard_D2ps_v5",
						Bucket:        "https://teststorageaccount.blob.core.windows.net/test-container/10305758427116646972?se=2034-11-06T15%3A03%3A32Z&sig=testtesttest%2Ftesttesttest%3D&sp=rwdlac&spr=https&srt=co&ss=b&st=2024-11-08T15%3A03%3A32Z&sv=2020-02-10",
						ImageRepo:     "gcr.io/pingcap-public/dbaas/br",
						ImageTag:      "v8.1.1",
						Configuration: "{\"checksum\":false,\"concurrency\":32}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/azure-log-backup-infra.yaml",
			wantErr:  false,
		},
		{
			name: "aws-log-backup-with-env",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10467160699038821215",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "eks",
								"cloud.pingcap.com/region":        "us-west-2",
								"tags.tidbcloud.com/cluster":      "10467160699038821215",
								"tags.tidbcloud.com/environment":  "dev",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									AWS: &cloudv1.CloudAWS{
										AdminNodeRole: "arn:aws:iam::1234556789123:role/ng-role-nightly-ms-3199728-10467160699038821215-62b9274a",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateLogBackupRequest{
					Backup: &apiv1.LogBackup{
						Id:            "log-test-env-420336",
						StartTs:       "456389151948800000",
						ClusterId:     "10467160699038821215",
						InstanceType:  "c5.xlarge",
						Bucket:        "s3://backup-test-bucket/cluster-10467160699038821215-sr(us-west-2)",
						ImageRepo:     "gcr.io/pingcap-public/dbaas/br",
						ImageTag:      "v8.1.1",
						Configuration: "{\"checksum\":false,\"concurrency\":32}",
						Env: map[string]string{
							"BACKUP_MODE": "incremental",
							"VERBOSE":     "true",
						},
					},
				},
			},
			wantYAML: "./ut_test_data/br/aws-log-backup-with-env-infra.yaml",
			wantErr:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			kubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.args.objs...).Build()
			s := &brServer{
				kubeClient:           kubeClient,
				clusterReader:        &util.LabelSelectorReader{},
				instanceTypeProvider: newTestInstanceTypeProvider(),
			}
			_, err := s.CreateLogBackup(tt.args.ctx, tt.args.req)
			if (err != nil) != tt.wantErr {
				t.Errorf("brServer.CreateSnapshotBackup() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			gotBackup := &cloudv1.Backup{}

			if err = s.kubeClient.Get(tt.args.ctx, client.ObjectKey{Name: tt.args.req.Backup.Id, Namespace: tt.args.objs[0].GetNamespace()}, gotBackup); err != nil {
				t.Errorf("Failed to get Backup object: %v", err)
				return
			}
			gotYAML, err := yaml.Marshal(gotBackup)
			fmt.Printf("%s", gotYAML)
			if err != nil {
				t.Errorf("Failed to marshal got object to YAML: %v", err)
				return
			}

			// // generate the expected YAML file
			// err = os.WriteFile(tt.wantYAML, gotYAML, 0644)
			// assert.NoError(t, err, "Failed to write generated YAML to file")
			// t.Logf("Generated YAML written to %s", tt.wantYAML)

			wantYAML, err := os.ReadFile(tt.wantYAML)
			if err != nil {
				t.Errorf("Failed to read want YAML file: %v", err)
				return
			}
			assert.Equal(t, string(wantYAML), string(gotYAML))
		})
	}
}

func TestBRServer_CreateRestore(t *testing.T) {
	type args struct {
		ctx  context.Context
		objs []client.Object
		req  *apiv1.CreateRestoreRequest
	}

	tests := []struct {
		name     string
		args     args
		wantYAML string
		wantErr  bool
	}{
		{
			name: "aws-snapshot-restore",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10958060413666057111",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "eks",
								"cloud.pingcap.com/region":        "us-west-2",
								"tags.tidbcloud.com/cluster":      "10958060413666057111",
								"tags.tidbcloud.com/environment":  "dev",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									AWS: &cloudv1.CloudAWS{
										AdminNodeRole: "arn:aws:iam::12345678123:role/ng-role-nightly-ms-3199728-10958060413666057111-f2e97a12",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateRestoreRequest{
					Restore: &apiv1.Restore{
						Id:             "cluster-10958060413666057111-restore-960547-f5a4a6a5",
						ClusterId:      "10958060413666057111",
						InstanceType:   "c5.xlarge",
						SnapshotBucket: "s3://backup-test-bucket/cluster-10918155850752108499-sr(us-west-2)/test-11595164",
						ImageRepo:      "gcr.io/pingcap-public/dbaas/br",
						ImageTag:       "v8.1.1",
						Configuration:  "{\"options\":[\"--ddl-batch-size=16\",\"--check-requirements=false\",\"--granularity=fine-grained\"]}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/aws-snapshot-restore-infra.yaml",
			wantErr:  false,
		},
		{
			name: "gcp-snapshot-restore",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10599001197235703340",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "gke",
								"cloud.pingcap.com/region":        "us-west1",
								"tags.tidbcloud.com/cluster":      "10599001197235703340",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									GCP: &cloudv1.CloudGCP{
										AdminNodeSa: "np-test-cluster-role@test-project.iam.gserviceaccount.com",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateRestoreRequest{
					Restore: &apiv1.Restore{
						Id:             "cluster-10599001197235703340-restore-960553-ed55adc3",
						ClusterId:      "10599001197235703340",
						InstanceType:   "c5.xlarge",
						SnapshotBucket: "gs://backup-test-bucket/10978899057524291419/test-11595165",
						ImageRepo:      "gcr.io/pingcap-public/dbaas/br",
						ImageTag:       "v8.1.1",
						Configuration:  "{\"options\":[\"--ddl-batch-size=16\",\"--check-requirements=false\",\"--granularity=fine-grained\"]}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/gcp-snapshot-restore-infra.yaml",
			wantErr:  false,
		},
		{
			name: "azure-snapshot-restore",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10284362471633498623",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "aks",
								"cloud.pingcap.com/region":        "eastus",
								"creator":                         "cluster-controller-server",
								"tags.tidbcloud.com/cluster":      "10284362471633498623",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
							},
						},
					},
				},
				req: &apiv1.CreateRestoreRequest{
					Restore: &apiv1.Restore{
						Id:             "cluster-10284362471633498623-restore-960555-f0196e5a",
						ClusterId:      "10284362471633498623",
						InstanceType:   "Standard_D2ps_v5",
						SnapshotBucket: "https://teststorageaccount.blob.core.windows.net/test-container/10171856717194597253/test-11595166?se=2034-11-06T15%3A03%3A32Z&sig=testtesttest%2Ftesttesttesttest%3D&sp=rwdlac&spr=https&srt=co&ss=b&st=2024-11-08T15%3A03%3A32Z&sv=2020-02-10",
						ImageRepo:      "gcr.io/pingcap-public/dbaas/br",
						ImageTag:       "v8.1.1",
						Configuration:  "{\"options\":[\"--ddl-batch-size=16\",\"--check-requirements=false\",\"--granularity=fine-grained\"]}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/azure-snapshot-restore-infra.yaml",
			wantErr:  false,
		},
		{
			name: "aws-log-restore",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10107021350669966373",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "eks",
								"cloud.pingcap.com/region":        "us-west-2",
								"tags.tidbcloud.com/cluster":      "10107021350669966373",
								"tags.tidbcloud.com/environment":  "dev",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									AWS: &cloudv1.CloudAWS{
										AdminNodeRole: "arn:aws:iam::1234456756812:role/ng-role-nightly-ms-3199728-10107021350669966373-2de913b4",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateRestoreRequest{
					Restore: &apiv1.Restore{
						Id:             "cluster-10107021350669966373-restore-960518-7a3affac",
						ClusterId:      "10107021350669966373",
						InstanceType:   "c5.xlarge",
						SnapshotBucket: "s3://backup-test-bucket/cluster-10467160699038821215-sr(us-west-2)/test-11595015",
						LogBucket:      "s3://backup-test-bucket/cluster-10467160699038821215-sr(us-west-2)/log-test-420336",
						PitrTs:         "456390985383936000",
						ImageRepo:      "gcr.io/pingcap-public/dbaas/br",
						ImageTag:       "v8.1.1",
						Configuration:  "{\"options\":[\"--ddl-batch-size=16\",\"--check-requirements=false\",\"--granularity=fine-grained\"]}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/aws-log-restore-infra.yaml",
			wantErr:  false,
		},
		{
			name: "gcp-log-restore",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10704996851453517706",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "gke",
								"cloud.pingcap.com/region":        "us-west1",
								"tags.tidbcloud.com/cluster":      "10704996851453517706",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									GCP: &cloudv1.CloudGCP{
										AdminNodeSa: "np-cluster-node-role@test-project.iam.gserviceaccount.com",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateRestoreRequest{
					Restore: &apiv1.Restore{
						Id:             "cluster-10704996851453517706-restore-960550-370f8d79",
						ClusterId:      "10704996851453517706",
						InstanceType:   "c5.xlarge",
						SnapshotBucket: "gs://backup-test-bucket/10978899057524291419/test-11595165",
						LogBucket:      "gs://backup-test-bucket/10978899057524291419/log-test-420360",
						PitrTs:         "456706141454336000",
						ImageRepo:      "gcr.io/pingcap-public/dbaas/br",
						ImageTag:       "v8.1.1",
						Configuration:  "{\"options\":[\"--ddl-batch-size=16\",\"--check-requirements=false\",\"--granularity=fine-grained\"]}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/gcp-log-restore-infra.yaml",
			wantErr:  false,
		},
		{
			name: "azure-log-restore",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10681438295529227200",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "aks",
								"cloud.pingcap.com/region":        "eastus",
								"creator":                         "cluster-controller-server",
								"tags.tidbcloud.com/cluster":      "10681438295529227200",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
							},
						},
					},
				},
				req: &apiv1.CreateRestoreRequest{
					Restore: &apiv1.Restore{
						Id:             "cluster-10681438295529227200-restore-960526-9917d4d2",
						ClusterId:      "10681438295529227200",
						InstanceType:   "Standard_D2ps_v5",
						SnapshotBucket: "https://teststorageaccount.blob.core.windows.net/test-container/10788950816883309084/test-11595042?se=2034-11-06T15%3A03%3A32Z&sig=testtesttes%2Ftesttesttest%3D&sp=rwdlac&spr=https&srt=co&ss=b&st=2024-11-08T15%3A03%3A32Z&sv=2020-02-10",
						LogBucket:      "https://teststorageaccount.blob.core.windows.net/test-container/10788950816883309084/log-test-420340?se=2034-11-06T15%3A03%3A32Z&sig=testtesttest%2Ftesttesttest%3D&sp=rwdlac&spr=https&srt=co&ss=b&st=2024-11-08T15%3A03%3A32Z&sv=2020-02-10",
						PitrTs:         "456420592713728000",
						ImageRepo:      "gcr.io/pingcap-public/dbaas/br",
						ImageTag:       "v8.1.1",
						Configuration:  "{\"options\":[\"--ddl-batch-size=16\",\"--check-requirements=false\",\"--granularity=fine-grained\"]}",
					},
				},
			},
			wantYAML: "./ut_test_data/br/azure-log-restore-infra.yaml",
			wantErr:  false,
		},
		{
			name: "aws-snapshot-restore-with-env",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10958060413666057111",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "eks",
								"cloud.pingcap.com/region":        "us-west-2",
								"tags.tidbcloud.com/cluster":      "10958060413666057111",
								"tags.tidbcloud.com/environment":  "dev",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									AWS: &cloudv1.CloudAWS{
										AdminNodeRole: "arn:aws:iam::12345678123:role/ng-role-nightly-ms-3199728-10958060413666057111-f2e97a12",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateRestoreRequest{
					Restore: &apiv1.Restore{
						Id:             "cluster-env-restore-960547-f5a4a6a5",
						ClusterId:      "10958060413666057111",
						InstanceType:   "c5.xlarge",
						SnapshotBucket: "s3://backup-test-bucket/cluster-10918155850752108499-sr(us-west-2)/test-11595164",
						ImageRepo:      "gcr.io/pingcap-public/dbaas/br",
						ImageTag:       "v8.1.1",
						Configuration:  "{\"options\":[\"--ddl-batch-size=16\",\"--check-requirements=false\",\"--granularity=fine-grained\"]}",
						Env: map[string]string{
							"RESTORE_MODE": "full",
							"DEBUG":        "true",
						},
					},
				},
			},
			wantYAML: "./ut_test_data/br/aws-snapshot-restore-with-env-infra.yaml",
			wantErr:  false,
		},
		{
			name: "aws-snapshot-restore-with-bucket-region",
			args: args{
				ctx: context.TODO(),
				objs: []client.Object{
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "10958060413666057111",
							Namespace: "nightly-ms-3199728",
							Labels: map[string]string{
								"cloud.pingcap.com/provider-type": "eks",
								"cloud.pingcap.com/region":        "us-west-2",
								"tags.tidbcloud.com/cluster":      "10958060413666057111",
								"tags.tidbcloud.com/environment":  "dev",
								"tags.tidbcloud.com/project":      "3199728",
								"tags.tidbcloud.com/servicetype":  "dedicated",
								"tags.tidbcloud.com/tenant":       "60205",
								"tags.tidbcloud.com/usedby":       "custom-tidb",
							},
						},
						Spec: cloudv1.ClusterSpec{
							Kubernetes: cloudv1.Kubernetes{
								Cloud: cloudv1.Cloud{
									AWS: &cloudv1.CloudAWS{
										AdminNodeRole: "arn:aws:iam::12345678123:role/ng-role-nightly-ms-3199728-10958060413666057111-f2e97a12",
									},
								},
							},
						},
					},
				},
				req: &apiv1.CreateRestoreRequest{
					Restore: &apiv1.Restore{
						Id:             "cluster-10958060413666057111-restore-bucket-region",
						ClusterId:      "10958060413666057111",
						InstanceType:   "c5.xlarge",
						SnapshotBucket: "s3://backup-test-bucket/cluster-10918155850752108499-sr(us-west-2)/test-11595164",
						ImageRepo:      "gcr.io/pingcap-public/dbaas/br",
						ImageTag:       "v8.1.1",
						Configuration:  "{\"options\":[\"--ddl-batch-size=16\",\"--check-requirements=false\",\"--granularity=fine-grained\"]}",
						BucketRegion:   ptr.To("us-east-1"),
					},
				},
			},
			wantYAML: "./ut_test_data/br/aws-snapshot-restore-with-bucket-region-infra.yaml",
			wantErr:  false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			kubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.args.objs...).Build()
			s := &brServer{
				kubeClient:           kubeClient,
				clusterReader:        &util.LabelSelectorReader{},
				instanceTypeProvider: newTestInstanceTypeProvider(),
			}
			_, err := s.CreateRestore(tt.args.ctx, tt.args.req)
			if (err != nil) != tt.wantErr {
				t.Errorf("brServer.CreateSnapshotBackup() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			gotRestore := &cloudv1.Restore{}

			if err = s.kubeClient.Get(tt.args.ctx, client.ObjectKey{Name: tt.args.req.Restore.Id, Namespace: tt.args.objs[0].GetNamespace()}, gotRestore); err != nil {
				t.Errorf("Failed to get Restore object: %v", err)
				return
			}
			gotYAML, err := yaml.Marshal(gotRestore)
			fmt.Printf("%s", gotYAML)
			if err != nil {
				t.Errorf("Failed to marshal got object to YAML: %v", err)
				return
			}

			// // generate the expected YAML file
			// err = os.WriteFile(tt.wantYAML, gotYAML, 0644)
			// assert.NoError(t, err, "Failed to write generated YAML to file")
			// t.Logf("Generated YAML written to %s", tt.wantYAML)

			wantYAML, err := os.ReadFile(tt.wantYAML)
			if err != nil {
				t.Errorf("Failed to read want YAML file: %v", err)
				return
			}
			assert.Equal(t, string(wantYAML), string(gotYAML))
		})
	}
}

func TestBRServer_GetSnapshotBackup(t *testing.T) {
	type args struct {
		ctx  context.Context
		objs []client.Object
		req  *apiv1.GetSnapshotBackupRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *apiv1.SnapshotBackup
		wantErr bool
	}{
		{
			name: "basic",
			args: args{
				ctx: context.TODO(),
				req: &apiv1.GetSnapshotBackupRequest{
					ClusterId: "cluster-123",
					BackupId:  "backup-123",
				},
				objs: []client.Object{
					&cloudv1.Backup{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "backup-123",
							Namespace: "tidb123",
						},
					},
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name:      "cluster-123",
							Namespace: "tidb123",
							Labels: map[string]string{
								"tags.tidbcloud.com/cluster": "cluster-123",
							},
						},
					},
				},
			},
			want: &apiv1.SnapshotBackup{
				Id:     "backup-123",
				Status: apiv1.SnapshotBackup_RUNNING,
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			kubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.args.objs...).Build()
			reader := util.LabelSelectorReader{}
			s := &brServer{
				kubeClient:           kubeClient,
				clusterReader:        &reader,
				instanceTypeProvider: newTestInstanceTypeProvider(),
			}
			got, err := s.GetSnapshotBackup(tt.args.ctx, tt.args.req)
			if (err != nil) != tt.wantErr {
				t.Errorf("brServer.GetSnapshotBackup() error = %v, wantErr %v", err, tt.wantErr)
				return
			}
			assert.Equal(t, tt.want, got)
		})
	}
}

func TestBRServer_DeleteSnapshotBackup(t *testing.T) {
	type args struct {
		ctx  context.Context
		objs []client.Object
		req  *apiv1.DeleteSnapshotBackupRequest
	}
	tests := []struct {
		name    string
		args    args
		want    *apiv1.SnapshotBackup
		wantErr bool
	}{
		{
			name: "basic",
			args: args{
				ctx: context.TODO(),
				req: &apiv1.DeleteSnapshotBackupRequest{
					ClusterId: "cluster-123",
					BackupId:  "backup-123",
				},
				objs: []client.Object{
					&cloudv1.Backup{
						ObjectMeta: metav1.ObjectMeta{
							Name: "backup-123",
						},
					},
					&cloudv1.Cluster{
						ObjectMeta: metav1.ObjectMeta{
							Name: "cluster-123",
							Labels: map[string]string{
								"tags.tidbcloud.com/cluster": "cluster-123",
							},
						},
					},
				},
			},
			want: &apiv1.SnapshotBackup{
				Id: "backup-123",
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			reader := util.LabelSelectorReader{}
			kubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.args.objs...).Build()
			s := &brServer{
				kubeClient:           kubeClient,
				clusterReader:        &reader,
				instanceTypeProvider: newTestInstanceTypeProvider(),
			}
			got, err := s.DeleteSnapshotBackup(tt.args.ctx, tt.args.req)
			if (err != nil) != tt.wantErr {
				t.Errorf("brServer.DeleteSnapshotBackup() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			// Check if the backup is really deleted
			deletedBackup := &cloudv1.Backup{}
			err = s.kubeClient.Get(tt.args.ctx, client.ObjectKey{Name: tt.args.req.BackupId, Namespace: tt.args.objs[0].GetNamespace()}, deletedBackup)
			if tt.wantErr {
				if err == nil {
					t.Errorf("Expected error when getting deleted backup, but got none")
				}
			} else {
				if !apierrors.IsNotFound(err) {
					t.Errorf("Expected not found error when getting deleted backup, but got %v", err)
				}
			}

			assert.Equal(t, tt.want, got)
		})
	}
}

func TestBRServer_DeleteLogBackupAfterClusterDeletion(t *testing.T) {
	tests := []struct {
		name        string
		clusterID   string
		backup      *cloudv1.Backup
		want        *apiv1.LogBackup
		wantErr     bool
		wantDeleted bool
	}{
		{
			name:      "deletes the matching log backup",
			clusterID: "cluster-deleted",
			backup: &cloudv1.Backup{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "log-782067",
					Namespace: "staging-project",
					Labels: map[string]string{
						cloudv1.ClusterTagKey:     "cluster-deleted",
						consts.BackupModeLabelKey: string(cloudv1.BackupModeLog),
					},
				},
			},
			want:        &apiv1.LogBackup{Id: "log-782067"},
			wantDeleted: true,
		},
		{
			name:      "does not delete a backup from another cluster",
			clusterID: "cluster-deleted",
			backup: &cloudv1.Backup{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "log-782067",
					Namespace: "staging-project",
					Labels: map[string]string{
						cloudv1.ClusterTagKey:     "another-cluster",
						consts.BackupModeLabelKey: string(cloudv1.BackupModeLog),
					},
				},
			},
			wantErr: true,
		},
		{
			name:      "does not delete a snapshot backup from the deleted cluster",
			clusterID: "cluster-deleted",
			backup: &cloudv1.Backup{
				ObjectMeta: metav1.ObjectMeta{
					Name:      "log-782067",
					Namespace: "staging-project",
					Labels: map[string]string{
						cloudv1.ClusterTagKey:     "cluster-deleted",
						consts.BackupModeLabelKey: string(cloudv1.BackupModeSnapshot),
					},
				},
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			kubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.backup).Build()
			server := &brServer{
				kubeClient:           kubeClient,
				clusterReader:        &util.LabelSelectorReader{},
				instanceTypeProvider: newTestInstanceTypeProvider(),
			}

			got, err := server.DeleteLogBackup(context.Background(), &apiv1.DeleteLogBackupRequest{
				ClusterId: tt.clusterID,
				BackupId:  tt.backup.Name,
			})
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.want, got)
			}

			err = kubeClient.Get(context.Background(), client.ObjectKeyFromObject(tt.backup), &cloudv1.Backup{})
			if tt.wantDeleted {
				assert.True(t, apierrors.IsNotFound(err), "matching log backup should be deleted")
			} else {
				assert.NoError(t, err)
			}
		})
	}
}

func Test_sortedInstanceTypes(t *testing.T) {
	tests := map[string]struct {
		input []cloudv1.InstanceType
		want  []cloudv1.InstanceType
	}{
		"sorts by memory then CPU": {
			input: []cloudv1.InstanceType{
				{Name: "large-cpu", CPU: *resource.NewQuantity(8, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
				{Name: "small", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI)},
				{Name: "medium", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
				{Name: "medium-large-mem", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI)},
			},
			want: []cloudv1.InstanceType{
				{Name: "small", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI)},
				{Name: "medium", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
				{Name: "large-cpu", CPU: *resource.NewQuantity(8, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
				{Name: "medium-large-mem", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI)},
			},
		},
		"sorts by CPU when memory is equal": {
			input: []cloudv1.InstanceType{
				{Name: "4cpu", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
				{Name: "2cpu", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
				{Name: "8cpu", CPU: *resource.NewQuantity(8, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
			},
			want: []cloudv1.InstanceType{
				{Name: "2cpu", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
				{Name: "4cpu", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
				{Name: "8cpu", CPU: *resource.NewQuantity(8, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI)},
			},
		},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			got := sortedInstanceTypes(tt.input)
			assert.Equal(t, tt.want, got)
		})
	}
}

func Test_getMachineSpecByResourceRequirements(t *testing.T) {
	type args struct {
		provider             *mockInstanceTypeProvider
		resourceRequirements *corev1.ResourceRequirements
	}

	tests := map[string]struct {
		args    args
		want    cloudv1.InstanceType
		wantErr bool
	}{
		"nil requirements match the minimum instance": {
			args: args{
				provider: &mockInstanceTypeProvider{
					instanceTypes: []cloudv1.InstanceType{
						{Name: "small", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
						{Name: "medium", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
					},
				},
				resourceRequirements: nil,
			},
			want:    cloudv1.InstanceType{Name: "small", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
			wantErr: false,
		},
		"finds exact match": {
			args: args{
				provider: &mockInstanceTypeProvider{
					instanceTypes: []cloudv1.InstanceType{
						{Name: "small", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
						{Name: "medium", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
					},
				},
				resourceRequirements: &corev1.ResourceRequirements{
					Limits: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(4, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI),
					},
					Requests: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(2, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI),
					},
				},
			},
			want:    cloudv1.InstanceType{Name: "medium", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
			wantErr: false,
		},
		"finds smallest instance that meets requirements": {
			args: args{
				provider: &mockInstanceTypeProvider{
					instanceTypes: []cloudv1.InstanceType{
						{Name: "small", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
						{Name: "medium", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
						{Name: "large", CPU: *resource.NewQuantity(8, resource.DecimalSI), Memory: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
					},
				},
				resourceRequirements: &corev1.ResourceRequirements{
					Limits: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(3, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(6*1024*1024*1024, resource.BinarySI),
					},
					Requests: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(1, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(2*1024*1024*1024, resource.BinarySI),
					},
				},
			},
			want:    cloudv1.InstanceType{Name: "medium", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
			wantErr: false,
		},
		"skips non-amd64 instances": {
			args: args{
				provider: &mockInstanceTypeProvider{
					instanceTypes: []cloudv1.InstanceType{
						{Name: "arm-small", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "arm64"},
						{Name: "amd-medium", CPU: *resource.NewQuantity(8, resource.DecimalSI), Memory: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
					},
				},
				resourceRequirements: &corev1.ResourceRequirements{
					Limits: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(4, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI),
					},
					Requests: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(2, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI),
					},
				},
			},
			want:    cloudv1.InstanceType{Name: "amd-medium", CPU: *resource.NewQuantity(8, resource.DecimalSI), Memory: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
			wantErr: false,
		},
		"returns error when no matching instance type": {
			args: args{
				provider: &mockInstanceTypeProvider{
					instanceTypes: []cloudv1.InstanceType{
						{Name: "small", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
					},
				},
				resourceRequirements: &corev1.ResourceRequirements{
					Limits: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(16, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(32*1024*1024*1024, resource.BinarySI),
					},
					Requests: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(8, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI),
					},
				},
			},
			want:    cloudv1.InstanceType{},
			wantErr: true,
		},
		"returns error when only non-amd64 instances available": {
			args: args{
				provider: &mockInstanceTypeProvider{
					instanceTypes: []cloudv1.InstanceType{
						{Name: "arm-only", CPU: *resource.NewQuantity(16, resource.DecimalSI), Memory: *resource.NewQuantity(32*1024*1024*1024, resource.BinarySI), Arch: "arm64"},
					},
				},
				resourceRequirements: &corev1.ResourceRequirements{
					Limits: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(4, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI),
					},
					Requests: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(2, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI),
					},
				},
			},
			want:    cloudv1.InstanceType{},
			wantErr: true,
		},
		"requires CPU greater than request and greater than or equal to limit": {
			args: args{
				provider: &mockInstanceTypeProvider{
					instanceTypes: []cloudv1.InstanceType{
						{Name: "exact", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
						{Name: "larger", CPU: *resource.NewQuantity(8, resource.DecimalSI), Memory: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
					},
				},
				resourceRequirements: &corev1.ResourceRequirements{
					Limits: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(4, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI),
					},
					Requests: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(3, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(6*1024*1024*1024, resource.BinarySI),
					},
				},
			},
			want:    cloudv1.InstanceType{Name: "exact", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
			wantErr: false,
		},
		"requires memory greater than request and greater than or equal to limit": {
			args: args{
				provider: &mockInstanceTypeProvider{
					instanceTypes: []cloudv1.InstanceType{
						{Name: "exact-memory", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
						{Name: "more-memory", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
					},
				},
				resourceRequirements: &corev1.ResourceRequirements{
					Limits: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(4, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI),
					},
					Requests: corev1.ResourceList{
						corev1.ResourceCPU:    *resource.NewQuantity(2, resource.DecimalSI),
						corev1.ResourceMemory: *resource.NewQuantity(7*1024*1024*1024, resource.BinarySI),
					},
				},
			},
			want:    cloudv1.InstanceType{Name: "exact-memory", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
			wantErr: false,
		},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			got, err := getMachineSpecByResourceRequirements(tt.args.provider, tt.args.resourceRequirements)
			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.want, got)
			}
		})
	}
}

func Test_brServer_resolveInstanceTypeAndResources(t *testing.T) {
	mockProvider := &mockInstanceTypeProvider{
		instanceTypes: []cloudv1.InstanceType{
			{Name: "c5.2xlarge", CPU: *resource.NewQuantity(8, resource.DecimalSI), Memory: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
			{Name: "c5.large", CPU: *resource.NewQuantity(2, resource.DecimalSI), Memory: *resource.NewQuantity(4*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
			{Name: "c5.xlarge", CPU: *resource.NewQuantity(4, resource.DecimalSI), Memory: *resource.NewQuantity(8*1024*1024*1024, resource.BinarySI), Arch: "amd64"},
		},
	}
	tests := map[string]struct {
		provider             *mockInstanceTypeProvider
		instanceType         string
		resourceRequirements *apiv1.ResourceRequirements
		wantInstanceType     string
		wantErr              bool
	}{
		"uses provided instance type when specified": {
			provider:             mockProvider,
			instanceType:         "c5.xlarge",
			resourceRequirements: nil,
			wantInstanceType:     "c5.xlarge",
			wantErr:              false,
		},
		"uses resourceRequirements to pick instance type when instanceType is empty": {
			provider:     mockProvider,
			instanceType: "",
			resourceRequirements: &apiv1.ResourceRequirements{
				Limits: map[string]string{
					"cpu":    "2",
					"memory": "4Gi",
				},
				Requests: map[string]string{
					"cpu":    "1",
					"memory": "2Gi",
				},
			},
			wantInstanceType: "c5.large",
			wantErr:          false,
		},
		"instanceType is empty and resourceRequirements is nil should pick the minimum instance": {
			provider:             mockProvider,
			instanceType:         "",
			resourceRequirements: nil,
			wantInstanceType:     "c5.large",
			wantErr:              false,
		},
		"both instanceType and resourceRequirements are empty": {
			provider:             mockProvider,
			instanceType:         "",
			resourceRequirements: &apiv1.ResourceRequirements{},
			wantInstanceType:     "c5.large", // the smallest instance type
			wantErr:              false,
		},
		"returns error when instance type not found": {
			provider:             mockProvider,
			instanceType:         "nonexistent",
			resourceRequirements: nil,
			wantInstanceType:     "nonexistent",
			wantErr:              true,
		},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			s := &brServer{
				instanceTypeProvider: tt.provider,
			}
			gotInstanceType, gotResources, err := s.resolveInstanceTypeAndResources(
				context.Background(),
				tt.instanceType,
				tt.resourceRequirements,
			)

			if tt.wantErr {
				assert.Error(t, err)
			} else {
				assert.NoError(t, err)
				assert.Equal(t, tt.wantInstanceType, gotInstanceType)
				assert.NotNil(t, gotResources)
			}
		})
	}
}

// mockInstanceTypeProvider implements gitops.InstanceTypeProvider for testing
type mockInstanceTypeProvider struct {
	instanceTypes []cloudv1.InstanceType
}

func (m *mockInstanceTypeProvider) GetInstanceTypes() ([]cloudv1.InstanceType, error) {
	return m.instanceTypes, nil
}

func (m *mockInstanceTypeProvider) FindInstanceType(name string) (cloudv1.InstanceType, error) {
	for _, it := range m.instanceTypes {
		if it.Name == name {
			return it, nil
		}
	}
	return cloudv1.InstanceType{}, fmt.Errorf("instance type %s not found", name)
}

func Test_convertEnvMapToEnvVars(t *testing.T) {
	tests := map[string]struct {
		input map[string]string
		want  []corev1.EnvVar
	}{
		"empty map returns nil": {
			input: map[string]string{},
			want:  nil,
		},
		"nil map returns nil": {
			input: nil,
			want:  nil,
		},
		"single env var": {
			input: map[string]string{
				"KEY1": "value1",
			},
			want: []corev1.EnvVar{
				{Name: "KEY1", Value: "value1"},
			},
		},
		"multiple env vars": {
			input: map[string]string{
				"LOG_LEVEL": "debug",
				"DEBUG":     "true",
				"TIMEOUT":   "30s",
			},
			want: []corev1.EnvVar{
				{Name: "DEBUG", Value: "true"},
				{Name: "LOG_LEVEL", Value: "debug"},
				{Name: "TIMEOUT", Value: "30s"},
			},
		},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			got := convertEnvMapToEnvVars(tt.input)
			// Since map iteration order is not guaranteed, we need to compare sets
			if tt.want == nil {
				assert.Nil(t, got)
			} else {
				assert.ElementsMatch(t, tt.want, got)
			}
		})
	}
}

func TestBRServer_RawCreateBackupCR(t *testing.T) {
	tests := map[string]struct {
		objs                 []client.Object
		req                  *apiv1.RawCreateBackupCRRequest
		instanceTypeProvider gitops.InstanceTypeProvider
		wantErr              bool
		errCode              string
		// check is an optional per-case validator called on the unmarshaled result Backup.
		check func(t *testing.T, backup *cloudv1.Backup)
	}{
		"Successfully create raw backup CR": {
			objs: []client.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-cluster-id",
						Namespace: "test-namespace",
						Labels: map[string]string{
							"tags.tidbcloud.com/cluster": "test-cluster-id",
						},
					},
				},
			},
			req: func() *apiv1.RawCreateBackupCRRequest {
				backup := &cloudv1.Backup{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "raw-backup-test",
						Namespace: "test-namespace",
					},
					Spec: cloudv1.BackupSpec{
						ClusterName: "test-cluster-id",
						Mode:        ptrTo(cloudv1.BackupModeLog),
						Storage:     ptrTo("s3://test-bucket/backup"),
					},
				}
				content, _ := yaml.Marshal(backup)
				return &apiv1.RawCreateBackupCRRequest{
					ClusterId: "test-cluster-id",
					Content:   string(content),
				}
			}(),
			wantErr: false,
			check: func(t *testing.T, backup *cloudv1.Backup) {
				assert.Equal(t, "raw-backup-test", backup.Name)
			},
		},
		"Cluster not found should return NotFound error": {
			objs: []client.Object{},
			req: &apiv1.RawCreateBackupCRRequest{
				ClusterId: "non-existent-cluster",
				Content:   "",
			},
			wantErr: true,
			errCode: "NotFound",
		},
		"Invalid YAML content should return InvalidArgument error": {
			objs: []client.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-cluster-id",
						Namespace: "test-namespace",
						Labels: map[string]string{
							"tags.tidbcloud.com/cluster": "test-cluster-id",
						},
					},
				},
			},
			req: &apiv1.RawCreateBackupCRRequest{
				ClusterId: "test-cluster-id",
				Content:   "invalid-yaml",
			},
			wantErr: true,
			errCode: "InvalidArgument",
		},
		"Absent InstanceType with satisfiable ResourceRequirements should resolve instance type": {
			objs: []client.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-cluster-id",
						Namespace: "test-namespace",
						Labels: map[string]string{
							"tags.tidbcloud.com/cluster": "test-cluster-id",
						},
					},
				},
			},
			req: func() *apiv1.RawCreateBackupCRRequest {
				backup := &cloudv1.Backup{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "raw-backup-resolve-instance-type",
						Namespace: "test-namespace",
					},
					Spec: cloudv1.BackupSpec{
						ClusterName: "test-cluster-id",
						Mode:        ptrTo(cloudv1.BackupModeLog),
						Storage:     ptrTo("s3://test-bucket/backup"),
						// InstanceType is intentionally absent; should be resolved from ResourceRequirements
						ResourceRequirements: &corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceCPU:    resource.MustParse("2"),
								corev1.ResourceMemory: resource.MustParse("4Gi"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceCPU:    resource.MustParse("4"),
								corev1.ResourceMemory: resource.MustParse("8Gi"),
							},
						},
					},
				}
				content, _ := yaml.Marshal(backup)
				return &apiv1.RawCreateBackupCRRequest{
					ClusterId: "test-cluster-id",
					Content:   string(content),
				}
			}(),
			wantErr: false,
			check: func(t *testing.T, backup *cloudv1.Backup) {
				assert.Equal(t, "raw-backup-resolve-instance-type", backup.Name)
				assert.NotNil(t, backup.Spec.InstanceType, "instance type should be resolved")
				assert.Equal(t, "c5.xlarge", *backup.Spec.InstanceType, "should resolve to the smallest matching instance type")
			},
		},
		"Absent InstanceType with unsatisfiable ResourceRequirements should return Internal error": {
			objs: []client.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-cluster-id",
						Namespace: "test-namespace",
						Labels: map[string]string{
							"tags.tidbcloud.com/cluster": "test-cluster-id",
						},
					},
				},
			},
			req: func() *apiv1.RawCreateBackupCRRequest {
				backup := &cloudv1.Backup{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "raw-backup-no-match",
						Namespace: "test-namespace",
					},
					Spec: cloudv1.BackupSpec{
						ClusterName: "test-cluster-id",
						Mode:        ptrTo(cloudv1.BackupModeLog),
						Storage:     ptrTo("s3://test-bucket/backup"),
						// InstanceType is intentionally absent; requirements exceed all available instance types
						ResourceRequirements: &corev1.ResourceRequirements{
							Requests: corev1.ResourceList{
								corev1.ResourceCPU:    resource.MustParse("1000"),
								corev1.ResourceMemory: resource.MustParse("10Ti"),
							},
							Limits: corev1.ResourceList{
								corev1.ResourceCPU:    resource.MustParse("1000"),
								corev1.ResourceMemory: resource.MustParse("10Ti"),
							},
						},
					},
				}
				content, _ := yaml.Marshal(backup)
				return &apiv1.RawCreateBackupCRRequest{
					ClusterId: "test-cluster-id",
					Content:   string(content),
				}
			}(),
			wantErr: true,
			errCode: "Internal",
		},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			instanceTypeProvider := tt.instanceTypeProvider
			if instanceTypeProvider == nil {
				instanceTypeProvider = newTestInstanceTypeProvider()
			}
			kubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.objs...).Build()
			s := &brServer{
				kubeClient:           kubeClient,
				clusterReader:        &util.LabelSelectorReader{},
				instanceTypeProvider: instanceTypeProvider,
			}
			resp, err := s.RawCreateBackupCR(context.TODO(), tt.req)
			if tt.wantErr {
				assert.Error(t, err)
				return
			}
			assert.NoError(t, err)
			assert.NotNil(t, resp)
			assert.NotEmpty(t, resp.Content)

			// Verify the response can be unmarshaled back to a Backup CR
			var resultBackup cloudv1.Backup
			assert.NoError(t, yaml.Unmarshal([]byte(resp.Content), &resultBackup))
			if tt.check != nil {
				tt.check(t, &resultBackup)
			}
		})
	}
}

func TestBRServer_RawGetBackupCR(t *testing.T) {
	tests := map[string]struct {
		objs    []client.Object
		req     *apiv1.RawGetBackupCRRequest
		wantErr bool
		errCode string
	}{
		"Successfully get raw backup CR": {
			objs: []client.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-cluster-id",
						Namespace: "test-namespace",
						Labels: map[string]string{
							"tags.tidbcloud.com/cluster": "test-cluster-id",
						},
					},
				},
				&cloudv1.Backup{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-backup",
						Namespace: "test-namespace",
					},
					Spec: cloudv1.BackupSpec{
						ClusterName: "test-cluster-id",
						Mode:        ptrTo(cloudv1.BackupModeLog),
						Storage:     ptrTo("s3://test-bucket/backup"),
					},
				},
			},
			req: &apiv1.RawGetBackupCRRequest{
				ClusterId: "test-cluster-id",
				Name:      "test-backup",
			},
			wantErr: false,
		},
		"Cluster not found should return NotFound error": {
			objs: []client.Object{},
			req: &apiv1.RawGetBackupCRRequest{
				ClusterId: "non-existent-cluster",
				Name:      "test-backup",
			},
			wantErr: true,
			errCode: "NotFound",
		},
		"Backup CR not found should return NotFound error": {
			objs: []client.Object{
				&cloudv1.Cluster{
					ObjectMeta: metav1.ObjectMeta{
						Name:      "test-cluster-id",
						Namespace: "test-namespace",
						Labels: map[string]string{
							"tags.tidbcloud.com/cluster": "test-cluster-id",
						},
					},
				},
			},
			req: &apiv1.RawGetBackupCRRequest{
				ClusterId: "test-cluster-id",
				Name:      "non-existent-backup",
			},
			wantErr: true,
			errCode: "NotFound",
		},
	}

	for name, tt := range tests {
		t.Run(name, func(t *testing.T) {
			kubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(tt.objs...).Build()
			s := &brServer{
				kubeClient:           kubeClient,
				clusterReader:        &util.LabelSelectorReader{},
				instanceTypeProvider: newTestInstanceTypeProvider(),
			}
			resp, err := s.RawGetBackupCR(context.TODO(), tt.req)
			if tt.wantErr {
				assert.Error(t, err)
				return
			}
			assert.NoError(t, err)
			assert.NotNil(t, resp)
			assert.NotEmpty(t, resp.Content)

			// Verify the response can be unmarshaled back to a Backup CR
			var resultBackup cloudv1.Backup
			assert.NoError(t, yaml.Unmarshal([]byte(resp.Content), &resultBackup))
			assert.Equal(t, "test-backup", resultBackup.Name)
		})
	}
}

func ptrTo[T any](v T) *T {
	return &v
}

func TestBRServer_CreateRestore_CompactBackupName(t *testing.T) {
	// Minimal cluster fixture reused across cases.
	clusterObj := &cloudv1.Cluster{
		ObjectMeta: metav1.ObjectMeta{
			Name:      "10107021350669966373",
			Namespace: "nightly-ms-3199728",
			Labels: map[string]string{
				"cloud.pingcap.com/provider-type": "eks",
				"cloud.pingcap.com/region":        "us-west-2",
				"tags.tidbcloud.com/cluster":      "10107021350669966373",
				"tags.tidbcloud.com/project":      "3199728",
				"tags.tidbcloud.com/servicetype":  "dedicated",
				"tags.tidbcloud.com/tenant":       "60205",
				"tags.tidbcloud.com/usedby":       "custom-tidb",
			},
			ResourceVersion: "1",
		},
		Spec: cloudv1.ClusterSpec{
			Kubernetes: cloudv1.Kubernetes{
				Cloud: cloudv1.Cloud{
					AWS: &cloudv1.CloudAWS{
						AdminNodeRole: "arn:aws:iam::123456789:role/ng-role",
					},
				},
			},
		},
	}

	pitrReq := func(name string) *apiv1.CreateRestoreRequest {
		return &apiv1.CreateRestoreRequest{
			Restore: &apiv1.Restore{
				Id:                "restore-cbn-pitr",
				ClusterId:         "10107021350669966373",
				InstanceType:      "c5.xlarge",
				SnapshotBucket:    "s3://bucket/snapshot",
				LogBucket:         "s3://bucket/log",
				PitrTs:            "456390985383936000",
				ImageRepo:         "gcr.io/pingcap-public/dbaas/br",
				ImageTag:          "v8.1.1",
				CompactBackupName: name,
			},
		}
	}
	snapshotReq := func(name string) *apiv1.CreateRestoreRequest {
		return &apiv1.CreateRestoreRequest{
			Restore: &apiv1.Restore{
				Id:                "restore-cbn-snapshot",
				ClusterId:         "10107021350669966373",
				InstanceType:      "c5.xlarge",
				SnapshotBucket:    "s3://bucket/snapshot",
				ImageRepo:         "gcr.io/pingcap-public/dbaas/br",
				ImageTag:          "v8.1.1",
				CompactBackupName: name,
			},
		}
	}

	cases := []struct {
		name     string
		req      *apiv1.CreateRestoreRequest
		wantErr  bool
		wantSpec string // expected Spec.CompactBackupName value; "" means absent
	}{
		{
			name:     "PiTR restore with compact_backup_name sets Spec.CompactBackupName",
			req:      pitrReq("pitr-compaction-test-20260430"),
			wantSpec: "pitr-compaction-test-20260430",
		},
		{
			name:    "snapshot restore with compact_backup_name is rejected",
			req:     snapshotReq("pitr-compaction-test-20260430"),
			wantErr: true,
		},
		{
			name:     "PiTR restore without compact_backup_name leaves Spec.CompactBackupName empty",
			req:      pitrReq(""),
			wantSpec: "",
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			kubeClient := fake.NewClientBuilder().WithScheme(scheme.Scheme).WithObjects(clusterObj).Build()
			s := &brServer{
				kubeClient:           kubeClient,
				clusterReader:        &util.LabelSelectorReader{},
				instanceTypeProvider: newTestInstanceTypeProvider(),
			}
			_, err := s.CreateRestore(context.Background(), tc.req)
			if tc.wantErr {
				assert.Error(t, err)
				return
			}
			assert.NoError(t, err)

			got := &cloudv1.Restore{}
			assert.NoError(t, kubeClient.Get(context.Background(),
				client.ObjectKey{Name: tc.req.Restore.Id, Namespace: clusterObj.Namespace}, got))

			val := got.Spec.CompactBackupName
			assert.Equal(t, tc.wantSpec, val)
		})
	}
}
