package app

import (
	"archive/tar"
	"compress/gzip"
	"context"
	"encoding/json"
	"errors"
	"io"
	"os"
	"path/filepath"
	"testing"

	"bee/audit/internal/platform"
	"bee/audit/internal/schema"
)

type fakeNetwork struct {
	listInterfacesFn func() ([]platform.InterfaceInfo, error)
	defaultRouteFn   func() string
	dhcpOneFn        func(string) (string, error)
	dhcpAllFn        func() (string, error)
	setStaticIPv4Fn  func(platform.StaticIPv4Config) (string, error)
}

func (f fakeNetwork) ListInterfaces() ([]platform.InterfaceInfo, error) {
	return f.listInterfacesFn()
}

func (f fakeNetwork) DefaultRoute() string {
	return f.defaultRouteFn()
}

func (f fakeNetwork) DHCPOne(iface string) (string, error) {
	return f.dhcpOneFn(iface)
}

func (f fakeNetwork) DHCPAll() (string, error) {
	return f.dhcpAllFn()
}

func (f fakeNetwork) SetStaticIPv4(cfg platform.StaticIPv4Config) (string, error) {
	return f.setStaticIPv4Fn(cfg)
}

func (f fakeNetwork) SetInterfaceState(_ string, _ bool) error { return nil }
func (f fakeNetwork) GetInterfaceState(_ string) (bool, error) { return true, nil }
func (f fakeNetwork) CaptureNetworkSnapshot() (platform.NetworkSnapshot, error) {
	return platform.NetworkSnapshot{}, nil
}
func (f fakeNetwork) RestoreNetworkSnapshot(platform.NetworkSnapshot) error { return nil }

type fakeServices struct {
	serviceStatusFn func(string) (string, error)
	serviceDoFn     func(string, platform.ServiceAction) (string, error)
}

func (f fakeServices) ListBeeServices() ([]string, error) {
	return nil, nil
}

func (f fakeServices) ServiceState(name string) string {
	return "active"
}

func (f fakeServices) ServiceStatus(name string) (string, error) {
	return f.serviceStatusFn(name)
}

func (f fakeServices) ServiceDo(name string, action platform.ServiceAction) (string, error) {
	return f.serviceDoFn(name, action)
}

type fakeExports struct {
	listTargetsFn    func() ([]platform.RemovableTarget, error)
	exportToTargetFn func(string, platform.RemovableTarget) (string, error)
}

func (f fakeExports) ListRemovableTargets() ([]platform.RemovableTarget, error) {
	if f.listTargetsFn != nil {
		return f.listTargetsFn()
	}
	return nil, nil
}

func (f fakeExports) ExportFileToTarget(src string, target platform.RemovableTarget) (string, error) {
	if f.exportToTargetFn != nil {
		return f.exportToTargetFn(src, target)
	}
	return "", nil
}

type fakeRuntime struct {
	collectFn func(string) (schema.RuntimeHealth, error)
	dumpFn    func(string) error
}

func (f fakeRuntime) CollectRuntimeHealth(exportDir string) (schema.RuntimeHealth, error) {
	return f.collectFn(exportDir)
}

func (f fakeRuntime) CaptureTechnicalDump(baseDir string) error {
	if f.dumpFn != nil {
		return f.dumpFn(baseDir)
	}
	return nil
}

type fakeTools struct {
	tailFileFn   func(string, int) string
	checkToolsFn func([]string) []platform.ToolStatus
}

func (f fakeTools) TailFile(path string, lines int) string {
	return f.tailFileFn(path, lines)
}

func (f fakeTools) CheckTools(names []string) []platform.ToolStatus {
	return f.checkToolsFn(names)
}

type fakeSAT struct {
	runNvidiaFn               func(string) (string, error)
	runNvidiaBenchmarkFn      func(string, platform.NvidiaBenchmarkOptions) (string, error)
	runNvidiaPowerBenchFn     func(string, platform.NvidiaBenchmarkOptions) (string, error)
	runNvidiaStressFn         func(string, platform.NvidiaStressOptions) (string, error)
	runNvidiaComputeFn        func(string, int, []int) (string, error)
	runNvidiaPowerFn          func(string, int, []int) (string, error)
	runNvidiaPulseFn          func(string, int, []int) (string, error)
	runNvidiaBandwidthFn      func(string, []int) (string, error)
	runNvidiaTargetedStressFn func(string, int, []int) (string, error)
	runMemoryFn               func(string) (string, error)
	runStorageFn              func(string) (string, error)
	runCPUFn                  func(string, int) (string, error)
	detectVendorFn            func() string
	listAMDGPUsFn             func() ([]platform.AMDGPUInfo, error)
	runAMDPackFn              func(string) (string, error)
	listNvidiaGPUsFn          func() ([]platform.NvidiaGPU, error)
	listNvidiaGPUStatusesFn   func() ([]platform.NvidiaGPUStatus, error)
	resetNvidiaGPUFn          func(int) (string, error)
}

