//go:build testing

package agent

import (
	"errors"
	"os"
	"path/filepath"
	"testing"

	"github.com/henrygd/beszel/internal/entities/smart"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestParseSmartForScsi(t *testing.T) {
	fixturePath := filepath.Join("test-data", "smart", "scsi.json")
	data, err := os.ReadFile(fixturePath)
	if err != nil {
		t.Fatalf("failed reading fixture: %v", err)
	}

	sm := &SmartManager{
		SmartDataMap: make(map[string]*smart.SmartData),
	}

	hasData, exitStatus := sm.parseSmartForScsi(data)
	if !hasData {
		t.Fatalf("expected SCSI data to parse successfully")
	}
	if exitStatus != 0 {
		t.Fatalf("expected exit status 0, got %d", exitStatus)
	}

	deviceData, ok := sm.SmartDataMap["9YHSDH9B"]
	if !ok {
		t.Fatalf("expected smart data entry for serial 9YHSDH9B")
	}

	assert.Equal(t, deviceData.ModelName, "YADRO WUH721414AL4204")
	assert.Equal(t, deviceData.SerialNumber, "9YHSDH9B")
	assert.Equal(t, deviceData.FirmwareVersion, "C240")
	assert.Equal(t, deviceData.DiskName, "/dev/sde")
	assert.Equal(t, deviceData.DiskType, "scsi")
	assert.EqualValues(t, deviceData.Temperature, 34)
	assert.Equal(t, deviceData.SmartStatus, "PASSED")
	assert.EqualValues(t, deviceData.Capacity, 14000519643136)

	if len(deviceData.Attributes) == 0 {
		t.Fatalf("expected attributes to be populated")
	}

	assertAttrValue(t, deviceData.Attributes, "PowerOnHours", 458)
	assertAttrValue(t, deviceData.Attributes, "PowerOnMinutes", 25)
	assertAttrValue(t, deviceData.Attributes, "GrownDefectList", 0)
	assertAttrValue(t, deviceData.Attributes, "StartStopCycles", 2)
	assertAttrValue(t, deviceData.Attributes, "LoadUnloadCycles", 418)
	assertAttrValue(t, deviceData.Attributes, "ReadGigabytesProcessed", 3641)
	assertAttrValue(t, deviceData.Attributes, "WriteGigabytesProcessed", 2124590)
	assertAttrValue(t, deviceData.Attributes, "VerifyGigabytesProcessed", 0)
}

func TestParseSmartForSata(t *testing.T) {
	fixturePath := filepath.Join("test-data", "smart", "sda.json")
	data, err := os.ReadFile(fixturePath)
	require.NoError(t, err)

	sm := &SmartManager{
		SmartDataMap: make(map[string]*smart.SmartData),
	}

	hasData, exitStatus := sm.parseSmartForSata(data)
	require.True(t, hasData)
	assert.Equal(t, 64, exitStatus)

	deviceData, ok := sm.SmartDataMap["9C40918040082"]
	require.True(t, ok, "expected smart data entry for serial 9C40918040082")

	assert.Equal(t, "P3-2TB", deviceData.ModelName)
	assert.Equal(t, "X0104A0", deviceData.FirmwareVersion)
	assert.Equal(t, "/dev/sda", deviceData.DiskName)
	assert.Equal(t, "sat", deviceData.DiskType)
	assert.Equal(t, uint8(31), deviceData.Temperature)
	assert.Equal(t, "PASSED", deviceData.SmartStatus)
	assert.Equal(t, uint64(2048408248320), deviceData.Capacity)
	if assert.NotEmpty(t, deviceData.Attributes) {
		assertAttrValue(t, deviceData.Attributes, "Temperature_Celsius", 31)
	}
}

func TestParseSmartForSataDeviceStatisticsTemperature(t *testing.T) {
	jsonPayload := []byte(`{
		"smartctl": {"exit_status": 0},
		"device": {"name": "/dev/sdb", "type": "sat"},
		"model_name": "SanDisk SSD U110 16GB",
		"serial_number": "DEVSTAT123",
		"firmware_version": "U21B001",
		"user_capacity": {"bytes": 16013942784},
		"smart_status": {"passed": true},
		"ata_smart_attributes": {"table": []},
		"ata_device_statistics": {
			"pages": [
				{
					"number": 5,
					"name": "Temperature Statistics",
					"table": [
						{"name": "Current Temperature", "value": 22, "flags": {"valid": true}}
					]
				}
			]
		}
	}`)

	sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
	hasData, exitStatus := sm.parseSmartForSata(jsonPayload)
	require.True(t, hasData)
	assert.Equal(t, 0, exitStatus)

	deviceData, ok := sm.SmartDataMap["DEVSTAT123"]
	require.True(t, ok, "expected smart data entry for serial DEVSTAT123")
	assert.Equal(t, uint8(22), deviceData.Temperature)
}

func TestParseSmartForSataAtaDeviceStatistics(t *testing.T) {
	// tests that ata_device_statistics values are parsed correctly
	jsonPayload := []byte(`{
		"smartctl": {"exit_status": 0},
		"device": {"name": "/dev/sdb", "type": "sat"},
		"model_name": "SanDisk SSD U110 16GB",
		"serial_number": "lksjfh23lhj",
		"firmware_version": "U21B001",
		"user_capacity": {"bytes": 16013942784},
		"smart_status": {"passed": true},
		"ata_smart_attributes": {"table": []},
		"ata_device_statistics": {
			"pages": [
				{
					"number": 5,
					"name": "Temperature Statistics",
					"table": [
						{"name": "Current Temperature", "value": 43, "flags": {"valid": true}},
						{"name": "Specified Minimum Operating Temperature", "value": -20, "flags": {"valid": true}}
					]
				}
			]
		}
	}`)

	sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
	hasData, exitStatus := sm.parseSmartForSata(jsonPayload)
	require.True(t, hasData)
	assert.Equal(t, 0, exitStatus)

	deviceData, ok := sm.SmartDataMap["lksjfh23lhj"]
	require.True(t, ok, "expected smart data entry for serial lksjfh23lhj")
	assert.Equal(t, uint8(43), deviceData.Temperature)
}

func TestParseSmartForSataNegativeDeviceStatistics(t *testing.T) {
	// Tests that negative values in ata_device_statistics (e.g. min operating temp)
	// do not cause the entire SAT parser to fail.
	jsonPayload := []byte(`{
		"smartctl": {"exit_status": 0},
		"device": {"name": "/dev/sdb", "type": "sat"},
		"model_name": "SanDisk SSD U110 16GB",
		"serial_number": "NEGATIVE123",
		"firmware_version": "U21B001",
		"user_capacity": {"bytes": 16013942784},
		"smart_status": {"passed": true},
		"temperature": {"current": 38},
		"ata_smart_attributes": {"table": []},
		"ata_device_statistics": {
			"pages": [
				{
					"number": 5,
					"name": "Temperature Statistics",
					"table": [
						{"name": "Current Temperature", "value": 38, "flags": {"valid": true}},
						{"name": "Specified Minimum Operating Temperature", "value": -20, "flags": {"valid": true}}
					]
				}
			]
		}
	}`)

	sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
	hasData, exitStatus := sm.parseSmartForSata(jsonPayload)
	require.True(t, hasData)
	assert.Equal(t, 0, exitStatus)

	deviceData, ok := sm.SmartDataMap["NEGATIVE123"]
	require.True(t, ok, "expected smart data entry for serial NEGATIVE123")
	assert.Equal(t, uint8(38), deviceData.Temperature)
}

func TestParseSmartForSataParentheticalRawValue(t *testing.T) {
	jsonPayload := []byte(`{
		"smartctl": {"exit_status": 0},
		"device": {"name": "/dev/sdz", "type": "sat"},
		"model_name": "Example",
		"serial_number": "PARENTHESES123",
		"firmware_version": "1.0",
		"user_capacity": {"bytes": 1024},
		"smart_status": {"passed": true},
		"temperature": {"current": 25},
		"ata_smart_attributes": {
			"table": [
				{
					"id": 9,
					"name": "Power_On_Hours",
					"value": 93,
					"worst": 55,
					"thresh": 0,
					"when_failed": "",
                    "raw": {
                        "value": 57891864217128,
                        "string": "39925 (212 206 0)"
                    }
				}
			]
		}
	}`)

	sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}

	hasData, exitStatus := sm.parseSmartForSata(jsonPayload)
	require.True(t, hasData)
	assert.Equal(t, 0, exitStatus)

	data, ok := sm.SmartDataMap["PARENTHESES123"]
	require.True(t, ok)
	require.Len(t, data.Attributes, 1)

	attr := data.Attributes[0]
	assert.Equal(t, uint64(39925), attr.RawValue)
	assert.Equal(t, "39925 (212 206 0)", attr.RawString)
}

