/*
 * Copyright (c) 2025 Huawei Technologies Co., Ltd.
 * openFuyao is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */

package plugin

import (
	"testing"

	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	v1 "k8s.io/api/core/v1"

	"volcano.sh/volcano/pkg/scheduler/plugins/volcano-xpu-plugin/common"
	"volcano.sh/volcano/pkg/scheduler/plugins/volcano-xpu-plugin/util"
)

func TestIsDeviceQualified(t *testing.T) {
	tests := []struct {
		name       string
		xpuDevice  *common.XPUDevice
		val        *util.ContainerResource
		want       bool
		realMemory int
	}{
		{
			name:      "nil xpuDevices",
			xpuDevice: nil,
			val: &util.ContainerResource{
				ReqXPUCores: 50,
				ReqXPUMem:   1024,
			},
			want: false,
		},
		{
			name: "nil val",
			xpuDevice: &common.XPUDevice{
				DieID:  "device-0",
				Memory: 8192,
				Count:  4,
			},
			val:  nil,
			want: false,
		},
		{
			name: "index out of range",
			xpuDevice: &common.XPUDevice{
				DieID:    "device-0",
				Memory:   8192,
				Count:    4,
				UsedVids: 0b1111,
			},
			val: &util.ContainerResource{
				ReqXPUCores: 50,
				ReqXPUMem:   1024,
			},
			want: false,
		},
		{
			name: "device count exhausted",
			xpuDevice: &common.XPUDevice{
				DieID:    "device-0",
				Memory:   8192,
				Count:    2,
				UsedVids: 0b11,
			},
			val: &util.ContainerResource{
				ReqXPUCores: 50,
				ReqXPUMem:   1024,
			},
			want: false,
		},
		{
			name: "device count over exhausted",
			xpuDevice: &common.XPUDevice{
				DieID:    "device-0",
				Memory:   8192,
				Count:    2,
				UsedVids: 0b111,
			},
			val: &util.ContainerResource{
				ReqXPUCores: 50,
				ReqXPUMem:   1024,
			},
			want: false,
		},
		{
			name: "insufficient memory with absolute value",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Memory:     8192,
				UsedMemory: 7000,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  20,
			},
			val: &util.ContainerResource{
				ReqXPUCores: 50,
				ReqXPUMem:   2000,
			},
			want: false,
		},
		{
			name: "memory percentage calculation",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Memory:     10000,
				UsedMemory: 5000,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  20,
			},
			val: &util.ContainerResource{
				ReqXPUCores:         50,
				ReqXPUMem:           -1,
				ReqXPUMemPercentage: 60,
			},
			want: false,
		},
		{
			name: "memory percentage calculation - sufficient",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Memory:     10000,
				UsedMemory: 5000,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  20,
			},
			val: &util.ContainerResource{
				ReqXPUCores:         50,
				ReqXPUMem:           -1,
				ReqXPUMemPercentage: 40,
			},
			want:       true,
			realMemory: 4000,
		},
		{
			name: "insufficient cores",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Memory:     8192,
				UsedMemory: 1000,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  60,
			},
			val: &util.ContainerResource{
				ReqXPUCores: 50,
				ReqXPUMem:   1024,
			},
			want: false,
		},
		{
			name: "xpu type mismatch",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Type:       "XPU-A100",
				Memory:     8192,
				UsedMemory: 1000,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  20,
			},
			val: &util.ContainerResource{
				ReqXPUType:  "XPU-V100",
				ReqXPUCores: 50,
				ReqXPUMem:   1024,
			},
			want: false,
		},
		{
			name: "xpu type match",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Type:       "XPU-A100",
				Memory:     8192,
				UsedMemory: 1000,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  20,
			},
			val: &util.ContainerResource{
				ReqXPUType:  "XPU-A100",
				ReqXPUCores: 50,
				ReqXPUMem:   1024,
			},
			want:       true,
			realMemory: 1024,
		},
		{
			name: "all resources sufficient",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Type:       "XPU-A100",
				Memory:     16384,
				UsedMemory: 2000,
				Count:      8,
				UsedVids:   0b0011,
				UsedCores:  30,
			},
			val: &util.ContainerResource{
				ReqXPUType:  "XPU-A100",
				ReqXPUCores: 50,
				ReqXPUMem:   10000,
			},
			want:       true,
			realMemory: 10000,
		},
		{
			name: "edge case - exact memory match",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Memory:     8192,
				UsedMemory: 4096,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  20,
			},
			val: &util.ContainerResource{
				ReqXPUCores: 50,
				ReqXPUMem:   4096,
			},
			want:       true,
			realMemory: 4096,
		},
		{
			name: "edge case - exact cores match",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Memory:     8192,
				UsedMemory: 1000,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  50,
			},
			val: &util.ContainerResource{
				ReqXPUCores: 50,
				ReqXPUMem:   1024,
			},
			want:       true,
			realMemory: 1024,
		},
		{
			name: "zero resource request",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Memory:     8192,
				UsedMemory: 8000,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  99,
			},
			val: &util.ContainerResource{
				ReqXPUCores: 0,
				ReqXPUMem:   0,
			},
			want:       true,
			realMemory: 0,
		},
		{
			name: "multiple devices - check correct index",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-1",
				Type:       "XPU-A100",
				Memory:     16384,
				UsedMemory: 2000,
				Count:      8,
				UsedVids:   0b01,
				UsedCores:  20,
			},
			val: &util.ContainerResource{
				ReqXPUType:  "XPU-A100",
				ReqXPUCores: 50,
				ReqXPUMem:   10000,
			},
			want:       true,
			realMemory: 10000,
		},
		{
			name: "memory percentage with zero percentage",
			xpuDevice: &common.XPUDevice{
				DieID:      "device-0",
				Memory:     10000,
				UsedMemory: 5000,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  20,
			},
			val: &util.ContainerResource{
				ReqXPUCores:         50,
				ReqXPUMem:           3000,
				ReqXPUMemPercentage: 0,
			},
			want:       true,
			realMemory: 3000,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, realMem := IsDeviceQualified(tt.xpuDevice, tt.val)

			if got != tt.want {
				t.Errorf("IsDeviceQualified() = %v, want %v", got, tt.want)
			}

			if got && realMem != tt.realMemory {
				t.Errorf("realMem after calculation = %v, want %v", realMem, tt.realMemory)
			}
		})
	}
}

