package runtimeconfig

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

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

func unsetEnv(t *testing.T, key string) {
	t.Helper()

	value, ok := os.LookupEnv(key)
	require.NoError(t, os.Unsetenv(key))
	t.Cleanup(func() {
		if !ok {
			require.NoError(t, os.Unsetenv(key))
			return
		}
		require.NoError(t, os.Setenv(key, value))
	})
}

func TestRuntimeResolverResolveBinaryUsesShellPath(t *testing.T) {
	SetShellPathForRuntimeResolution("")
	SetShellEnvForRuntimeResolution(nil)
	t.Cleanup(func() {
		SetShellPathForRuntimeResolution("")
		SetShellEnvForRuntimeResolution(nil)
	})

	binDir := t.TempDir()
	binaryPath := filepath.Join(binDir, "claude")
	require.NoError(t, os.WriteFile(binaryPath, []byte("stub"), 0o755))
	SetShellPathForRuntimeResolution(binDir)

	resolver := RuntimeResolver{
		LookPath:          func(string) (string, error) { return "", exec.ErrNotFound },
		Stat:              os.Stat,
		GOOS:              "linux",
		PathListSeparator: string(os.PathListSeparator),
	}

	path, err := resolver.ResolveBinary("claude", nil)
	require.NoError(t, err)
	require.Equal(t, binaryPath, path)
}

func TestRuntimeEnvOverridesAddsShellPathWithoutMutatingProcessPath(t *testing.T) {
	SetShellPathForRuntimeResolution("")
	SetShellEnvForRuntimeResolution(nil)
	t.Cleanup(func() {
		SetShellPathForRuntimeResolution("")
		SetShellEnvForRuntimeResolution(nil)
	})

	sep := string(os.PathListSeparator)
	processPath := filepath.Join(t.TempDir(), "process-bin")
	shellPath := filepath.Join(t.TempDir(), "shell-bin")
	overridePath := filepath.Join(t.TempDir(), "runtime-bin")
	t.Setenv("PATH", processPath)
	SetShellPathForRuntimeResolution(shellPath + sep + processPath)

	env := RuntimeEnvOverrides(map[string]string{
		"PATH": overridePath,
	}, nil)

	require.Equal(t, overridePath+sep+shellPath+sep+processPath, env["PATH"])
	require.Equal(t, processPath, os.Getenv("PATH"))
}

func TestRuntimeProbeEnvReturnsNilWithoutShellPathOrEnv(t *testing.T) {
	unsetEnv(t, "AGENTTEAM_EXEC_BASE_URL")
	unsetEnv(t, "AGENTTEAM_EXEC_TOKEN")
	unsetEnv(t, "AGENTTEAM_EXEC_TOKEN_FILE")
	SetShellPathForRuntimeResolution("")
	SetShellEnvForRuntimeResolution(nil)

	require.Nil(t, runtimeProbeEnv())
}

func TestRuntimeProbeEnvAddsPathOverride(t *testing.T) {
	SetShellPathForRuntimeResolution("")
	SetShellEnvForRuntimeResolution(nil)
	t.Cleanup(func() {
		SetShellPathForRuntimeResolution("")
		SetShellEnvForRuntimeResolution(nil)
	})

	sep := string(os.PathListSeparator)
	processPath := filepath.Join(t.TempDir(), "process-bin")
	shellPath := filepath.Join(t.TempDir(), "shell-bin")
	t.Setenv("PATH", processPath)
	SetShellPathForRuntimeResolution(shellPath)

	env := runtimeProbeEnv()

	require.Contains(t, env, "PATH="+shellPath+sep+processPath)
	require.Equal(t, processPath, os.Getenv("PATH"))
}

