package main

import (
	"strings"
	"testing"

	"github.com/charmbracelet/lipgloss"
)

func TestFormatRate(t *testing.T) {
	tests := []struct {
		name  string
		input float64
		want  string
	}{
		// Below threshold (< 0.01).
		{"zero", 0, "0 MB/s"},
		{"tiny", 0.001, "0 MB/s"},
		{"just under threshold", 0.009, "0 MB/s"},

		// Small rates (0.01 to < 1) — 2 decimal places.
		{"at threshold", 0.01, "0.01 MB/s"},
		{"small rate", 0.5, "0.50 MB/s"},
		{"just under 1", 0.99, "0.99 MB/s"},

		// Medium rates (1 to < 10) — 1 decimal place.
		{"exactly 1", 1.0, "1.0 MB/s"},
		{"medium rate", 5.5, "5.5 MB/s"},
		{"just under 10", 9.9, "9.9 MB/s"},

		// Large rates (>= 10) — no decimal places.
		{"exactly 10", 10.0, "10 MB/s"},
		{"large rate", 100.5, "100 MB/s"},
		{"very large", 1000.0, "1000 MB/s"},
	}

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

func TestColorizePercent(t *testing.T) {
	tests := []struct {
		name         string
		percent      float64
		input        string
		expectDanger bool
		expectWarn   bool
		expectOk     bool
	}{
		{"low usage", 30.0, "30%", false, false, true},
		{"just below warn", 59.9, "59.9%", false, false, true},
		{"at warn threshold", 60.0, "60%", false, true, false},
		{"mid range", 70.0, "70%", false, true, false},
		{"just below danger", 84.9, "84.9%", false, true, false},
		{"at danger threshold", 85.0, "85%", true, false, false},
		{"high usage", 95.0, "95%", true, false, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := colorizePercent(tt.percent, tt.input)

			if got == "" {
				t.Errorf("colorizePercent(%v, %q) returned empty string", tt.percent, tt.input)
				return
			}

			expected := ""
			if tt.expectDanger {
				expected = dangerStyle.Render(tt.input)
			} else if tt.expectWarn {
				expected = warnStyle.Render(tt.input)
			} else if tt.expectOk {
				expected = okStyle.Render(tt.input)
			}

			if got != expected {
				t.Errorf("colorizePercent(%v, %q) = %q, want %q (danger=%v warn=%v ok=%v)",
					tt.percent, tt.input, got, expected, tt.expectDanger, tt.expectWarn, tt.expectOk)
			}
		})
	}
}

func TestColorizeBattery(t *testing.T) {
	tests := []struct {
		name         string
		percent      float64
		input        string
		expectDanger bool
		expectWarn   bool
		expectOk     bool
	}{
		{"critical low", 10.0, "10%", true, false, false},
		{"just below low", 19.9, "19.9%", true, false, false},
		{"at low threshold", 20.0, "20%", false, true, false},
		{"mid range", 35.0, "35%", false, true, false},
		{"just below ok", 49.9, "49.9%", false, true, false},
		{"at ok threshold", 50.0, "50%", false, false, true},
		{"healthy", 80.0, "80%", false, false, true},
		{"full", 100.0, "100%", false, false, true},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := colorizeBattery(tt.percent, tt.input)

			if got == "" {
				t.Errorf("colorizeBattery(%v, %q) returned empty string", tt.percent, tt.input)
				return
			}

			expected := ""
			if tt.expectDanger {
				expected = dangerStyle.Render(tt.input)
			} else if tt.expectWarn {
				expected = warnStyle.Render(tt.input)
			} else if tt.expectOk {
				expected = okStyle.Render(tt.input)
			}

			if got != expected {
				t.Errorf("colorizeBattery(%v, %q) = %q, want %q (danger=%v warn=%v ok=%v)",
					tt.percent, tt.input, got, expected, tt.expectDanger, tt.expectWarn, tt.expectOk)
			}
		})
	}
}