func TestParseSmartForNvme(t *testing.T) {
	fixturePath := filepath.Join("test-data", "smart", "nvme0.json")
	data, err := os.ReadFile(fixturePath)
	require.NoError(t, err)

	sm := &SmartManager{
		SmartDataMap: make(map[string]*smart.SmartData),
	}

	hasData, exitStatus := sm.parseSmartForNvme(data)
	require.True(t, hasData)
	assert.Equal(t, 0, exitStatus)

	deviceData, ok := sm.SmartDataMap["2024031600129"]
	require.True(t, ok, "expected smart data entry for serial 2024031600129")

	assert.Equal(t, "PELADN 512GB", deviceData.ModelName)
	assert.Equal(t, "VC2S038E", deviceData.FirmwareVersion)
	assert.Equal(t, "/dev/nvme0", deviceData.DiskName)
	assert.Equal(t, "nvme", deviceData.DiskType)
	assert.Equal(t, uint8(61), deviceData.Temperature)
	assert.Equal(t, "PASSED", deviceData.SmartStatus)
	assert.Equal(t, uint64(512110190592), deviceData.Capacity)
	if assert.NotEmpty(t, deviceData.Attributes) {
		assertAttrValue(t, deviceData.Attributes, "PercentageUsed", 0)
		assertAttrValue(t, deviceData.Attributes, "DataUnitsWritten", 16040567)
	}
}

func TestHasDataForDevice(t *testing.T) {
	sm := &SmartManager{
		SmartDataMap: map[string]*smart.SmartData{
			"serial-1": {DiskName: "/dev/sda"},
			"serial-2": nil,
		},
	}

	assert.True(t, sm.hasDataForDevice("/dev/sda"))
	assert.False(t, sm.hasDataForDevice("/dev/sdb"))
}

