package tioperator

import (
	"context"
	"testing"

	"github.com/pingcap/tidb-operator/api/v2/core/v1alpha1"
	"github.com/samber/lo"
	"github.com/stretchr/testify/assert"
	"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"
	schemav1 "github.com/tidbcloud/cluster-service-ng/pkg/storage/etcd/schema/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

func TestGetStorageType(t *testing.T) {
	tests := []struct {
		name string
		want string
	}{
		{
			name: "gp3-3000-400",
			want: "gp3",
		},
		{
			name: "gp3-6000-625",
			want: "gp3",
		},
		{
			name: "io2-5000",
			want: "io2",
		},
		{
			name: "local-ssd",
			want: "local-ssd",
		},
		{
			name: "gp2",
			want: "gp2",
		},
		{
			name: "cloud-auto",
			want: "cloud-auto",
		},
		{
			name: "",
			want: "",
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := getStorageType(tt.name); got != tt.want {
				t.Errorf("GetStorageType() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestSplitImageRepoTag(t *testing.T) {
	tests := []struct {
		name     string
		image    *string
		wantRepo string
		wantTag  string
	}{
		{
			name:     "nil image",
			image:    nil,
			wantRepo: "",
			wantTag:  "",
		},
		{
			name:     "empty image",
			image:    lo.ToPtr(""),
			wantRepo: "",
			wantTag:  "",
		},
		{
			name:     "image with tag",
			image:    lo.ToPtr("pingcap/tidb:v8.2.0"),
			wantRepo: "pingcap/tidb",
			wantTag:  "v8.2.0",
		},
		{
			name:     "image without tag",
			image:    lo.ToPtr("pingcap/tidb"),
			wantRepo: "pingcap/tidb",
			wantTag:  "",
		},
		{
			name:     "registry with port and tag",
			image:    lo.ToPtr("registry.example.com:5000/pingcap/tidb:v8.2.0"),
			wantRepo: "registry.example.com:5000/pingcap/tidb",
			wantTag:  "v8.2.0",
		},
		{
			name:     "digest only",
			image:    lo.ToPtr("pingcap/tidb@sha256:abc123"),
			wantRepo: "pingcap/tidb",
			wantTag:  "",
		},
		{
			name:     "tag with digest",
			image:    lo.ToPtr("pingcap/tidb:v8.2.0@sha256:abc123"),
			wantRepo: "pingcap/tidb",
			wantTag:  "v8.2.0",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo, tag := splitImageRepoTag(tt.image)
			assert.Equal(t, tt.wantRepo, repo)
			assert.Equal(t, tt.wantTag, tag)
		})
	}
}

func TestGetStorageIops(t *testing.T) {
	tests := []struct {
		name string
		want uint32
	}{
		{
			name: "gp3-3000-400",
			want: 3000,
		},
		{
			name: "gp3-6000-625",
			want: 6000,
		},
		{
			name: "io2-5000",
			want: 5000,
		},
		{
			name: "local-ssd",
			want: 0,
		},
		{
			name: "gp2",
			want: 0,
		},
		{
			name: "invalid-format",
			want: 0,
		},
		{
			name: "gp3-invalid-400",
			want: 0,
		},
		{
			name: "cloud-auto-4000",
			want: 4000,
		},
		{
			name: "cloud-auto-8000",
			want: 8000,
		},
		{
			name: "cloud-auto-incomplete",
			want: 0,
		},
		{
			name: "cloud-auto",
			want: 0,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := getStorageIops(tt.name); got != tt.want {
				t.Errorf("GetStorageIops() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestGetStorageThroughput(t *testing.T) {
	tests := []struct {
		name string
		want uint32
	}{
		{
			name: "gp3-3000-400",
			want: 400,
		},
		{
			name: "gp3-6000-625",
			want: 625,
		},
		{
			name: "io2-5000",
			want: 0,
		},
		{
			name: "local-ssd",
			want: 0,
		},
		{
			name: "gp2",
			want: 0,
		},
		{
			name: "invalid-format",
			want: 0,
		},
		{
			name: "gp3-3000-invalid",
			want: 0,
		},
		{
			name: "cloud-auto-8000",
			want: 0,
		},
		{
			name: "cloud-auto-incomplete",
			want: 0,
		},
		{
			name: "cloud-auto-3000",
			want: 0,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := getStorageThroughput(tt.name); got != tt.want {
				t.Errorf("GetStorageThroughput() = %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCvtStorageNameToEn(t *testing.T) {
	tests := []struct {
		name        string
		inputName   string
		expected    apiv1.Component_Storage_Name
		expectError bool
	}{
		{
			name:      "data storage",
			inputName: string(schemav1.StorageData),
			expected:  apiv1.Component_Storage_DATA,
		},
		{
			name:      "raftlog storage",
			inputName: string(schemav1.StorageRaftLog),
			expected:  apiv1.Component_Storage_RAFTLOG,
		},
		{
			name:      "raftdb storage",               // Critical test for your fix
			inputName: string(schemav1.StorageRaftDB), // deprecated
			expected:  apiv1.Component_Storage_RAFTDB, // deprecated
		},
		{
			name:      "slowlog storage",
			inputName: string(schemav1.StorageSlowLog),
			expected:  apiv1.Component_Storage_SLOWLOG,
		},
		{
			name:        "unknown storage",
			inputName:   "unknown",
			expected:    apiv1.Component_Storage_NAME_UNSPECIFIED,
			expectError: true,
		},
		{
			name:        "empty storage name",
			inputName:   "",
			expected:    apiv1.Component_Storage_NAME_UNSPECIFIED,
			expectError: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := cvtStorageNameToEn(tt.inputName)

			if tt.expectError {
				require.Error(t, err)
				assert.Equal(t, tt.expected, result)
			}
		})
	}
}

func TestCvtStoragesToEn(t *testing.T) {
	tests := []struct {
		name        string
		volumes     []v1alpha1.Volume
		expected    []*entity.Storage
		expectError bool
		errorMsg    string
	}{
		{
			name:     "empty volumes",
			volumes:  []v1alpha1.Volume{},
			expected: nil,
		},
		{
			name: "valid gp3 storage",
			volumes: []v1alpha1.Volume{
				{
					Name:                      "data",
					StorageClassName:          stringPtr("cloud-ssd"),
					VolumeAttributesClassName: stringPtr("gp3-3000-400"),
					Storage:                   resource.MustParse("100Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/data"},
					},
				},
			},
			expected: []*entity.Storage{
				{
					Name:          apiv1.Component_Storage_DATA,
					Type:          "gp3",
					SizeGi:        100,
					Iops:          3000,
					ThroughputMbs: 400,
					MountPath:     "/data",
				},
			},
		},
		{
			name: "critical test: raftdb storage support",
			volumes: []v1alpha1.Volume{
				{
					Name:                      "raftdb",
					StorageClassName:          stringPtr("cloud-ssd"),
					VolumeAttributesClassName: stringPtr("gp3-3000-125"),
					Storage:                   resource.MustParse("50Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/var/lib/raft-db"},
					},
				},
			},
			expected: []*entity.Storage{
				{
					Name:          apiv1.Component_Storage_RAFTDB,
					Type:          "gp3",
					SizeGi:        50,
					Iops:          3000,
					ThroughputMbs: 125,
					MountPath:     "/var/lib/raft-db",
				},
			},
		},
		{
			name: "valid local-ssd storage with zero iops",
			volumes: []v1alpha1.Volume{
				{
					Name:             "data",
					StorageClassName: stringPtr("local-ssd"),
					Storage:          resource.MustParse("500Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/data"},
					},
				},
			},
			expected: []*entity.Storage{
				{
					Name:          apiv1.Component_Storage_DATA,
					Type:          "local-ssd",
					SizeGi:        500,
					Iops:          0, // Local SSD can have zero IOPS
					ThroughputMbs: 0,
					MountPath:     "/data",
				},
			},
		},
		{
			name: "critical error: non-local disk with zero iops",
			volumes: []v1alpha1.Volume{
				{
					Name:                      "data",
					StorageClassName:          stringPtr("cloud-ssd"), // Non-local disk
					VolumeAttributesClassName: stringPtr("gp3-0-400"), // Zero IOPS!
					Storage:                   resource.MustParse("100Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/data"},
					},
				},
			},
			expectError: true,
			errorMsg:    "invalid iops",
		},
		{
			name: "error: unknown storage name",
			volumes: []v1alpha1.Volume{
				{
					Name:                      "unknown-storage",
					StorageClassName:          stringPtr("unknown-type"),
					VolumeAttributesClassName: stringPtr("gp3-3000-400"),
					Storage:                   resource.MustParse("100Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/data"},
					},
				},
			},
			expectError: true,
			errorMsg:    "unspecified storage",
		},
		{
			name: "cloud-auto storage requires IOPS",
			volumes: []v1alpha1.Volume{
				{
					Name:             "data",
					StorageClassName: stringPtr("cloud-ssd"),
					Storage:          resource.MustParse("300Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/data"},
					},
				},
			},
			expectError: true,
			errorMsg:    "invalid iops",
		},
		{
			name: "valid cloud-auto storage with VolumeAttributesClassName",
			volumes: []v1alpha1.Volume{
				{
					Name:                      "data",
					StorageClassName:          stringPtr("cloud-ssd"),
					VolumeAttributesClassName: stringPtr("cloud-auto-4000"),
					Storage:                   resource.MustParse("300Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/data"},
					},
				},
			},
			expected: []*entity.Storage{
				{
					Name:          apiv1.Component_Storage_DATA,
					Type:          "cloud-auto",
					SizeGi:        300,
					Iops:          4000,
					ThroughputMbs: 0,
					MountPath:     "/data",
				},
			},
		},
		{
			name: "multiple volumes with mixed storage types",
			volumes: []v1alpha1.Volume{
				{
					Name:                      "data",
					StorageClassName:          stringPtr("cloud-ssd"),
					VolumeAttributesClassName: stringPtr("gp3-6000-625"),
					Storage:                   resource.MustParse("500Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/data"},
					},
				},
				{
					Name:                      "raftlog",
					StorageClassName:          stringPtr("cloud-ssd"),
					VolumeAttributesClassName: stringPtr("io2-10000"),
					Storage:                   resource.MustParse("100Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/var/lib/raft-log"},
					},
				},
			},
			expected: []*entity.Storage{
				{
					Name:          apiv1.Component_Storage_DATA,
					Type:          "gp3",
					SizeGi:        500,
					Iops:          6000,
					ThroughputMbs: 625,
					MountPath:     "/data",
				},
				{
					Name:          apiv1.Component_Storage_RAFTLOG,
					Type:          "io2",
					SizeGi:        100,
					Iops:          10000,
					ThroughputMbs: 0,
					MountPath:     "/var/lib/raft-log",
				},
			},
		},
		{
			name: "io2 storage without IOPS specified",
			volumes: []v1alpha1.Volume{
				{
					Name:             "data",
					StorageClassName: stringPtr("cloud-ssd"),
					Storage:          resource.MustParse("100Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/data"},
					},
				},
			},
			expectError: true,
			errorMsg:    "invalid iops",
		},
		{
			name: "gp2 storage requires IOPS in current implementation",
			volumes: []v1alpha1.Volume{
				{
					Name:             "data",
					StorageClassName: stringPtr("cloud-ssd"),
					Storage:          resource.MustParse("100Gi"),
					Mounts: []v1alpha1.VolumeMount{
						{MountPath: "/data"},
					},
				},
			},
			expectError: true,
			errorMsg:    "invalid iops",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ctx := context.Background()
			result, err := cvtStoragesToEn(ctx, tt.volumes)

			if tt.expectError {
				require.Error(t, err)
				if tt.errorMsg != "" {
					assert.Contains(t, err.Error(), tt.errorMsg)
				}
			} else {
				require.NoError(t, err)
				assert.Equal(t, tt.expected, result)
			}
		})
	}
}