func TestRuntimeProbeEnvDoesNotIncludeShellCredentialEnv(t *testing.T) {
	unsetEnv(t, "AGENTTEAM_EXEC_BASE_URL")
	unsetEnv(t, "AGENTTEAM_EXEC_TOKEN")
	unsetEnv(t, "AGENTTEAM_EXEC_TOKEN_FILE")
	SetShellPathForRuntimeResolution("")
	SetShellEnvForRuntimeResolution(map[string]string{
		"OPENAI_API_KEY": "sk-shell",
	})
	t.Cleanup(func() {
		SetShellEnvForRuntimeResolution(nil)
	})

	require.Nil(t, runtimeProbeEnv())
}

func TestRuntimeProbeEnvMaterializesFilteredEnvironmentForAmbientExecCapability(t *testing.T) {
	SetShellPathForRuntimeResolution("")
	SetShellEnvForRuntimeResolution(nil)
	t.Cleanup(func() {
		SetShellPathForRuntimeResolution("")
		SetShellEnvForRuntimeResolution(nil)
	})
	t.Setenv("AGENTTEAM_EXEC_BASE_URL", "http://127.0.0.1:2")
	t.Setenv("AGENTTEAM_EXEC_TOKEN", "ambient-bearer")
	t.Setenv("AGENTTEAM_EXEC_TOKEN_FILE", "/private/ambient-token")
	t.Setenv("AGENTTEAM_EXTENSION_BASE_URL", "http://127.0.0.1:3")
	t.Setenv("AGENTTEAM_EXTENSION_TOKEN_FILE", "/private/ambient-extension-token")
	t.Setenv("AGENTTEAM_EXTENSION_TOKEN", "ambient-extension-bearer")

	env := runtimeProbeEnv()
	require.NotNil(t, env, "nil would make os/exec inherit the unfiltered daemon environment")
	joined := "\n" + strings.ToLower(strings.Join(env, "\n")) + "\n"
	require.NotContains(t, joined, "\nagentteam_exec_base_url=")
	require.NotContains(t, joined, "\nagentteam_exec_token=")
	require.NotContains(t, joined, "\nagentteam_exec_token_file=")
	require.NotContains(t, joined, "\nagentteam_extension_base_url=")
	require.NotContains(t, joined, "\nagentteam_extension_token_file=")
	require.NotContains(t, joined, "\nagentteam_extension_token=")
}

func TestAppendShellEnvForRuntimeAddsMissingShellValuesOnly(t *testing.T) {
	SetShellPathForRuntimeResolution("")
	SetShellEnvForRuntimeResolution(map[string]string{
		"CUSTOM_LLM_REGION": "us-east-1",
		"OPENAI_API_KEY":    "sk-shell",
	})
	t.Cleanup(func() {
		SetShellEnvForRuntimeResolution(nil)
	})

	env := AppendShellEnvForRuntime([]string{
		"OPENAI_API_KEY=sk-process",
	})

	require.Contains(t, env, "OPENAI_API_KEY=sk-process")
	require.NotContains(t, env, "OPENAI_API_KEY=sk-shell")
	require.Contains(t, env, "CUSTOM_LLM_REGION=us-east-1")
}

func TestRuntimeProcessEnvAddsShellEnvAndPath(t *testing.T) {
	const shellOnlyKey = "AGENTTEAM_RUNTIMECONFIG_TEST_SHELL_ENV"

	SetShellPathForRuntimeResolution("")
	SetShellEnvForRuntimeResolution(map[string]string{
		shellOnlyKey: "from-shell",
	})
	t.Cleanup(func() {
		SetShellPathForRuntimeResolution("")
		SetShellEnvForRuntimeResolution(nil)
	})

	sep := string(os.PathListSeparator)
	processPath := filepath.Join(t.TempDir(), "process-bin")
	shellPath := filepath.Join(t.TempDir(), "shell-bin")
	unsetEnv(t, shellOnlyKey)
	t.Setenv("PATH", processPath)
	SetShellPathForRuntimeResolution(shellPath)

	env := RuntimeProcessEnv()

	require.Contains(t, env, shellOnlyKey+"=from-shell")
	require.Contains(t, env, "PATH="+shellPath+sep+processPath)
	require.Equal(t, processPath, os.Getenv("PATH"))
}