func TestDevicesSnapshotReturnsCopy(t *testing.T) {
	originalDevice := &DeviceInfo{Name: "/dev/sda"}
	sm := &SmartManager{
		SmartDevices: []*DeviceInfo{
			originalDevice,
			{Name: "/dev/sdb"},
		},
	}

	snapshot := sm.devicesSnapshot()
	require.Len(t, snapshot, 2)

	sm.SmartDevices[0] = &DeviceInfo{Name: "/dev/sdz"}
	assert.Equal(t, "/dev/sda", snapshot[0].Name)

	snapshot[1] = &DeviceInfo{Name: "/dev/nvme0"}
	assert.Equal(t, "/dev/sdb", sm.SmartDevices[1].Name)

	sm.SmartDevices = append(sm.SmartDevices, &DeviceInfo{Name: "/dev/nvme1"})
	assert.Len(t, snapshot, 2)
}

func TestScanDevicesWithEnvOverrideAndSeparator(t *testing.T) {
	t.Setenv("SMART_DEVICES_SEPARATOR", "|")
	t.Setenv("SMART_DEVICES", "/dev/sda:jmb39x-q,0|/dev/nvme0:nvme")

	sm := &SmartManager{
		SmartDataMap: make(map[string]*smart.SmartData),
	}

	err := sm.ScanDevices(true)
	require.NoError(t, err)

	require.Len(t, sm.SmartDevices, 2)
	assert.Equal(t, "/dev/sda", sm.SmartDevices[0].Name)
	assert.Equal(t, "jmb39x-q,0", sm.SmartDevices[0].Type)
	assert.Equal(t, "/dev/nvme0", sm.SmartDevices[1].Name)
	assert.Equal(t, "nvme", sm.SmartDevices[1].Type)
}

func TestScanDevicesWithEnvOverride(t *testing.T) {
	t.Setenv("SMART_DEVICES", "/dev/sda:sat, /dev/nvme0:nvme")

	sm := &SmartManager{
		SmartDataMap: make(map[string]*smart.SmartData),
	}

	err := sm.ScanDevices(true)
	require.NoError(t, err)

	require.Len(t, sm.SmartDevices, 2)
	assert.Equal(t, "/dev/sda", sm.SmartDevices[0].Name)
	assert.Equal(t, "sat", sm.SmartDevices[0].Type)
	assert.Equal(t, "/dev/nvme0", sm.SmartDevices[1].Name)
	assert.Equal(t, "nvme", sm.SmartDevices[1].Type)
}

func TestScanDevicesWithEnvOverrideInvalid(t *testing.T) {
	t.Setenv("SMART_DEVICES", ":sat")

	sm := &SmartManager{
		SmartDataMap: make(map[string]*smart.SmartData),
	}

	err := sm.ScanDevices(true)
	require.Error(t, err)
}

func TestScanDevicesWithEnvOverrideEmpty(t *testing.T) {
	t.Setenv("SMART_DEVICES", "   ")

	sm := &SmartManager{
		SmartDataMap: make(map[string]*smart.SmartData),
	}

	err := sm.ScanDevices(true)
	assert.ErrorIs(t, err, errNoValidSmartData)
	assert.Empty(t, sm.SmartDevices)
}

func TestSmartctlArgsWithoutType(t *testing.T) {
	device := &DeviceInfo{Name: "/dev/sda"}

	sm := &SmartManager{}

	args := sm.smartctlArgs(device, true)
	assert.Equal(t, []string{"-a", "--json=c", "-n", "standby", "/dev/sda"}, args)
}

func TestSmartctlArgs(t *testing.T) {
	sm := &SmartManager{}

	sataDevice := &DeviceInfo{Name: "/dev/sda", Type: "sat"}
	assert.Equal(t,
		[]string{"-d", "sat", "-a", "--json=c", "-l", "devstat", "-n", "standby", "/dev/sda"},
		sm.smartctlArgs(sataDevice, true),
	)

	assert.Equal(t,
		[]string{"-d", "sat", "-a", "--json=c", "-l", "devstat", "/dev/sda"},
		sm.smartctlArgs(sataDevice, false),
	)

	nvmeDevice := &DeviceInfo{Name: "/dev/nvme0", Type: "nvme"}
	assert.Equal(t,
		[]string{"-d", "nvme", "-a", "--json=c", "-n", "standby", "/dev/nvme0"},
		sm.smartctlArgs(nvmeDevice, true),
	)

	assert.Equal(t,
		[]string{"-a", "--json=c", "-n", "standby"},
		sm.smartctlArgs(nil, true),
	)
}

func TestResolveRefreshError(t *testing.T) {
	scanErr := errors.New("scan failed")
	collectErr := errors.New("collect failed")

	tests := []struct {
		name        string
		devices     []*DeviceInfo
		data        map[string]*smart.SmartData
		scanErr     error
		collectErr  error
		expectedErr error
		expectNoErr bool
	}{
		{
			name:        "no devices returns scan error",
			devices:     nil,
			data:        make(map[string]*smart.SmartData),
			scanErr:     scanErr,
			expectedErr: scanErr,
		},
		{
			name:        "has data ignores errors",
			devices:     []*DeviceInfo{{Name: "/dev/sda"}},
			data:        map[string]*smart.SmartData{"serial": {}},
			scanErr:     scanErr,
			collectErr:  collectErr,
			expectNoErr: true,
		},
		{
			name:        "collect error preferred",
			devices:     []*DeviceInfo{{Name: "/dev/sda"}},
			data:        make(map[string]*smart.SmartData),
			collectErr:  collectErr,
			expectedErr: collectErr,
		},
		{
			name:        "scan error returned when no data",
			devices:     []*DeviceInfo{{Name: "/dev/sda"}},
			data:        make(map[string]*smart.SmartData),
			scanErr:     scanErr,
			expectedErr: scanErr,
		},
		{
			name:        "no errors returns sentinel",
			devices:     []*DeviceInfo{{Name: "/dev/sda"}},
			data:        make(map[string]*smart.SmartData),
			expectedErr: errNoValidSmartData,
		},
		{
			name:        "no devices collect error",
			devices:     nil,
			data:        make(map[string]*smart.SmartData),
			collectErr:  collectErr,
			expectedErr: collectErr,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sm := &SmartManager{
				SmartDevices: tt.devices,
				SmartDataMap: tt.data,
			}

			err := sm.resolveRefreshError(tt.scanErr, tt.collectErr)
			if tt.expectNoErr {
				assert.NoError(t, err)
				return
			}

			if tt.expectedErr == nil {
				assert.NoError(t, err)
			} else {
				assert.Equal(t, tt.expectedErr, err)
			}
		})
	}
}