func (f fakeSAT) RunNvidiaAcceptancePack(baseDir string, _ func(string)) (string, error) {
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) RunNvidiaAcceptancePackWithOptions(_ context.Context, baseDir string, _ int, _ []int, _ func(string)) (string, error) {
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) RunNvidiaBenchmark(_ context.Context, baseDir string, opts platform.NvidiaBenchmarkOptions, _ func(string)) (string, error) {
	if f.runNvidiaBenchmarkFn != nil {
		return f.runNvidiaBenchmarkFn(baseDir, opts)
	}
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) RunNvidiaPowerBench(_ context.Context, baseDir string, opts platform.NvidiaBenchmarkOptions, _ func(string)) (string, error) {
	if f.runNvidiaPowerBenchFn != nil {
		return f.runNvidiaPowerBenchFn(baseDir, opts)
	}
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) RunNvidiaTargetedStressValidatePack(_ context.Context, baseDir string, durationSec int, gpuIndices []int, _ func(string)) (string, error) {
	if f.runNvidiaTargetedStressFn != nil {
		return f.runNvidiaTargetedStressFn(baseDir, durationSec, gpuIndices)
	}
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) RunNvidiaOfficialComputePack(_ context.Context, baseDir string, durationSec int, gpuIndices []int, _ int, _ func(string)) (string, error) {
	if f.runNvidiaComputeFn != nil {
		return f.runNvidiaComputeFn(baseDir, durationSec, gpuIndices)
	}
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) RunNvidiaTargetedPowerPack(_ context.Context, baseDir string, durationSec int, gpuIndices []int, _ func(string)) (string, error) {
	if f.runNvidiaPowerFn != nil {
		return f.runNvidiaPowerFn(baseDir, durationSec, gpuIndices)
	}
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) RunNvidiaPulseTestPack(_ context.Context, baseDir string, durationSec int, gpuIndices []int, _ func(string)) (string, error) {
	if f.runNvidiaPulseFn != nil {
		return f.runNvidiaPulseFn(baseDir, durationSec, gpuIndices)
	}
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) RunNvidiaBandwidthPack(_ context.Context, baseDir string, gpuIndices []int, _ func(string)) (string, error) {
	if f.runNvidiaBandwidthFn != nil {
		return f.runNvidiaBandwidthFn(baseDir, gpuIndices)
	}
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) RunNvidiaStressPack(_ context.Context, baseDir string, opts platform.NvidiaStressOptions, _ func(string)) (string, error) {
	if f.runNvidiaStressFn != nil {
		return f.runNvidiaStressFn(baseDir, opts)
	}
	return f.runNvidiaFn(baseDir)
}

func (f fakeSAT) ListNvidiaGPUs() ([]platform.NvidiaGPU, error) {
	if f.listNvidiaGPUsFn != nil {
		return f.listNvidiaGPUsFn()
	}
	return nil, nil
}

func (f fakeSAT) ListNvidiaGPUStatuses() ([]platform.NvidiaGPUStatus, error) {
	if f.listNvidiaGPUStatusesFn != nil {
		return f.listNvidiaGPUStatusesFn()
	}
	return nil, nil
}

func (f fakeSAT) ResetNvidiaGPU(index int) (string, error) {
	if f.resetNvidiaGPUFn != nil {
		return f.resetNvidiaGPUFn(index)
	}
	return "", nil
}

func (f fakeSAT) RunMemoryAcceptancePack(_ context.Context, baseDir string, _, _ int, _ func(string)) (string, error) {
	return f.runMemoryFn(baseDir)
}

func (f fakeSAT) RunStorageAcceptancePack(_ context.Context, baseDir string, _ bool, _ func(string)) (string, error) {
	return f.runStorageFn(baseDir)
}

func (f fakeSAT) RunCPUAcceptancePack(_ context.Context, baseDir string, durationSec int, _ func(string)) (string, error) {
	if f.runCPUFn != nil {
		return f.runCPUFn(baseDir, durationSec)
	}
	return "", nil
}

func (f fakeSAT) DetectGPUVendor() string {
	if f.detectVendorFn != nil {
		return f.detectVendorFn()
	}
	return ""
}

func (f fakeSAT) ListAMDGPUs() ([]platform.AMDGPUInfo, error) {
	if f.listAMDGPUsFn != nil {
		return f.listAMDGPUsFn()
	}
	return nil, nil
}

func (f fakeSAT) RunAMDAcceptancePack(_ context.Context, baseDir string, _ func(string)) (string, error) {
	if f.runAMDPackFn != nil {
		return f.runAMDPackFn(baseDir)
	}
	return "", nil
}

func (f fakeSAT) RunAMDMemIntegrityPack(_ context.Context, _ string, _ func(string)) (string, error) {
	return "", nil
}