func TestShorten(t *testing.T) {
	tests := []struct {
		name   string
		input  string
		maxLen int
		want   string
	}{
		// No truncation needed.
		{"empty string", "", 10, ""},
		{"shorter than max", "hello", 10, "hello"},
		{"exactly at max", "hello", 5, "hello"},

		// Truncation needed.
		{"one over max", "hello!", 5, "hell…"},
		{"much longer", "hello world", 5, "hell…"},

		// Edge cases.
		{"maxLen 1", "hello", 1, "…"},
		{"maxLen 2", "hello", 2, "h…"},
	}

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

func TestHumanBytesShort(t *testing.T) {
	tests := []struct {
		name  string
		input uint64
		want  string
	}{
		// Zero and small values.
		{"zero", 0, "0"},
		{"one byte", 1, "1"},
		{"999 bytes", 999, "999"},

		// Kilobyte boundaries.
		{"exactly 1KB", 1 << 10, "1K"},
		{"just under 1KB", (1 << 10) - 1, "1023"},
		{"1.5KB rounds to 2K", 1536, "2K"},
		{"999KB", 999 << 10, "999K"},

		// Megabyte boundaries.
		{"exactly 1MB", 1 << 20, "1M"},
		{"just under 1MB", (1 << 20) - 1, "1024K"},
		{"500MB", 500 << 20, "500M"},

		// Gigabyte boundaries.
		{"exactly 1GB", 1 << 30, "1G"},
		{"just under 1GB", (1 << 30) - 1, "1024M"},
		{"100GB", 100 << 30, "100G"},

		// Terabyte boundaries.
		{"exactly 1TB", 1 << 40, "1T"},
		{"just under 1TB", (1 << 40) - 1, "1024G"},
		{"2TB", 2 << 40, "2T"},
	}

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

func TestHumanBytes(t *testing.T) {
	tests := []struct {
		name  string
		input uint64
		want  string
	}{
		// Zero and small values.
		{"zero", 0, "0 B"},
		{"one byte", 1, "1 B"},
		{"1023 bytes", 1023, "1023 B"},

		// Kilobyte boundaries (uses > not >=).
		{"exactly 1KB", 1 << 10, "1024 B"},
		{"just over 1KB", (1 << 10) + 1, "1.0 KB"},
		{"1.5KB", 1536, "1.5 KB"},

		// Megabyte boundaries (uses > not >=).
		{"exactly 1MB", 1 << 20, "1024.0 KB"},
		{"just over 1MB", (1 << 20) + 1, "1.0 MB"},
		{"500MB", 500 << 20, "500.0 MB"},

		// Gigabyte boundaries (uses > not >=).
		{"exactly 1GB", 1 << 30, "1024.0 MB"},
		{"just over 1GB", (1 << 30) + 1, "1.0 GB"},
		{"100GB", 100 << 30, "100.0 GB"},

		// Terabyte boundaries (uses > not >=).
		{"exactly 1TB", 1 << 40, "1024.0 GB"},
		{"just over 1TB", (1 << 40) + 1, "1.0 TB"},
		{"2TB", 2 << 40, "2.0 TB"},
	}

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

func TestHumanBytesCompact(t *testing.T) {
	tests := []struct {
		name  string
		input uint64
		want  string
	}{
		// Zero and small values.
		{"zero", 0, "0"},
		{"one byte", 1, "1"},
		{"1023 bytes", 1023, "1023"},

		// Kilobyte boundaries (uses >= not >).
		{"exactly 1KB", 1 << 10, "1.0K"},
		{"1.5KB", 1536, "1.5K"},

		// Megabyte boundaries.
		{"exactly 1MB", 1 << 20, "1.0M"},
		{"500MB", 500 << 20, "500.0M"},

		// Gigabyte boundaries.
		{"exactly 1GB", 1 << 30, "1.0G"},
		{"100GB", 100 << 30, "100.0G"},

		// Terabyte boundaries.
		{"exactly 1TB", 1 << 40, "1.0T"},
		{"2TB", 2 << 40, "2.0T"},
	}

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

func TestSplitDisks(t *testing.T) {
	tests := []struct {
		name         string
		disks        []DiskStatus
		wantInternal int
		wantExternal int
	}{
		{
			name:         "empty slice",
			disks:        []DiskStatus{},
			wantInternal: 0,
			wantExternal: 0,
		},
		{
			name: "all internal",
			disks: []DiskStatus{
				{Mount: "/", External: false},
				{Mount: "/System", External: false},
			},
			wantInternal: 2,
			wantExternal: 0,
		},
		{
			name: "all external",
			disks: []DiskStatus{
				{Mount: "/Volumes/USB", External: true},
				{Mount: "/Volumes/Backup", External: true},
			},
			wantInternal: 0,
			wantExternal: 2,
		},
		{
			name: "mixed",
			disks: []DiskStatus{
				{Mount: "/", External: false},
				{Mount: "/Volumes/USB", External: true},
				{Mount: "/System", External: false},
			},
			wantInternal: 2,
			wantExternal: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			internal, external := splitDisks(tt.disks)
			if len(internal) != tt.wantInternal {
				t.Errorf("splitDisks() internal count = %d, want %d", len(internal), tt.wantInternal)
			}
			if len(external) != tt.wantExternal {
				t.Errorf("splitDisks() external count = %d, want %d", len(external), tt.wantExternal)
			}
		})
	}
}

func TestDiskLabel(t *testing.T) {
	tests := []struct {
		name   string
		prefix string
		index  int
		total  int
		want   string
	}{
		// Single disk — no numbering.
		{"single disk", "INTR", 0, 1, "INTR"},
		{"single external", "EXTR", 0, 1, "EXTR"},

		// Multiple disks — numbered (1-indexed).
		{"first of two", "INTR", 0, 2, "INTR1"},
		{"second of two", "INTR", 1, 2, "INTR2"},
		{"third of three", "EXTR", 2, 3, "EXTR3"},

		// Edge case: total 0 treated as single.
		{"total zero", "DISK", 0, 0, "DISK"},
	}

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

func TestParseInt(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  int
	}{
		// Basic integers.
		{"simple number", "123", 123},
		{"zero", "0", 0},
		{"single digit", "5", 5},

		// With whitespace.
		{"leading space", "  42", 42},
		{"trailing space", "42  ", 42},
		{"both spaces", "  42  ", 42},

		// With non-numeric padding.
		{"leading @", "@60", 60},
		{"trailing Hz", "120Hz", 120},
		{"both padding", "@60Hz", 60},

		// Decimals (truncated to int).
		{"decimal", "60.00", 60},
		{"decimal with suffix", "119.88hz", 119},

		// Edge cases.
		{"empty string", "", 0},
		{"only spaces", "   ", 0},
		{"no digits", "abc", 0},
		{"negative strips sign", "-5", 5}, // Strips non-numeric prefix.
	}

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

func TestParseRefreshRate(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  string
	}{
		// Standard formats.
		{"60Hz format", "Resolution: 1920x1080 @ 60Hz", "60Hz"},
		{"120Hz format", "Resolution: 2560x1600 @ 120Hz", "120Hz"},
		{"separated Hz", "Refresh Rate: 60 Hz", "60Hz"},

		// Decimal refresh rates.
		{"decimal Hz", "Resolution: 3840x2160 @ 59.94Hz", "59Hz"},
		{"ProMotion", "Resolution: 3456x2234 @ 120.00Hz", "120Hz"},

		// Multiple lines — picks highest valid.
		{"multiple rates", "Display 1: 60Hz\nDisplay 2: 120Hz", "120Hz"},

		// Edge cases.
		{"empty string", "", ""},
		{"no Hz found", "Resolution: 1920x1080", ""},
		{"invalid Hz value", "Rate: abcHz", ""},
		{"Hz too high filtered", "Rate: 600Hz", ""},

		// Case insensitivity.
		{"lowercase hz", "60hz", "60Hz"},
		{"uppercase HZ", "60HZ", "60Hz"},
	}

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

func TestIsNoiseInterface(t *testing.T) {
	tests := []struct {
		name  string
		input string
		want  bool
	}{
		// Noise interfaces (should return true).
		{"loopback", "lo0", true},
		{"awdl", "awdl0", true},
		{"utun", "utun0", true},
		{"llw", "llw0", true},
		{"bridge", "bridge0", true},
		{"gif", "gif0", true},
		{"stf", "stf0", true},
		{"xhc", "xhc0", true},
		{"anpi", "anpi0", true},
		{"ap", "ap1", true},

		// Real interfaces (should return false).
		{"ethernet", "en0", false},
		{"wifi", "en1", false},
		{"thunderbolt", "en5", false},

		// Case insensitivity.
		{"uppercase LO", "LO0", true},
		{"mixed case Awdl", "Awdl0", true},

		// Edge cases.
		{"empty string", "", false},
	}

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

func TestParsePMSet(t *testing.T) {
	tests := []struct {
		name     string
		raw      string
		health   string
		cycles   int
		capacity int
		wantLen  int
		wantPct  float64
		wantStat string
		wantTime string
	}{
		{
			name: "charging with time",
			raw: `Now drawing from 'AC Power'
 -InternalBattery-0 (id=1234)	85%; charging; 0:45 remaining present: true`,
			health:   "Good",
			cycles:   150,
			capacity: 92,
			wantLen:  1,
			wantPct:  85,
			wantStat: "charging",
			wantTime: "0:45",
		},
		{
			name: "discharging",
			raw: `Now drawing from 'Battery Power'
 -InternalBattery-0 (id=1234)	45%; discharging; 2:30 remaining present: true`,
			health:   "Normal",
			cycles:   200,
			capacity: 88,
			wantLen:  1,
			wantPct:  45,
			wantStat: "discharging",
			wantTime: "2:30",
		},
		{
			name: "fully charged",
			raw: `Now drawing from 'AC Power'
 -InternalBattery-0 (id=1234)	100%; charged; present: true`,
			health:   "Good",
			cycles:   50,
			capacity: 100,
			wantLen:  1,
			wantPct:  100,
			wantStat: "charged",
			wantTime: "",
		},
		{
			name:     "empty output",
			raw:      "",
			health:   "",
			cycles:   0,
			capacity: 0,
			wantLen:  0,
		},
		{
			name:     "no battery line",
			raw:      "Now drawing from 'AC Power'\nNo batteries found.",
			health:   "",
			cycles:   0,
			capacity: 0,
			wantLen:  0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := parsePMSet(tt.raw, tt.health, tt.cycles, tt.capacity)
			if len(got) != tt.wantLen {
				t.Errorf("parsePMSet() returned %d batteries, want %d", len(got), tt.wantLen)
				return
			}
			if tt.wantLen == 0 {
				return
			}
			b := got[0]
			if b.Percent != tt.wantPct {
				t.Errorf("Percent = %v, want %v", b.Percent, tt.wantPct)
			}
			if b.Status != tt.wantStat {
				t.Errorf("Status = %q, want %q", b.Status, tt.wantStat)
			}
			if b.TimeLeft != tt.wantTime {
				t.Errorf("TimeLeft = %q, want %q", b.TimeLeft, tt.wantTime)
			}
			if b.Health != tt.health {
				t.Errorf("Health = %q, want %q", b.Health, tt.health)
			}
			if b.CycleCount != tt.cycles {
				t.Errorf("CycleCount = %d, want %d", b.CycleCount, tt.cycles)
			}
			if b.Capacity != tt.capacity {
				t.Errorf("Capacity = %d, want %d", b.Capacity, tt.capacity)
			}
		})
	}
}

func TestProgressBar(t *testing.T) {
	tests := []struct {
		name     string
		percent  float64
		wantRune int
	}{
		{"zero percent", 0, 16},
		{"negative clamped", -10, 16},
		{"low percent", 25, 16},
		{"half", 50, 16},
		{"high percent", 75, 16},
		{"full", 100, 16},
		{"over 100 clamped", 150, 16},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := progressBar(tt.percent)
			if len(got) == 0 {
				t.Errorf("progressBar(%v) returned empty string", tt.percent)
				return
			}
			gotClean := stripANSI(got)
			gotRuneCount := len([]rune(gotClean))
			if gotRuneCount != tt.wantRune {
				t.Errorf("progressBar(%v) rune count = %d, want %d", tt.percent, gotRuneCount, tt.wantRune)
			}
		})
	}
}

func TestBatteryProgressBar(t *testing.T) {
	tests := []struct {
		name     string
		percent  float64
		wantRune int
	}{
		{"zero percent", 0, 16},
		{"negative clamped", -10, 16},
		{"critical low", 15, 16},
		{"low", 25, 16},
		{"medium", 50, 16},
		{"high", 75, 16},
		{"full", 100, 16},
		{"over 100 clamped", 120, 16},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := batteryProgressBar(tt.percent)
			if len(got) == 0 {
				t.Errorf("batteryProgressBar(%v) returned empty string", tt.percent)
				return
			}
			gotClean := stripANSI(got)
			gotRuneCount := len([]rune(gotClean))
			if gotRuneCount != tt.wantRune {
				t.Errorf("batteryProgressBar(%v) rune count = %d, want %d", tt.percent, gotRuneCount, tt.wantRune)
			}
		})
	}
}