func TestIsDeviceQualified_MemoryPercentageCalculation(t *testing.T) {
	tests := []struct {
		name                string
		deviceMemory        uint64
		reqXPUMemPercentage int
		initialReqXPUMem    int
		expectedReqXPUMem   int
	}{
		{
			name:                "10% of 10000",
			deviceMemory:        10000,
			reqXPUMemPercentage: 10,
			initialReqXPUMem:    -1,
			expectedReqXPUMem:   1000,
		},
		{
			name:                "50% of 8192",
			deviceMemory:        8192,
			reqXPUMemPercentage: 50,
			initialReqXPUMem:    -1,
			expectedReqXPUMem:   4096,
		},
		{
			name:                "100% of 1024",
			deviceMemory:        1024,
			reqXPUMemPercentage: 100,
			initialReqXPUMem:    -1,
			expectedReqXPUMem:   1024,
		},
		{
			name:                "positive ReqXPUMem not recalculated",
			deviceMemory:        10000,
			reqXPUMemPercentage: 50,
			initialReqXPUMem:    3000,
			expectedReqXPUMem:   3000,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			device := &common.XPUDevice{
				DieID:      "device-0",
				Memory:     tt.deviceMemory,
				UsedMemory: 0,
				Count:      4,
				UsedVids:   0b01,
				UsedCores:  0,
			}

			val := &util.ContainerResource{
				ReqXPUCores:         10,
				ReqXPUMem:           tt.initialReqXPUMem,
				ReqXPUMemPercentage: tt.reqXPUMemPercentage,
			}

			_, realMem := IsDeviceQualified(device, val)

			if realMem != tt.expectedReqXPUMem {
				t.Errorf("ReqXPUMem = %v, want %v", val.ReqXPUMem, tt.expectedReqXPUMem)
			}
		})
	}
}

