package runtimeconfig

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

	"github.com/stretchr/testify/require"
)

func TestRuntimeBinaryAvailableDoesNotMutatePath(t *testing.T) {
	homeDir := t.TempDir()
	binDir := filepath.Join(homeDir, ".local", "bin")
	require.NoError(t, os.MkdirAll(binDir, 0o755))

	claudePath := filepath.Join(binDir, "claude")
	require.NoError(t, os.WriteFile(claudePath, []byte("#!/bin/sh\nexit 0\n"), 0o755))

	initialPath := filepath.Join(t.TempDir(), "path-bin")
	t.Setenv("HOME", homeDir)
	t.Setenv("PATH", initialPath)

	path, found := RuntimeBinaryAvailable(TypeClaudeCode, "claude")
	require.True(t, found)
	require.Equal(t, claudePath, path)
	require.Equal(t, initialPath, os.Getenv("PATH"))
}

func TestRuntimeBinaryAvailableTreatsCodexBinaryAsCandidate(t *testing.T) {
	dir := t.TempDir()
	codexPath := filepath.Join(dir, "codex")
	require.NoError(t, os.WriteFile(codexPath, []byte("#!/bin/sh\nexit 0\n"), 0o755))
	t.Setenv("PATH", dir)

	oldProbe := CodexAppServerAvailable
	t.Cleanup(func() { CodexAppServerAvailable = oldProbe })
	CodexAppServerAvailable = func(string) bool {
		t.Fatal("RuntimeBinaryAvailable should not run codex app-server probes")
		return false
	}

	path, found := RuntimeBinaryAvailable(TypeCodex, "codex")
	require.True(t, found)
	require.Equal(t, codexPath, path)
}

func TestProbeRuntimeBinarySeparatesInstalledFromCapability(t *testing.T) {
	dir := t.TempDir()
	codexPath := filepath.Join(dir, "codex")
	require.NoError(t, os.WriteFile(codexPath, []byte("#!/bin/sh\nexit 0\n"), 0o755))
	t.Setenv("PATH", dir)

	oldProbe := CodexAppServerAvailable
	t.Cleanup(func() { CodexAppServerAvailable = oldProbe })
	CodexAppServerAvailable = func(string) bool { return false }

	probe := ProbeRuntimeBinary(TypeCodex, "codex")
	require.Equal(t, codexPath, probe.Path)
	require.True(t, probe.Installed)
	require.False(t, probe.Capable)
	require.Equal(t, RuntimeProbeReasonCapabilityMissing, probe.ReasonCode)
	require.Contains(t, probe.Message, "app-server")
	require.Error(t, probe.Err)
}

func TestCodexAppServerAvailableUsesShellRuntimeEnvWithoutShellCredentialEnv(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("test uses a POSIX shell script")
	}

	SetShellPathForRuntimeResolution("")
	fnmDir := t.TempDir()
	SetShellEnvForRuntimeResolution(map[string]string{
		"FNM_DIR":        fnmDir,
		"OPENAI_API_KEY": "sk-shell",
	})
	t.Cleanup(func() {
		SetShellPathForRuntimeResolution("")
		SetShellEnvForRuntimeResolution(nil)
	})
	unsetEnv(t, "FNM_DIR")
	unsetEnv(t, "OPENAI_API_KEY")

	dir := t.TempDir()
	codexPath := filepath.Join(dir, "codex")
	script := "#!/bin/sh\n" +
		`if [ "$1" = "app-server" ] && [ "$2" = "--help" ]; then` + "\n" +
		`  if [ "$FNM_DIR" != "` + fnmDir + `" ]; then exit 17; fi` + "\n" +
		`  if [ -n "$OPENAI_API_KEY" ]; then exit 18; fi` + "\n" +
		"  exit 0\n" +
		"fi\n" +
		"exit 19\n"
	require.NoError(t, os.WriteFile(codexPath, []byte(script), 0o755))

	require.True(t, CodexAppServerAvailable(codexPath))
}

func TestProbeRuntimeBinaryReportsMissingBinary(t *testing.T) {
	t.Setenv("PATH", t.TempDir())

	probe := ProbeRuntimeBinary(TypeClaudeCode, "definitely-missing-claude-agentteam-test")
	require.Empty(t, probe.Path)
	require.False(t, probe.Installed)
	require.False(t, probe.Capable)
	require.Equal(t, RuntimeProbeReasonBinaryMissing, probe.ReasonCode)
	require.Contains(t, probe.Message, "definitely-missing-claude-agentteam-test")
	require.Error(t, probe.Err)
}