func (f fakeSAT) RunAMDMemBandwidthPack(_ context.Context, _ string, _ func(string)) (string, error) {
	return "", nil
}

func (f fakeSAT) RunAMDStressPack(_ context.Context, _ string, _ int, _ func(string)) (string, error) {
	return "", nil
}
func (f fakeSAT) RunMemoryStressPack(_ context.Context, _ string, _ int, _ func(string)) (string, error) {
	return "", nil
}
func (f fakeSAT) RunSATStressPack(_ context.Context, _ string, _ int, _ func(string)) (string, error) {
	return "", nil
}

func (f fakeSAT) RunFanStressTest(_ context.Context, _ string, _ platform.FanStressOptions) (string, error) {
	return "", nil
}

func (f fakeSAT) RunPlatformStress(_ context.Context, _ string, _ platform.PlatformStressOptions, _ func(string)) (string, error) {
	return "", nil
}

func (f fakeSAT) RunNCCLTests(_ context.Context, _ string, _ func(string)) (string, error) {
	return "", nil
}

func TestNetworkStatusFormatsInterfacesAndRoute(t *testing.T) {
	t.Parallel()

	a := &App{
		network: fakeNetwork{
			listInterfacesFn: func() ([]platform.InterfaceInfo, error) {
				return []platform.InterfaceInfo{
					{Name: "eth0", State: "UP", IPv4: []string{"10.0.0.2/24"}},
					{Name: "eth1", State: "DOWN", IPv4: nil},
				}, nil
			},
			defaultRouteFn: func() string { return "10.0.0.1" },
		},
		runtime: fakeRuntime{
			collectFn: func(string) (schema.RuntimeHealth, error) { return schema.RuntimeHealth{}, nil },
		},
	}

	result, err := a.NetworkStatus()
	if err != nil {
		t.Fatalf("NetworkStatus error: %v", err)
	}
	if result.Title != "Network status" {
		t.Fatalf("title=%q want %q", result.Title, "Network status")
	}
	if want := "- eth0: state=UP ip=10.0.0.2/24"; !contains(result.Body, want) {
		t.Fatalf("body missing %q\nbody=%s", want, result.Body)
	}
	if want := "- eth1: state=DOWN ip=(no IPv4)"; !contains(result.Body, want) {
		t.Fatalf("body missing %q\nbody=%s", want, result.Body)
	}
	if want := "Default route: 10.0.0.1"; !contains(result.Body, want) {
		t.Fatalf("body missing %q\nbody=%s", want, result.Body)
	}
}

func TestNetworkStatusHandlesNoInterfaces(t *testing.T) {
	t.Parallel()

	a := &App{
		network: fakeNetwork{
			listInterfacesFn: func() ([]platform.InterfaceInfo, error) { return nil, nil },
			defaultRouteFn:   func() string { return "" },
		},
		runtime: fakeRuntime{
			collectFn: func(string) (schema.RuntimeHealth, error) { return schema.RuntimeHealth{}, nil },
		},
	}

	result, err := a.NetworkStatus()
	if err != nil {
		t.Fatalf("NetworkStatus error: %v", err)
	}
	if result.Body != "No physical interfaces found." {
		t.Fatalf("body=%q want %q", result.Body, "No physical interfaces found.")
	}
}

func TestNetworkStatusPropagatesListError(t *testing.T) {
	t.Parallel()

	a := &App{
		network: fakeNetwork{
			listInterfacesFn: func() ([]platform.InterfaceInfo, error) {
				return nil, errors.New("boom")
			},
			defaultRouteFn: func() string { return "" },
		},
		runtime: fakeRuntime{
			collectFn: func(string) (schema.RuntimeHealth, error) { return schema.RuntimeHealth{}, nil },
		},
	}

	result, err := a.NetworkStatus()
	if err == nil {
		t.Fatal("expected error")
	}
	if result.Title != "Network status" {
		t.Fatalf("title=%q want %q", result.Title, "Network status")
	}
}

func TestParseStaticIPv4ConfigAndDefaults(t *testing.T) {
	t.Parallel()

	a := &App{
		network: fakeNetwork{
			defaultRouteFn: func() string { return " 192.168.1.1 " },
			listInterfacesFn: func() ([]platform.InterfaceInfo, error) {
				return nil, nil
			},
			dhcpOneFn:       func(string) (string, error) { return "", nil },
			dhcpAllFn:       func() (string, error) { return "", nil },
			setStaticIPv4Fn: func(platform.StaticIPv4Config) (string, error) { return "", nil },
		},
		runtime: fakeRuntime{
			collectFn: func(string) (schema.RuntimeHealth, error) { return schema.RuntimeHealth{}, nil },
		},
	}

	defaults := a.DefaultStaticIPv4FormFields("eth0")
	if len(defaults) != 4 {
		t.Fatalf("len(defaults)=%d want 4", len(defaults))
	}
	if defaults[1] != "24" || defaults[2] != "192.168.1.1" {
		t.Fatalf("unexpected defaults: %#v", defaults)
	}

	cfg := a.ParseStaticIPv4Config("eth0", []string{
		" 10.10.0.5 ",
		" 23 ",
		" 10.10.0.1 ",
		" 1.1.1.1  8.8.8.8 ",
	})
	if cfg.Interface != "eth0" || cfg.Address != "10.10.0.5" || cfg.Prefix != "23" || cfg.Gateway != "10.10.0.1" {
		t.Fatalf("unexpected cfg: %#v", cfg)
	}
	if len(cfg.DNS) != 2 || cfg.DNS[0] != "1.1.1.1" || cfg.DNS[1] != "8.8.8.8" {
		t.Fatalf("unexpected dns: %#v", cfg.DNS)
	}
}