func TestColorizeTemp(t *testing.T) {
	tests := []struct {
		name string
		temp float64
	}{
		{"very low", 20.0},
		{"low", 40.0},
		{"normal threshold", 55.9},
		{"at warn threshold", 56.0},
		{"warn range", 65.0},
		{"just below danger", 75.9},
		{"at danger threshold", 76.0},
		{"high", 85.0},
		{"very high", 95.0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := colorizeTemp(tt.temp)
			if got == "" {
				t.Errorf("colorizeTemp(%v) returned empty string", tt.temp)
			}
		})
	}
}

func TestIoBar(t *testing.T) {
	tests := []struct {
		name string
		rate float64
	}{
		{"zero", 0},
		{"very low", 5},
		{"low normal", 20},
		{"at warn threshold", 30},
		{"warn range", 50},
		{"just below danger", 79},
		{"at danger threshold", 80},
		{"high", 100},
		{"very high", 200},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ioBar(tt.rate)
			if got == "" {
				t.Errorf("ioBar(%v) returned empty string", tt.rate)
				return
			}
			gotClean := stripANSI(got)
			gotRuneCount := len([]rune(gotClean))
			if gotRuneCount != 5 {
				t.Errorf("ioBar(%v) rune count = %d, want 5", tt.rate, gotRuneCount)
			}
		})
	}
}

