package scripts

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"sort"
	"strings"
	"testing"

	"go.yaml.in/yaml/v3"
)

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

	data, err := os.ReadFile("../.goreleaser.yaml")
	if err != nil {
		t.Fatalf("read .goreleaser.yaml: %v", err)
	}

	var config struct {
		Before struct {
			Hooks []string `yaml:"hooks"`
		} `yaml:"before"`
		Builds []struct {
			ID     string   `yaml:"id"`
			Main   string   `yaml:"main"`
			Binary string   `yaml:"binary"`
			GOOS   []string `yaml:"goos"`
			GOARCH []string `yaml:"goarch"`
		} `yaml:"builds"`
		Archives []struct {
			IDs     []string `yaml:"ids"`
			Formats []string `yaml:"formats"`
			Files   []struct {
				Source      string `yaml:"src"`
				Destination string `yaml:"dst"`
				StripParent bool   `yaml:"strip_parent"`
				Info        struct {
					Mode int `yaml:"mode"`
				} `yaml:"info"`
			} `yaml:"files"`
		} `yaml:"archives"`
	}
	if err := yaml.Unmarshal(data, &config); err != nil {
		t.Fatalf("parse .goreleaser.yaml: %v", err)
	}

	if len(config.Builds) != 2 {
		t.Fatalf("GoReleaser builds = %d, want exactly linux/amd64 and linux/arm64", len(config.Builds))
	}
	gotBuilds := make([]string, 0, len(config.Builds))
	for _, build := range config.Builds {
		if build.Main != "./cmd/agentteam" || build.Binary != "agentteam" {
			t.Fatalf("unexpected GoReleaser binary build: id=%q main=%q binary=%q", build.ID, build.Main, build.Binary)
		}
		if len(build.GOOS) != 1 || build.GOOS[0] != "linux" || len(build.GOARCH) != 1 {
			t.Fatalf("build %q targets goos=%v goarch=%v, want one Linux architecture", build.ID, build.GOOS, build.GOARCH)
		}
		gotBuilds = append(gotBuilds, build.GOOS[0]+"/"+build.GOARCH[0])
	}
	sort.Strings(gotBuilds)
	if got, want := strings.Join(gotBuilds, ","), "linux/amd64,linux/arm64"; got != want {
		t.Fatalf("GoReleaser targets = %q, want %q", got, want)
	}

	if len(config.Archives) != 1 {
		t.Fatalf("GoReleaser archives = %d, want 1", len(config.Archives))
	}
	archive := config.Archives[0]
	if got, want := strings.Join(archive.Formats, ","), "tar.gz"; got != want {
		t.Fatalf("archive formats = %q, want %q", got, want)
	}
	sort.Strings(archive.IDs)
	if got, want := strings.Join(archive.IDs, ","), "agentteam_linux_amd64,agentteam_linux_arm64"; got != want {
		t.Fatalf("archive build IDs = %q, want %q", got, want)
	}

	gotFiles := make([]string, 0, len(archive.Files))
	for _, file := range archive.Files {
		if !file.StripParent {
			t.Fatalf("archive file %q must strip its staging parent", file.Source)
		}
		gotFiles = append(gotFiles, file.Source+" -> "+file.Destination+" mode="+os.FileMode(file.Info.Mode).String())
	}
	sort.Strings(gotFiles)
	wantFiles := []string{
		".goreleaser-managed-opencode/linux-{{ .Arch }}/models.json -> runtimes/opencode mode=-rw-r--r--",
		".goreleaser-managed-opencode/linux-{{ .Arch }}/opencode -> runtimes/opencode mode=-rwxr-xr-x",
	}
	if got, want := strings.Join(gotFiles, "\n"), strings.Join(wantFiles, "\n"); got != want {
		t.Fatalf("archive supplemental files:\n%s\nwant exactly:\n%s", got, want)
	}

	for _, hook := range config.Before.Hooks {
		lowerHook := strings.ToLower(hook)
		for _, forbidden := range []string{"electron", "renderer", "webassets", "node ", "npm "} {
			if strings.Contains(lowerHook, forbidden) {
				t.Fatalf("GoReleaser hook must remain pure Go/shell and must not invoke %q: %s", forbidden, hook)
			}
		}
	}
}

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

	data, err := os.ReadFile("agentteam-url-handler.desktop")
	if err != nil {
		t.Fatalf("read agentteam-url-handler.desktop: %v", err)
	}
	entry := string(data)
	for _, expected := range []string{
		"[Desktop Entry]",
		"Type=Application",
		"NoDisplay=true",
		"Terminal=false",
		"Exec=agentteam connect %u",
		"MimeType=x-scheme-handler/agentteam;",
	} {
		if !strings.Contains(entry, expected) {
			t.Fatalf("expected agentteam URL handler to contain %q", expected)
		}
	}
	for _, forbidden := range []string{"dashboard", "http://", "https://", "electron", "renderer"} {
		if strings.Contains(strings.ToLower(entry), forbidden) {
			t.Fatalf("agentteam URL handler must not contain %q", forbidden)
		}
	}
}

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

	data, err := os.ReadFile("install.sh")
	if err != nil {
		t.Fatalf("read install.sh: %v", err)
	}
	script := string(data)
	for _, expected := range []string{
		`DEFAULT_BASE_URL="https://static.pingcap.com"`,
		`DEFAULT_REPO="agentteam/client"`,
		`DEFAULT_INSTALL_DIR="${HOME}/.agentteam/bin"`,
		`SHELL_PATH_SNIPPET_HEADER="# Added by AgentTeam CLI"`,
		`release_base_url="${trimmed_base_url}/${trimmed_repo}/${version_tag}"`,
		`curl -fsSL https://static.pingcap.com/agentteam/client/install.sh | bash`,
	} {
		if !strings.Contains(script, expected) {
			t.Fatalf("expected install.sh to contain %q", expected)
		}
	}
	if strings.Contains(script, `/releases/download/${version_tag}`) {
		t.Fatalf("install.sh should not default to Gitea release layout")
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho agentteam\n")
	if err := writeSingleFileTarGz(archivePath, "agentteam", binaryContent); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}

	installDir := filepath.Join(t.TempDir(), "bin")
	if err := os.MkdirAll(installDir, 0755); err != nil {
		t.Fatalf("mkdir install dir: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", version,
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
		"--install-dir", installDir,
		"--binary", "agentteam",
	}

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}

	installedPath := filepath.Join(installDir, "agentteam")
	stat, err := os.Stat(installedPath)
	if err != nil {
		t.Fatalf("stat installed binary %s: %v", installedPath, err)
	}
	if stat.Mode()&0111 == 0 {
		t.Fatalf("installed binary is not executable: mode=%v", stat.Mode())
	}
	defaultHandlerPath := filepath.Join(
		envValue(cmd.Env, "HOME"),
		".local", "share", "applications", "agentteam-url-handler.desktop",
	)
	if _, err := os.Stat(defaultHandlerPath); !os.IsNotExist(err) {
		t.Fatalf("URL handler must remain optional, stat %s: %v", defaultHandlerPath, err)
	}
	outputText := string(output)
	if !strings.Contains(outputText, "installation succeeded: agentteam v0.2.0") {
		t.Fatalf("expected success log, got=%s", outputText)
	}
	for _, noisyLog := range []string{"install.sh:", "resolving stable version", "downloading agentteam"} {
		if strings.Contains(outputText, noisyLog) {
			t.Fatalf("expected concise install output without %q, got=%s", noisyLog, outputText)
		}
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho agentteam\n")
	runtimeContent := []byte("#!/usr/bin/env bash\necho opencode\n")
	modelsContent := []byte(`{"opencode/test":{"name":"Test"}}`)
	if err := writeTarGz(
		archivePath,
		tarEntry{name: "agentteam", mode: 0755, body: binaryContent},
		tarEntry{name: "runtimes/opencode/opencode", mode: 0755, body: runtimeContent},
		tarEntry{name: "runtimes/opencode/models.json", mode: 0644, body: modelsContent},
		tarEntry{name: "renderer/index.html", mode: 0644, body: []byte("<html>must not install</html>")},
		tarEntry{name: "electron/main.js", mode: 0644, body: []byte("throw new Error('must not install')")},
		tarEntry{name: "webassets/index.html", mode: 0644, body: []byte("<html>must not install</html>")},
	); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}

	installDir := filepath.Join(t.TempDir(), "bin")
	oldRuntimeDir := filepath.Join(installDir, "runtimes", "opencode")
	if err := os.MkdirAll(oldRuntimeDir, 0755); err != nil {
		t.Fatalf("mkdir old runtime dir: %v", err)
	}
	if err := os.WriteFile(filepath.Join(oldRuntimeDir, "stale"), []byte("old"), 0644); err != nil {
		t.Fatalf("write stale runtime marker: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", version,
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
		"--install-dir", installDir,
		"--binary", "agentteam",
	}

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}

	installedRuntime := filepath.Join(installDir, "runtimes", "opencode", "opencode")
	data, err := os.ReadFile(installedRuntime)
	if err != nil {
		t.Fatalf("read installed runtime: %v", err)
	}
	if string(data) != string(runtimeContent) {
		t.Fatalf("installed runtime content = %q, want %q", string(data), string(runtimeContent))
	}
	stat, err := os.Stat(installedRuntime)
	if err != nil {
		t.Fatalf("stat installed runtime: %v", err)
	}
	if got := stat.Mode().Perm(); got != 0755 {
		t.Fatalf("installed runtime mode = %04o, want 0755", got)
	}
	installedModels := filepath.Join(installDir, "runtimes", "opencode", "models.json")
	data, err = os.ReadFile(installedModels)
	if err != nil {
		t.Fatalf("read installed models catalog: %v", err)
	}
	if string(data) != string(modelsContent) {
		t.Fatalf("installed models content = %q, want %q", string(data), string(modelsContent))
	}
	stat, err = os.Stat(installedModels)
	if err != nil {
		t.Fatalf("stat installed models catalog: %v", err)
	}
	if got := stat.Mode().Perm(); got != 0644 {
		t.Fatalf("installed models mode = %04o, want 0644", got)
	}
	for _, forbidden := range []string{"renderer", "electron", "webassets"} {
		if _, err := os.Stat(filepath.Join(installDir, forbidden)); !os.IsNotExist(err) {
			t.Fatalf("installer must ignore %s payloads, stat err=%v", forbidden, err)
		}
	}
	if _, err := os.Stat(filepath.Join(oldRuntimeDir, "stale")); !os.IsNotExist(err) {
		t.Fatalf("expected stale runtime directory to be replaced, stat err=%v", err)
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	if err := writeTarGz(
		archivePath,
		tarEntry{name: "agentteam", mode: 0755, body: []byte("#!/usr/bin/env bash\necho agentteam\n")},
		tarEntry{name: "runtimes/opencode/opencode", mode: 0755, body: []byte("#!/usr/bin/env bash\necho opencode\n")},
		tarEntry{name: "runtimes/opencode/models.json", mode: 0644, body: []byte(`{"opencode/test":{"name":"Test"}}`)},
	); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}

	installDir := filepath.Join(t.TempDir(), "bin with space")
	if err := os.MkdirAll(installDir, 0755); err != nil {
		t.Fatalf("mkdir install dir: %v", err)
	}
	xdgDataHome := filepath.Join(t.TempDir(), "share")
	fakeBinDir := t.TempDir()
	xdgMimeLog := filepath.Join(t.TempDir(), "xdg-mime.log")
	if err := writeExecutable(
		filepath.Join(fakeBinDir, "xdg-mime"),
		"#!/usr/bin/env bash\nset -euo pipefail\nprintf '%s\\n' \"$*\" >> \"$FAKE_XDG_MIME_LOG\"\n",
	); err != nil {
		t.Fatalf("write fake xdg-mime: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", version,
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
		"--install-dir", installDir,
		"--binary", "agentteam",
		"--install-url-handler",
	}
	cmd.Env = append(cmd.Env,
		"PATH="+fakeBinDir+string(os.PathListSeparator)+envValue(cmd.Env, "PATH"),
		"XDG_DATA_HOME="+xdgDataHome,
		"FAKE_XDG_MIME_LOG="+xdgMimeLog,
	)

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}

	handlerPath := filepath.Join(xdgDataHome, "applications", "agentteam-url-handler.desktop")
	handlerData, err := os.ReadFile(handlerPath)
	if err != nil {
		t.Fatalf("read installed URL handler: %v", err)
	}
	handler := string(handlerData)
	expectedExec := `Exec="` + filepath.Join(installDir, "agentteam") + `" connect %u`
	for _, expected := range []string{expectedExec, "MimeType=x-scheme-handler/agentteam;"} {
		if !strings.Contains(handler, expected) {
			t.Fatalf("installed URL handler missing %q:\n%s", expected, handler)
		}
	}
	for _, forbidden := range []string{"dashboard", "http://", "https://", "electron", "renderer"} {
		if strings.Contains(strings.ToLower(handler), forbidden) {
			t.Fatalf("installed URL handler must not contain %q", forbidden)
		}
	}

	xdgMimeData, err := os.ReadFile(xdgMimeLog)
	if err != nil {
		t.Fatalf("read xdg-mime log: %v", err)
	}
	if got, want := strings.TrimSpace(string(xdgMimeData)), "default agentteam-url-handler.desktop x-scheme-handler/agentteam"; got != want {
		t.Fatalf("xdg-mime invocation = %q, want %q", got, want)
	}
}