// Helper function to create string pointer
func stringPtr(s string) *string {
	return &s
}

func TestDataplaneNamespace(t *testing.T) {
	tests := []struct {
		name      string
		clusterId string
		expected  string
	}{
		{
			name:      "simple cluster id",
			clusterId: "test-cluster",
			expected:  "tidbtest-cluster",
		},
		{
			name:      "empty cluster id",
			clusterId: "",
			expected:  "tidb",
		},
		{
			name:      "cluster id with special chars",
			clusterId: "cluster-123-abc",
			expected:  "tidbcluster-123-abc",
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got := dataplaneNamespace(tc.clusterId)
			if got != tc.expected {
				t.Errorf("dataplaneNamespace() = %v, want %v", got, tc.expected)
			}
		})
	}
}

func TestIsReady(t *testing.T) {
	tests := []struct {
		name       string
		conditions []metav1.Condition
		expected   bool
	}{
		{
			name: "ready condition true",
			conditions: []metav1.Condition{
				{Type: v1alpha1.CondReady, Status: metav1.ConditionTrue},
			},
			expected: true,
		},
		{
			name: "ready condition false",
			conditions: []metav1.Condition{
				{Type: v1alpha1.CondReady, Status: metav1.ConditionFalse},
			},
			expected: false,
		},
		{
			name:       "no conditions",
			conditions: nil,
			expected:   false,
		},
		{
			name: "ready condition unknown",
			conditions: []metav1.Condition{
				{Type: v1alpha1.CondReady, Status: metav1.ConditionUnknown},
			},
			expected: false,
		},
		{
			name: "multiple conditions with ready true",
			conditions: []metav1.Condition{
				{Type: v1alpha1.CondSynced, Status: metav1.ConditionTrue},
				{Type: v1alpha1.CondReady, Status: metav1.ConditionTrue},
			},
			expected: true,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got := isReady(tc.conditions)
			if got != tc.expected {
				t.Errorf("isReady() = %v, want %v", got, tc.expected)
			}
		})
	}
}