func TestMiniBar(t *testing.T) {
	tests := []struct {
		name    string
		percent float64
	}{
		{"zero", 0},
		{"negative", -5},
		{"low", 15},
		{"at first step", 20},
		{"mid", 50},
		{"high", 75},
		{"full", 100},
		{"over 100", 120},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := miniBar(tt.percent)
			if got == "" {
				t.Errorf("miniBar(%v) returned empty string", tt.percent)
				return
			}
			gotClean := stripANSI(got)
			gotRuneCount := len([]rune(gotClean))
			if gotRuneCount != 5 {
				t.Errorf("miniBar(%v) rune count = %d, want 5", tt.percent, gotRuneCount)
			}
		})
	}
}

func TestFormatDiskLine(t *testing.T) {
	tests := []struct {
		name  string
		label string
		disk  DiskStatus
	}{
		{
			name:  "empty label defaults to DISK",
			label: "",
			disk:  DiskStatus{UsedPercent: 50.5, Used: 100 << 30, Total: 200 << 30},
		},
		{
			name:  "internal disk",
			label: "INTR",
			disk:  DiskStatus{UsedPercent: 67.2, Used: 336 << 30, Total: 500 << 30},
		},
		{
			name:  "external disk",
			label: "EXTR1",
			disk:  DiskStatus{UsedPercent: 85.0, Used: 850 << 30, Total: 1000 << 30},
		},
		{
			name:  "low usage",
			label: "INTR",
			disk:  DiskStatus{UsedPercent: 15.3, Used: 15 << 30, Total: 100 << 30},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := formatDiskLine(tt.label, tt.disk)
			if got == "" {
				t.Errorf("formatDiskLine(%q, ...) returned empty string", tt.label)
				return
			}
			expectedLabel := tt.label
			if expectedLabel == "" {
				expectedLabel = "DISK"
			}
			if !contains(got, expectedLabel) {
				t.Errorf("formatDiskLine(%q, ...) = %q, should contain label %q", tt.label, got, expectedLabel)
			}
		})
	}
}