func TestInstallScriptResolvesStableVersionWhenVersionMissing(t *testing.T) {
	t.Run("legacy bridge is newer", func(t *testing.T) {
		runStableInstallCase(t, "v0.1.0", "v0.2.0", "v0.2.0")
	})
	t.Run("Desktop catalog is newer", func(t *testing.T) {
		runStableInstallCase(t, "v0.3.0", "v0.2.0", "v0.3.0")
	})
}

func runStableInstallCase(t *testing.T, desktopVersion string, daemonVersion string, expectedVersion string) {
	t.Helper()
	versionNumber := strings.TrimPrefix(expectedVersion, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho agentteam\n")
	if err := writeSingleFileTarGz(archivePath, "agentteam", binaryContent); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, "desktop-stable.json"),
		[]byte(fmt.Sprintf(`{"current_version":%q}`, desktopVersion)),
		0644,
	); err != nil {
		t.Fatalf("write Desktop stable version fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, "daemon-stable.json"),
		[]byte(fmt.Sprintf(`{"current_version":%q}`, daemonVersion)),
		0644,
	); err != nil {
		t.Fatalf("write daemon stable version fixture: %v", err)
	}

	installDir := filepath.Join(t.TempDir(), "bin")
	if err := os.MkdirAll(installDir, 0755); err != nil {
		t.Fatalf("mkdir install dir: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
		"--install-dir", installDir,
		"--binary", "agentteam",
	}
	urlLog := filepath.Join(t.TempDir(), "curl-urls.log")
	cmd.Env = append(cmd.Env,
		"FAKE_CURL_URL_LOG="+urlLog,
		"FAKE_DESKTOP_STABLE_FILE="+filepath.Join(fixtureDir, "desktop-stable.json"),
		"FAKE_DAEMON_STABLE_FILE="+filepath.Join(fixtureDir, "daemon-stable.json"),
	)

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}
	outputText := string(output)
	if !strings.Contains(outputText, "installation succeeded: agentteam "+expectedVersion) {
		t.Fatalf("expected success log, got=%s", outputText)
	}
	for _, noisyLog := range []string{"install.sh:", "resolving stable version", "downloading agentteam"} {
		if strings.Contains(outputText, noisyLog) {
			t.Fatalf("expected concise install output without %q, got=%s", noisyLog, outputText)
		}
	}
	urlData, err := os.ReadFile(urlLog)
	if err != nil {
		t.Fatalf("read curl URL log: %v", err)
	}
	if !strings.Contains(
		string(urlData),
		"https://static.pingcap.com/agentteam/releases/desktop/stable.json",
	) {
		t.Fatalf("expected Desktop stable manifest request, got=%s", string(urlData))
	}
	if !strings.Contains(
		string(urlData),
		"https://static.pingcap.com/agentteam/releases/daemon/stable.json",
	) {
		t.Fatalf("expected bridge stable manifest request, got=%s", string(urlData))
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_arm64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho agentteam\n")
	if err := writeSingleFileTarGz(archivePath, "agentteam", binaryContent); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}

	installDir := filepath.Join(t.TempDir(), "bin")
	if err := os.MkdirAll(installDir, 0755); err != nil {
		t.Fatalf("mkdir install dir: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", version,
		"--archive-url", "https://static.pingcap.com/agentteam/client/v0.2.0/agentteam_0.2.0_linux_arm64.tar.gz",
		"--checksums-url", "https://static.pingcap.com/agentteam/client/v0.2.0/agentteam_0.2.0_checksums.txt",
		"--install-dir", installDir,
		"--binary", "agentteam",
	}
	cmd.Env = append(cmd.Env, "FAKE_UNAME_S=Linux", "FAKE_UNAME_M=arm64")

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}
	outputText := string(output)
	if !strings.Contains(outputText, "installation succeeded: agentteam v0.2.0") {
		t.Fatalf("expected success log, got=%s", outputText)
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_arm64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho agentteam\n")
	if err := writeSingleFileTarGz(archivePath, "agentteam", binaryContent); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}

	installDir := filepath.Join(t.TempDir(), "bin")
	if err := os.MkdirAll(installDir, 0755); err != nil {
		t.Fatalf("mkdir install dir: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", version,
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
		"--install-dir", installDir,
		"--binary", "agentteam",
	}
	cmd.Env = append(cmd.Env, "FAKE_UNAME_S=Linux", "FAKE_UNAME_M=aarch64")

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}
	if !strings.Contains(string(output), "installation succeeded: agentteam v0.2.0") {
		t.Fatalf("expected success log, got=%s", string(output))
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho agentteam\n")
	if err := writeSingleFileTarGz(archivePath, "agentteam", binaryContent); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, "stable.json"),
		[]byte(`{"current_version":"v0.2.0"}`),
		0644,
	); err != nil {
		t.Fatalf("write stable version fixture: %v", err)
	}

	fakeHome := t.TempDir()
	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--stable-version-url", "https://static.pingcap.com/agentteam/releases/daemon/stable.json",
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
	}
	urlLog := filepath.Join(t.TempDir(), "curl-urls.log")
	cmd.Env = append(cmd.Env,
		"HOME="+fakeHome,
		"SHELL=/bin/zsh",
		"FAKE_CURL_URL_LOG="+urlLog,
	)

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}
	outputText := string(output)
	expectedDir := filepath.Join(fakeHome, ".agentteam", "bin")
	profilePath := filepath.Join(fakeHome, ".zshrc")
	if !strings.Contains(outputText, "not currently in PATH") {
		t.Fatalf("expected PATH hint, got=%s", outputText)
	}
	if !strings.Contains(outputText, expectedDir) {
		t.Fatalf("expected install dir in PATH hint, got=%s", outputText)
	}
	if !strings.Contains(outputText, "updated shell profile: "+profilePath) {
		t.Fatalf("expected shell profile update log, got=%s", outputText)
	}
	urlData, err := os.ReadFile(urlLog)
	if err != nil {
		t.Fatalf("read curl URL log: %v", err)
	}
	if strings.Contains(string(urlData), "/agentteam/releases/desktop/stable.json") {
		t.Fatalf("explicit stable URL unexpectedly used the default Desktop feed: %s", string(urlData))
	}

	profileData, err := os.ReadFile(profilePath)
	if err != nil {
		t.Fatalf("read updated shell profile: %v", err)
	}
	if !strings.Contains(string(profileData), "# Added by AgentTeam CLI") {
		t.Fatalf("expected shell profile snippet header, got=%s", string(profileData))
	}
	if !strings.Contains(string(profileData), `export PATH="`+expectedDir+`:$PATH"`) {
		t.Fatalf("expected canonical PATH export, got=%s", string(profileData))
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho agentteam\n")
	if err := writeSingleFileTarGz(archivePath, "agentteam", binaryContent); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, "stable.json"),
		[]byte(`{"current_version":"v0.2.0"}`),
		0644,
	); err != nil {
		t.Fatalf("write stable version fixture: %v", err)
	}

	fakeHome := t.TempDir()
	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--stable-version-url", "https://static.pingcap.com/agentteam/releases/daemon/stable.json",
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
	}
	cmd.Env = append(cmd.Env, "HOME="+fakeHome, "SHELL=/opt/homebrew/bin/fish")

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}
	outputText := string(output)
	expectedDir := filepath.Join(fakeHome, ".agentteam", "bin")
	profilePath := filepath.Join(fakeHome, ".config", "fish", "config.fish")
	if !strings.Contains(outputText, "updated shell profile: "+profilePath) {
		t.Fatalf("expected fish profile update log, got=%s", outputText)
	}
	if !strings.Contains(outputText, "source "+profilePath) {
		t.Fatalf("expected fish source hint, got=%s", outputText)
	}
	if !strings.Contains(outputText, `set -gx PATH "`+expectedDir+`" $PATH`) {
		t.Fatalf("expected fish PATH hint, got=%s", outputText)
	}
	if strings.Contains(outputText, `export PATH="`+expectedDir+`:$PATH"`) {
		t.Fatalf("expected fish hint instead of POSIX export, got=%s", outputText)
	}

	profileData, err := os.ReadFile(profilePath)
	if err != nil {
		t.Fatalf("read updated fish profile: %v", err)
	}
	profileText := string(profileData)
	if !strings.Contains(profileText, "# Added by AgentTeam CLI") {
		t.Fatalf("expected fish profile snippet header, got=%s", profileText)
	}
	if !strings.Contains(profileText, `if not contains "`+expectedDir+`" $PATH`) {
		t.Fatalf("expected fish contains guard, got=%s", profileText)
	}
	if !strings.Contains(profileText, `set -gx PATH "`+expectedDir+`" $PATH`) {
		t.Fatalf("expected fish PATH update, got=%s", profileText)
	}
	if strings.Contains(profileText, `export PATH="`) {
		t.Fatalf("expected no POSIX export in fish profile, got=%s", profileText)
	}

	fishPath, err := exec.LookPath("fish")
	if err != nil {
		t.Logf("fish not found; skipped real fish syntax smoke: %v", err)
		return
	}
	if output, err := exec.Command(fishPath, "-n", profilePath).CombinedOutput(); err != nil {
		t.Fatalf("fish rejected generated config: %v, output=%s", err, string(output))
	}
	fishCmd := exec.Command(fishPath, "-c", "set -gx PATH /usr/bin /bin; source ~/.config/fish/config.fish; command -v agentteam")
	fishCmd.Env = append(os.Environ(), "HOME="+fakeHome)
	output, err = fishCmd.CombinedOutput()
	if err != nil {
		t.Fatalf("fish could not resolve agentteam after sourcing generated config: %v, output=%s", err, string(output))
	}
	if strings.TrimSpace(string(output)) != filepath.Join(expectedDir, "agentteam") {
		t.Fatalf("fish resolved unexpected agentteam path, got=%s", string(output))
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho agentteam\n")
	if err := writeSingleFileTarGz(archivePath, "agentteam", binaryContent); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, "stable.json"),
		[]byte(`{"current_version":"v0.2.0"}`),
		0644,
	); err != nil {
		t.Fatalf("write stable version fixture: %v", err)
	}

	fakeHome := t.TempDir()
	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--stable-version-url", "https://static.pingcap.com/agentteam/releases/daemon/stable.json",
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
		"--no-modify-path",
	}
	cmd.Env = append(cmd.Env, "HOME="+fakeHome, "SHELL=/bin/zsh")

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}
	if strings.Contains(string(output), "updated shell profile:") {
		t.Fatalf("expected no shell profile update, got=%s", string(output))
	}
	if _, err := os.Stat(filepath.Join(fakeHome, ".zshrc")); !os.IsNotExist(err) {
		t.Fatalf("expected no shell profile file, stat err=%v", err)
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho agentteam\n")
	if err := writeSingleFileTarGz(archivePath, "agentteam", binaryContent); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, "stable.json"),
		[]byte(`{"current_version":"v0.2.0"}`),
		0644,
	); err != nil {
		t.Fatalf("write stable version fixture: %v", err)
	}

	fakeHome := t.TempDir()
	profilePath := filepath.Join(fakeHome, ".zshrc")
	if err := os.Mkdir(profilePath, 0755); err != nil {
		t.Fatalf("create non-appendable shell profile path: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--stable-version-url", "https://static.pingcap.com/agentteam/releases/daemon/stable.json",
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
	}
	cmd.Env = append(cmd.Env, "HOME="+fakeHome, "SHELL=/bin/zsh")

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script should keep install success when shell profile cannot be updated: %v, output=%s", err, string(output))
	}
	outputText := string(output)
	if !strings.Contains(outputText, "installation succeeded: agentteam v0.2.0") {
		t.Fatalf("expected install success, got=%s", outputText)
	}
	if !strings.Contains(outputText, "warning: failed to update shell profile "+profilePath) {
		t.Fatalf("expected shell profile warning, got=%s", outputText)
	}
	if strings.Contains(outputText, "updated shell profile:") {
		t.Fatalf("expected no updated profile log, got=%s", outputText)
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	binaryContent := []byte("#!/usr/bin/env bash\necho new-agentteam\n")
	if err := writeSingleFileTarGz(archivePath, "agentteam", binaryContent); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}

	fakeHome := t.TempDir()
	oldBinDir := t.TempDir()
	oldAgentTeamDir := filepath.Join(oldBinDir, "old")
	if err := os.MkdirAll(oldAgentTeamDir, 0755); err != nil {
		t.Fatalf("mkdir old agentteam dir: %v", err)
	}
	if err := writeExecutable(
		filepath.Join(oldAgentTeamDir, "agentteam"),
		"#!/usr/bin/env bash\necho old-agentteam\n",
	); err != nil {
		t.Fatalf("write old agentteam shim: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	basePath := envValue(cmd.Env, "PATH")
	if basePath == "" {
		t.Fatalf("missing PATH in prepared command env")
	}
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", version,
	}
	cmd.Env = append(cmd.Env,
		"HOME="+fakeHome,
		"SHELL=/bin/zsh",
		"FAKE_UNAME_S=Linux",
		"FAKE_UNAME_M=x86_64",
		"PATH="+oldAgentTeamDir+string(os.PathListSeparator)+basePath,
	)

	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("install script failed: %v, output=%s", err, string(output))
	}
	outputText := string(output)
	expectedInstallPath := filepath.Join(fakeHome, ".agentteam", "bin", "agentteam")
	expectedProfilePath := filepath.Join(fakeHome, ".zshrc")
	if !strings.Contains(outputText, "current shell resolves 'agentteam' to "+filepath.Join(oldAgentTeamDir, "agentteam")) {
		t.Fatalf("expected old path warning, got=%s", outputText)
	}
	if !strings.Contains(outputText, "source "+expectedProfilePath) {
		t.Fatalf("expected source profile hint, got=%s", outputText)
	}
	if !strings.Contains(outputText, "hash -r") {
		t.Fatalf("expected hash -r hint, got=%s", outputText)
	}
	if !strings.Contains(outputText, expectedInstallPath+" version") {
		t.Fatalf("expected direct binary verification hint, got=%s", outputText)
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	if err := writeSingleFileTarGz(archivePath, "agentteam", []byte("fake-binary")); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte("0000000000000000000000000000000000000000000000000000000000000000  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}

	installDir := filepath.Join(t.TempDir(), "bin")
	if err := os.MkdirAll(installDir, 0755); err != nil {
		t.Fatalf("mkdir install dir: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", version,
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
		"--install-dir", installDir,
		"--binary", "agentteam",
	}

	output, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatalf("expected install script to fail on checksum mismatch, output=%s", string(output))
	}
	if !strings.Contains(string(output), "checksum validation failed") {
		t.Fatalf("expected checksum failure in output, got=%s", string(output))
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	if err := writeSingleFileTarGz(archivePath, "agentteam", []byte("#!/usr/bin/env bash\necho new\n")); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}

	installDir := filepath.Join(t.TempDir(), "bin")
	if err := os.MkdirAll(installDir, 0755); err != nil {
		t.Fatalf("mkdir install dir: %v", err)
	}
	installedPath := filepath.Join(installDir, "agentteam")
	if err := writeExecutable(installedPath, "#!/usr/bin/env bash\necho old\n"); err != nil {
		t.Fatalf("write old binary: %v", err)
	}

	failMvDir := t.TempDir()
	if err := writeExecutable(
		filepath.Join(failMvDir, "mv"),
		"#!/usr/bin/env bash\nset -euo pipefail\nsrc=\"${1:-}\"\nbase=\"${src##*/}\"\nif [[ \"$base\" == .agentteam.install.* ]]; then\n  echo final mv failed >&2\n  exit 1\nfi\nexec \"$REAL_MV\" \"$@\"\n",
	); err != nil {
		t.Fatalf("write fake mv: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	basePath := envValue(cmd.Env, "PATH")
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", version,
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
		"--install-dir", installDir,
		"--binary", "agentteam",
	}
	cmd.Env = append(cmd.Env,
		"PATH="+failMvDir+string(os.PathListSeparator)+basePath,
		"REAL_MV="+realToolPath(t, "mv"),
	)

	output, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatalf("expected install script to fail, output=%s", string(output))
	}
	data, err := os.ReadFile(installedPath)
	if err != nil {
		t.Fatalf("read restored binary: %v", err)
	}
	if string(data) != "#!/usr/bin/env bash\necho old\n" {
		t.Fatalf("expected previous binary restored, got=%s", string(data))
	}
}

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

	version := "v0.2.0"
	versionNumber := strings.TrimPrefix(version, "v")
	archiveName := "agentteam_" + versionNumber + "_linux_amd64.tar.gz"
	checksumsName := "agentteam_" + versionNumber + "_checksums.txt"

	fixtureDir := t.TempDir()
	archivePath := filepath.Join(fixtureDir, archiveName)
	if err := writeTarGz(
		archivePath,
		tarEntry{name: "agentteam", mode: 0755, body: []byte("#!/usr/bin/env bash\necho new\n")},
		tarEntry{name: "runtimes/opencode/opencode", mode: 0755, body: []byte("#!/usr/bin/env bash\necho new-runtime\n")},
	); err != nil {
		t.Fatalf("write archive fixture: %v", err)
	}
	checksum, err := fileSHA256Hex(archivePath)
	if err != nil {
		t.Fatalf("checksum archive fixture: %v", err)
	}
	if err := os.WriteFile(
		filepath.Join(fixtureDir, checksumsName),
		[]byte(checksum+"  "+archiveName+"\n"),
		0644,
	); err != nil {
		t.Fatalf("write checksums fixture: %v", err)
	}

	installDir := filepath.Join(t.TempDir(), "bin")
	if err := os.MkdirAll(filepath.Join(installDir, "runtimes", "opencode"), 0755); err != nil {
		t.Fatalf("mkdir install dir: %v", err)
	}
	installedPath := filepath.Join(installDir, "agentteam")
	if err := writeExecutable(installedPath, "#!/usr/bin/env bash\necho old\n"); err != nil {
		t.Fatalf("write old binary: %v", err)
	}
	installedRuntime := filepath.Join(installDir, "runtimes", "opencode", "opencode")
	if err := writeExecutable(installedRuntime, "#!/usr/bin/env bash\necho old-runtime\n"); err != nil {
		t.Fatalf("write old runtime: %v", err)
	}

	failMvDir := t.TempDir()
	if err := writeExecutable(
		filepath.Join(failMvDir, "mv"),
		"#!/usr/bin/env bash\nset -euo pipefail\nsrc=\"${1:-}\"\nbase=\"${src##*/}\"\nif [[ \"$base\" == .runtimes.install.* ]]; then\n  echo runtime mv failed >&2\n  exit 1\nfi\nexec \"$REAL_MV\" \"$@\"\n",
	); err != nil {
		t.Fatalf("write fake mv: %v", err)
	}

	cmd := prepareInstallCommand(t, fixtureDir)
	basePath := envValue(cmd.Env, "PATH")
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", version,
		"--base-url", "https://gitea.test",
		"--repo", "test/agentteam",
		"--install-dir", installDir,
		"--binary", "agentteam",
	}
	cmd.Env = append(cmd.Env,
		"PATH="+failMvDir+string(os.PathListSeparator)+basePath,
		"REAL_MV="+realToolPath(t, "mv"),
	)

	output, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatalf("expected install script to fail, output=%s", string(output))
	}
	data, err := os.ReadFile(installedPath)
	if err != nil {
		t.Fatalf("read restored binary: %v", err)
	}
	if string(data) != "#!/usr/bin/env bash\necho old\n" {
		t.Fatalf("expected previous binary restored, got=%s", string(data))
	}
	data, err = os.ReadFile(installedRuntime)
	if err != nil {
		t.Fatalf("read restored runtime: %v", err)
	}
	if string(data) != "#!/usr/bin/env bash\necho old-runtime\n" {
		t.Fatalf("expected previous runtime restored, got=%s", string(data))
	}
}

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

	cmd := prepareInstallCommand(t, t.TempDir())

	isolatedHome := envValue(cmd.Env, "HOME")
	if isolatedHome == "" {
		t.Fatalf("expected isolated HOME in prepared command env")
	}
	if isolatedHome == os.Getenv("HOME") {
		t.Fatalf("expected prepared command HOME to differ from real HOME %q", os.Getenv("HOME"))
	}
	if info, err := os.Stat(isolatedHome); err != nil {
		t.Fatalf("expected isolated HOME %q to exist: %v", isolatedHome, err)
	} else if !info.IsDir() {
		t.Fatalf("expected isolated HOME %q to be a directory", isolatedHome)
	}
}