func TestServiceActionResults(t *testing.T) {
	t.Parallel()

	a := &App{
		services: fakeServices{
			serviceStatusFn: func(name string) (string, error) {
				return "active", nil
			},
			serviceDoFn: func(name string, action platform.ServiceAction) (string, error) {
				return string(action) + " ok", nil
			},
		},
		runtime: fakeRuntime{
			collectFn: func(string) (schema.RuntimeHealth, error) { return schema.RuntimeHealth{}, nil },
		},
	}

	statusResult, err := a.ServiceStatusResult("bee-audit")
	if err != nil {
		t.Fatalf("ServiceStatusResult error: %v", err)
	}
	if statusResult.Title != "service status: bee-audit" || statusResult.Body != "active" {
		t.Fatalf("unexpected status result: %#v", statusResult)
	}

	actionResult, err := a.ServiceActionResult("bee-audit", platform.ServiceRestart)
	if err != nil {
		t.Fatalf("ServiceActionResult error: %v", err)
	}
	if actionResult.Title != "service restart: bee-audit" || actionResult.Body != "restart ok" {
		t.Fatalf("unexpected action result: %#v", actionResult)
	}
}

func TestToolCheckAndLogTailResults(t *testing.T) {
	t.Parallel()

	a := &App{
		tools: fakeTools{
			tailFileFn: func(path string, lines int) string {
				return path
			},
			checkToolsFn: func(names []string) []platform.ToolStatus {
				return []platform.ToolStatus{
					{Name: "dmidecode", OK: true, Path: "/usr/bin/dmidecode"},
					{Name: "smartctl", OK: false},
				}
			},
		},
	}

	toolsResult := a.ToolCheckResult([]string{"dmidecode", "smartctl"})
	if toolsResult.Title != "Required tools" {
		t.Fatalf("title=%q want %q", toolsResult.Title, "Required tools")
	}
	if want := "- dmidecode: OK (/usr/bin/dmidecode)"; !contains(toolsResult.Body, want) {
		t.Fatalf("body missing %q\nbody=%s", want, toolsResult.Body)
	}
	if want := "- smartctl: MISSING"; !contains(toolsResult.Body, want) {
		t.Fatalf("body missing %q\nbody=%s", want, toolsResult.Body)
	}

	logResult := a.AuditLogTailResult()
	if logResult.Title != "Audit log tail" {
		t.Fatalf("title=%q want %q", logResult.Title, "Audit log tail")
	}
	if want := DefaultAuditLogPath + "\n\n" + DefaultAuditJSONPath; logResult.Body != want {
		t.Fatalf("body=%q want %q", logResult.Body, want)
	}
}

