package env

import (
	"encoding/json"
	"os"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
)

type EnvSource map[string]string

type LoadedEnv struct {
	AgentBin          string
	AgentNode         string
	AgentScript       string
	CommandShell      string
	Host              string
	Port              int
	RequiredKey       string
	DefaultModel      string
	Force             bool
	ApproveMcps       bool
	StrictModel       bool
	Workspace         string
	TimeoutMs         int
	TLSCertPath       string
	TLSKeyPath        string
	SessionsLogPath   string
	ChatOnlyWorkspace bool
	Verbose           bool
	MaxMode           bool
	ConfigDirs        []string
	MultiPort         bool
	WinCmdlineMax     int
}

type AgentCommand struct {
	Command                  string
	Args                     []string
	Env                      map[string]string
	WindowsVerbatimArguments bool
	AgentScriptPath          string
	ConfigDir                string
}

func getEnvVal(e EnvSource, names []string) string {
	for _, name := range names {
		if v, ok := e[name]; ok && strings.TrimSpace(v) != "" {
			return strings.TrimSpace(v)
		}
	}
	return ""
}

func envBool(e EnvSource, names []string, def bool) bool {
	raw := getEnvVal(e, names)
	if raw == "" {
		return def
	}
	switch strings.ToLower(raw) {
	case "1", "true", "yes", "on":
		return true
	case "0", "false", "no", "off":
		return false
	}
	return def
}

func envInt(e EnvSource, names []string, def int) int {
	raw := getEnvVal(e, names)
	if raw == "" {
		return def
	}
	v, err := strconv.Atoi(raw)
	if err != nil {
		return def
	}
	return v
}

func normalizeModelId(raw string) string {
	raw = strings.TrimSpace(raw)
	if raw == "" {
		return "auto"
	}
	parts := strings.Split(raw, "/")
	last := parts[len(parts)-1]
	if last == "" {
		return "auto"
	}
	return last
}

func resolveAbs(raw, cwd string) string {
	if raw == "" {
		return ""
	}
	if filepath.IsAbs(raw) {
		return raw
	}
	return filepath.Join(cwd, raw)
}

func isAuthenticatedAccountDir(dir string) bool {
	data, err := os.ReadFile(filepath.Join(dir, "cli-config.json"))
	if err != nil {
		return false
	}
	var cfg struct {
		AuthInfo *struct {
			Email string `json:"email"`
		} `json:"authInfo"`
	}
	if err := json.Unmarshal(data, &cfg); err != nil {
		return false
	}
	return cfg.AuthInfo != nil && cfg.AuthInfo.Email != ""
}

func discoverAccountDirs(homeDir string) []string {
	if homeDir == "" {
		return nil
	}
	accountsDir := filepath.Join(homeDir, ".cursor-api-proxy", "accounts")
	entries, err := os.ReadDir(accountsDir)
	if err != nil {
		return nil
	}
	var dirs []string
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}
		dir := filepath.Join(accountsDir, e.Name())
		if isAuthenticatedAccountDir(dir) {
			dirs = append(dirs, dir)
		}
	}
	return dirs
}

func parseDotEnv(path string) EnvSource {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil
	}
	m := make(EnvSource)
	for _, line := range strings.Split(string(data), "\n") {
		line = strings.TrimSpace(line)
		if line == "" || strings.HasPrefix(line, "#") {
			continue
		}
		parts := strings.SplitN(line, "=", 2)
		if len(parts) == 2 {
			m[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
		}
	}
	return m
}

func OsEnvToMap(cwdHint ...string) EnvSource {
	m := make(EnvSource)
	for _, kv := range os.Environ() {
		parts := strings.SplitN(kv, "=", 2)
		if len(parts) == 2 {
			m[parts[0]] = parts[1]
		}
	}

	cwd := ""
	if len(cwdHint) > 0 && cwdHint[0] != "" {
		cwd = cwdHint[0]
	} else {
		cwd, _ = os.Getwd()
	}

	if dotenv := parseDotEnv(filepath.Join(cwd, ".env")); dotenv != nil {
		for k, v := range dotenv {
			if _, exists := m[k]; !exists {
				m[k] = v
			}
		}
	}

	return m
}