func TestParseScan(t *testing.T) {
	sm := &SmartManager{
		SmartDataMap: map[string]*smart.SmartData{
			"serial-active": {DiskName: "/dev/sda"},
			"serial-stale":  {DiskName: "/dev/sdb"},
		},
	}

	scanJSON := []byte(`{
        "devices": [
            {"name": "/dev/sda", "type": "sat", "info_name": "/dev/sda [SAT]", "protocol": "ATA"},
            {"name": "/dev/nvme0", "type": "nvme", "info_name": "/dev/nvme0", "protocol": "NVMe"}
        ]
    }`)

	devices, hasData := sm.parseScan(scanJSON)
	assert.True(t, hasData)

	sm.updateSmartDevices(devices)

	require.Len(t, sm.SmartDevices, 2)
	assert.Equal(t, "/dev/sda", sm.SmartDevices[0].Name)
	assert.Equal(t, "sat", sm.SmartDevices[0].Type)
	assert.Equal(t, "/dev/nvme0", sm.SmartDevices[1].Name)
	assert.Equal(t, "nvme", sm.SmartDevices[1].Type)

	_, activeExists := sm.SmartDataMap["serial-active"]
	assert.True(t, activeExists, "active smart data should be preserved when device path remains")

	_, staleExists := sm.SmartDataMap["serial-stale"]
	assert.False(t, staleExists, "stale smart data entry should be removed when device path disappears")
}

func TestMergeDeviceListsPrefersConfigured(t *testing.T) {
	scanned := []*DeviceInfo{
		{Name: "/dev/sda", Type: "sat", InfoName: "scan-info", Protocol: "ATA"},
		{Name: "/dev/nvme0", Type: "nvme"},
	}

	configured := []*DeviceInfo{
		{Name: "/dev/sda", Type: "sat-override"},
		{Name: "/dev/sdb", Type: "sat"},
	}

	merged := mergeDeviceLists(nil, scanned, configured)
	require.Len(t, merged, 3)

	byName := make(map[string]*DeviceInfo, len(merged))
	for _, dev := range merged {
		byName[dev.Name] = dev
	}

	require.Contains(t, byName, "/dev/sda")
	assert.Equal(t, "sat-override", byName["/dev/sda"].Type, "configured type should override scanned type")
	assert.Equal(t, "scan-info", byName["/dev/sda"].InfoName, "scan metadata should be preserved when config does not provide it")

	require.Contains(t, byName, "/dev/nvme0")
	assert.Equal(t, "nvme", byName["/dev/nvme0"].Type)

	require.Contains(t, byName, "/dev/sdb")
	assert.Equal(t, "sat", byName["/dev/sdb"].Type)
}

func TestMergeDeviceListsPreservesVerification(t *testing.T) {
	existing := []*DeviceInfo{
		{Name: "/dev/sda", Type: "sat+megaraid", parserType: "sat", typeVerified: true},
	}

	scanned := []*DeviceInfo{
		{Name: "/dev/sda", Type: "nvme"},
	}

	merged := mergeDeviceLists(existing, scanned, nil)
	require.Len(t, merged, 1)

	device := merged[0]
	assert.True(t, device.typeVerified)
	assert.Equal(t, "sat", device.parserType)
	assert.Equal(t, "sat+megaraid", device.Type)
}

func TestMergeDeviceListsUpdatesTypeWhenUnverified(t *testing.T) {
	existing := []*DeviceInfo{
		{Name: "/dev/sda", Type: "sat", parserType: "sat", typeVerified: false},
	}

	scanned := []*DeviceInfo{
		{Name: "/dev/sda", Type: "nvme"},
	}

	merged := mergeDeviceLists(existing, scanned, nil)
	require.Len(t, merged, 1)

	device := merged[0]
	assert.False(t, device.typeVerified)
	assert.Equal(t, "nvme", device.Type)
	assert.Equal(t, "", device.parserType)
}

func TestMergeDeviceListsHandlesDevicesWithSameNameAndDifferentTypes(t *testing.T) {
	// There are use cases where the same device name is re-used,
	// for example, a RAID controller with multiple drives.
	scanned := []*DeviceInfo{
		{Name: "/dev/sda", Type: "megaraid,0"},
		{Name: "/dev/sda", Type: "megaraid,1"},
		{Name: "/dev/sda", Type: "megaraid,2"},
	}

	merged := mergeDeviceLists(nil, scanned, nil)
	require.Len(t, merged, 3, "should have 3 separate devices for RAID controller")

	byKey := make(map[string]*DeviceInfo, len(merged))
	for _, dev := range merged {
		key := dev.Name + "|" + dev.Type
		byKey[key] = dev
	}

	assert.Contains(t, byKey, "/dev/sda|megaraid,0")
	assert.Contains(t, byKey, "/dev/sda|megaraid,1")
	assert.Contains(t, byKey, "/dev/sda|megaraid,2")
}

