package toolchain

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

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

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

	installDir := t.TempDir()
	binDir := filepath.Join(installDir, "bin")
	require.NoError(t, os.MkdirAll(binDir, 0o755))
	bunPath := filepath.Join(binDir, "bun")
	require.NoError(t, os.WriteFile(bunPath, []byte("stub"), 0o755))

	resolver := Resolver{
		LookPath: func(string) (string, error) { return "", os.ErrNotExist },
		Stat:     os.Stat,
		Getenv: func(key string) string {
			if key == "BUN_INSTALL" {
				return installDir
			}
			return ""
		},
		UserHomeDir: func() (string, error) { return "", errors.New("no home") },
		GOOS:        "linux",
	}

	path, err := resolver.ResolveBun()
	require.NoError(t, err)
	require.Equal(t, bunPath, path)
}

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

	homeDir := t.TempDir()
	resolver := Resolver{
		Getenv: func(string) string { return "" },
		UserHomeDir: func() (string, error) {
			return homeDir, nil
		},
	}

	env := resolver.WithBunInstallEnv([]string{"PATH=/usr/bin"})
	require.Contains(t, env, "PATH=/usr/bin")
	require.Contains(t, env, "BUN_INSTALL="+filepath.Join(homeDir, ".bun"))
}

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

	var pathValue string
	resolver := Resolver{
		Getenv: func(key string) string {
			if key == "PATH" {
				return pathValue
			}
			return ""
		},
		Setenv: func(key, value string) error {
			require.Equal(t, "PATH", key)
			pathValue = value
			return nil
		},
		GOOS:              "linux",
		PathListSeparator: ":",
	}

	pathValue = "/usr/bin:/bin"
	require.NoError(t, resolver.PrependBinaryDirToPATH("/opt/bun/bin/bun"))
	require.Equal(t, "/opt/bun/bin:/usr/bin:/bin", pathValue)

	require.NoError(t, resolver.PrependBinaryDirToPATH("/opt/bun/bin/bun"))
	require.Equal(t, "/opt/bun/bin:/usr/bin:/bin", pathValue)
}

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

	homeDir := t.TempDir()
	managedGo := filepath.Join(homeDir, ".local", "share", "agentteam", "toolchains", "go", "bin", "go")
	require.NoError(t, os.MkdirAll(filepath.Dir(managedGo), 0o755))
	require.NoError(t, os.WriteFile(managedGo, []byte("managed"), 0o755))

	resolver := Resolver{
		LookPath: func(string) (string, error) {
			return "/usr/bin/go", nil
		},
		Stat: os.Stat,
		UserHomeDir: func() (string, error) {
			return homeDir, nil
		},
		GOOS: "linux",
	}

	path, err := resolver.ResolveGo()
	require.NoError(t, err)
	require.Equal(t, managedGo, path)
}

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

	resolver := Resolver{
		GOOS: "windows",
		Getenv: func(key string) string {
			switch key {
			case "ProgramFiles":
				return `C:\Program Files`
			case "ProgramFiles(x86)":
				return `C:\Program Files (x86)`
			case "LocalAppData":
				return `C:\Users\demo\AppData\Local`
			case "USERPROFILE":
				return `C:\Users\demo`
			}
			return ""
		},
		UserHomeDir: func() (string, error) {
			return `C:\Users\demo`, nil
		},
	}

	require.Equal(t,
		[]string{
			filepath.Join(`C:\Users\demo`, ".bun", "bin", "bun.exe"),
			filepath.Join(`C:\Users\demo`, "scoop", "apps", "bun", "current", "bun.exe"),
		},
		resolver.BunCandidatePaths(),
	)
	require.Equal(t,
		[]string{
			filepath.Join(`C:\Program Files`, "nodejs", "npm.cmd"),
			filepath.Join(`C:\Program Files (x86)`, "nodejs", "npm.cmd"),
			filepath.Join(`C:\Users\demo\AppData\Local`, "Programs", "nodejs", "npm.cmd"),
			filepath.Join(`C:\Users\demo\AppData\Local`, "Microsoft", "WindowsApps", "npm.cmd"),
		},
		resolver.NPMCandidatePaths(),
	)
	require.Equal(t,
		[]string{
			filepath.Join(`C:\Program Files`, "Go", "bin", "go.exe"),
			filepath.Join(`C:\Users\demo\AppData\Local`, "Programs", "Go", "bin", "go.exe"),
			filepath.Join(`C:\Users\demo`, "scoop", "apps", "go", "current", "bin", "go.exe"),
		},
		resolver.GoCandidatePaths(),
	)
}

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

	homeDir := t.TempDir()

	darwinResolver := Resolver{
		GOOS: "darwin",
		UserHomeDir: func() (string, error) {
			return homeDir, nil
		},
	}
	darwinPath, ok := darwinResolver.GoInstallDir()
	require.True(t, ok)
	require.Equal(t, filepath.Join(homeDir, "Library", "Application Support", "agentteam", "toolchains"), darwinPath)

	linuxResolver := Resolver{
		GOOS: "linux",
		UserHomeDir: func() (string, error) {
			return homeDir, nil
		},
	}
	linuxPath, ok := linuxResolver.GoInstallDir()
	require.True(t, ok)
	require.Equal(t, filepath.Join(homeDir, ".local", "share", "agentteam", "toolchains"), linuxPath)
}
