package common

import (
	"testing"

	"github.com/stretchr/testify/require"
)

const sampleNodeMetrics = `# HELP node_filesystem_size_bytes Filesystem size in bytes.
# TYPE node_filesystem_size_bytes gauge
node_filesystem_size_bytes{device="/dev/nvme0n1p1",device_error="",fstype="xfs",mountpoint="/local"} 5.3573738496e+10
node_filesystem_size_bytes{device="/dev/nvme1n1p12",device_error="",fstype="ext4",mountpoint="/.bottlerocket"} 3.6073472e+07
node_filesystem_size_bytes{device="/dev/nvme2n1",device_error="",fstype="ext4",mountpoint="/var/lib/kubelet/plugins/kubernetes.io/csi/ebs.csi.aws.com/abc123/globalmount"} 4.1956900864e+10
# HELP node_disk_reads_completed_total The total number of reads completed successfully.
# TYPE node_disk_reads_completed_total counter
node_disk_reads_completed_total{device="nvme0n1"} 920
node_disk_reads_completed_total{device="nvme1n1"} 1618
node_disk_reads_completed_total{device="nvme2n1"} 2148
# HELP node_disk_writes_completed_total The total number of writes completed successfully.
# TYPE node_disk_writes_completed_total counter
node_disk_writes_completed_total{device="nvme0n1"} 889957
node_disk_writes_completed_total{device="nvme1n1"} 119
node_disk_writes_completed_total{device="nvme2n1"} 8134516
# HELP node_disk_read_bytes_total The total number of bytes read successfully.
# TYPE node_disk_read_bytes_total counter
node_disk_read_bytes_total{device="nvme0n1"} 3.3104384e+07
node_disk_read_bytes_total{device="nvme1n1"} 1.64206592e+08
node_disk_read_bytes_total{device="nvme2n1"} 2.5131008e+08
# HELP node_disk_written_bytes_total The total number of bytes written successfully.
# TYPE node_disk_written_bytes_total counter
node_disk_written_bytes_total{device="nvme0n1"} 1.5029256192e+10
node_disk_written_bytes_total{device="nvme1n1"} 4.993024e+06
node_disk_written_bytes_total{device="nvme2n1"} 7.8940286976e+10
`

func TestParseNodeDiskMetrics_CSIDevice(t *testing.T) {
	counters, err := ParseNodeDiskMetrics([]byte(sampleNodeMetrics))
	require.NoError(t, err)
	require.NotNil(t, counters)

	// CSI device is nvme2n1 (mountpoint contains "csi")
	require.Equal(t, "/dev/nvme2n1", counters.Device)
	require.InDelta(t, 2148, counters.ReadsCompleted, 0.01)
	require.InDelta(t, 8134516, counters.WritesCompleted, 0.01)
	require.InDelta(t, 2.5131008e+08, counters.ReadBytesTotal, 1)
	require.InDelta(t, 7.8940286976e+10, counters.WrittenBytesTotal, 1)
}

func TestParseNodeDiskMetrics_MultipleCSIDevices(t *testing.T) {
	// Two CSI volumes: nvme2n1 (42GB raft/WAL) and nvme3n1 (1.5TB data disk).
	// Must pick the largest CSI device (nvme3n1), not the first one encountered.
	multiCSI := `# HELP node_filesystem_size_bytes Filesystem size in bytes.
# TYPE node_filesystem_size_bytes gauge
node_filesystem_size_bytes{device="/dev/nvme0n1p1",device_error="",fstype="xfs",mountpoint="/local"} 5.3573738496e+10
node_filesystem_size_bytes{device="/dev/nvme2n1",device_error="",fstype="ext4",mountpoint="/var/lib/kubelet/plugins/kubernetes.io/csi/ebs.csi.aws.com/abc/globalmount"} 4.1956900864e+10
node_filesystem_size_bytes{device="/dev/nvme3n1",device_error="",fstype="ext4",mountpoint="/var/lib/kubelet/plugins/kubernetes.io/csi/ebs.csi.aws.com/def/globalmount"} 1.584180465664e+12
# HELP node_disk_reads_completed_total counters
# TYPE node_disk_reads_completed_total counter
node_disk_reads_completed_total{device="nvme2n1"} 100
node_disk_reads_completed_total{device="nvme3n1"} 5000
# HELP node_disk_writes_completed_total counters
# TYPE node_disk_writes_completed_total counter
node_disk_writes_completed_total{device="nvme2n1"} 200
node_disk_writes_completed_total{device="nvme3n1"} 90000
# HELP node_disk_read_bytes_total counters
# TYPE node_disk_read_bytes_total counter
node_disk_read_bytes_total{device="nvme2n1"} 1e+08
node_disk_read_bytes_total{device="nvme3n1"} 5e+09
# HELP node_disk_written_bytes_total counters
# TYPE node_disk_written_bytes_total counter
node_disk_written_bytes_total{device="nvme2n1"} 2e+08
node_disk_written_bytes_total{device="nvme3n1"} 8e+10
`
	counters, err := ParseNodeDiskMetrics([]byte(multiCSI))
	require.NoError(t, err)
	require.NotNil(t, counters)

	// Must select nvme3n1 (the larger CSI disk), not nvme2n1
	require.Equal(t, "/dev/nvme3n1", counters.Device)
	require.InDelta(t, 5000, counters.ReadsCompleted, 0.01)
	require.InDelta(t, 90000, counters.WritesCompleted, 0.01)
	require.InDelta(t, 5e+09, counters.ReadBytesTotal, 1)
	require.InDelta(t, 8e+10, counters.WrittenBytesTotal, 1)
}