func TestGetScoreStyle(t *testing.T) {
	tests := []struct {
		name  string
		score int
	}{
		{"critical low", 10},
		{"poor low", 25},
		{"just below fair", 39},
		{"at fair threshold", 40},
		{"fair range", 50},
		{"just below good", 59},
		{"at good threshold", 60},
		{"good range", 70},
		{"just below excellent", 74},
		{"at excellent threshold", 75},
		{"excellent range", 85},
		{"just below perfect", 89},
		{"perfect", 90},
		{"max", 100},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			style := getScoreStyle(tt.score)
			if style.GetForeground() == nil {
				t.Errorf("getScoreStyle(%d) returned style with no foreground color", tt.score)
			}
		})
	}
}

func TestSparkline(t *testing.T) {
	tests := []struct {
		name    string
		history []float64
		current float64
		width   int
		wantLen int
	}{
		{
			name:    "empty history",
			history: []float64{},
			current: 1.5,
			width:   10,
			wantLen: 10,
		},
		{
			name:    "short history padded",
			history: []float64{1.0, 2.0, 3.0},
			current: 3.0,
			width:   10,
			wantLen: 10,
		},
		{
			name:    "exact width",
			history: []float64{1.0, 2.0, 3.0, 4.0, 5.0},
			current: 5.0,
			width:   5,
			wantLen: 5,
		},
		{
			name:    "history longer than width",
			history: []float64{1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0},
			current: 10.0,
			width:   5,
			wantLen: 5,
		},
		{
			name:    "low current value ok style",
			history: []float64{1.0, 1.5, 2.0},
			current: 2.0,
			width:   5,
			wantLen: 5,
		},
		{
			name:    "medium current value warn style",
			history: []float64{3.0, 4.0, 5.0},
			current: 5.0,
			width:   5,
			wantLen: 5,
		},
		{
			name:    "high current value danger style",
			history: []float64{8.0, 9.0, 10.0},
			current: 10.0,
			width:   5,
			wantLen: 5,
		},
		{
			name:    "all identical values flatline",
			history: []float64{5.0, 5.0, 5.0, 5.0, 5.0},
			current: 5.0,
			width:   5,
			wantLen: 5,
		},
		{
			name:    "zero width edge case",
			history: []float64{1.0, 2.0, 3.0},
			current: 2.0,
			width:   0,
			wantLen: 0,
		},
		{
			name:    "width of 1",
			history: []float64{1.0, 2.0, 3.0},
			current: 2.0,
			width:   1,
			wantLen: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := sparkline(tt.history, tt.current, tt.width)
			if tt.width == 0 {
				return
			}
			if got == "" {
				t.Errorf("sparkline() returned empty string")
				return
			}
			gotClean := stripANSI(got)
			if len([]rune(gotClean)) != tt.wantLen {
				t.Errorf("sparkline() rune length = %d, want %d", len([]rune(gotClean)), tt.wantLen)
			}
		})
	}
}