func TestProbeRuntimeBinaryTreatsBuiltinAsManagedOpenCode(t *testing.T) {
	opencodePath := writeManagedOpenCodeBinary(t, "opencode 1.2.3\n", 0o755)
	writeManagedOpenCodeModels(t, opencodePath)
	t.Setenv(managedOpenCodePathEnv, opencodePath)
	setManagedOpenCodeVersion(t, "")

	path, found := RuntimeBinaryAvailable(TypeBuiltin, "")
	require.True(t, found)
	require.Equal(t, opencodePath, path)

	probe := ProbeRuntimeBinary(TypeBuiltin, "")
	require.Equal(t, opencodePath, probe.Path)
	require.True(t, probe.Installed)
	require.True(t, probe.Capable)
	require.Empty(t, probe.ReasonCode)
	require.NoError(t, probe.Err)

	path, found = RuntimeBinaryAvailable(TypeBuiltin, "builtin")
	require.True(t, found)
	require.Equal(t, opencodePath, path)

	path, found = RuntimeBinaryAvailable(TypeBuiltin, "claude")
	require.True(t, found)
	require.Equal(t, opencodePath, path)

	path, found = RuntimeBinaryAvailable(TypeBuiltin, filepath.Join(t.TempDir(), "external-opencode"))
	require.True(t, found)
	require.Equal(t, opencodePath, path)
}

func TestProbeRuntimeBinaryUsesExternalOpenCodeByDefault(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("test uses POSIX shell scripts")
	}
	managedPath := writeManagedOpenCodeBinary(t, "opencode 1.2.3\n", 0o755)
	writeManagedOpenCodeModels(t, managedPath)
	t.Setenv(managedOpenCodePathEnv, managedPath)
	setManagedOpenCodeVersion(t, "")

	dir := t.TempDir()
	externalPath := filepath.Join(dir, "opencode")
	require.NoError(t, os.WriteFile(externalPath, []byte("#!/bin/sh\nexit 0\n"), 0o755))
	t.Setenv("PATH", dir)

	path, found := RuntimeBinaryAvailable(TypeOpenCode, "")
	require.True(t, found)
	require.Equal(t, externalPath, path)

	probe := ProbeRuntimeBinary(TypeOpenCode, "")
	require.Equal(t, externalPath, probe.Path)
	require.True(t, probe.Installed)
	require.True(t, probe.Capable)
}

func TestProbeRuntimeBinaryUsesExternalOpenCodeWhenBinaryIsCustom(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("test uses POSIX shell scripts")
	}
	dir := t.TempDir()
	opencodePath := filepath.Join(dir, "custom-opencode")
	require.NoError(t, os.WriteFile(opencodePath, []byte("#!/bin/sh\nexit 0\n"), 0o755))
	t.Setenv("PATH", t.TempDir())
	t.Setenv(managedOpenCodePathEnv, filepath.Join(t.TempDir(), "missing-managed-opencode"))
	setManagedOpenCodeVersion(t, "")

	probe := ProbeRuntimeBinary(TypeOpenCode, opencodePath)

	require.Equal(t, opencodePath, probe.Path)
	require.True(t, probe.Installed)
	require.True(t, probe.Capable)
	require.Empty(t, probe.ReasonCode)
	require.NoError(t, probe.Err)
}

func TestRuntimeBinaryAvailableTreatsKimiBinaryAsCandidate(t *testing.T) {
	dir := t.TempDir()
	kimiPath := filepath.Join(dir, "kimi.cmd")
	require.NoError(t, os.WriteFile(kimiPath, []byte("#!/bin/sh\nexit 0\n"), 0o755))
	t.Setenv("PATH", dir)

	oldProbe := KimiACPAvailable
	t.Cleanup(func() { KimiACPAvailable = oldProbe })
	KimiACPAvailable = func(string) bool {
		t.Fatal("RuntimeBinaryAvailable should not run Kimi ACP probes")
		return false
	}

	path, found := RuntimeBinaryAvailable(TypeKimi, kimiPath)
	require.True(t, found)
	require.Equal(t, kimiPath, path)
}

func TestProbeRuntimeBinarySeparatesKimiACPFromInstall(t *testing.T) {
	dir := t.TempDir()
	kimiPath := filepath.Join(dir, "kimi.cmd")
	require.NoError(t, os.WriteFile(kimiPath, []byte("#!/bin/sh\nexit 0\n"), 0o755))
	t.Setenv("PATH", dir)

	oldProbe := KimiACPAvailable
	t.Cleanup(func() { KimiACPAvailable = oldProbe })
	KimiACPAvailable = func(string) bool { return false }

	probe := ProbeRuntimeBinary(TypeKimi, kimiPath)
	require.Equal(t, kimiPath, probe.Path)
	require.True(t, probe.Installed)
	require.False(t, probe.Capable)
	require.Equal(t, RuntimeProbeReasonCapabilityMissing, probe.ReasonCode)
	require.Contains(t, probe.Message, "ACP")
	require.Error(t, probe.Err)
}