func prepareInstallCommand(t *testing.T, fixtureDir string) *exec.Cmd {
	t.Helper()

	scriptsDir, err := filepath.Abs(".")
	if err != nil {
		t.Fatalf("resolve scripts directory: %v", err)
	}

	fakeBinDir := t.TempDir()
	if err := writeExecutable(
		filepath.Join(fakeBinDir, "uname"),
		"#!/usr/bin/env bash\nset -euo pipefail\ncase \"${1:-}\" in\n  -s)\n    printf '%s\\n' \"${FAKE_UNAME_S:-Linux}\"\n    ;;\n  -m)\n    printf '%s\\n' \"${FAKE_UNAME_M:-x86_64}\"\n    ;;\n  *)\n    exec \"$REAL_UNAME\" \"$@\"\n    ;;\nesac\n",
	); err != nil {
		t.Fatalf("write fake uname: %v", err)
	}
	if err := writeExecutable(
		filepath.Join(fakeBinDir, "curl"),
		"#!/usr/bin/env bash\nset -euo pipefail\nif [[ -n \"${FAKE_CURL_MARKER:-}\" ]]; then : > \"$FAKE_CURL_MARKER\"; fi\nout=\"\"\nurl=\"\"\nwhile [[ $# -gt 0 ]]; do\n  case \"$1\" in\n    -o)\n      out=\"$2\"\n      shift 2\n      ;;\n    -f|-s|-S|-L)\n      shift\n      ;;\n    --max-time)\n      shift 2\n      ;;\n    *)\n      url=\"$1\"\n      shift\n      ;;\n  esac\ndone\n[[ -n \"$url\" ]] || { echo \"missing url\" >&2; exit 2; }\nif [[ -n \"${FAKE_CURL_URL_LOG:-}\" ]]; then printf '%s\\n' \"$url\" >> \"$FAKE_CURL_URL_LOG\"; fi\ncase \"$url\" in\n  */agentteam/releases/desktop/stable.json) source_path=\"${FAKE_DESKTOP_STABLE_FILE:-${FAKE_DOWNLOAD_DIR}/stable.json}\" ;;\n  */agentteam/releases/daemon/stable.json) source_path=\"${FAKE_DAEMON_STABLE_FILE:-${FAKE_DOWNLOAD_DIR}/stable.json}\" ;;\n  *) name=\"${url##*/}\"; source_path=\"${FAKE_DOWNLOAD_DIR}/${name}\" ;;\nesac\nif [[ -n \"$out\" ]]; then\n  cp \"$source_path\" \"$out\"\nelse\n  cat \"$source_path\"\nfi\n",
	); err != nil {
		t.Fatalf("write fake curl: %v", err)
	}

	cmd := exec.Command("bash")
	cmd.Dir = scriptsDir
	isolatedHome := t.TempDir()
	inheritedEnv := os.Environ()
	cmd.Env = make([]string, 0, len(inheritedEnv)+6)
	for _, entry := range inheritedEnv {
		if strings.HasPrefix(entry, "HOME=") {
			continue
		}
		cmd.Env = append(cmd.Env, entry)
	}
	cmd.Env = append(cmd.Env,
		"HOME="+isolatedHome,
		"PATH="+fakeBinDir+string(os.PathListSeparator)+os.Getenv("PATH"),
		"FAKE_DOWNLOAD_DIR="+fixtureDir,
		"FAKE_UNAME_S=Linux",
		"FAKE_UNAME_M=x86_64",
		"REAL_UNAME="+realUnamePath(t),
	)
	return cmd
}