func TestRuntimeProbeAndAdjacentProcessEnvStripInheritedExecCapability(t *testing.T) {
	SetShellPathForRuntimeResolution("/shell/bin")
	SetShellEnvForRuntimeResolution(map[string]string{
		"agentteam_exec_base_url":   "http://127.0.0.1:1",
		"AgentTeam_Exec_Token":      "shell-bearer",
		"AGENTTEAM_EXEC_TOKEN_FILE": "/private/shell-token",
		"SAFE_SHELL_VALUE":          "kept",
	})
	t.Cleanup(func() {
		SetShellPathForRuntimeResolution("")
		SetShellEnvForRuntimeResolution(nil)
	})
	t.Setenv("AGENTTEAM_EXEC_BASE_URL", "http://127.0.0.1:2")
	t.Setenv("AGENTTEAM_EXEC_TOKEN", "ambient-bearer")
	t.Setenv("AGENTTEAM_EXEC_TOKEN_FILE", "/private/ambient-token")

	for name, env := range map[string][]string{
		"probe":    runtimeProbeEnv(),
		"adjacent": RuntimeProcessEnv(),
	} {
		t.Run(name, func(t *testing.T) {
			joined := "\n" + strings.ToLower(strings.Join(env, "\n")) + "\n"
			require.NotContains(t, joined, "\nagentteam_exec_base_url=")
			require.NotContains(t, joined, "\nagentteam_exec_token=")
			require.NotContains(t, joined, "\nagentteam_exec_token_file=")
		})
	}
	require.Contains(t, RuntimeProcessEnv(), "SAFE_SHELL_VALUE=kept")
}

func TestRuntimeEnvValuePrefersDaemonEnvThenShellSnapshot(t *testing.T) {
	SetShellEnvForRuntimeResolution(map[string]string{
		"CUSTOM_LLM_REGION": "us-east-1",
		"OPENAI_API_KEY":    "sk-shell",
	})
	t.Cleanup(func() {
		SetShellEnvForRuntimeResolution(nil)
	})
	t.Setenv("OPENAI_API_KEY", "sk-process")

	value, ok := RuntimeEnvValue("OPENAI_API_KEY")
	require.True(t, ok)
	require.Equal(t, "sk-process", value)

	value, ok = RuntimeEnvValue("CUSTOM_LLM_REGION")
	require.True(t, ok)
	require.Equal(t, "us-east-1", value)
}

func TestRuntimeEnvValueNeverRematerializesExecCapability(t *testing.T) {
	SetShellEnvForRuntimeResolution(map[string]string{
		"agentteam_exec_token":      "shell-bearer",
		"AGENTTEAM_EXEC_TOKEN_FILE": "/private/shell-token",
		"SAFE_SHELL_VALUE":          "kept",
	})
	t.Cleanup(func() { SetShellEnvForRuntimeResolution(nil) })
	t.Setenv("AGENTTEAM_EXEC_BASE_URL", "http://127.0.0.1:1")
	t.Setenv("AGENTTEAM_EXEC_TOKEN", "ambient-bearer")

	for _, key := range []string{
		"AGENTTEAM_EXEC_BASE_URL", "agentteam_exec_token", "AgentTeam_Exec_Token_File",
	} {
		value, ok := RuntimeEnvValue(key)
		require.False(t, ok, key)
		require.Empty(t, value, key)
	}
	value, ok := RuntimeEnvValue("SAFE_SHELL_VALUE")
	require.True(t, ok)
	require.Equal(t, "kept", value)
	require.NotContains(t, ShellEnvForRuntimeResolution(), "agentteam_exec_token")
	require.NotContains(t, ShellEnvForRuntimeResolution(), "AGENTTEAM_EXEC_TOKEN_FILE")
}

func TestMergeRuntimePathListsKeepsPrimaryOrder(t *testing.T) {
	merged := mergeRuntimePathLists(":", "linux", "/a:/b", "/b:/c:/a:/d")
	require.Equal(t, "/a:/b:/c:/d", merged)
}