func BenchmarkIsDeviceQualified_Pass(b *testing.B) {
	device := &common.XPUDevice{
		DieID:      "device-0",
		Type:       "XPU-A100",
		Memory:     16384,
		UsedMemory: 2000,
		Count:      8,
		UsedVids:   0b01,
		UsedCores:  20,
	}

	val := &util.ContainerResource{
		ReqXPUType:  "XPU-A100",
		ReqXPUCores: 50,
		ReqXPUMem:   10000,
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		IsDeviceQualified(device, val)
	}
}

func BenchmarkIsDeviceQualified_Fail(b *testing.B) {
	device := &common.XPUDevice{
		DieID:      "device-0",
		Memory:     8192,
		UsedMemory: 7000,
		Count:      2,
		UsedVids:   0b11,
		UsedCores:  60,
	}

	val := &util.ContainerResource{
		ReqXPUCores: 50,
		ReqXPUMem:   2000,
	}

	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		IsDeviceQualified(device, val)
	}
}

func vxpuTestAbsFloat(x float64) float64 {
	if x < 0 {
		return -x
	}
	return x
}

func vxpuTestBuildPod(ns, name string) *v1.Pod {
	return &v1.Pod{
		ObjectMeta: metav1.ObjectMeta{
			Namespace:   ns,
			Name:        name,
			Annotations: map[string]string{},
		},
	}
}

