package hub

import (
	"context"
	"os"
	"path/filepath"
	"testing"
)

// writeSysfs creates path under root with the given content (sysfs files are tiny text files).
func writeSysfs(t *testing.T, path, content string) {
	t.Helper()
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		t.Fatalf("mkdir: %v", err)
	}
	if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
		t.Fatalf("write %s: %v", path, err)
	}
}

// A coretemp hwmon with a "Package id 0" label must win over a per-core sensor.
func TestSysfsTempReader_HwmonPackagePreferred(t *testing.T) {
	root := t.TempDir()
	dir := filepath.Join(root, "class", "hwmon", "hwmon0")
	writeSysfs(t, filepath.Join(dir, "name"), "coretemp\n")
	// temp1 = Package id 0 = 47°C; temp2 = Core 0 = 52°C. The package must be chosen.
	writeSysfs(t, filepath.Join(dir, "temp1_input"), "47000\n")
	writeSysfs(t, filepath.Join(dir, "temp1_label"), "Package id 0\n")
	writeSysfs(t, filepath.Join(dir, "temp2_input"), "52000\n")
	writeSysfs(t, filepath.Join(dir, "temp2_label"), "Core 0\n")

	got := SysfsTempReader{Root: root}.CPUTempC(context.Background())
	if got == nil || *got != 47 {
		t.Fatalf("CPUTempC = %v, want 47", got)
	}
}

// With no package label, the first readable hwmon input is used.
func TestSysfsTempReader_HwmonFirstInputFallback(t *testing.T) {
	root := t.TempDir()
	dir := filepath.Join(root, "class", "hwmon", "hwmon0")
	writeSysfs(t, filepath.Join(dir, "name"), "k10temp\n")
	writeSysfs(t, filepath.Join(dir, "temp1_input"), "39000\n") // Tctl, no label
	got := SysfsTempReader{Root: root}.CPUTempC(context.Background())
	if got == nil || *got != 39 {
		t.Fatalf("CPUTempC = %v, want 39", got)
	}
}

// A non-CPU hwmon (e.g. a NIC) must be ignored; the thermal-zone CPU sensor is used instead.
func TestSysfsTempReader_ThermalZoneByType(t *testing.T) {
	root := t.TempDir()
	// hwmon is a non-CPU driver → skipped.
	nic := filepath.Join(root, "class", "hwmon", "hwmon0")
	writeSysfs(t, filepath.Join(nic, "name"), "iwlwifi\n")
	writeSysfs(t, filepath.Join(nic, "temp1_input"), "60000\n")
	// thermal zones: acpitz (40°C) + x86_pkg_temp (55°C). The CPU package type must win.
	z0 := filepath.Join(root, "class", "thermal", "thermal_zone0")
	writeSysfs(t, filepath.Join(z0, "type"), "acpitz\n")
	writeSysfs(t, filepath.Join(z0, "temp"), "40000\n")
	z1 := filepath.Join(root, "class", "thermal", "thermal_zone1")
	writeSysfs(t, filepath.Join(z1, "type"), "x86_pkg_temp\n")
	writeSysfs(t, filepath.Join(z1, "temp"), "55000\n")

	got := SysfsTempReader{Root: root}.CPUTempC(context.Background())
	if got == nil || *got != 55 {
		t.Fatalf("CPUTempC = %v, want 55 (x86_pkg_temp), got %v", got, got)
	}
}

// The headline graceful-null case: a host that exposes NO sensor (empty /sys) returns nil, and
// no error propagates (CPUTempC has no error return — a missing sensor is "n/a", never a failure).
func TestSysfsTempReader_GracefulNullWhenAbsent(t *testing.T) {
	root := t.TempDir() // empty: no hwmon, no thermal zones
	if got := (SysfsTempReader{Root: root}).CPUTempC(context.Background()); got != nil {
		t.Fatalf("CPUTempC on a sensorless host = %v, want nil", got)
	}
}

// Out-of-range / garbage readings degrade to nil rather than reporting a bogus temperature.
func TestSysfsTempReader_RejectsImplausibleValues(t *testing.T) {
	root := t.TempDir()
	z := filepath.Join(root, "class", "thermal", "thermal_zone0")
	writeSysfs(t, filepath.Join(z, "type"), "x86_pkg_temp\n")
	writeSysfs(t, filepath.Join(z, "temp"), "0\n") // 0 m°C → implausible → ignored
	if got := (SysfsTempReader{Root: root}).CPUTempC(context.Background()); got != nil {
		t.Fatalf("CPUTempC on a 0°C reading = %v, want nil", got)
	}
}