func TestIsSynced(t *testing.T) {
	tests := []struct {
		name       string
		conditions []metav1.Condition
		expected   bool
	}{
		{
			name: "synced condition true",
			conditions: []metav1.Condition{
				{Type: v1alpha1.CondSynced, Status: metav1.ConditionTrue},
			},
			expected: true,
		},
		{
			name: "synced condition false",
			conditions: []metav1.Condition{
				{Type: v1alpha1.CondSynced, Status: metav1.ConditionFalse},
			},
			expected: false,
		},
		{
			name:       "no conditions",
			conditions: nil,
			expected:   false,
		},
		{
			name: "synced condition unknown",
			conditions: []metav1.Condition{
				{Type: v1alpha1.CondSynced, Status: metav1.ConditionUnknown},
			},
			expected: false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got := isSynced(tc.conditions)
			if got != tc.expected {
				t.Errorf("isSynced() = %v, want %v", got, tc.expected)
			}
		})
	}
}

func TestIsUpToDate(t *testing.T) {
	tests := []struct {
		name         string
		generation   int64
		commonStatus v1alpha1.CommonStatus
		expected     bool
	}{
		{
			name:       "up to date",
			generation: 1,
			commonStatus: v1alpha1.CommonStatus{
				CurrentRevision:    "rev1",
				UpdateRevision:     "rev1",
				ObservedGeneration: 1,
			},
			expected: true,
		},
		{
			name:       "different revisions",
			generation: 1,
			commonStatus: v1alpha1.CommonStatus{
				CurrentRevision:    "rev1",
				UpdateRevision:     "rev2",
				ObservedGeneration: 1,
			},
			expected: false,
		},
		{
			name:       "different generation",
			generation: 2,
			commonStatus: v1alpha1.CommonStatus{
				CurrentRevision:    "rev1",
				UpdateRevision:     "rev1",
				ObservedGeneration: 1,
			},
			expected: false,
		},
		{
			name:       "both different",
			generation: 2,
			commonStatus: v1alpha1.CommonStatus{
				CurrentRevision:    "rev1",
				UpdateRevision:     "rev2",
				ObservedGeneration: 1,
			},
			expected: false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			got := isUpToDate(tc.generation, tc.commonStatus)
			if got != tc.expected {
				t.Errorf("isUpToDate() = %v, want %v", got, tc.expected)
			}
		})
	}
}