func TestActionResultsUseFallbackBody(t *testing.T) {
	t.Parallel()

	a := &App{
		network: fakeNetwork{
			dhcpOneFn:       func(string) (string, error) { return "   ", nil },
			dhcpAllFn:       func() (string, error) { return "", nil },
			setStaticIPv4Fn: func(platform.StaticIPv4Config) (string, error) { return "", nil },
			listInterfacesFn: func() ([]platform.InterfaceInfo, error) {
				return nil, nil
			},
			defaultRouteFn: func() string { return "" },
		},
		services: fakeServices{
			serviceStatusFn: func(string) (string, error) { return "", nil },
			serviceDoFn:     func(string, platform.ServiceAction) (string, error) { return "", nil },
		},
		tools: fakeTools{
			tailFileFn:   func(string, int) string { return "   " },
			checkToolsFn: func([]string) []platform.ToolStatus { return nil },
		},
		sat: fakeSAT{
			runNvidiaFn:  func(string) (string, error) { return "", nil },
			runMemoryFn:  func(string) (string, error) { return "", nil },
			runStorageFn: func(string) (string, error) { return "", nil },
		},
		runtime: fakeRuntime{
			collectFn: func(string) (schema.RuntimeHealth, error) {
				return schema.RuntimeHealth{Status: "PARTIAL", ExportDir: "/tmp/export"}, nil
			},
		},
	}

	if got, _ := a.DHCPOneResult("eth0"); got.Body != "DHCP completed." {
		t.Fatalf("dhcp one body=%q", got.Body)
	}
	if got, _ := a.DHCPAllResult(); got.Body != "DHCP completed." {
		t.Fatalf("dhcp all body=%q", got.Body)
	}
	if got, _ := a.SetStaticIPv4Result(platform.StaticIPv4Config{Interface: "eth0"}); got.Body != "Static IPv4 updated." {
		t.Fatalf("static body=%q", got.Body)
	}
	if got, _ := a.ServiceStatusResult("bee-audit"); got.Body != "No status output." {
		t.Fatalf("status body=%q", got.Body)
	}
	if got, _ := a.ServiceActionResult("bee-audit", platform.ServiceRestart); got.Body != "Action completed." {
		t.Fatalf("action body=%q", got.Body)
	}
	if got := a.ToolCheckResult(nil); got.Body != "No tools checked." {
		t.Fatalf("tool body=%q", got.Body)
	}
	if got := a.AuditLogTailResult(); got.Body != "No audit logs found." {
		t.Fatalf("log body=%q", got.Body)
	}
	if got, _ := a.RunNvidiaAcceptancePackResult(""); got.Body != "Archive written." {
		t.Fatalf("sat body=%q", got.Body)
	}
	if got, _ := a.RunMemoryAcceptancePackResult(""); got.Body != "No output produced." {
		t.Fatalf("memory sat body=%q", got.Body)
	}
	if got, _ := a.RunStorageAcceptancePackResult(""); got.Body != "No output produced." {
		t.Fatalf("storage sat body=%q", got.Body)
	}
}

func TestExportSupportBundleResultMentionsUnmountedUSB(t *testing.T) {
	tmp := t.TempDir()
	oldExportDir := DefaultExportDir
	DefaultExportDir = tmp
	t.Cleanup(func() { DefaultExportDir = oldExportDir })

	if err := os.WriteFile(filepath.Join(tmp, "bee-audit.json"), []byte("{}\n"), 0644); err != nil {
		t.Fatalf("write bee-audit.json: %v", err)
	}
	if err := os.WriteFile(filepath.Join(tmp, "bee-audit.log"), []byte("audit ok\n"), 0644); err != nil {
		t.Fatalf("write bee-audit.log: %v", err)
	}

	a := &App{
		exports: fakeExports{
			exportToTargetFn: func(src string, target platform.RemovableTarget) (string, error) {
				if filepath.Base(src) == "" {
					t.Fatalf("expected non-empty source path")
				}
				return "/media/bee/" + filepath.Base(src), nil
			},
		},
	}

	result, err := a.ExportSupportBundleResult(platform.RemovableTarget{Device: "/dev/sdb1"})
	if err != nil {
		t.Fatalf("ExportSupportBundleResult error: %v", err)
	}
	if result.Title != "Export support bundle" {
		t.Fatalf("title=%q want %q", result.Title, "Export support bundle")
	}
	if want := "USB target unmounted and safe to remove."; !contains(result.Body, want) {
		t.Fatalf("body missing %q\nbody=%s", want, result.Body)
	}
}

func TestExportSupportBundleResultDoesNotPretendSuccessOnError(t *testing.T) {
	tmp := t.TempDir()
	oldExportDir := DefaultExportDir
	DefaultExportDir = tmp
	t.Cleanup(func() { DefaultExportDir = oldExportDir })

	if err := os.WriteFile(filepath.Join(tmp, "bee-audit.json"), []byte("{}\n"), 0644); err != nil {
		t.Fatalf("write bee-audit.json: %v", err)
	}
	if err := os.WriteFile(filepath.Join(tmp, "bee-audit.log"), []byte("audit ok\n"), 0644); err != nil {
		t.Fatalf("write bee-audit.log: %v", err)
	}

	a := &App{
		exports: fakeExports{
			exportToTargetFn: func(string, platform.RemovableTarget) (string, error) {
				return "", errors.New("mount /dev/sda1: exFAT support is missing in this ISO build")
			},
		},
	}

	result, err := a.ExportSupportBundleResult(platform.RemovableTarget{Device: "/dev/sda1", FSType: "exfat"})
	if err == nil {
		t.Fatal("expected export error")
	}
	if contains(result.Body, "exported to") {
		t.Fatalf("body should not claim success:\n%s", result.Body)
	}
	if result.Body != "Support bundle export failed." {
		t.Fatalf("body=%q want %q", result.Body, "Support bundle export failed.")
	}
}

