package system

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"runtime"
	"testing"
)

func TestWriteJSONRoundtrip(t *testing.T) {
	path := filepath.Join(t.TempDir(), "out.json")
	value := map[string]any{"name": "banger", "n": 42.0}
	if err := WriteJSON(path, value); err != nil {
		t.Fatalf("WriteJSON: %v", err)
	}
	data, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("ReadFile: %v", err)
	}
	var got map[string]any
	if err := json.Unmarshal(data, &got); err != nil {
		t.Fatalf("Unmarshal: %v", err)
	}
	if got["name"] != "banger" || got["n"].(float64) != 42.0 {
		t.Fatalf("decoded = %v", got)
	}
}

func TestWriteJSONErrorsForUnmarshalable(t *testing.T) {
	path := filepath.Join(t.TempDir(), "out.json")
	if err := WriteJSON(path, make(chan int)); err == nil {
		t.Fatal("expected marshal error for channel value")
	}
	if _, err := os.Stat(path); !os.IsNotExist(err) {
		t.Fatalf("expected no file when marshal fails, got %v", err)
	}
}

func TestTailCommand(t *testing.T) {
	cmd := TailCommand("/tmp/log.txt", false)
	if cmd == nil || cmd.Path == "" {
		t.Fatal("TailCommand(false) returned nil/empty")
	}
	// follow=false → cat, follow=true → tail -f.
	if !hasArg(cmd.Args, "/tmp/log.txt") {
		t.Fatalf("cat args missing path: %v", cmd.Args)
	}

	followCmd := TailCommand("/tmp/log.txt", true)
	if !hasArg(followCmd.Args, "-f") {
		t.Fatalf("follow cmd missing -f: %v", followCmd.Args)
	}
	if !hasArg(followCmd.Args, "/tmp/log.txt") {
		t.Fatalf("follow cmd missing path: %v", followCmd.Args)
	}
}

func hasArg(args []string, want string) bool {
	for _, a := range args {
		if a == want {
			return true
		}
	}
	return false
}

func TestReportAddWarnAndHasFailures(t *testing.T) {
	var r Report
	r.AddPass("a")
	r.AddWarn("b", "detail-1", "detail-2")
	if r.HasFailures() {
		t.Fatal("HasFailures should be false with only pass+warn")
	}
	if len(r.Checks) != 2 {
		t.Fatalf("len(Checks) = %d, want 2", len(r.Checks))
	}
	if r.Checks[1].Status != CheckStatusWarn {
		t.Fatalf("check[1].Status = %v, want warn", r.Checks[1].Status)
	}
	if len(r.Checks[1].Details) != 2 {
		t.Fatalf("warn details lost: %v", r.Checks[1].Details)
	}

	r.AddFail("c")
	if !r.HasFailures() {
		t.Fatal("HasFailures should be true after AddFail")
	}
}

func TestRequireCommandsMissing(t *testing.T) {
	err := RequireCommands(context.Background(), "this-command-cannot-possibly-exist-xyz-123")
	if err == nil {
		t.Fatal("expected error for missing command")
	}
}

func TestRequireCommandsPresent(t *testing.T) {
	// `go` is guaranteed on PATH during test runs.
	if err := RequireCommands(context.Background(), "go"); err != nil {
		t.Fatalf("RequireCommands(go): %v", err)
	}
}

func TestReadHostResources(t *testing.T) {
	if runtime.GOOS != "linux" {
		t.Skip("ReadHostResources reads /proc/meminfo; Linux-only")
	}
	res, err := ReadHostResources()
	if err != nil {
		t.Fatalf("ReadHostResources: %v", err)
	}
	if res.CPUCount <= 0 {
		t.Errorf("CPUCount = %d, want > 0", res.CPUCount)
	}
	if res.TotalMemoryBytes <= 0 {
		t.Errorf("TotalMemoryBytes = %d, want > 0", res.TotalMemoryBytes)
	}
}

func TestShortIDEdgeCases(t *testing.T) {
	if got := ShortID(""); got != "" {
		t.Errorf("ShortID('') = %q, want ''", got)
	}
	if got := ShortID("short"); got != "short" {
		t.Errorf("ShortID('short') = %q, want 'short'", got)
	}
	long := "0123456789abcdef"
	if got := ShortID(long); got != "01234567" {
		t.Errorf("ShortID(long) = %q, want 01234567", got)
	}
}