func envValue(env []string, key string) string {
	prefix := key + "="
	for i := len(env) - 1; i >= 0; i-- {
		entry := env[i]
		if strings.HasPrefix(entry, prefix) {
			return strings.TrimPrefix(entry, prefix)
		}
	}
	return ""
}

func writeExecutable(path string, content string) error {
	return os.WriteFile(path, []byte(content), 0755)
}

func writeSingleFileTarGz(path string, fileName string, fileBody []byte) error {
	return writeTarGz(path, tarEntry{name: fileName, mode: 0755, body: fileBody})
}

type tarEntry struct {
	name string
	mode int64
	body []byte
}

func writeTarGz(path string, entries ...tarEntry) error {
	out, err := os.Create(path)
	if err != nil {
		return err
	}
	defer out.Close()

	gzipWriter := gzip.NewWriter(out)
	defer gzipWriter.Close()

	tarWriter := tar.NewWriter(gzipWriter)
	defer tarWriter.Close()

	for _, entry := range entries {
		header := &tar.Header{
			Name: entry.name,
			Mode: entry.mode,
			Size: int64(len(entry.body)),
		}
		if err := tarWriter.WriteHeader(header); err != nil {
			return err
		}
		if _, err := tarWriter.Write(entry.body); err != nil {
			return err
		}
	}
	return nil
}