func TestRunNvidiaAcceptancePackResult(t *testing.T) {
	t.Parallel()

	a := &App{
		sat: fakeSAT{
			runNvidiaFn: func(baseDir string) (string, error) {
				if baseDir != "/tmp/sat" {
					t.Fatalf("baseDir=%q want %q", baseDir, "/tmp/sat")
				}
				return "/tmp/sat/out.tar.gz", nil
			},
			runMemoryFn:  func(string) (string, error) { return "", nil },
			runStorageFn: func(string) (string, error) { return "", nil },
		},
		runtime: fakeRuntime{
			collectFn: func(string) (schema.RuntimeHealth, error) { return schema.RuntimeHealth{}, nil },
		},
	}

	result, err := a.RunNvidiaAcceptancePackResult("/tmp/sat")
	if err != nil {
		t.Fatalf("RunNvidiaAcceptancePackResult error: %v", err)
	}
	if result.Title != "NVIDIA SAT" || result.Body != "Archive written to /tmp/sat/out.tar.gz" {
		t.Fatalf("unexpected result: %#v", result)
	}
}

func TestRunSATDefaultsToExportDir(t *testing.T) {
	oldSATBaseDir := DefaultSATBaseDir
	DefaultSATBaseDir = "/tmp/export/bee-sat"
	t.Cleanup(func() { DefaultSATBaseDir = oldSATBaseDir })

	a := &App{
		sat: fakeSAT{
			runNvidiaFn: func(baseDir string) (string, error) {
				if baseDir != "/tmp/export/bee-sat" {
					t.Fatalf("nvidia baseDir=%q", baseDir)
				}
				return "", nil
			},
			runMemoryFn: func(baseDir string) (string, error) {
				if baseDir != "/tmp/export/bee-sat" {
					t.Fatalf("memory baseDir=%q", baseDir)
				}
				return "", nil
			},
			runStorageFn: func(baseDir string) (string, error) {
				if baseDir != "/tmp/export/bee-sat" {
					t.Fatalf("storage baseDir=%q", baseDir)
				}
				return "", nil
			},
		},
		runtime: fakeRuntime{
			collectFn: func(string) (schema.RuntimeHealth, error) { return schema.RuntimeHealth{}, nil },
		},
	}

	if _, err := a.RunNvidiaAcceptancePack("", nil); err != nil {
		t.Fatal(err)
	}
	if _, err := a.RunMemoryAcceptancePack("", nil); err != nil {
		t.Fatal(err)
	}
	if _, err := a.RunStorageAcceptancePack("", nil); err != nil {
		t.Fatal(err)
	}
}

func TestFormatSATSummary(t *testing.T) {
	t.Parallel()

	got := formatSATSummary("Memory SAT", "overall_status=PARTIAL\njob_ok=2\njob_failed=0\njob_unsupported=1\ndevices=3\n")
	want := "Memory SAT: PARTIAL ok=2 failed=0 unsupported=1\nDevices: 3"
	if got != want {
		t.Fatalf("got %q want %q", got, want)
	}
}

func TestHealthSummaryResultIncludesCompactSATSummary(t *testing.T) {
	tmp := t.TempDir()
	oldAuditPath := DefaultAuditJSONPath
	oldSATBaseDir := DefaultSATBaseDir
	DefaultAuditJSONPath = filepath.Join(tmp, "audit.json")
	DefaultSATBaseDir = filepath.Join(tmp, "sat")
	t.Cleanup(func() { DefaultAuditJSONPath = oldAuditPath })
	t.Cleanup(func() { DefaultSATBaseDir = oldSATBaseDir })

	satDir := filepath.Join(DefaultSATBaseDir, "memory-testcase")
	if err := os.MkdirAll(satDir, 0755); err != nil {
		t.Fatalf("mkdir sat dir: %v", err)
	}

	raw := `{"collected_at":"2026-03-15T10:00:00Z","hardware":{"board":{"serial_number":"SRV123"},"storage":[{"serial_number":"DISK1","status":"Warning"}]}}`
	if err := os.WriteFile(DefaultAuditJSONPath, []byte(raw), 0644); err != nil {
		t.Fatalf("write audit json: %v", err)
	}
	if err := os.WriteFile(filepath.Join(satDir, "summary.txt"), []byte("overall_status=OK\njob_ok=3\njob_failed=0\njob_unsupported=0\n"), 0644); err != nil {
		t.Fatalf("write sat summary: %v", err)
	}

	result := (&App{}).HealthSummaryResult()
	if !contains(result.Body, "Memory SAT: OK ok=3 failed=0") {
		t.Fatalf("body missing compact sat summary:\n%s", result.Body)
	}
}

