package exporter

import (
	"encoding/json"
	"strings"
	"testing"
	"time"

	"git.mchus.pro/mchus/logpile/internal/models"
)

func TestConvertToReanimator(t *testing.T) {
	tests := []struct {
		name    string
		input   *models.AnalysisResult
		wantErr bool
		errMsg  string
	}{
		{
			name:    "nil result",
			input:   nil,
			wantErr: true,
			errMsg:  "no data available",
		},
		{
			name: "no hardware",
			input: &models.AnalysisResult{
				Filename: "test.json",
			},
			wantErr: true,
			errMsg:  "no hardware data available",
		},
		{
			name: "no board serial",
			input: &models.AnalysisResult{
				Filename: "test.json",
				Hardware: &models.HardwareConfig{
					BoardInfo: models.BoardInfo{},
				},
			},
			wantErr: true,
			errMsg:  "board serial_number is required",
		},
		{
			name: "valid minimal data",
			input: &models.AnalysisResult{
				Filename:    "test.json",
				SourceType:  "api",
				Protocol:    "redfish",
				TargetHost:  "10.10.10.10",
				CollectedAt: time.Date(2026, 2, 10, 15, 30, 0, 0, time.UTC),
				Hardware: &models.HardwareConfig{
					BoardInfo: models.BoardInfo{
						Manufacturer: "Supermicro",
						ProductName:  "X12DPG-QT6",
						SerialNumber: "TEST123",
					},
				},
			},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := ConvertToReanimator(tt.input)
			if tt.wantErr {
				if err == nil {
					t.Errorf("expected error containing %q, got nil", tt.errMsg)
				}
				return
			}
			if err != nil {
				t.Errorf("unexpected error: %v", err)
				return
			}
			if result == nil {
				t.Error("expected non-nil result")
				return
			}
			if result.Hardware.Board.SerialNumber != tt.input.Hardware.BoardInfo.SerialNumber {
				t.Errorf("board serial mismatch: got %q, want %q",
					result.Hardware.Board.SerialNumber,
					tt.input.Hardware.BoardInfo.SerialNumber)
			}
		})
	}
}

func TestInferCPUManufacturer(t *testing.T) {
	tests := []struct {
		model string
		want  string
	}{
		{"INTEL(R) XEON(R) GOLD 6530", "Intel"},
		{"Intel Core i9-12900K", "Intel"},
		{"AMD EPYC 7763", "AMD"},
		{"AMD Ryzen 9 5950X", "AMD"},
		{"ARM Cortex-A78", "ARM"},
		{"Ampere Altra Max", "Ampere"},
		{"Unknown CPU Model", ""},
	}

	for _, tt := range tests {
		t.Run(tt.model, func(t *testing.T) {
			got := inferCPUManufacturer(tt.model)
			if got != tt.want {
				t.Errorf("inferCPUManufacturer(%q) = %q, want %q", tt.model, got, tt.want)
			}
		})
	}
}