func TestRenderHeaderErrorReturnsMoleOnce(t *testing.T) {
	header, mole := renderHeader(MetricsSnapshot{}, "boom", 0, 120, false)

	if mole != "" {
		t.Fatalf("renderHeader() mole return should be empty on error to avoid duplicate render, got %q", mole)
	}
	if !strings.Contains(header, "ERROR: boom") {
		t.Fatalf("renderHeader() missing error text, got %q", header)
	}
	if strings.Count(header, "/\\_/\\") != 1 {
		t.Fatalf("renderHeader() should contain one mole frame in error state, got %d", strings.Count(header, "/\\_/\\"))
	}
}

func TestRenderHeaderWrapsOnNarrowWidth(t *testing.T) {
	m := MetricsSnapshot{
		HealthScore: 91,
		Hardware: HardwareInfo{
			Model:       "MacBook Pro",
			CPUModel:    "Apple M3 Max",
			TotalRAM:    "128GB",
			DiskSize:    "4TB",
			RefreshRate: "120Hz",
			OSVersion:   "macOS 15.0",
		},
		Uptime: "10d 3h",
	}

	header, _ := renderHeader(m, "", 0, 38, true)
	for line := range strings.Lines(header) {
		if lipgloss.Width(stripANSI(line)) > 38 {
			t.Fatalf("renderHeader() line exceeds width: %q", line)
		}
	}
}

func TestRenderHeaderHidesOSAndUptimeOnNarrowWidth(t *testing.T) {
	m := MetricsSnapshot{
		HealthScore: 91,
		Hardware: HardwareInfo{
			Model:       "MacBook Pro",
			CPUModel:    "Apple M3 Max",
			TotalRAM:    "128GB",
			DiskSize:    "4TB",
			RefreshRate: "120Hz",
			OSVersion:   "macOS 15.0",
		},
		Uptime: "10d 3h",
	}

	header, _ := renderHeader(m, "", 0, 80, true)
	plain := stripANSI(header)
	if strings.Contains(plain, "macOS 15.0") {
		t.Fatalf("renderHeader() narrow width should hide os version, got %q", plain)
	}
	if strings.Contains(plain, "up 10d 3h") {
		t.Fatalf("renderHeader() narrow width should hide uptime, got %q", plain)
	}
}