func LoadEnvConfig(e EnvSource, cwd string) LoadedEnv {
	if e == nil {
		e = OsEnvToMap()
	}
	if cwd == "" {
		var err error
		cwd, err = os.Getwd()
		if err != nil {
			cwd = "."
		}
	}

	host := getEnvVal(e, []string{"CURSOR_BRIDGE_HOST"})
	if host == "" {
		host = "127.0.0.1"
	}
	port := envInt(e, []string{"CURSOR_BRIDGE_PORT"}, 8765)
	if port <= 0 {
		port = 8765
	}

	home := getEnvVal(e, []string{"HOME", "USERPROFILE"})

	sessionsLogPath := func() string {
		if p := resolveAbs(getEnvVal(e, []string{"CURSOR_BRIDGE_SESSIONS_LOG"}), cwd); p != "" {
			return p
		}
		if home != "" {
			return filepath.Join(home, ".cursor-api-proxy", "sessions.log")
		}
		return filepath.Join(cwd, "sessions.log")
	}()

	var configDirs []string
	if raw := getEnvVal(e, []string{"CURSOR_CONFIG_DIRS", "CURSOR_ACCOUNT_DIRS"}); raw != "" {
		for _, d := range strings.Split(raw, ",") {
			d = strings.TrimSpace(d)
			if d != "" {
				if p := resolveAbs(d, cwd); p != "" {
					configDirs = append(configDirs, p)
				}
			}
		}
	}
	if len(configDirs) == 0 {
		configDirs = discoverAccountDirs(home)
	}

	winMax := envInt(e, []string{"CURSOR_BRIDGE_WIN_CMDLINE_MAX"}, 30000)
	if winMax < 4096 {
		winMax = 4096
	}
	if winMax > 32700 {
		winMax = 32700
	}

	agentBin := getEnvVal(e, []string{"CURSOR_AGENT_BIN", "CURSOR_CLI_BIN", "CURSOR_CLI_PATH"})
	if agentBin == "" {
		agentBin = "agent"
	}
	commandShell := getEnvVal(e, []string{"COMSPEC"})
	if commandShell == "" {
		commandShell = "cmd.exe"
	}
	workspace := resolveAbs(getEnvVal(e, []string{"CURSOR_BRIDGE_WORKSPACE"}), cwd)
	if workspace == "" {
		workspace = cwd
	}

	return LoadedEnv{
		AgentBin:          agentBin,
		AgentNode:         getEnvVal(e, []string{"CURSOR_AGENT_NODE"}),
		AgentScript:       getEnvVal(e, []string{"CURSOR_AGENT_SCRIPT"}),
		CommandShell:      commandShell,
		Host:              host,
		Port:              port,
		RequiredKey:       getEnvVal(e, []string{"CURSOR_BRIDGE_API_KEY"}),
		DefaultModel:      normalizeModelId(getEnvVal(e, []string{"CURSOR_BRIDGE_DEFAULT_MODEL"})),
		Force:             envBool(e, []string{"CURSOR_BRIDGE_FORCE"}, false),
		ApproveMcps:       envBool(e, []string{"CURSOR_BRIDGE_APPROVE_MCPS"}, false),
		StrictModel:       envBool(e, []string{"CURSOR_BRIDGE_STRICT_MODEL"}, true),
		Workspace:         workspace,
		TimeoutMs:         envInt(e, []string{"CURSOR_BRIDGE_TIMEOUT_MS"}, 300000),
		TLSCertPath:       resolveAbs(getEnvVal(e, []string{"CURSOR_BRIDGE_TLS_CERT"}), cwd),
		TLSKeyPath:        resolveAbs(getEnvVal(e, []string{"CURSOR_BRIDGE_TLS_KEY"}), cwd),
		SessionsLogPath:   sessionsLogPath,
		ChatOnlyWorkspace: envBool(e, []string{"CURSOR_BRIDGE_CHAT_ONLY_WORKSPACE"}, false),
		Verbose:           envBool(e, []string{"CURSOR_BRIDGE_VERBOSE"}, false),
		MaxMode:           envBool(e, []string{"CURSOR_BRIDGE_MAX_MODE"}, false),
		ConfigDirs:        configDirs,
		MultiPort:         envBool(e, []string{"CURSOR_BRIDGE_MULTI_PORT"}, false),
		WinCmdlineMax:     winMax,
	}
}

func ResolveAgentCommand(cmd string, args []string, e EnvSource, cwd string) AgentCommand {
	if e == nil {
		e = OsEnvToMap()
	}
	loaded := LoadEnvConfig(e, cwd)

	cloneEnv := func() map[string]string {
		m := make(map[string]string, len(e))
		for k, v := range e {
			m[k] = v
		}
		return m
	}

	if runtime.GOOS == "windows" {
		if loaded.AgentNode != "" && loaded.AgentScript != "" {
			agentScriptPath := loaded.AgentScript
			if !filepath.IsAbs(agentScriptPath) {
				agentScriptPath = filepath.Join(cwd, agentScriptPath)
			}
			agentDir := filepath.Dir(agentScriptPath)
			configDir := filepath.Join(agentDir, "..", "data", "config")
			env2 := cloneEnv()
			env2["CURSOR_INVOKED_AS"] = "agent.cmd"
			ac := AgentCommand{
				Command:         loaded.AgentNode,
				Args:            append([]string{loaded.AgentScript}, args...),
				Env:             env2,
				AgentScriptPath: agentScriptPath,
			}
			if _, err := os.Stat(filepath.Join(configDir, "cli-config.json")); err == nil {
				ac.ConfigDir = configDir
			}
			return ac
		}

		if strings.HasSuffix(strings.ToLower(cmd), ".cmd") {
			cmdResolved := cmd
			if !filepath.IsAbs(cmd) {
				cmdResolved = filepath.Join(cwd, cmd)
			}
			dir := filepath.Dir(cmdResolved)
			nodeBin := filepath.Join(dir, "node.exe")
			script := filepath.Join(dir, "index.js")
			if _, err1 := os.Stat(nodeBin); err1 == nil {
				if _, err2 := os.Stat(script); err2 == nil {
					configDir := filepath.Join(dir, "..", "data", "config")
					env2 := cloneEnv()
					env2["CURSOR_INVOKED_AS"] = "agent.cmd"
					ac := AgentCommand{
						Command:         nodeBin,
						Args:            append([]string{script}, args...),
						Env:             env2,
						AgentScriptPath: script,
					}
					if _, err := os.Stat(filepath.Join(configDir, "cli-config.json")); err == nil {
						ac.ConfigDir = configDir
					}
					return ac
				}
			}

			quotedArgs := make([]string, len(args))
			for i, a := range args {
				if strings.Contains(a, " ") {
					quotedArgs[i] = `"` + a + `"`
				} else {
					quotedArgs[i] = a
				}
			}
			cmdLine := `""` + cmd + `" ` + strings.Join(quotedArgs, " ") + `"`
			return AgentCommand{
				Command:                  loaded.CommandShell,
				Args:                     []string{"/d", "/s", "/c", cmdLine},
				Env:                      cloneEnv(),
				WindowsVerbatimArguments: true,
			}
		}
	}

	return AgentCommand{Command: cmd, Args: args, Env: cloneEnv()}
}