func TestNormalizedSerial(t *testing.T) {
	tests := []struct {
		name string
		in   string
		want string
	}{
		{
			name: "empty",
			in:   "",
			want: "",
		},
		{
			name: "n_a",
			in:   "N/A",
			want: "",
		},
		{
			name: "unknown",
			in:   "unknown",
			want: "",
		},
		{
			name: "normal",
			in:   "SN123",
			want: "SN123",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := normalizedSerial(tt.in)
			if got != tt.want {
				t.Errorf("normalizedSerial() = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestInferStorageStatus(t *testing.T) {
	tests := []struct {
		name string
		stor models.Storage
		want string
	}{
		{
			name: "present",
			stor: models.Storage{
				Present: true,
			},
			want: "Unknown",
		},
		{
			name: "not present",
			stor: models.Storage{
				Present: false,
			},
			want: "Unknown",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := inferStorageStatus(tt.stor)
			if got != tt.want {
				t.Errorf("inferStorageStatus() = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestNormalizeStatus_PassFail(t *testing.T) {
	if got := normalizeStatus("PASS", false); got != "OK" {
		t.Fatalf("expected PASS -> OK, got %q", got)
	}
	if got := normalizeStatus("FAIL", false); got != "Critical" {
		t.Fatalf("expected FAIL -> Critical, got %q", got)
	}
}

func TestConvertCPUs(t *testing.T) {
	cpus := []models.CPU{
		{
			Socket:       0,
			Model:        "INTEL(R) XEON(R) GOLD 6530",
			Cores:        32,
			Threads:      64,
			FrequencyMHz: 2100,
			MaxFreqMHz:   4000,
		},
		{
			Socket:       1,
			Model:        "AMD EPYC 7763",
			Cores:        64,
			Threads:      128,
			FrequencyMHz: 2450,
			MaxFreqMHz:   3500,
		},
	}

	result := convertCPUs(cpus, "2026-02-10T15:30:00Z")

	if len(result) != 2 {
		t.Fatalf("expected 2 CPUs, got %d", len(result))
	}

	if result[0].Manufacturer != "Intel" {
		t.Errorf("expected Intel manufacturer for first CPU, got %q", result[0].Manufacturer)
	}

	if result[1].Manufacturer != "AMD" {
		t.Errorf("expected AMD manufacturer for second CPU, got %q", result[1].Manufacturer)
	}

	if result[0].Status != "Unknown" {
		t.Errorf("expected Unknown status, got %q", result[0].Status)
	}
}

func TestConvertMemory(t *testing.T) {
	memory := []models.MemoryDIMM{
		{
			Slot:         "CPU0_C0D0",
			Present:      true,
			SizeMB:       32768,
			Type:         "DDR5",
			SerialNumber: "TEST-MEM-001",
			Status:       "OK",
		},
		{
			Slot:    "CPU0_C1D0",
			Present: false,
		},
	}

	result := convertMemory(memory, "2026-02-10T15:30:00Z")

	if len(result) != 2 {
		t.Fatalf("expected 2 memory modules, got %d", len(result))
	}

	if result[0].Status != "OK" {
		t.Errorf("expected OK status for first module, got %q", result[0].Status)
	}

	if result[1].Status != "Empty" {
		t.Errorf("expected Empty status for second module, got %q", result[1].Status)
	}
}

func TestConvertStorage(t *testing.T) {
	storage := []models.Storage{
		{
			Slot:         "OB01",
			Type:         "NVMe",
			Model:        "INTEL SSDPF2KX076T1",
			SerialNumber: "BTAX41900GF87P6DGN",
			Present:      true,
		},
		{
			Slot:         "OB02",
			Type:         "NVMe",
			Model:        "INTEL SSDPF2KX076T1",
			SerialNumber: "", // No serial - should be skipped
			Present:      true,
		},
	}

	result := convertStorage(storage, "2026-02-10T15:30:00Z")

	if len(result) != 1 {
		t.Fatalf("expected 1 storage device (skipped one without serial), got %d", len(result))
	}

	if result[0].Status != "Unknown" {
		t.Errorf("expected Unknown status, got %q", result[0].Status)
	}
}

func TestConvertPCIeDevices(t *testing.T) {
	hw := &models.HardwareConfig{
		PCIeDevices: []models.PCIeDevice{
			{
				Slot:         "PCIeCard1",
				VendorID:     32902,
				DeviceID:     2912,
				BDF:          "0000:18:00.0",
				DeviceClass:  "MassStorageController",
				Manufacturer: "Intel",
				PartNumber:   "RSP3DD080F",
				SerialNumber: "RAID-001",
			},
			{
				Slot:         "PCIeCard2",
				DeviceClass:  "NetworkController",
				Manufacturer: "Mellanox",
				SerialNumber: "", // Should be generated
			},
		},
		GPUs: []models.GPU{
			{
				Slot:         "GPU1",
				Model:        "NVIDIA A100",
				Manufacturer: "NVIDIA",
				SerialNumber: "GPU-001",
				Status:       "OK",
			},
		},
		NetworkAdapters: []models.NetworkAdapter{
			{
				Slot:         "NIC1",
				Model:        "ConnectX-6",
				Vendor:       "Mellanox",
				Present:      true,
				SerialNumber: "NIC-001",
			},
		},
	}

	result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")

	// Should have: 2 PCIe devices + 1 GPU + 1 NIC = 4 total
	if len(result) != 4 {
		t.Fatalf("expected 4 PCIe devices total, got %d", len(result))
	}

	// Check that serial is empty for second PCIe device (no auto-generation)
	if result[1].SerialNumber != "" {
		t.Errorf("expected empty serial for missing device serial, got %q", result[1].SerialNumber)
	}

	// Check GPU was included
	foundGPU := false
	for _, dev := range result {
		if dev.SerialNumber == "GPU-001" {
			foundGPU = true
			if dev.DeviceClass != "DisplayController" {
				t.Errorf("expected GPU device_class DisplayController, got %q", dev.DeviceClass)
			}
			break
		}
	}
	if !foundGPU {
		t.Error("expected GPU to be included in PCIe devices")
	}
}

func TestConvertPCIeDevices_NVSwitchWithoutSerialRemainsEmpty(t *testing.T) {
	hw := &models.HardwareConfig{
		Firmware: []models.FirmwareInfo{
			{
				DeviceName: "NVSwitch NVSWITCH1 (965-25612-0002-000)",
				Version:    "96.10.6D.00.01",
			},
		},
		PCIeDevices: []models.PCIeDevice{
			{
				Slot:        "NVSWITCH1",
				DeviceClass: "NVSwitch",
				BDF:         "0000:06:00.0",
				// SerialNumber empty on purpose; should remain empty.
			},
		},
	}

	result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")

	if len(result) != 1 {
		t.Fatalf("expected 1 PCIe device, got %d", len(result))
	}

	if result[0].SerialNumber != "" {
		t.Fatalf("expected empty NVSwitch serial, got %q", result[0].SerialNumber)
	}
	if result[0].Firmware != "96.10.6D.00.01" {
		t.Fatalf("expected NVSwitch firmware 96.10.6D.00.01, got %q", result[0].Firmware)
	}
}

func TestConvertPCIeDevices_SkipsDisplayControllerDuplicates(t *testing.T) {
	hw := &models.HardwareConfig{
		PCIeDevices: []models.PCIeDevice{
			{
				Slot:        "#GPU0",
				DeviceClass: "3D Controller",
			},
		},
		GPUs: []models.GPU{
			{
				Slot:         "#GPU0",
				Model:        "B200 180GB HBM3e",
				Manufacturer: "NVIDIA",
				SerialNumber: "1655024043371",
				Status:       "OK",
			},
		},
	}

	result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
	if len(result) != 1 {
		t.Fatalf("expected only dedicated GPU record without duplicate display PCIe, got %d", len(result))
	}
	if result[0].DeviceClass != "DisplayController" {
		t.Fatalf("expected GPU record with DisplayController class, got %q", result[0].DeviceClass)
	}
	if result[0].Status != "OK" {
		t.Fatalf("expected GPU status OK, got %q", result[0].Status)
	}
}

func TestConvertPCIeDevices_MapsGPUStatusHistory(t *testing.T) {
	hw := &models.HardwareConfig{
		GPUs: []models.GPU{
			{
				Slot:         "#GPU6",
				Model:        "B200 180GB HBM3e",
				Manufacturer: "NVIDIA",
				SerialNumber: "1655024043204",
				Status:       "Critical",
				StatusHistory: []models.StatusHistoryEntry{
					{
						Status:    "Critical",
						ChangedAt: time.Date(2026, 1, 12, 15, 5, 18, 0, time.UTC),
						Details:   "BIOS miss F_GPU6",
					},
				},
				ErrorDescription: "BIOS miss F_GPU6",
			},
		},
	}

	result := convertPCIeDevices(hw, "2026-02-10T15:30:00Z")
	if len(result) != 1 {
		t.Fatalf("expected 1 converted GPU, got %d", len(result))
	}

	if len(result[0].StatusHistory) != 1 {
		t.Fatalf("expected 1 history entry, got %d", len(result[0].StatusHistory))
	}
	if result[0].StatusHistory[0].ChangedAt != "2026-01-12T15:05:18Z" {
		t.Fatalf("unexpected history changed_at: %q", result[0].StatusHistory[0].ChangedAt)
	}
	if result[0].StatusAtCollect == nil || result[0].StatusAtCollect.At != "2026-02-10T15:30:00Z" {
		t.Fatalf("expected status_at_collection to be populated from collected_at")
	}
}

func TestConvertPowerSupplies(t *testing.T) {
	psus := []models.PSU{
		{
			Slot:         "0",
			Present:      true,
			Model:        "GW-CRPS3000LW",
			Vendor:       "Great Wall",
			WattageW:     3000,
			SerialNumber: "PSU-001",
			Status:       "OK",
		},
		{
			Slot:         "1",
			Present:      false,
			SerialNumber: "", // Not present, should be skipped
		},
	}

	result := convertPowerSupplies(psus, "2026-02-10T15:30:00Z")

	if len(result) != 1 {
		t.Fatalf("expected 1 PSU (skipped empty), got %d", len(result))
	}

	if result[0].Status != "OK" {
		t.Errorf("expected OK status, got %q", result[0].Status)
	}
}

func TestConvertBoardNormalizesNULL(t *testing.T) {
	board := convertBoard(models.BoardInfo{
		Manufacturer: " NULL ",
		ProductName:  "null",
		SerialNumber: "TEST123",
	})

	if board.Manufacturer != "" {
		t.Fatalf("expected empty manufacturer, got %q", board.Manufacturer)
	}
	if board.ProductName != "" {
		t.Fatalf("expected empty product_name, got %q", board.ProductName)
	}
}

func TestSourceTypeOmittedWhenInvalidOrEmpty(t *testing.T) {
	result, err := ConvertToReanimator(&models.AnalysisResult{
		Filename:   "redfish://10.0.0.1",
		SourceType: "archive",
		TargetHost: "10.0.0.1",
		Hardware: &models.HardwareConfig{
			BoardInfo: models.BoardInfo{SerialNumber: "TEST123"},
		},
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	payload, err := json.Marshal(result)
	if err != nil {
		t.Fatalf("marshal failed: %v", err)
	}
	if strings.Contains(string(payload), `"source_type"`) {
		t.Fatalf("expected source_type to be omitted for invalid value, got %s", string(payload))
	}
}

func TestTargetHostOmittedWhenUnavailable(t *testing.T) {
	result, err := ConvertToReanimator(&models.AnalysisResult{
		Filename:   "test.json",
		SourceType: "api",
		Hardware: &models.HardwareConfig{
			BoardInfo: models.BoardInfo{SerialNumber: "TEST123"},
		},
	})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	payload, err := json.Marshal(result)
	if err != nil {
		t.Fatalf("marshal failed: %v", err)
	}
	if strings.Contains(string(payload), `"target_host"`) {
		t.Fatalf("expected target_host to be omitted when unavailable, got %s", string(payload))
	}
}

func TestInferTargetHost(t *testing.T) {
	tests := []struct {
		name       string
		targetHost string
		filename   string
		want       string
	}{
		{
			name:       "explicit target host wins",
			targetHost: "10.0.0.10",
			filename:   "redfish://10.0.0.20",
			want:       "10.0.0.10",
		},
		{
			name:     "hostname from URL",
			filename: "redfish://10.10.10.103",
			want:     "10.10.10.103",
		},
		{
			name:     "ip extracted from archive name",
			filename: "nvidia_bug_report_192.168.12.34.tar.gz",
			want:     "192.168.12.34",
		},
		{
			name:     "no host available",
			filename: "test.json",
			want:     "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := inferTargetHost(tt.targetHost, tt.filename)
			if got != tt.want {
				t.Fatalf("inferTargetHost() = %q, want %q", got, tt.want)
			}
		})
	}
}

func TestConvertToReanimator_DeduplicatesAllSections(t *testing.T) {
	input := &models.AnalysisResult{
		Filename:    "dup-test.json",
		CollectedAt: time.Date(2026, 2, 10, 15, 30, 0, 0, time.UTC),
		Hardware: &models.HardwareConfig{
			BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
			Firmware: []models.FirmwareInfo{
				{DeviceName: "BMC", Version: "1.0"},
				{DeviceName: "BMC", Version: "1.1"},
			},
			CPUs: []models.CPU{
				{Socket: 0, Model: "CPU-A"},
				{Socket: 0, Model: "CPU-A-DUP"},
			},
			Memory: []models.MemoryDIMM{
				{Slot: "DIMM_A1", Present: true, SizeMB: 32768, SerialNumber: "MEM-1", Status: "OK"},
				{Slot: "DIMM_A1", Present: true, SizeMB: 32768, SerialNumber: "MEM-1-DUP", Status: "OK"},
			},
			Storage: []models.Storage{
				{Slot: "U.2-1", SerialNumber: "SSD-1", Model: "Disk1", Present: true},
				{Slot: "U.2-2", SerialNumber: "SSD-1", Model: "Disk1-dup", Present: true},
			},
			PCIeDevices: []models.PCIeDevice{
				{Slot: "#GPU0", DeviceClass: "3D Controller", BDF: "17:00.0"},
				{Slot: "SLOT-NIC1", DeviceClass: "NetworkController", BDF: "18:00.0"},
				{Slot: "SLOT-NIC1", DeviceClass: "NetworkController", BDF: "18:00.1"},
			},
			GPUs: []models.GPU{
				{Slot: "#GPU0", Model: "B200 180GB HBM3e", SerialNumber: "GPU-1", Status: "OK"},
			},
			PowerSupply: []models.PSU{
				{Slot: "0", Present: true, SerialNumber: "PSU-1", Status: "OK"},
				{Slot: "1", Present: true, SerialNumber: "PSU-1", Status: "OK"},
			},
		},
	}

	out, err := ConvertToReanimator(input)
	if err != nil {
		t.Fatalf("ConvertToReanimator() failed: %v", err)
	}

	if len(out.Hardware.Firmware) != 1 {
		t.Fatalf("expected deduped firmware len=1, got %d", len(out.Hardware.Firmware))
	}
	if len(out.Hardware.CPUs) != 2 {
		t.Fatalf("expected cpus len=2 (no serial/bdf dedupe), got %d", len(out.Hardware.CPUs))
	}
	if len(out.Hardware.Memory) != 2 {
		t.Fatalf("expected memory len=2 (different serials), got %d", len(out.Hardware.Memory))
	}
	if len(out.Hardware.Storage) != 1 {
		t.Fatalf("expected deduped storage len=1, got %d", len(out.Hardware.Storage))
	}
	if len(out.Hardware.PowerSupplies) != 1 {
		t.Fatalf("expected deduped psu len=1, got %d", len(out.Hardware.PowerSupplies))
	}
	if len(out.Hardware.PCIeDevices) != 4 {
		t.Fatalf("expected pcie len=4 with serial->bdf dedupe, got %d", len(out.Hardware.PCIeDevices))
	}

	gpuCount := 0
	for _, dev := range out.Hardware.PCIeDevices {
		if dev.Slot == "#GPU0" {
			gpuCount++
		}
	}
	if gpuCount != 2 {
		t.Fatalf("expected two #GPU0 records (pcie+gpu kinds), got %d", gpuCount)
	}
}

func TestConvertToReanimator_StatusFallbackUsesCollectedAt(t *testing.T) {
	collectedAt := time.Date(2026, 2, 10, 15, 30, 0, 0, time.UTC)
	input := &models.AnalysisResult{
		Filename:    "status-fallback.json",
		CollectedAt: collectedAt,
		Hardware: &models.HardwareConfig{
			BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
			Storage: []models.Storage{
				{
					Slot:         "U.2-1",
					Model:        "PM9A3",
					SerialNumber: "SSD-001",
					Present:      true,
					Status:       "OK",
				},
			},
		},
	}

	out, err := ConvertToReanimator(input)
	if err != nil {
		t.Fatalf("ConvertToReanimator() failed: %v", err)
	}
	if len(out.Hardware.Storage) != 1 {
		t.Fatalf("expected 1 storage entry, got %d", len(out.Hardware.Storage))
	}

	wantTs := collectedAt.UTC().Format(time.RFC3339)
	got := out.Hardware.Storage[0]
	if got.StatusCheckedAt != wantTs {
		t.Fatalf("expected status_checked_at=%q, got %q", wantTs, got.StatusCheckedAt)
	}
	if got.StatusAtCollect == nil || got.StatusAtCollect.At != wantTs {
		t.Fatalf("expected status_at_collection.at=%q, got %#v", wantTs, got.StatusAtCollect)
	}
}

func TestConvertToReanimator_FirmwareExcludesDeviceBoundEntries(t *testing.T) {
	input := &models.AnalysisResult{
		Filename: "fw-filter-test.json",
		Hardware: &models.HardwareConfig{
			BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
			Firmware: []models.FirmwareInfo{
				{DeviceName: "BIOS", Version: "1.0.0"},
				{DeviceName: "BMC", Version: "2.0.0"},
				{DeviceName: "GPU GPUSXM1 (692-2G520-0280-501)", Version: "96.00.D0.00.03"},
				{DeviceName: "NVSwitch NVSWITCH0 (965-25612-0002-000)", Version: "96.10.6D.00.01"},
				{DeviceName: "NIC #CPU1_PCIE9 (MCX512A-ACAT)", Version: "28.38.1900"},
				{DeviceName: "CPU0 Microcode", Version: "0x2b000643"},
			},
		},
	}

	out, err := ConvertToReanimator(input)
	if err != nil {
		t.Fatalf("ConvertToReanimator() failed: %v", err)
	}

	if len(out.Hardware.Firmware) != 2 {
		t.Fatalf("expected only machine-level firmware entries, got %d", len(out.Hardware.Firmware))
	}

	got := map[string]string{}
	for _, fw := range out.Hardware.Firmware {
		got[fw.DeviceName] = fw.Version
	}

	if got["BIOS"] != "1.0.0" {
		t.Fatalf("expected BIOS firmware to be kept")
	}
	if got["BMC"] != "2.0.0" {
		t.Fatalf("expected BMC firmware to be kept")
	}
	if _, exists := got["GPU GPUSXM1 (692-2G520-0280-501)"]; exists {
		t.Fatalf("expected GPU firmware to be excluded from hardware.firmware")
	}
	if _, exists := got["NVSwitch NVSWITCH0 (965-25612-0002-000)"]; exists {
		t.Fatalf("expected NVSwitch firmware to be excluded from hardware.firmware")
	}
}

func TestConvertToReanimator_UsesCanonicalDevices(t *testing.T) {
	input := &models.AnalysisResult{
		Filename: "canonical.json",
		Hardware: &models.HardwareConfig{
			BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
			Devices: []models.HardwareDevice{
				{
					Kind:         models.DeviceKindCPU,
					Slot:         "CPU0",
					Model:        "INTEL(R) XEON(R)",
					Cores:        32,
					Threads:      64,
					FrequencyMHz: 2100,
				},
				{
					Kind:         models.DeviceKindStorage,
					Slot:         "U.2-1",
					Model:        "Disk1",
					SerialNumber: "SSD-1",
					Present:      boolPtr(true),
				},
			},
		},
	}

	out, err := ConvertToReanimator(input)
	if err != nil {
		t.Fatalf("ConvertToReanimator() failed: %v", err)
	}
	if len(out.Hardware.CPUs) != 1 {
		t.Fatalf("expected cpu from hardware.devices, got %d", len(out.Hardware.CPUs))
	}
	if len(out.Hardware.Storage) != 1 {
		t.Fatalf("expected storage from hardware.devices, got %d", len(out.Hardware.Storage))
	}
}

func TestConvertToReanimator_BindsDeviceVitals(t *testing.T) {
	input := &models.AnalysisResult{
		Filename: "vitals.json",
		Hardware: &models.HardwareConfig{
			BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
			Devices: []models.HardwareDevice{
				{
					Kind:         models.DeviceKindGPU,
					Slot:         "#GPU0",
					Model:        "B200 180GB HBM3e",
					SerialNumber: "GPU-001",
					BDF:          "0000:17:00.0",
					Details: map[string]any{
						"temperature": 71,
						"power":       350,
						"voltage":     12.2,
					},
				},
				{
					Kind:         models.DeviceKindPSU,
					Slot:         "PSU0",
					SerialNumber: "PSU-001",
					Present:      boolPtr(true),
					InputPowerW:  1400,
					OutputPowerW: 1300,
					InputVoltage: 229.5,
					TemperatureC: 44,
				},
			},
		},
	}

	out, err := ConvertToReanimator(input)
	if err != nil {
		t.Fatalf("ConvertToReanimator() failed: %v", err)
	}

	if len(out.Hardware.PCIeDevices) != 1 {
		t.Fatalf("expected one pcie device, got %d", len(out.Hardware.PCIeDevices))
	}
	pcie := out.Hardware.PCIeDevices[0]
	if pcie.TemperatureC != 71 {
		t.Fatalf("expected GPU temperature 71C, got %d", pcie.TemperatureC)
	}
	if pcie.PowerW != 350 {
		t.Fatalf("expected GPU power 350W, got %d", pcie.PowerW)
	}
	if pcie.VoltageV != 12.2 {
		t.Fatalf("expected device voltage 12.2V, got %.2f", pcie.VoltageV)
	}

	if len(out.Hardware.PowerSupplies) != 1 {
		t.Fatalf("expected one PSU, got %d", len(out.Hardware.PowerSupplies))
	}
	psu := out.Hardware.PowerSupplies[0]
	if psu.TemperatureC != 44 {
		t.Fatalf("expected PSU temperature 44C, got %d", psu.TemperatureC)
	}
}

func TestConvertToReanimator_PreservesVitalsAcrossCanonicalDedup(t *testing.T) {
	input := &models.AnalysisResult{
		Filename: "dedup-vitals.json",
		Hardware: &models.HardwareConfig{
			BoardInfo: models.BoardInfo{SerialNumber: "BOARD-001"},
			PCIeDevices: []models.PCIeDevice{
				{
					Slot:         "#GPU0",
					BDF:          "0000:17:00.0",
					DeviceClass:  "3D Controller",
					PartNumber:   "Generic Display",
					Manufacturer: "NVIDIA",
					SerialNumber: "GPU-SN-001",
				},
			},
			GPUs: []models.GPU{
				{
					Slot:         "#GPU0",
					BDF:          "0000:17:00.0",
					Model:        "B200 180GB HBM3e",
					Manufacturer: "NVIDIA",
					SerialNumber: "GPU-SN-001",
					Temperature:  67,
					Power:        330,
					Status:       "OK",
				},
			},
		},
	}

	out, err := ConvertToReanimator(input)
	if err != nil {
		t.Fatalf("ConvertToReanimator() failed: %v", err)
	}
	if len(out.Hardware.PCIeDevices) != 1 {
		t.Fatalf("expected deduped one pcie entry, got %d", len(out.Hardware.PCIeDevices))
	}
	got := out.Hardware.PCIeDevices[0]
	if got.TemperatureC != 67 {
		t.Fatalf("expected deduped GPU temperature 67C, got %d", got.TemperatureC)
	}
	if got.PowerW != 330 {
		t.Fatalf("expected deduped GPU power 330W, got %d", got.PowerW)
	}
}

func boolPtr(v bool) *bool { return &v }