func TestCalculateDeviceScore_SoftMode(t *testing.T) {
	tests := []struct {
		name          string
		device        *common.XPUDevice
		val           *util.ContainerResource
		realReqXPUMem int
		expected      float64
	}{
		{
			name: "empty device, low request",
			device: &common.XPUDevice{
				Cores: 100, UsedCores: 0, Memory: 10000, UsedMemory: 0,
			},
			val:           &util.ContainerResource{ReqXPUCores: 50},
			realReqXPUMem: 5000,
			expected:      100.0 * (50.0/100.0 + 5000.0/10000.0),
		},
		{
			name: "partially used device",
			device: &common.XPUDevice{
				Cores: 100, UsedCores: 25, Memory: 10000, UsedMemory: 2500,
			},
			val:           &util.ContainerResource{ReqXPUCores: 25},
			realReqXPUMem: 2500,
			expected:      100.0 * ((25+25.0)/100.0 + (2500+2500.0)/10000.0),
		},
		{
			name: "zero request",
			device: &common.XPUDevice{
				Cores: 100, UsedCores: 0, Memory: 10000, UsedMemory: 0,
			},
			val:           &util.ContainerResource{ReqXPUCores: 0},
			realReqXPUMem: 0,
			expected:      0.0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := calculateDeviceScore(tt.device, tt.val, tt.realReqXPUMem, tt.device.Cores, false)
			if diff := vxpuTestAbsFloat(result - tt.expected); diff > 0.001 {
				t.Errorf("calculateDeviceScore(soft) = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestCalculateDeviceScore_HardMode(t *testing.T) {
	tests := []struct {
		name       string
		device     *common.XPUDevice
		val        *util.ContainerResource
		totalCores int
		expected   float64
	}{
		{
			name:       "empty device, low request",
			device:     &common.XPUDevice{Cores: 100, UsedCores: 0},
			val:        &util.ContainerResource{ReqXPUCores: 25},
			totalCores: 20,
			expected:   100.0 * (25 / 20.0),
		},
		{
			name:       "partially used device",
			device:     &common.XPUDevice{Cores: 100, UsedCores: 5},
			val:        &util.ContainerResource{ReqXPUCores: 25},
			totalCores: 20,
			expected:   100.0 * ((25 + 5.0) / 20.0),
		},
		{
			name:       "full card request",
			device:     &common.XPUDevice{Cores: 100, UsedCores: 0},
			val:        &util.ContainerResource{ReqXPUCores: util.FullCore},
			totalCores: 20,
			expected:   100.0 * (util.FullCore / 20.0),
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := calculateDeviceScore(tt.device, tt.val, 0, tt.totalCores, true)
			if diff := vxpuTestAbsFloat(result - tt.expected); diff > 0.001 {
				t.Errorf("calculateDeviceScore(hard) = %v, want %v", result, tt.expected)
			}
		})
	}
}

func TestCheckSoftModeDevice_NoMutate(t *testing.T) {
	device := &common.XPUDevice{
		DieID: "dev-0", Cores: 100, Memory: 10000, Count: 4,
		UsedVids: 0b01, UsedCores: 10, UsedMemory: 1000,
	}
	val := &util.ContainerResource{ReqXPUCores: 50, ReqXPUMem: 5000}
	origCores, origMem, origVids := device.UsedCores, device.UsedMemory, device.UsedVids

	fits, realMem, score := checkSoftModeDevice(device, val)
	if !fits {
		t.Fatalf("expected fit")
	}
	if realMem != 5000 {
		t.Errorf("realMem = %v, want 5000", realMem)
	}
	if score <= 0 {
		t.Errorf("score = %v, expected > 0", score)
	}
	if device.UsedCores != origCores || device.UsedMemory != origMem || device.UsedVids != origVids {
		t.Error("checkSoftModeDevice mutated device state")
	}
}

func TestApplySoftModeDevice_Mutates(t *testing.T) {
	device := &common.XPUDevice{
		PhysicID: 0, DieID: "dev-0", Cores: 100, Memory: 10000,
		Count: 4, UsedVids: 0b01, UsedCores: 10, UsedMemory: 1000,
	}
	val := &util.ContainerResource{ReqXPUCores: 50}
	cdev := applySoftModeDevice(device, val, 5000)
	if cdev == nil {
		t.Fatal("returned nil")
	}
	if device.UsedCores != 60 || device.UsedMemory != 6000 {
		t.Errorf("device state wrong: cores=%d mem=%d", device.UsedCores, device.UsedMemory)
	}
}

func TestCheckHardModeDevice_FullCard(t *testing.T) {
	templates := TemplateInfos{
		"910B": {"vir05": {Name: "vir05", AiCore: 5, AiCPU: 1, TotalAiCore: 20, TotalAiCpu: 7}},
	}

	t.Run("empty device fits full card", func(t *testing.T) {
		device := &common.XPUDevice{
			DieID: "dev-0", Type: "NPU-ASCEND-910B", Cores: 100, UsedCores: 0,
		}
		val := &util.ContainerResource{ReqXPUCores: util.FullCore}
		res := checkHardModeDevice(device, val, templates)
		if !res.fits || !res.isFullCard || res.template.Name != util.FullCardTemplate || res.score != float64(scoreWeight) {
			t.Errorf("fits=%v isFullCard=%v template=%v score=%v", res.fits, res.isFullCard, res.template.Name, res.score)
		}
		if device.UsedCores != 0 {
			t.Error("checkHardModeDevice mutated device")
		}
	})

	t.Run("used device rejects full card", func(t *testing.T) {
		device := &common.XPUDevice{
			DieID: "dev-1", Type: "NPU-ASCEND-910B", Cores: 100, UsedCores: 5,
		}
		val := &util.ContainerResource{ReqXPUCores: util.FullCore}
		res := checkHardModeDevice(device, val, templates)
		if res.fits {
			t.Error("expected rejection")
		}
		if !res.isFullCard {
			t.Error("isFullCard should be true")
		}
	})
}

func TestCheckHardModeDevice_Template(t *testing.T) {
	templates := TemplateInfos{
		"910B": {"vir05": {Name: "vir05", AiCore: 5, AiCPU: 1, TotalAiCore: 20, TotalAiCpu: 7}},
	}
	device := &common.XPUDevice{
		DieID: "dev-0", Type: "NPU-ASCEND-910B", Cores: 100, UsedCores: 0,
	}
	val := &util.ContainerResource{ReqXPUCores: 20, ReqCpuLevel: util.AiCpuLevelLow}
	res := checkHardModeDevice(device, val, templates)
	if !res.fits {
		t.Fatal("expected fit")
	}
	if res.template == nil {
		t.Fatal("template nil")
	}
	if res.score <= 0 {
		t.Errorf("score = %v, want > 0", res.score)
	}
	if res.isFullCard {
		t.Error("isFullCard should be false")
	}
}

func TestApplyHardModeDevice_Mutates(t *testing.T) {
	device := &common.XPUDevice{PhysicID: 0, DieID: "dev-0", UsedCores: 0}
	template := &TemplateInfo{Name: "vir05", AiCore: 5, AiCPU: 1}
	cdev := applyHardModeDevice(device, template)
	if cdev == nil {
		t.Fatal("returned nil")
	}
	if device.UsedCores != 5 {
		t.Errorf("UsedCores = %d, want 5", device.UsedCores)
	}
}

func TestAllocateContainerDevices_BinpackSoft(t *testing.T) {
	devices := []*common.XPUDevice{
		{PhysicID: 0, DieID: "d0", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b001, UsedCores: 10, UsedMemory: 1000},
		{PhysicID: 1, DieID: "d1", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b001, UsedCores: 60, UsedMemory: 6000},
		{PhysicID: 2, DieID: "d2", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b001, UsedCores: 30, UsedMemory: 3000},
	}
	req := &util.ContainerResource{ReqXPUNum: 1, ReqXPUCores: 10, ReqXPUMem: 500}
	result, _ := AllocateContainerDevices(devices, req, ScheduleConfig{Policy: util.SchedulerPolicyBinpack, PodMode: util.SoftMode})
	if len(result) != 1 || result[0].Id != "d1" {
		t.Errorf("binpack soft: want d1, got %v", result)
	}
}

func TestAllocateContainerDevices_SpreadSoft(t *testing.T) {
	devices := []*common.XPUDevice{
		{PhysicID: 0, DieID: "d0", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b001, UsedCores: 10, UsedMemory: 1000},
		{PhysicID: 1, DieID: "d1", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b001, UsedCores: 60, UsedMemory: 6000},
		{PhysicID: 2, DieID: "d2", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b001, UsedCores: 30, UsedMemory: 3000},
	}
	req := &util.ContainerResource{ReqXPUNum: 1, ReqXPUCores: 10, ReqXPUMem: 500}
	result, _ := AllocateContainerDevices(devices, req, ScheduleConfig{Policy: util.SchedulerPolicySpread, PodMode: util.SoftMode})
	if len(result) != 1 || result[0].Id != "d0" {
		t.Errorf("spread soft: want d0, got %v", result)
	}
}

func TestAllocateContainerDevices_BinpackHard(t *testing.T) {
	templates := TemplateInfos{
		"910B": {"vir05": {Name: "vir05", AiCore: 5, AiCPU: 1, TotalAiCore: 20, TotalAiCpu: 7}},
	}
	devices := []*common.XPUDevice{
		{PhysicID: 0, DieID: "d0", Type: "NPU-ASCEND-910B", Cores: 100, Count: 4, UsedVids: 0b001, UsedCores: 0, UsedCpu: 0},
		{PhysicID: 1, DieID: "d1", Type: "NPU-ASCEND-910B", Cores: 100, Count: 4, UsedVids: 0b001, UsedCores: 5, UsedCpu: 5},
		{PhysicID: 2, DieID: "d2", Type: "NPU-ASCEND-910B", Cores: 100, Count: 4, UsedVids: 0b001, UsedCores: 2, UsedCpu: 2},
	}
	req := &util.ContainerResource{ReqXPUNum: 1, ReqXPUCores: 20, ReqCpuLevel: util.AiCpuLevelLow}
	result, _ := AllocateContainerDevices(devices, req, ScheduleConfig{Templates: templates, Policy: util.SchedulerPolicyBinpack, PodMode: util.HardMode})
	if len(result) != 1 || result[0].Id != "d1" {
		t.Errorf("binpack hard: want d1, got %v", result)
	}
}

func TestAllocateContainerDevices_SpreadHard(t *testing.T) {
	templates := TemplateInfos{
		"910B": {"vir05": {Name: "vir05", AiCore: 5, AiCPU: 1, TotalAiCore: 20, TotalAiCpu: 7}},
	}
	devices := []*common.XPUDevice{
		{PhysicID: 0, DieID: "d0", Type: "NPU-ASCEND-910B", Cores: 100, Count: 4, UsedVids: 0b001, UsedCores: 0, UsedCpu: 0},
		{PhysicID: 1, DieID: "d1", Type: "NPU-ASCEND-910B", Cores: 100, Count: 4, UsedVids: 0b001, UsedCores: 5, UsedCpu: 5},
		{PhysicID: 2, DieID: "d2", Type: "NPU-ASCEND-910B", Cores: 100, Count: 4, UsedVids: 0b001, UsedCores: 2, UsedCpu: 2},
	}
	req := &util.ContainerResource{ReqXPUNum: 1, ReqXPUCores: 20, ReqCpuLevel: util.AiCpuLevelLow}
	result, _ := AllocateContainerDevices(devices, req, ScheduleConfig{Templates: templates, Policy: util.SchedulerPolicySpread, PodMode: util.HardMode})
	if len(result) != 1 || result[0].Id != "d0" {
		t.Errorf("spread hard: want d0, got %v", result)
	}
}

func TestAllocateContainerDevices_MultipleDevices(t *testing.T) {
	devices := []*common.XPUDevice{
		{PhysicID: 0, DieID: "d0", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b001, UsedCores: 10, UsedMemory: 1000},
		{PhysicID: 1, DieID: "d1", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b001, UsedCores: 60, UsedMemory: 6000},
		{PhysicID: 2, DieID: "d2", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b001, UsedCores: 30, UsedMemory: 3000},
	}
	req := &util.ContainerResource{ReqXPUNum: 2, ReqXPUCores: 5, ReqXPUMem: 100}
	result, _ := AllocateContainerDevices(devices, req, ScheduleConfig{Policy: util.SchedulerPolicySpread, PodMode: util.SoftMode})
	if len(result) != 2 {
		t.Fatalf("expected 2 devices, got %d", len(result))
	}
	if req.ReqXPUNum != 0 {
		t.Errorf("ReqXPUNum = %d, want 0 after allocation", req.ReqXPUNum)
	}
}

func TestAllocateContainerDevices_NoFit(t *testing.T) {
	devices := []*common.XPUDevice{
		{PhysicID: 0, DieID: "d0", Cores: 100, Memory: 10000, Count: 4, UsedVids: 0b1111, UsedCores: 99, UsedMemory: 9999},
	}
	req := &util.ContainerResource{ReqXPUNum: 1, ReqXPUCores: 50, ReqXPUMem: 5000}
	result, _ := AllocateContainerDevices(devices, req, ScheduleConfig{Policy: util.SchedulerPolicyBinpack, PodMode: util.SoftMode})
	if len(result) != 0 {
		t.Errorf("expected 0 devices, got %d", len(result))
	}
}

func TestIsVnpuModeMatch_DefaultsToSoft(t *testing.T) {
	tests := []struct {
		name      string
		annotVal  string
		annotSet  bool
		modes     []string
		expectErr bool
	}{
		{"unannotated pod with soft devices", "", false, []string{util.SoftMode, util.SoftMode}, false},
		{"unannotated pod with hard devices (should fail)", "", false, []string{util.HardMode}, true},
		{"hard pod matches hard devices", util.HardMode, true, []string{util.HardMode}, false},
		{"hard pod with soft devices (should fail)", util.HardMode, true, []string{util.SoftMode}, true},
		{"soft pod matches soft devices", util.SoftMode, true, []string{util.SoftMode}, false},
		{"soft pod with mixed devices (should fail)", util.SoftMode, true, []string{util.SoftMode, util.HardMode}, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			var devices []*common.XPUDevice
			for _, m := range tt.modes {
				devices = append(devices, &common.XPUDevice{Mode: m})
			}
			pod := vxpuTestBuildPod("default", "test-pod")
			if tt.annotSet {
				pod.Annotations = map[string]string{util.VNPUModeAnnotation: tt.annotVal}
			}
			err := isVnpuModeMatch(pod, devices)
			if (err != nil) != tt.expectErr {
				t.Errorf("isVnpuModeMatch() error = %v, wantErr %v", err, tt.expectErr)
			}
		})
	}
}