func TestRenderHeaderDropsLowPriorityInfoToStaySingleLine(t *testing.T) {
	m := MetricsSnapshot{
		HealthScore: 90,
		Hardware: HardwareInfo{
			Model:       "MacBook Pro",
			CPUModel:    "Apple M2 Pro",
			TotalRAM:    "32.0 GB",
			DiskSize:    "460.4 GB",
			RefreshRate: "60Hz",
			OSVersion:   "macOS 26.3",
		},
		GPU:    []GPUStatus{{CoreCount: 19}},
		Uptime: "9d 13h",
	}

	header, _ := renderHeader(m, "", 0, 100, true)
	plain := stripANSI(header)
	if strings.Contains(plain, "\n") {
		t.Fatalf("renderHeader() should stay single line when trimming low-priority fields, got %q", plain)
	}
	if strings.Contains(plain, "macOS 26.3") {
		t.Fatalf("renderHeader() should drop os version when width is tight, got %q", plain)
	}
	if strings.Contains(plain, "up 9d 13h") {
		t.Fatalf("renderHeader() should drop uptime when width is tight, got %q", plain)
	}
}

func TestRenderCardWrapsOnNarrowWidth(t *testing.T) {
	card := cardData{
		icon:  iconCPU,
		title: "CPU",
		lines: []string{
			"Total  ████████████████  100.0% @ 85.0°C",
			"Load   12.34 / 8.90 / 7.65, 4P+4E",
		},
	}

	rendered := renderCard(card, 26, 0)
	for line := range strings.Lines(rendered) {
		if lipgloss.Width(stripANSI(line)) > 26 {
			t.Fatalf("renderCard() line exceeds width: %q", line)
		}
	}
}

func TestRenderMemoryCardHidesSwapSizeOnNarrowWidth(t *testing.T) {
	card := renderMemoryCard(MemoryStatus{
		Used:        8 << 30,
		Total:       16 << 30,
		UsedPercent: 50.0,
		SwapUsed:    482,
		SwapTotal:   1000,
	}, 38)

	if len(card.lines) < 3 {
		t.Fatalf("renderMemoryCard() expected at least 3 lines, got %d", len(card.lines))
	}

	swapLine := stripANSI(card.lines[2])
	if strings.Contains(swapLine, "/") {
		t.Fatalf("renderMemoryCard() narrow width should hide swap size, got %q", swapLine)
	}
}

func TestRenderMemoryCardShowsSwapSizeOnWideWidth(t *testing.T) {
	card := renderMemoryCard(MemoryStatus{
		Used:        8 << 30,
		Total:       16 << 30,
		UsedPercent: 50.0,
		SwapUsed:    482,
		SwapTotal:   1000,
	}, 60)

	if len(card.lines) < 3 {
		t.Fatalf("renderMemoryCard() expected at least 3 lines, got %d", len(card.lines))
	}

	swapLine := stripANSI(card.lines[2])
	if !strings.Contains(swapLine, "/") {
		t.Fatalf("renderMemoryCard() wide width should include swap size, got %q", swapLine)
	}
}

func TestModelViewErrorRendersSingleMole(t *testing.T) {
	m := model{
		width:      120,
		height:     40,
		ready:      true,
		metrics:    MetricsSnapshot{},
		errMessage: "boom",
		animFrame:  0,
		catHidden:  false,
	}

	view := m.View()
	if strings.Count(view, "/\\_/\\") != 1 {
		t.Fatalf("model.View() should render one mole frame in error state, got %d", strings.Count(view, "/\\_/\\"))
	}
}

func stripANSI(s string) string {
	var result strings.Builder
	i := 0
	for i < len(s) {
		if i < len(s)-1 && s[i] == '\x1b' && s[i+1] == '[' {
			i += 2
			for i < len(s) && (s[i] < 'A' || s[i] > 'Z') && (s[i] < 'a' || s[i] > 'z') {
				i++
			}
			if i < len(s) {
				i++
			}
		} else {
			result.WriteByte(s[i])
			i++
		}
	}
	return result.String()
}

func contains(s, substr string) bool {
	return len(s) >= len(substr) && (s == substr || len(s) > len(substr) && (s[:len(substr)] == substr || s[len(s)-len(substr):] == substr || containsMiddle(s, substr)))
}

func containsMiddle(s, substr string) bool {
	for i := 0; i <= len(s)-len(substr); i++ {
		if s[i:i+len(substr)] == substr {
			return true
		}
	}
	return false
}