func TestMergeDeviceListsHandlesMixedRAIDAndRegular(t *testing.T) {
	// Test mixing RAID drives with regular devices
	scanned := []*DeviceInfo{
		{Name: "/dev/sda", Type: "megaraid,0"},
		{Name: "/dev/sda", Type: "megaraid,1"},
		{Name: "/dev/sdb", Type: "sat"},
		{Name: "/dev/nvme0", Type: "nvme"},
	}

	merged := mergeDeviceLists(nil, scanned, nil)
	require.Len(t, merged, 4, "should have 4 separate devices")

	byKey := make(map[string]*DeviceInfo, len(merged))
	for _, dev := range merged {
		key := dev.Name + "|" + dev.Type
		byKey[key] = dev
	}

	assert.Contains(t, byKey, "/dev/sda|megaraid,0")
	assert.Contains(t, byKey, "/dev/sda|megaraid,1")
	assert.Contains(t, byKey, "/dev/sdb|sat")
	assert.Contains(t, byKey, "/dev/nvme0|nvme")
}

func TestUpdateSmartDevicesPreservesRAIDDrives(t *testing.T) {
	// Test that updateSmartDevices correctly validates RAID drives using composite keys
	sm := &SmartManager{
		SmartDevices: []*DeviceInfo{
			{Name: "/dev/sda", Type: "megaraid,0"},
			{Name: "/dev/sda", Type: "megaraid,1"},
		},
		SmartDataMap: map[string]*smart.SmartData{
			"serial-0": {
				DiskName:     "/dev/sda",
				DiskType:     "megaraid,0",
				SerialNumber: "serial-0",
			},
			"serial-1": {
				DiskName:     "/dev/sda",
				DiskType:     "megaraid,1",
				SerialNumber: "serial-1",
			},
			"serial-stale": {
				DiskName:     "/dev/sda",
				DiskType:     "megaraid,2",
				SerialNumber: "serial-stale",
			},
		},
	}

	sm.updateSmartDevices(sm.SmartDevices)

	// serial-0 and serial-1 should be preserved (matching devices exist)
	assert.Contains(t, sm.SmartDataMap, "serial-0")
	assert.Contains(t, sm.SmartDataMap, "serial-1")
	// serial-stale should be removed (no matching device)
	assert.NotContains(t, sm.SmartDataMap, "serial-stale")
}

func TestParseSmartOutputMarksVerified(t *testing.T) {
	fixturePath := filepath.Join("test-data", "smart", "nvme0.json")
	data, err := os.ReadFile(fixturePath)
	require.NoError(t, err)

	sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
	device := &DeviceInfo{Name: "/dev/nvme0"}

	require.True(t, sm.parseSmartOutput(device, data))
	assert.Equal(t, "nvme", device.Type)
	assert.Equal(t, "nvme", device.parserType)
	assert.True(t, device.typeVerified)
}

func TestParseSmartOutputKeepsCustomType(t *testing.T) {
	fixturePath := filepath.Join("test-data", "smart", "sda.json")
	data, err := os.ReadFile(fixturePath)
	require.NoError(t, err)

	sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
	device := &DeviceInfo{Name: "/dev/sda", Type: "sat+megaraid"}

	require.True(t, sm.parseSmartOutput(device, data))
	assert.Equal(t, "sat+megaraid", device.Type)
	assert.Equal(t, "sat", device.parserType)
	assert.True(t, device.typeVerified)
}

func TestParseSmartOutputResetsVerificationOnFailure(t *testing.T) {
	sm := &SmartManager{SmartDataMap: make(map[string]*smart.SmartData)}
	device := &DeviceInfo{Name: "/dev/sda", Type: "sat", parserType: "sat", typeVerified: true}

	assert.False(t, sm.parseSmartOutput(device, []byte("not json")))
	assert.False(t, device.typeVerified)
	assert.Equal(t, "sat", device.parserType)
}

func assertAttrValue(t *testing.T, attributes []*smart.SmartAttribute, name string, expected uint64) {
	t.Helper()
	attr := findAttr(attributes, name)
	if attr == nil {
		t.Fatalf("expected attribute %s to be present", name)
	}
	if attr.RawValue != expected {
		t.Fatalf("unexpected attribute %s value: got %d, want %d", name, attr.RawValue, expected)
	}
}

func findAttr(attributes []*smart.SmartAttribute, name string) *smart.SmartAttribute {
	for _, attr := range attributes {
		if attr != nil && attr.Name == name {
			return attr
		}
	}
	return nil
}