func TestComputeAvailableMemory(t *testing.T) {
	tests := []struct {
		name        string
		memoryInMiB uint32
		expected    uint64
	}{
		{
			name:        "0 MiB",
			memoryInMiB: 0,
			expected:    0,
		},
		{
			name:        "45 MiB - very small memory",
			memoryInMiB: 45,
			expected:    0,
		},
		{
			name:        "2GB - minimum practical value",
			memoryInMiB: 2 * 1024,
			expected:    0,
		},
		{
			name:        "4GB",
			memoryInMiB: 4 * 1024,
			expected:    1536,
		},
		{
			name:        "6GB",
			memoryInMiB: 6 * 1024,
			expected:    3174,
		},
		{
			name:        "8GB",
			memoryInMiB: 8 * 1024,
			expected:    4812,
		},
		{
			name:        "12GB",
			memoryInMiB: 12 * 1024,
			expected:    9522,
		},
		{
			name:        "16GB",
			memoryInMiB: 16 * 1024,
			expected:    13208,
		},
		{
			name:        "32GB",
			memoryInMiB: 32 * 1024,
			expected:    27585,
		},
		{
			name:        "64GB",
			memoryInMiB: 64 * 1024,
			expected:    58387,
		},
		{
			name:        "128GB",
			memoryInMiB: 128 * 1024,
			expected:    119991,
		},
		{
			name:        "256GB",
			memoryInMiB: 256 * 1024,
			expected:    248443,
		},
		{
			name:        "512GB",
			memoryInMiB: 512 * 1024,
			expected:    505344,
		},
		{
			name:        "1TB",
			memoryInMiB: 1024 * 1024,
			expected:    1019146,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ComputeAvailableMemory(uint64(tt.memoryInMiB))
			assert.Equal(t, tt.expected, got, "ComputeAvailableMemory(%d) = %d, want %d", tt.memoryInMiB, got, tt.expected)
		})
	}
}