func TestApplySATOverlayFiltersIgnoredLegacyDevices(t *testing.T) {
	tmp := t.TempDir()
	oldSATBaseDir := DefaultSATBaseDir
	DefaultSATBaseDir = filepath.Join(tmp, "sat")
	t.Cleanup(func() { DefaultSATBaseDir = oldSATBaseDir })

	raw := `{
	  "collected_at": "2026-03-15T10:00:00Z",
	  "hardware": {
	    "board": {"serial_number": "SRV123"},
	    "storage": [
	      {"model": "Virtual HDisk0", "serial_number": "AAAABBBBCCCC3"},
	      {"model": "PASCARI", "serial_number": "DISK1", "status": "OK"}
	    ],
	    "pcie_devices": [
	      {"device_class": "Co-processor", "model": "402xx Series QAT", "status": "OK"},
	      {"device_class": "VideoController", "model": "NVIDIA H100", "status": "OK"}
	    ]
	  }
	}`

	got, err := ApplySATOverlay([]byte(raw))
	if err != nil {
		t.Fatalf("ApplySATOverlay error: %v", err)
	}
	text := string(got)
	if contains(text, "Virtual HDisk0") {
		t.Fatalf("overlaid audit should drop virtual hdisk:\n%s", text)
	}
	if contains(text, "\"device_class\": \"Co-processor\"") {
		t.Fatalf("overlaid audit should drop co-processors:\n%s", text)
	}
	if !contains(text, "PASCARI") || !contains(text, "NVIDIA H100") {
		t.Fatalf("overlaid audit should keep real devices:\n%s", text)
	}
}

func TestBuildSupportBundleIncludesExportDirContents(t *testing.T) {
	tmp := t.TempDir()
	exportDir := filepath.Join(tmp, "export")
	if err := os.MkdirAll(filepath.Join(exportDir, "bee-sat", "memory-run"), 0755); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(exportDir, "bee-audit.json"), []byte(`{"collected_at":"2026-03-15T10:00:00Z","hardware":{"board":{"serial_number":"SRV123"},"storage":[{"model":"Virtual HDisk0","serial_number":"AAAABBBBCCCC3"},{"model":"PASCARI","serial_number":"DISK1"}],"pcie_devices":[{"device_class":"Co-processor","model":"402xx Series QAT"},{"device_class":"VideoController","model":"NVIDIA H100"}]}}`), 0644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(exportDir, "bee-sat", "memory-run", "verbose.log"), []byte("sat verbose"), 0644); err != nil {
		t.Fatal(err)
	}
	if err := os.WriteFile(filepath.Join(exportDir, "bee-sat", "memory-run.tar.gz"), []byte("nested sat archive"), 0644); err != nil {
		t.Fatal(err)
	}

	archive, err := BuildSupportBundle(exportDir)
	if err != nil {
		t.Fatalf("BuildSupportBundle error: %v", err)
	}
	if _, err := os.Stat(archive); err != nil {
		t.Fatalf("archive stat: %v", err)
	}

	file, err := os.Open(archive)
	if err != nil {
		t.Fatalf("open archive: %v", err)
	}
	defer file.Close()

	gzr, err := gzip.NewReader(file)
	if err != nil {
		t.Fatalf("gzip reader: %v", err)
	}
	defer gzr.Close()

	tr := tar.NewReader(gzr)
	var names []string
	var auditJSON string
	for {
		hdr, err := tr.Next()
		if errors.Is(err, io.EOF) {
			break
		}
		if err != nil {
			t.Fatalf("read tar entry: %v", err)
		}
		names = append(names, hdr.Name)
		if contains(hdr.Name, "/export/bee-audit.json") {
			body, err := io.ReadAll(tr)
			if err != nil {
				t.Fatalf("read audit entry: %v", err)
			}
			auditJSON = string(body)
		}
	}

	for _, want := range []string{
		"/system/ip-link.txt",
		"/system/ip-link-stats.txt",
		"/system/kernel-aer-nvidia.txt",
		"/system/lspci-nvidia-bridges-vv.txt",
		"/system/pcie-aer-sysfs.txt",
		"/system/ethtool-info.txt",
		"/system/ethtool-link.txt",
		"/system/ethtool-module.txt",
		"/system/mstflint-query.txt",
	} {
		var found bool
		for _, name := range names {
			if contains(name, want) {
				found = true
				break
			}
		}
		if !found {
			t.Fatalf("support bundle missing %s, names=%v", want, names)
		}
	}

	var foundRaw bool
	for _, name := range names {
		if contains(name, "/export/bee-sat/memory-run/verbose.log") {
			foundRaw = true
		}
		if contains(name, "/export/bee-sat/memory-run.tar.gz") {
			t.Fatalf("support bundle should not contain nested SAT archive: %s", name)
		}
	}
	if !foundRaw {
		t.Fatalf("support bundle missing raw SAT log, names=%v", names)
	}
	if contains(auditJSON, "Virtual HDisk0") || contains(auditJSON, "\"device_class\": \"Co-processor\"") {
		t.Fatalf("support bundle should normalize ignored devices:\n%s", auditJSON)
	}
	if !contains(auditJSON, "PASCARI") || !contains(auditJSON, "NVIDIA H100") {
		t.Fatalf("support bundle should keep real devices:\n%s", auditJSON)
	}
}

