package main

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

// TestIsCharDeviceRejectsNonConsoles pins down the half of the check that can
// be tested without a terminal: a pipe, a regular file and a closed handle
// must all read as "not a console", because each of them means something other
// than a person waiting at a keyboard.
//
// Getting this wrong in this direction is the dangerous one: it would drop a
// script or a build pipeline into an interactive prompt and hang it forever.
func TestIsCharDeviceRejectsNonConsoles(t *testing.T) {
	r, w, err := os.Pipe()
	if err != nil {
		t.Fatalf("pipe: %v", err)
	}
	defer r.Close()
	defer w.Close()

	tmp, err := os.CreateTemp(t.TempDir(), "out")
	if err != nil {
		t.Fatalf("temp file: %v", err)
	}
	defer tmp.Close()

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

	for _, tc := range []struct {
		name string
		file *os.File
		want bool
	}{
		{"read end of a pipe", r, false},
		{"write end of a pipe", w, false},
		{"regular file", tmp, false},
		// os.DevNull IS a character device on Unix, so this case documents a
		// real limitation rather than asserting a value that differs by
		// platform: `prog < /dev/null` on Unix looks interactive on stdin.
		// It is harmless, because stdout must ALSO be a console, and a script
		// redirecting stdin almost always redirects stdout too.
	} {
		if got := isCharDevice(tc.file); got != tc.want {
			t.Errorf("isCharDevice(%s) = %v, want %v", tc.name, got, tc.want)
		}
	}
	_ = devNull
}

// TestIsCharDeviceHandlesAClosedFile makes sure a handle we cannot stat is
// treated as "not a console" rather than panicking or defaulting to true.
func TestIsCharDeviceHandlesAClosedFile(t *testing.T) {
	f, err := os.CreateTemp(t.TempDir(), "closed")
	if err != nil {
		t.Fatalf("temp file: %v", err)
	}
	f.Close()
	if isCharDevice(f) {
		t.Error("isCharDevice(closed file) = true, want false")
	}
}

// TestInteractiveConsoleNeedsBothEnds asserts the AND: one console end is not
// enough. Under `go test` stdout is not a terminal, so this must be false.
func TestInteractiveConsoleNeedsBothEnds(t *testing.T) {
	if interactiveConsole() {
		t.Error("interactiveConsole() = true under `go test`, where output is captured; " +
			"the guided prompt would hang any non-interactive run")
	}
}

// TestSuggestedLayoutIsRealOrEmpty guards the one-keypress default: whatever
// layout it offers must exist and must actually load, or pressing Enter greets
// the reader with an error on their very first interaction.
func TestSuggestedLayoutIsRealOrEmpty(t *testing.T) {
	got := suggestedLayout()
	if got == "" {
		return // no layout lying around; the prompt asks for one instead
	}
	info, err := os.Stat(got)
	if err != nil {
		t.Fatalf("suggestedLayout() = %q, which does not exist: %v", got, err)
	}
	if info.IsDir() {
		t.Errorf("suggestedLayout() = %q, which is a directory, not a layout file", got)
	}
	if _, err := LoadLayout(got); err != nil {
		t.Errorf("suggestedLayout() = %q, which does not load as a layout: %v", got, err)
	}
}

// TestLayoutInAndTopologyInTellTheFilesApart pins the two defaults down: an
// unrelated JSON file must be offered as neither, and a layout must never be
// offered as the screen description it is supposed to be checked against.
func TestLayoutInAndTopologyInTellTheFilesApart(t *testing.T) {
	dir := t.TempDir()
	if err := os.WriteFile(filepath.Join(dir, "a-settings.json"),
		[]byte(`{"theme":"dark"}`), 0o644); err != nil {
		t.Fatalf("write: %v", err)
	}
	if got := layoutIn(dir); got != "" {
		t.Errorf("layoutIn(folder of unrelated JSON) = %q, want \"\"", got)
	}
	if got := topologyIn(dir); got != "" {
		t.Errorf("topologyIn(folder of unrelated JSON) = %q, want \"\"", got)
	}

	layout := filepath.Join(dir, "b-desk.layout.json")
	if err := os.WriteFile(layout, []byte(`{
	  "name": "guided-test",
	  "monitors": [{"monitor": "center", "root": {"zone": "editor"}}],
	  "rules": [{"zone": "editor", "app": "prefix:com."}]
	}`), 0o644); err != nil {
		t.Fatalf("write: %v", err)
	}
	topo := filepath.Join(dir, "c-desk.topology.json")
	if err := os.WriteFile(topo, []byte(`{
	  "name": "guided-test-topo",
	  "monitors": [{"id": "center", "primary": true,
	                "bounds": {"x": 0, "y": 0, "w": 2560, "h": 1440}}]
	}`), 0o644); err != nil {
		t.Fatalf("write: %v", err)
	}

	if got := layoutIn(dir); got != layout {
		t.Errorf("layoutIn(...) = %q, want %q", got, layout)
	}
	if got := topologyIn(dir); got != topo {
		t.Errorf("topologyIn(...) = %q, want %q", got, topo)
	}
}