func fileSHA256Hex(path string) (string, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return "", err
	}
	sum := sha256.Sum256(data)
	return hex.EncodeToString(sum[:]), nil
}

func realUnamePath(t *testing.T) string {
	t.Helper()
	path, err := exec.LookPath("uname")
	if err == nil {
		return path
	}

	switch runtime.GOOS {
	case "darwin", "linux":
		return "/usr/bin/uname"
	default:
		t.Fatalf("failed to locate uname executable: %v", err)
		return ""
	}
}

func realToolPath(t *testing.T, name string) string {
	t.Helper()
	path, err := exec.LookPath(name)
	if err != nil {
		t.Fatalf("failed to locate %s executable: %v", name, err)
	}
	return path
}

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

	fixtureDir := t.TempDir()
	curlMarker := filepath.Join(t.TempDir(), "curl-called")
	cmd := prepareInstallCommand(t, fixtureDir)
	cmd.Args = []string{
		"bash", "./install.sh",
	}
	cmd.Env = append(cmd.Env,
		"FAKE_UNAME_S=Darwin",
		"FAKE_CURL_MARKER="+curlMarker,
	)

	output, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatalf("expected install script to fail on unsupported os, output=%s", string(output))
	}
	if !strings.Contains(string(output), "supports Linux only") {
		t.Fatalf("expected unsupported os message, got=%s", string(output))
	}
	if _, statErr := os.Stat(curlMarker); !os.IsNotExist(statErr) {
		t.Fatalf("unsupported OS must fail before curl, stat %s: %v", curlMarker, statErr)
	}
}

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

	cmd := prepareInstallCommand(t, t.TempDir())
	cmd.Args = []string{
		"bash", "./install.sh",
		"--version", "v0.2.0",
	}
	cmd.Env = append(cmd.Env, "FAKE_UNAME_S=Linux", "FAKE_UNAME_M=i386")

	output, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatalf("expected install script to fail on unsupported Linux arch, output=%s", string(output))
	}
	if !strings.Contains(string(output), "supports amd64 and arm64 only") {
		t.Fatalf("expected unsupported Linux arch message, got=%s", string(output))
	}
}

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

	cmd := exec.Command("bash", "./install.sh", "--version", "v0.2.0", "--unknown")
	scriptsDir, err := filepath.Abs(".")
	if err != nil {
		t.Fatalf("resolve scripts directory: %v", err)
	}
	cmd.Dir = scriptsDir

	output, err := cmd.CombinedOutput()
	if err == nil {
		t.Fatalf("expected install script to fail for unknown argument, output=%s", string(output))
	}
	if !bytes.Contains(output, []byte("unknown argument")) {
		t.Fatalf("expected unknown argument message, got=%s", string(output))
	}
}
