package imagepull

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

// runFirstBootPlan executes first-boot.sh in planning mode (RUN_PLAN=1)
// against a synthetic /etc/os-release. Returns the planned install
// command or an error.
func runFirstBootPlan(t *testing.T, osReleaseContent string) string {
	t.Helper()
	if _, err := exec.LookPath("sh"); err != nil {
		t.Skip("sh not available")
	}

	dir := t.TempDir()
	osRelease := filepath.Join(dir, "os-release")
	if err := os.WriteFile(osRelease, []byte(osReleaseContent), 0o644); err != nil {
		t.Fatal(err)
	}
	scriptPath := filepath.Join(dir, "banger-first-boot")
	if err := os.WriteFile(scriptPath, []byte(FirstBootScript()), 0o755); err != nil {
		t.Fatal(err)
	}
	marker := filepath.Join(dir, "first-boot-pending")
	if err := os.WriteFile(marker, nil, 0o644); err != nil {
		t.Fatal(err)
	}

	cmd := exec.Command("sh", scriptPath)
	cmd.Env = append(os.Environ(),
		"RUN_PLAN=1",
		"OS_RELEASE_FILE="+osRelease,
		"BANGER_FIRST_BOOT_MARKER="+marker,
	)
	out, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("first-boot script: %v\noutput:\n%s", err, out)
	}
	// Planned command is printed to stdout (no [banger-first-boot] prefix);
	// log output goes to stderr. CombinedOutput merges them, so pick the
	// last non-log line.
	lines := strings.Split(strings.TrimRight(string(out), "\n"), "\n")
	for i := len(lines) - 1; i >= 0; i-- {
		l := lines[i]
		if strings.TrimSpace(l) == "" {
			continue
		}
		if strings.HasPrefix(l, "[banger-first-boot]") {
			continue
		}
		return l
	}
	t.Fatalf("no planned command in output:\n%s", out)
	return ""
}

func TestFirstBootScriptDispatchesByDistro(t *testing.T) {
	cases := []struct {
		name   string
		osRel  string
		wantRe string
	}{
		{"debian", `ID=debian` + "\n" + `ID_LIKE=""`, "systemd-sysv openssh-server"},
		{"ubuntu", `ID=ubuntu`, "systemd-sysv openssh-server"},
		{"alpine", `ID=alpine`, "apk add"},
		{"fedora", `ID=fedora`, "dnf install -y systemd openssh-server"},
		{"arch", `ID=arch`, "pacman -Sy --noconfirm openssh"},
		{"opensuse-leap", `ID="opensuse-leap"`, "zypper --non-interactive install"},
		{"unknown-with-debian-like", `ID=someweirddistro` + "\n" + `ID_LIKE=debian`, "systemd-sysv openssh-server"},
		{"unknown-with-rhel-like", `ID=something` + "\n" + `ID_LIKE="rhel fedora"`, "dnf install -y systemd openssh-server"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := runFirstBootPlan(t, tc.osRel)
			if !strings.Contains(got, tc.wantRe) {
				t.Errorf("got=%q, want contains %q", got, tc.wantRe)
			}
		})
	}
}

func TestFirstBootScriptContainsDistroCases(t *testing.T) {
	s := FirstBootScript()
	for _, snippet := range []string{
		"debian|ubuntu|kali|raspbian",
		"apt-get",
		"systemd-sysv",
		"openssh-server",
		"alpine)",
		"apk add",
		"fedora|rhel|centos|rocky|almalinux",
		"dnf install",
		"arch|archlinux|manjaro",
		"pacman -Sy",
		"opensuse*|suse",
		"zypper",
		`ID_LIKE`,
		"RUN_PLAN",
		"/usr/lib/systemd/systemd",
		"mount -t proc",
	} {
		if !strings.Contains(s, snippet) {
			t.Errorf("script missing %q", snippet)
		}
	}
}

func TestFirstBootScriptIsShSyntaxValid(t *testing.T) {
	if _, err := exec.LookPath("sh"); err != nil {
		t.Skip("sh not available")
	}
	dir := t.TempDir()
	path := filepath.Join(dir, "first-boot")
	if err := os.WriteFile(path, []byte(FirstBootScript()), 0o755); err != nil {
		t.Fatal(err)
	}
	out, err := exec.Command("sh", "-n", path).CombinedOutput()
	if err != nil {
		t.Fatalf("sh -n first-boot: %v: %s", err, out)
	}
}

func TestFirstBootUnitReferencesScript(t *testing.T) {
	u := FirstBootUnit()
	for _, want := range []string{
		FirstBootScriptPath,
		"ConditionPathExists=" + FirstBootMarkerPath,
		"After=network-online.target",
		"Before=sshd.service",
	} {
		if !strings.Contains(u, want) {
			t.Errorf("unit missing %q", want)
		}
	}
}