func TestIsVirtualDevice(t *testing.T) {
	sm := &SmartManager{}

	tests := []struct {
		name     string
		vendor   string
		product  string
		model    string
		expected bool
	}{
		{"regular drive", "SEAGATE", "ST1000DM003", "ST1000DM003-1CH162", false},
		{"qemu virtual", "QEMU", "QEMU HARDDISK", "QEMU HARDDISK", true},
		{"virtualbox virtual", "VBOX", "HARDDISK", "VBOX HARDDISK", true},
		{"vmware virtual", "VMWARE", "Virtual disk", "VMWARE Virtual disk", true},
		{"virtual in model", "ATA", "VIRTUAL", "VIRTUAL DISK", true},
		{"iet virtual", "IET", "VIRTUAL-DISK", "VIRTUAL-DISK", true},
		{"hyper-v virtual", "MSFT", "VIRTUAL HD", "VIRTUAL HD", true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			data := &smart.SmartInfoForSata{
				ScsiVendor:  tt.vendor,
				ScsiProduct: tt.product,
				ModelName:   tt.model,
			}
			result := sm.isVirtualDevice(data)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestIsVirtualDeviceNvme(t *testing.T) {
	sm := &SmartManager{}

	tests := []struct {
		name     string
		model    string
		expected bool
	}{
		{"regular nvme", "Samsung SSD 970 EVO Plus 1TB", false},
		{"qemu virtual", "QEMU NVMe Ctrl", true},
		{"virtualbox virtual", "VBOX NVMe", true},
		{"vmware virtual", "VMWARE NVMe", true},
		{"virtual in model", "Virtual NVMe Device", true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			data := &smart.SmartInfoForNvme{
				ModelName: tt.model,
			}
			result := sm.isVirtualDeviceNvme(data)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestIsVirtualDeviceScsi(t *testing.T) {
	sm := &SmartManager{}

	tests := []struct {
		name     string
		vendor   string
		product  string
		model    string
		expected bool
	}{
		{"regular scsi", "SEAGATE", "ST1000DM003", "ST1000DM003-1CH162", false},
		{"qemu virtual", "QEMU", "QEMU HARDDISK", "QEMU HARDDISK", true},
		{"virtualbox virtual", "VBOX", "HARDDISK", "VBOX HARDDISK", true},
		{"vmware virtual", "VMWARE", "Virtual disk", "VMWARE Virtual disk", true},
		{"virtual in model", "ATA", "VIRTUAL", "VIRTUAL DISK", true},
		{"iet virtual", "IET", "VIRTUAL-DISK", "VIRTUAL-DISK", true},
		{"hyper-v virtual", "MSFT", "VIRTUAL HD", "VIRTUAL HD", true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			data := &smart.SmartInfoForScsi{
				ScsiVendor:    tt.vendor,
				ScsiProduct:   tt.product,
				ScsiModelName: tt.model,
			}
			result := sm.isVirtualDeviceScsi(data)
			assert.Equal(t, tt.expected, result)
		})
	}
}

func TestFindAtaDeviceStatisticsValue(t *testing.T) {
	val42 := int64(42)
	val100 := int64(100)
	valMinus20 := int64(-20)

	tests := []struct {
		name           string
		data           smart.SmartInfoForSata
		ataDeviceStats smart.AtaDeviceStatistics
		entryNumber    uint8
		entryName      string
		minValue       int64
		maxValue       int64
		expectedValue  *int64
	}{
		{
			name: "value in ataDeviceStats",
			ataDeviceStats: smart.AtaDeviceStatistics{
				Pages: []smart.AtaDeviceStatisticsPage{
					{
						Number: 5,
						Table: []smart.AtaDeviceStatisticsEntry{
							{Name: "Current Temperature", Value: &val42},
						},
					},
				},
			},
			entryNumber:   5,
			entryName:     "Current Temperature",
			minValue:      0,
			maxValue:      100,
			expectedValue: &val42,
		},
		{
			name: "value unmarshaled from data",
			data: smart.SmartInfoForSata{
				AtaDeviceStatistics: []byte(`{"pages":[{"number":5,"table":[{"name":"Current Temperature","value":100}]}]}`),
			},
			entryNumber:   5,
			entryName:     "Current Temperature",
			minValue:      0,
			maxValue:      255,
			expectedValue: &val100,
		},
		{
			name: "value out of range (too high)",
			ataDeviceStats: smart.AtaDeviceStatistics{
				Pages: []smart.AtaDeviceStatisticsPage{
					{
						Number: 5,
						Table: []smart.AtaDeviceStatisticsEntry{
							{Name: "Current Temperature", Value: &val100},
						},
					},
				},
			},
			entryNumber:   5,
			entryName:     "Current Temperature",
			minValue:      0,
			maxValue:      50,
			expectedValue: nil,
		},
		{
			name: "value out of range (too low)",
			ataDeviceStats: smart.AtaDeviceStatistics{
				Pages: []smart.AtaDeviceStatisticsPage{
					{
						Number: 5,
						Table: []smart.AtaDeviceStatisticsEntry{
							{Name: "Min Temp", Value: &valMinus20},
						},
					},
				},
			},
			entryNumber:   5,
			entryName:     "Min Temp",
			minValue:      0,
			maxValue:      100,
			expectedValue: nil,
		},
		{
			name:          "no statistics available",
			data:          smart.SmartInfoForSata{},
			entryNumber:   5,
			entryName:     "Current Temperature",
			minValue:      0,
			maxValue:      255,
			expectedValue: nil,
		},
		{
			name: "wrong page number",
			ataDeviceStats: smart.AtaDeviceStatistics{
				Pages: []smart.AtaDeviceStatisticsPage{
					{
						Number: 1,
						Table: []smart.AtaDeviceStatisticsEntry{
							{Name: "Current Temperature", Value: &val42},
						},
					},
				},
			},
			entryNumber:   5,
			entryName:     "Current Temperature",
			minValue:      0,
			maxValue:      100,
			expectedValue: nil,
		},
		{
			name: "wrong entry name",
			ataDeviceStats: smart.AtaDeviceStatistics{
				Pages: []smart.AtaDeviceStatisticsPage{
					{
						Number: 5,
						Table: []smart.AtaDeviceStatisticsEntry{
							{Name: "Other Stat", Value: &val42},
						},
					},
				},
			},
			entryNumber:   5,
			entryName:     "Current Temperature",
			minValue:      0,
			maxValue:      100,
			expectedValue: nil,
		},
		{
			name: "case insensitive name match",
			ataDeviceStats: smart.AtaDeviceStatistics{
				Pages: []smart.AtaDeviceStatisticsPage{
					{
						Number: 5,
						Table: []smart.AtaDeviceStatisticsEntry{
							{Name: "CURRENT TEMPERATURE", Value: &val42},
						},
					},
				},
			},
			entryNumber:   5,
			entryName:     "Current Temperature",
			minValue:      0,
			maxValue:      100,
			expectedValue: &val42,
		},
		{
			name: "entry value is nil",
			ataDeviceStats: smart.AtaDeviceStatistics{
				Pages: []smart.AtaDeviceStatisticsPage{
					{
						Number: 5,
						Table: []smart.AtaDeviceStatisticsEntry{
							{Name: "Current Temperature", Value: nil},
						},
					},
				},
			},
			entryNumber:   5,
			entryName:     "Current Temperature",
			minValue:      0,
			maxValue:      100,
			expectedValue: nil,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := findAtaDeviceStatisticsValue(&tt.data, &tt.ataDeviceStats, tt.entryNumber, tt.entryName, tt.minValue, tt.maxValue)
			if tt.expectedValue == nil {
				assert.Nil(t, result)
			} else {
				require.NotNil(t, result)
				assert.Equal(t, *tt.expectedValue, *result)
			}
		})
	}
}

func TestRefreshExcludedDevices(t *testing.T) {
	tests := []struct {
		name         string
		envValue     string
		expectedDevs map[string]struct{}
	}{
		{
			name:         "empty env",
			envValue:     "",
			expectedDevs: map[string]struct{}{},
		},
		{
			name:     "single device",
			envValue: "/dev/sda",
			expectedDevs: map[string]struct{}{
				"/dev/sda": {},
			},
		},
		{
			name:     "multiple devices",
			envValue: "/dev/sda,/dev/sdb,/dev/nvme0",
			expectedDevs: map[string]struct{}{
				"/dev/sda":   {},
				"/dev/sdb":   {},
				"/dev/nvme0": {},
			},
		},
		{
			name:     "devices with whitespace",
			envValue: " /dev/sda , /dev/sdb ,  /dev/nvme0  ",
			expectedDevs: map[string]struct{}{
				"/dev/sda":   {},
				"/dev/sdb":   {},
				"/dev/nvme0": {},
			},
		},
		{
			name:     "duplicate devices",
			envValue: "/dev/sda,/dev/sdb,/dev/sda",
			expectedDevs: map[string]struct{}{
				"/dev/sda": {},
				"/dev/sdb": {},
			},
		},
		{
			name:     "empty entries and whitespace",
			envValue: "/dev/sda,, /dev/sdb , , ",
			expectedDevs: map[string]struct{}{
				"/dev/sda": {},
				"/dev/sdb": {},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if tt.envValue != "" {
				t.Setenv("EXCLUDE_SMART", tt.envValue)
			} else {
				// Ensure env var is not set for empty test
				t.Setenv("EXCLUDE_SMART", "")
			}

			sm := &SmartManager{}
			sm.refreshExcludedDevices()

			assert.Equal(t, tt.expectedDevs, sm.excludedDevices)
		})
	}
}

func TestIsExcludedDevice(t *testing.T) {
	sm := &SmartManager{
		excludedDevices: map[string]struct{}{
			"/dev/sda":   {},
			"/dev/nvme0": {},
		},
	}

	tests := []struct {
		name         string
		deviceName   string
		expectedBool bool
	}{
		{"excluded device sda", "/dev/sda", true},
		{"excluded device nvme0", "/dev/nvme0", true},
		{"non-excluded device sdb", "/dev/sdb", false},
		{"non-excluded device nvme1", "/dev/nvme1", false},
		{"empty device name", "", false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := sm.isExcludedDevice(tt.deviceName)
			assert.Equal(t, tt.expectedBool, result)
		})
	}
}

func TestFilterExcludedDevices(t *testing.T) {
	tests := []struct {
		name           string
		excludedDevs   map[string]struct{}
		inputDevices   []*DeviceInfo
		expectedDevs   []*DeviceInfo
		expectedLength int
	}{
		{
			name:         "no exclusions",
			excludedDevs: map[string]struct{}{},
			inputDevices: []*DeviceInfo{
				{Name: "/dev/sda"},
				{Name: "/dev/sdb"},
				{Name: "/dev/nvme0"},
			},
			expectedDevs: []*DeviceInfo{
				{Name: "/dev/sda"},
				{Name: "/dev/sdb"},
				{Name: "/dev/nvme0"},
			},
			expectedLength: 3,
		},
		{
			name: "some devices excluded",
			excludedDevs: map[string]struct{}{
				"/dev/sda":   {},
				"/dev/nvme0": {},
			},
			inputDevices: []*DeviceInfo{
				{Name: "/dev/sda"},
				{Name: "/dev/sdb"},
				{Name: "/dev/nvme0"},
				{Name: "/dev/nvme1"},
			},
			expectedDevs: []*DeviceInfo{
				{Name: "/dev/sdb"},
				{Name: "/dev/nvme1"},
			},
			expectedLength: 2,
		},
		{
			name: "all devices excluded",
			excludedDevs: map[string]struct{}{
				"/dev/sda": {},
				"/dev/sdb": {},
			},
			inputDevices: []*DeviceInfo{
				{Name: "/dev/sda"},
				{Name: "/dev/sdb"},
			},
			expectedDevs:   []*DeviceInfo{},
			expectedLength: 0,
		},
		{
			name:           "nil devices",
			excludedDevs:   map[string]struct{}{},
			inputDevices:   nil,
			expectedDevs:   []*DeviceInfo{},
			expectedLength: 0,
		},
		{
			name: "filter nil and empty name devices",
			excludedDevs: map[string]struct{}{
				"/dev/sda": {},
			},
			inputDevices: []*DeviceInfo{
				{Name: "/dev/sda"},
				nil,
				{Name: ""},
				{Name: "/dev/sdb"},
			},
			expectedDevs: []*DeviceInfo{
				{Name: "/dev/sdb"},
			},
			expectedLength: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			sm := &SmartManager{
				excludedDevices: tt.excludedDevs,
			}

			result := sm.filterExcludedDevices(tt.inputDevices)

			assert.Len(t, result, tt.expectedLength)
			assert.Equal(t, tt.expectedDevs, result)
		})
	}
}

func TestIsNvmeControllerPath(t *testing.T) {
	tests := []struct {
		path     string
		expected bool
	}{
		// Controller paths (should return true)
		{"/dev/nvme0", true},
		{"/dev/nvme1", true},
		{"/dev/nvme10", true},
		{"nvme0", true},

		// Namespace paths (should return false)
		{"/dev/nvme0n1", false},
		{"/dev/nvme1n1", false},
		{"/dev/nvme0n1p1", false},
		{"nvme0n1", false},

		// Non-NVMe paths (should return false)
		{"/dev/sda", false},
		{"/dev/sda1", false},
		{"/dev/hda", false},
		{"", false},
		{"/dev/nvme", false},
	}

	for _, tt := range tests {
		t.Run(tt.path, func(t *testing.T) {
			result := isNvmeControllerPath(tt.path)
			assert.Equal(t, tt.expected, result, "path: %s", tt.path)
		})
	}
}

func TestParseSmartForNvmeAppleSSD(t *testing.T) {
	// Apple SSDs don't report user_capacity via smartctl; capacity should be fetched
	// from system_profiler via the darwinNvmeProvider fallback.
	fixturePath := filepath.Join("test-data", "smart", "apple_nvme.json")
	data, err := os.ReadFile(fixturePath)
	require.NoError(t, err)

	providerCalls := 0
	fakeProvider := func() ([]byte, error) {
		providerCalls++
		return []byte(`{
			"SPNVMeDataType": [{
				"_items": [{
					"device_serial": "0ba0147940253c15",
					"size_in_bytes": 251000193024
				}]
			}]
		}`), nil
	}

	sm := &SmartManager{
		SmartDataMap:       make(map[string]*smart.SmartData),
		darwinNvmeProvider: fakeProvider,
	}

	hasData, _ := sm.parseSmartForNvme(data)
	require.True(t, hasData)

	deviceData, ok := sm.SmartDataMap["0ba0147940253c15"]
	require.True(t, ok)
	assert.Equal(t, "APPLE SSD AP0256Q", deviceData.ModelName)
	assert.Equal(t, uint64(251000193024), deviceData.Capacity)
	assert.Equal(t, uint8(42), deviceData.Temperature)
	assert.Equal(t, "PASSED", deviceData.SmartStatus)
	assert.Equal(t, 1, providerCalls, "system_profiler should be called once")

	// Second parse: provider should NOT be called again (cache hit)
	_, _ = sm.parseSmartForNvme(data)
	assert.Equal(t, 1, providerCalls, "system_profiler should not be called again after caching")
}

func TestLookupDarwinNvmeCapacityMultipleDisks(t *testing.T) {
	fakeProvider := func() ([]byte, error) {
		return []byte(`{
			"SPNVMeDataType": [
				{
					"_items": [
						{"device_serial": "serial-disk0", "size_in_bytes": 251000193024},
						{"device_serial": "serial-disk1", "size_in_bytes": 1000204886016}
					]
				},
				{
					"_items": [
						{"device_serial": "serial-disk2", "size_in_bytes": 512110190592}
					]
				}
			]
		}`), nil
	}

	sm := &SmartManager{darwinNvmeProvider: fakeProvider}
	assert.Equal(t, uint64(251000193024), sm.lookupDarwinNvmeCapacity("serial-disk0"))
	assert.Equal(t, uint64(1000204886016), sm.lookupDarwinNvmeCapacity("serial-disk1"))
	assert.Equal(t, uint64(512110190592), sm.lookupDarwinNvmeCapacity("serial-disk2"))
	assert.Equal(t, uint64(0), sm.lookupDarwinNvmeCapacity("unknown-serial"))
}

func TestLookupDarwinNvmeCapacityProviderError(t *testing.T) {
	fakeProvider := func() ([]byte, error) {
		return nil, errors.New("system_profiler not found")
	}

	sm := &SmartManager{darwinNvmeProvider: fakeProvider}
	assert.Equal(t, uint64(0), sm.lookupDarwinNvmeCapacity("any-serial"))
	// Cache should be initialized even on error so we don't retry (Once already fired)
	assert.NotNil(t, sm.darwinNvmeCapacity)
}
