package runtimeconfig

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"sync"

	"github.com/tidbcloud/AgentTeam/desktop/internal/execruntimeenv"
)

var shellPathForRuntimeResolution struct {
	sync.RWMutex
	value string
}

var shellEnvForRuntimeResolution struct {
	sync.RWMutex
	values map[string]string
}

// SetShellPathForRuntimeResolution records the PATH computed by the user's
// shell for runtime binary detection and runtime child processes. It does not
// mutate the daemon process environment.
func SetShellPathForRuntimeResolution(value string) {
	shellPathForRuntimeResolution.Lock()
	defer shellPathForRuntimeResolution.Unlock()
	shellPathForRuntimeResolution.value = strings.TrimSpace(value)
}

// ShellPathForRuntimeResolution returns the shell-computed PATH used for
// runtime resolution.
func ShellPathForRuntimeResolution() string {
	shellPathForRuntimeResolution.RLock()
	defer shellPathForRuntimeResolution.RUnlock()
	return shellPathForRuntimeResolution.value
}

// SetShellEnvForRuntimeResolution records exported variables computed by the
// user's shell for runtime child processes. It does not mutate the daemon
// process environment.
func SetShellEnvForRuntimeResolution(values map[string]string) {
	shellEnvForRuntimeResolution.Lock()
	defer shellEnvForRuntimeResolution.Unlock()

	shellEnvForRuntimeResolution.values = cloneRuntimeEnvMap(values)
}

// AddShellEnvForRuntimeResolution records exported variables without replacing
// any variables already captured from a higher-priority source.
func AddShellEnvForRuntimeResolution(values map[string]string) int {
	if len(values) == 0 {
		return 0
	}

	shellEnvForRuntimeResolution.Lock()
	defer shellEnvForRuntimeResolution.Unlock()

	if shellEnvForRuntimeResolution.values == nil {
		shellEnvForRuntimeResolution.values = make(map[string]string, len(values))
	}
	added := 0
	for key, value := range values {
		key = strings.TrimSpace(key)
		if key == "" || isDaemonOwnedRuntimeKey(key) {
			continue
		}
		if _, exists := shellEnvForRuntimeResolution.values[key]; exists {
			continue
		}
		shellEnvForRuntimeResolution.values[key] = value
		added++
	}
	return added
}

// ShellEnvForRuntimeResolution returns the shell-computed environment used for
// runtime child processes.
func ShellEnvForRuntimeResolution() map[string]string {
	shellEnvForRuntimeResolution.RLock()
	defer shellEnvForRuntimeResolution.RUnlock()

	return cloneRuntimeEnvMap(shellEnvForRuntimeResolution.values)
}

// RuntimeEnvValue returns an environment variable as a runtime child process
// would see it before per-runtime overrides are applied.
func RuntimeEnvValue(key string) (string, bool) {
	key = strings.TrimSpace(key)
	if key == "" || isDaemonOwnedRuntimeKey(key) {
		return "", false
	}
	if value, ok := os.LookupEnv(key); ok {
		return value, true
	}

	shellEnvForRuntimeResolution.RLock()
	defer shellEnvForRuntimeResolution.RUnlock()
	value, ok := shellEnvForRuntimeResolution.values[key]
	return value, ok
}

func runtimeEnvOverridesWithShellPath(env map[string]string) map[string]string {
	shellPath := ShellPathForRuntimeResolution()
	if strings.TrimSpace(shellPath) == "" {
		return env
	}

	if env == nil {
		env = make(map[string]string, 1)
	}

	pathKey := runtimePathEnvKey(env)
	envPath := envPathValue(env, pathKey)
	processPath := os.Getenv("PATH")
	env[pathKey] = mergeRuntimePathLists(string(os.PathListSeparator), runtime.GOOS, envPath, shellPath, processPath)
	return env
}

func runtimeProbeEnv() []string {
	overrides := runtimeEnvOverridesWithShellPath(nil)
	shellEnv := ShellEnvForRuntimeResolution()
	processEnv := os.Environ()
	filteredProcessEnv := execruntimeenv.FilterInherited(processEnv)
	removedInheritedCapability := len(filteredProcessEnv) != len(processEnv)
	if len(overrides) == 0 && !runtimeEnvHasIncludedValue(shellEnv, allowRuntimeProbeShellEnv) && !removedInheritedCapability {
		return nil
	}

	env := appendRuntimeEnvMissingIf(
		filteredProcessEnv,
		shellEnv,
		func(key, value string) bool {
			return !isDaemonOwnedRuntimeKey(key) && allowRuntimeProbeShellEnv(key, value)
		},
	)
	for key, value := range overrides {
		env = append(env, fmt.Sprintf("%s=%s", key, value))
	}
	return env
}

func allowRuntimeProbeShellEnv(key, _ string) bool {
	key = strings.ToUpper(strings.TrimSpace(key))
	if key == "" {
		return false
	}
	for _, marker := range []string{"KEY", "TOKEN", "SECRET", "PASS", "CREDENTIAL", "AUTH", "COOKIE", "SESSION"} {
		if strings.Contains(key, marker) {
			return false
		}
	}
	return true
}