func TestComputeRequestMemory(t *testing.T) {
	tests := []struct {
		name       string
		availMem   uint64
		sidecarMem uint64
		expected   uint64
	}{
		{
			name:       "ALI 2C4G with TiDB sidecar",
			availMem:   1943,
			sidecarMem: TiDBSidecarMemoryMiB,
			expected:   1426, // 1943 - 261 - 256
		},
		{
			name:       "ALI 2C4G sidecar 0",
			availMem:   1943,
			sidecarMem: 0,
			expected:   1687, // 1943 - 256
		},
		{
			name:       "AWS 2C4G with TiDB sidecar",
			availMem:   2039,
			sidecarMem: TiDBSidecarMemoryMiB,
			expected:   1522, // 2039 - 261 - 256
		},
		{
			name:       "AWS 4C8G with TiDB sidecar",
			availMem:   6169,
			sidecarMem: TiDBSidecarMemoryMiB,
			expected:   5600, // 6169 - 261 - 308
		},
		{
			name:       "AWS 4C8G sidecar 0",
			availMem:   6169,
			sidecarMem: 0,
			expected:   5861, // 6169 - 308
		},
		{
			name:       "AWS 8C32G with TiDB sidecar reserve capped at 1024",
			availMem:   29899,
			sidecarMem: TiDBSidecarMemoryMiB,
			expected:   28614, // 29899 - 261 - 1024
		},
		{
			name:       "AWS 8C32G sidecar 0",
			availMem:   29899,
			sidecarMem: 0,
			expected:   28875, // 29899 - 1024
		},
		{
			name:       "avail smaller than sidecar",
			availMem:   100,
			sidecarMem: TiDBSidecarMemoryMiB,
			expected:   0,
		},
		{
			name:       "reserve would underflow so keep avail minus sidecar",
			availMem:   200,
			sidecarMem: 0,
			expected:   200,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ComputeRequestMemory(tt.availMem, tt.sidecarMem)
			assert.Equal(t, tt.expected, got)
		})
	}
}