func TestMainBanner(t *testing.T) {
	tmp := t.TempDir()
	oldAuditPath := DefaultAuditJSONPath
	DefaultAuditJSONPath = filepath.Join(tmp, "audit.json")
	t.Cleanup(func() { DefaultAuditJSONPath = oldAuditPath })

	trueValue := true
	manufacturer := "Dell"
	product := "PowerEdge R760"
	cpuModel := "Intel Xeon Gold 6430"
	memoryType := "DDR5"
	memorySerialA := "DIMM-A"
	memorySerialB := "DIMM-B"
	storageSerialA := "DISK-A"
	storageSerialB := "DISK-B"
	gpuClass := "VideoController"
	gpuModel := "NVIDIA H100"

	payload := schema.HardwareIngestRequest{
		Hardware: schema.HardwareSnapshot{
			Board: schema.HardwareBoard{
				Manufacturer: &manufacturer,
				ProductName:  &product,
				SerialNumber: "SRV123",
			},
			CPUs: []schema.HardwareCPU{
				{Model: &cpuModel},
				{Model: &cpuModel},
			},
			Memory: []schema.HardwareMemory{
				{Present: &trueValue, SizeMB: intPtr(524288), Type: &memoryType, SerialNumber: &memorySerialA},
				{Present: &trueValue, SizeMB: intPtr(524288), Type: &memoryType, SerialNumber: &memorySerialB},
			},
			Storage: []schema.HardwareStorage{
				{Present: &trueValue, SizeGB: intPtr(3840), SerialNumber: &storageSerialA},
				{Present: &trueValue, SizeGB: intPtr(3840), SerialNumber: &storageSerialB},
			},
			PCIeDevices: []schema.HardwarePCIeDevice{
				{DeviceClass: &gpuClass, Model: &gpuModel},
				{DeviceClass: &gpuClass, Model: &gpuModel},
			},
		},
	}

	raw, err := json.Marshal(payload)
	if err != nil {
		t.Fatalf("marshal: %v", err)
	}
	if err := os.WriteFile(DefaultAuditJSONPath, raw, 0644); err != nil {
		t.Fatalf("write audit json: %v", err)
	}

	a := &App{
		network: fakeNetwork{
			listInterfacesFn: func() ([]platform.InterfaceInfo, error) {
				return []platform.InterfaceInfo{
					{Name: "eth0", IPv4: []string{"10.0.0.10"}},
					{Name: "eth1", IPv4: []string{"192.168.1.10"}},
				}, nil
			},
		},
	}

	got := a.MainBanner()
	for _, want := range []string{
		"System: Dell PowerEdge R760 | S/N SRV123",
		"CPU: 2 x Intel Xeon Gold 6430",
		"Memory: 1.0 TB DDR5 (2 DIMMs)",
		"Storage: 2 drives / 7.5 TB",
		"GPU: 2 x NVIDIA H100",
		"IP: 10.0.0.10, 192.168.1.10",
	} {
		if !contains(got, want) {
			t.Fatalf("banner missing %q:\n%s", want, got)
		}
	}
}

func TestRuntimeHealthResultUsesAMDLabels(t *testing.T) {
	tmp := t.TempDir()
	oldRuntimePath := DefaultRuntimeJSONPath
	DefaultRuntimeJSONPath = filepath.Join(tmp, "runtime-health.json")
	t.Cleanup(func() { DefaultRuntimeJSONPath = oldRuntimePath })

	raw, err := json.Marshal(schema.RuntimeHealth{
		Status:        "OK",
		ExportDir:     "/appdata/bee/export",
		DriverReady:   true,
		CUDAReady:     true,
		NetworkStatus: "OK",
	})
	if err != nil {
		t.Fatalf("marshal runtime health: %v", err)
	}
	if err := os.WriteFile(DefaultRuntimeJSONPath, raw, 0644); err != nil {
		t.Fatalf("write runtime health: %v", err)
	}

	a := &App{
		sat: fakeSAT{
			detectVendorFn: func() string { return "amd" },
		},
	}

	result := a.RuntimeHealthResult()
	if !contains(result.Body, "AMDGPU ready: true") {
		t.Fatalf("body missing AMD driver label:\n%s", result.Body)
	}
	if !contains(result.Body, "ROCm SMI ready: true") {
		t.Fatalf("body missing ROCm label:\n%s", result.Body)
	}
	if contains(result.Body, "CUDA ready") {
		t.Fatalf("body should not mention CUDA on AMD:\n%s", result.Body)
	}
}

func intPtr(v int) *int { return &v }

func contains(haystack, needle string) bool {
	return len(needle) == 0 || (len(haystack) >= len(needle) && (haystack == needle || containsAt(haystack, needle)))
}

func containsAt(haystack, needle string) bool {
	for i := 0; i+len(needle) <= len(haystack); i++ {
		if haystack[i:i+len(needle)] == needle {
			return true
		}
	}
	return false
}