// AppendShellEnvForRuntime appends shell-captured environment variables that do
// not already exist in env. It is used by runtime launchers after filtering the
// daemon environment and before per-runtime overrides are appended.
func AppendShellEnvForRuntime(env []string) []string {
	return AppendShellEnvForRuntimeIf(env, nil)
}

// RuntimeProcessEnv returns the base environment for runtime-adjacent child
// processes, including daemon env, shell-captured env, and runtime overrides.
func RuntimeProcessEnv() []string {
	env := AppendShellEnvForRuntime(os.Environ())
	for key, value := range RuntimeEnvOverrides(nil, nil) {
		env = append(env, fmt.Sprintf("%s=%s", key, value))
	}
	return env
}

// AppendShellEnvForRuntimeIf appends shell-captured environment variables that
// pass include and do not already exist in env.
func AppendShellEnvForRuntimeIf(env []string, include func(key, value string) bool) []string {
	values := ShellEnvForRuntimeResolution()
	return appendRuntimeEnvMissingIf(
		execruntimeenv.FilterInherited(env),
		values,
		func(key, value string) bool {
			return !isDaemonOwnedRuntimeKey(key) && (include == nil || include(key, value))
		},
	)
}

func runtimePathEnvKey(env map[string]string) string {
	for key := range env {
		if key == "PATH" {
			return key
		}
	}
	if runtime.GOOS == "windows" {
		for key := range env {
			if strings.EqualFold(key, "PATH") {
				return key
			}
		}
		for _, entry := range os.Environ() {
			key, _, ok := strings.Cut(entry, "=")
			if ok && strings.EqualFold(key, "PATH") {
				return key
			}
		}
	}
	return "PATH"
}

func envPathValue(env map[string]string, key string) string {
	if value, ok := env[key]; ok {
		return value
	}
	if runtime.GOOS == "windows" {
		for candidate, value := range env {
			if strings.EqualFold(candidate, key) {
				return value
			}
		}
	}
	return ""
}

func mergeRuntimePathLists(separator, goos string, values ...string) string {
	if separator == "" {
		separator = string(os.PathListSeparator)
	}
	seen := make(map[string]struct{})
	entries := make([]string, 0)
	for _, value := range values {
		for _, entry := range splitRuntimePathList(value, separator) {
			entry = strings.TrimSpace(entry)
			if entry == "" {
				continue
			}
			key := runtimePathDedupeKey(entry, goos)
			if _, ok := seen[key]; ok {
				continue
			}
			seen[key] = struct{}{}
			entries = append(entries, entry)
		}
	}
	return strings.Join(entries, separator)
}

func runtimePathDedupeKey(value, goos string) string {
	key := filepath.Clean(strings.TrimSpace(value))
	if goos == "windows" {
		key = strings.ToLower(key)
	}
	return key
}

func appendRuntimeEnvMissing(env []string, values map[string]string) []string {
	return appendRuntimeEnvMissingIf(env, values, nil)
}

func appendRuntimeEnvMissingIf(env []string, values map[string]string, include func(key, value string) bool) []string {
	if len(values) == 0 {
		return env
	}
	seen := make(map[string]struct{}, len(env))
	for _, entry := range env {
		key, _, ok := strings.Cut(entry, "=")
		if !ok {
			continue
		}
		seen[runtimeEnvDedupeKey(key)] = struct{}{}
	}
	for key, value := range values {
		key = strings.TrimSpace(key)
		if key == "" || isDaemonOwnedRuntimeKey(key) {
			continue
		}
		if include != nil && !include(key, value) {
			continue
		}
		dedupeKey := runtimeEnvDedupeKey(key)
		if _, exists := seen[dedupeKey]; exists {
			continue
		}
		env = append(env, fmt.Sprintf("%s=%s", key, value))
		seen[dedupeKey] = struct{}{}
	}
	return env
}

func runtimeEnvHasIncludedValue(values map[string]string, include func(key, value string) bool) bool {
	for key, value := range values {
		key = strings.TrimSpace(key)
		if key == "" {
			continue
		}
		if include == nil || include(key, value) {
			return true
		}
	}
	return false
}

func runtimeEnvDedupeKey(key string) string {
	key = strings.TrimSpace(key)
	if runtime.GOOS == "windows" {
		return strings.ToLower(key)
	}
	return key
}

func cloneRuntimeEnvMap(values map[string]string) map[string]string {
	if len(values) == 0 {
		return nil
	}
	clone := make(map[string]string, len(values))
	for key, value := range values {
		key = strings.TrimSpace(key)
		if key == "" || isDaemonOwnedRuntimeKey(key) {
			continue
		}
		clone[key] = value
	}
	return clone
}

func isDaemonOwnedRuntimeKey(key string) bool {
	return execruntimeenv.IsDaemonOwnedKey(key)
}