func TestComputeRequestCPU(t *testing.T) {
	tests := []struct {
		name       string
		availCPU   uint64
		sidecarCPU uint64
		expected   uint64
	}{
		{
			name:       "2C availableCpu=1 hits 100m floor",
			availCPU:   1000,
			sidecarCPU: TiDBSidecarCPUMilli,
			expected:   860, // 1000 - 40 - 100
		},
		{
			name:       "worker 2C hits 100m floor",
			availCPU:   1000,
			sidecarCPU: TiDBWorkerSidecarCPUMilli,
			expected:   800, // 1000 - 100 - 100
		},
		{
			name:       "4C milli stays inside 100-200 band",
			availCPU:   3470,
			sidecarCPU: TiDBSidecarCPUMilli,
			expected:   3257, // 3470 - 40 - 173
		},
		{
			name:       "8C milli hits 200m cap",
			availCPU:   7460,
			sidecarCPU: TiDBSidecarCPUMilli,
			expected:   7220, // 7460 - 40 - 200
		},
		{
			name:       "avail smaller than sidecar",
			availCPU:   20,
			sidecarCPU: TiDBSidecarCPUMilli,
			expected:   0,
		},
		{
			name:       "reserve would underflow so keep avail minus sidecar",
			availCPU:   80,
			sidecarCPU: 0,
			expected:   80,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ComputeRequestCPU(tt.availCPU, tt.sidecarCPU)
			assert.Equal(t, tt.expected, got)
		})
	}
}

func TestSidecarResourcesFor(t *testing.T) {
	tests := []struct {
		name     string
		compType apiv1.ComponentType
		wantCPU  uint64
		wantMem  uint64
	}{
		{name: "tidb", compType: apiv1.ComponentType_TIDB, wantCPU: TiDBSidecarCPUMilli, wantMem: TiDBSidecarMemoryMiB},
		{name: "tidb worker", compType: apiv1.ComponentType_TIDB_WORKER, wantCPU: TiDBWorkerSidecarCPUMilli, wantMem: TiDBWorkerSidecarMemoryMiB},
		{name: "tiflash write", compType: apiv1.ComponentType_TIFLASH_WRITE, wantCPU: TiFlashSidecarCPUMilli, wantMem: TiFlashSidecarMemoryMiB},
		{name: "tiflash compute", compType: apiv1.ComponentType_TIFLASH_COMPUTE, wantCPU: TiFlashSidecarCPUMilli, wantMem: TiFlashSidecarMemoryMiB},
		{name: "tiflash learner", compType: apiv1.ComponentType_TIFLASH_LEARNER, wantCPU: TiFlashSidecarCPUMilli, wantMem: TiFlashSidecarMemoryMiB},
		{name: "tiflash compute learner", compType: apiv1.ComponentType_TIFLASH_COMPUTE_LEARNER, wantCPU: TiFlashSidecarCPUMilli, wantMem: TiFlashSidecarMemoryMiB},
		{name: "tikv", compType: apiv1.ComponentType_TIKV, wantCPU: 0, wantMem: 0},
		{name: "pd", compType: apiv1.ComponentType_PD, wantCPU: 0, wantMem: 0},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			gotCPU, gotMem := SidecarResourcesFor(tt.compType)
			assert.Equal(t, tt.wantCPU, gotCPU)
			assert.Equal(t, tt.wantMem, gotMem)
		})
	}
}