func TestParseNodeDiskMetrics_FallbackToLargest(t *testing.T) {
	// No CSI mountpoint — should use the largest filesystem device
	noCSI := `# HELP node_filesystem_size_bytes Filesystem size in bytes.
# TYPE node_filesystem_size_bytes gauge
node_filesystem_size_bytes{device="/dev/nvme0n1p1",device_error="",fstype="xfs",mountpoint="/local"} 5e+10
node_filesystem_size_bytes{device="/dev/nvme2n1",device_error="",fstype="ext4",mountpoint="/data"} 1e+11
# HELP node_disk_reads_completed_total counters
# TYPE node_disk_reads_completed_total counter
node_disk_reads_completed_total{device="nvme0n1"} 100
node_disk_reads_completed_total{device="nvme2n1"} 200
`
	counters, err := ParseNodeDiskMetrics([]byte(noCSI))
	require.NoError(t, err)
	require.NotNil(t, counters)
	require.Equal(t, "/dev/nvme2n1", counters.Device)
	require.InDelta(t, 200, counters.ReadsCompleted, 0.01)
}

func TestParseNodeDiskMetrics_Empty(t *testing.T) {
	counters, err := ParseNodeDiskMetrics([]byte(""))
	require.NoError(t, err)
	require.Nil(t, counters)
}

func TestStripPartition(t *testing.T) {
	tests := []struct {
		input, expected string
	}{
		{"/dev/nvme2n1p1", "/dev/nvme2n1"},
		{"/dev/nvme0n1p12", "/dev/nvme0n1"},
		{"/dev/nvme2n1", "/dev/nvme2n1"},
		{"nvme2n1p1", "nvme2n1"},
		{"nvme2n1", "nvme2n1"},
		{"/dev/sda1", "/dev/sda"},
		{"/dev/sda", "/dev/sda"},
	}
	for _, tt := range tests {
		t.Run(tt.input, func(t *testing.T) {
			require.Equal(t, tt.expected, stripPartition(tt.input))
		})
	}
}

func TestExtractLabel(t *testing.T) {
	line := `node_disk_info{device="nvme2n1",major="259",minor="2"} 1`
	require.Equal(t, "nvme2n1", extractLabel(line, "device"))
	require.Equal(t, "259", extractLabel(line, "major"))
	require.Empty(t, extractLabel(line, "serial"))
}

func TestDiskCounters_TotalIOPS(t *testing.T) {
	c := &DiskCounters{ReadsCompleted: 100, WritesCompleted: 200}
	require.InDelta(t, 300, c.TotalIOPS(), 0.01)
}

func TestDiskCounters_TotalBytes(t *testing.T) {
	c := &DiskCounters{ReadBytesTotal: 1000, WrittenBytesTotal: 2000}
	require.InDelta(t, 3000, c.TotalBytes(), 0.01)
}

const sampleNodeDiskInfo = `# HELP node_disk_info Info of /sys/block/<block_device>.
# TYPE node_disk_info gauge
node_disk_info{device="nvme0n1",major="259",minor="0",model="Amazon Elastic Block Store",path="ip-10-0-0-1",revision="1.0",serial="vol0abc1230000000001",wwn=""} 1
node_disk_info{device="nvme1n1",major="259",minor="1",model="Amazon EC2 NVMe Instance Storage",path="",revision="",serial="AWS1234567890",wwn=""} 1
node_disk_info{device="nvme2n1",major="259",minor="2",model="Amazon Elastic Block Store",path="ip-10-0-0-1",revision="1.0",serial="vol0def4560000000002",wwn=""} 1
node_disk_info{device="nvme3n1",major="259",minor="3",model="Amazon Elastic Block Store",path="ip-10-0-0-1",revision="1.0",serial="vol0789abc0000000003",wwn=""} 1
` + sampleNodeMetrics

func TestFindDeviceBySerial(t *testing.T) {
	raw := []byte(sampleNodeDiskInfo)

	tests := []struct {
		name     string
		serial   string
		expected string
	}{
		{"match first EBS volume", "vol0abc1230000000001", "/dev/nvme0n1"},
		{"match third EBS volume", "vol0789abc0000000003", "/dev/nvme3n1"},
		{"no match", "vol099999", ""},
		{"empty serial", "", ""},
		{"instance store (non-EBS)", "AWS1234567890", "/dev/nvme1n1"},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			require.Equal(t, tt.expected, FindDeviceBySerial(raw, tt.serial))
		})
	}
}

func TestExtractDiskCountersForDevice(t *testing.T) {
	raw := []byte(sampleNodeMetrics)

	counters := ExtractDiskCountersForDevice(raw, "/dev/nvme2n1")
	require.Equal(t, "/dev/nvme2n1", counters.Device)
	require.InDelta(t, 2148, counters.ReadsCompleted, 0.01)
	require.InDelta(t, 8134516, counters.WritesCompleted, 0.01)
	require.InDelta(t, 2.5131008e+08, counters.ReadBytesTotal, 1)
	require.InDelta(t, 7.8940286976e+10, counters.WrittenBytesTotal, 1)
}

func TestExtractDiskCountersForDevice_UnknownDevice(t *testing.T) {
	raw := []byte(sampleNodeMetrics)

	counters := ExtractDiskCountersForDevice(raw, "/dev/nvme99n1")
	require.Equal(t, "/dev/nvme99n1", counters.Device)
	require.InDelta(t, 0, counters.ReadsCompleted, 0.01)
	require.InDelta(t, 0, counters.WritesCompleted, 0.01)
}