func TestEffectiveNodeSpecFallbacks(t *testing.T) {
	t.Run("available cpu milli prefers explicit milli", func(t *testing.T) {
		ns := &entity.NodeSpec{Cpu: 8, AvailableCpu: 7, AvailableCpuMilli: 7460}
		assert.Equal(t, uint64(7460), EffectiveAvailableCPUMilli(ns))
	})
	t.Run("available cpu milli falls back to whole cores", func(t *testing.T) {
		ns := &entity.NodeSpec{Cpu: 8, AvailableCpu: 7}
		assert.Equal(t, uint64(7000), EffectiveAvailableCPUMilli(ns))
	})
	t.Run("available cpu milli falls back to cpu minus one", func(t *testing.T) {
		ns := &entity.NodeSpec{Cpu: 8}
		assert.Equal(t, uint64(7000), EffectiveAvailableCPUMilli(ns))
	})
	t.Run("max cpu milli falls back to available milli", func(t *testing.T) {
		ns := &entity.NodeSpec{AvailableCpuMilli: 7460}
		assert.Equal(t, uint64(7460), EffectiveMaxCPUMilli(ns))
	})
	t.Run("max cpu milli uses explicit max", func(t *testing.T) {
		ns := &entity.NodeSpec{AvailableCpuMilli: 7460, MaxCpuMilli: 7910}
		assert.Equal(t, uint64(7910), EffectiveMaxCPUMilli(ns))
	})
	t.Run("max memory falls back to available", func(t *testing.T) {
		ns := &entity.NodeSpec{AvailableMemoryMi: 1943}
		assert.Equal(t, uint64(1943), EffectiveMaxMemoryMi(ns))
	})
	t.Run("max memory uses explicit max", func(t *testing.T) {
		ns := &entity.NodeSpec{AvailableMemoryMi: 1943, MaxMemoryMi: 2843}
		assert.Equal(t, uint64(2843), EffectiveMaxMemoryMi(ns))
	})
	t.Run("nil node spec", func(t *testing.T) {
		assert.Equal(t, uint64(0), EffectiveAvailableCPUMilli(nil))
		assert.Equal(t, uint64(0), EffectiveMaxCPUMilli(nil))
		assert.Equal(t, uint64(0), EffectiveMaxMemoryMi(nil))
	})
}

func TestCvtNodeSpecToQuota_NodeVersion(t *testing.T) {
	tests := []struct {
		name                string
		labels              map[string]string
		annotations         map[string]string
		replicas            uint32
		nodeSelector        map[string]string
		expectedNodeVersion string
	}{
		{
			name: "nodeSelector present with k8s version",
			labels: map[string]string{
				schemav1.LabelNodeSpecTypeKey: "8c16g",
			},
			annotations: map[string]string{},
			replicas:    3,
			nodeSelector: map[string]string{
				schemav1.K8sVersionLabelKey: "1.28",
			},
			expectedNodeVersion: "1.28",
		},
		{
			name: "nodeSelector present with different k8s version",
			labels: map[string]string{
				schemav1.LabelNodeSpecTypeKey: "8c16g",
			},
			annotations: map[string]string{},
			replicas:    2,
			nodeSelector: map[string]string{
				schemav1.K8sVersionLabelKey: "1.29",
				"other-key":                 "other-value",
			},
			expectedNodeVersion: "1.29",
		},
		{
			name: "nodeSelector missing k8s version key",
			labels: map[string]string{
				schemav1.LabelNodeSpecTypeKey: "8c16g",
			},
			annotations: map[string]string{},
			replicas:    1,
			nodeSelector: map[string]string{
				"other-key": "other-value",
			},
			expectedNodeVersion: "",
		},
		{
			name: "nodeSelector is nil",
			labels: map[string]string{
				schemav1.LabelNodeSpecTypeKey: "8c16g",
			},
			annotations:         map[string]string{},
			replicas:            1,
			nodeSelector:        nil,
			expectedNodeVersion: "",
		},
		{
			name: "nodeSelector is empty map",
			labels: map[string]string{
				schemav1.LabelNodeSpecTypeKey: "8c16g",
			},
			annotations:         map[string]string{},
			replicas:            1,
			nodeSelector:        map[string]string{},
			expectedNodeVersion: "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			quota, err := CvtNodeSpecToQuota(tt.labels, tt.annotations, tt.replicas, nil, tt.nodeSelector)
			require.NoError(t, err)
			require.NotNil(t, quota)
			require.NotNil(t, quota.NodeSpec)
			assert.Equal(t, tt.expectedNodeVersion, quota.NodeSpec.NodeVersion, "NodeVersion mismatch")
		})
	}
}
