package runtimeconfig

import (
	"fmt"
	"strings"
)

// RuntimeType represents the type of an AI coding agent runtime.
type RuntimeType string

// Supported runtime types.
const (
	TypeOpenCode   RuntimeType = "opencode"
	TypeClaudeCode RuntimeType = "claude"
	TypeCodex      RuntimeType = "codex"
	TypeKimi       RuntimeType = "kimi"
	TypePi         RuntimeType = "pi"
	// TypeBuiltin is the AgentTeam-managed OpenCode runtime shipped with AgentTeam
	// Desktop. It uses the OpenCode driver, but resolves the binary from the
	// Desktop artifact instead of the user's external opencode install.
	TypeBuiltin RuntimeType = "builtin"
)

// AllRuntimeTypes is the list of active runtime types, ordered by priority:
// builtin > opencode > claude > codex > kimi > pi.
var AllRuntimeTypes = []RuntimeType{TypeBuiltin, TypeOpenCode, TypeClaudeCode, TypeCodex, TypeKimi, TypePi}

// RuntimePriority returns the priority rank of a runtime type.
// Lower numbers indicate higher priority. Unrecognized types return len(AllRuntimeTypes).
func RuntimePriority(t RuntimeType) int {
	t = NormalizeType(string(t))
	for i, rt := range AllRuntimeTypes {
		if rt == t {
			return i
		}
	}
	return len(AllRuntimeTypes)
}

// Runtime holds the configuration for a single AI coding agent runtime.
type Runtime struct {
	Name   string              `yaml:"name"    toml:"name"`
	Type   RuntimeType         `yaml:"type"    toml:"type"`
	Binary string              `yaml:"binary"  toml:"binary"`
	Args   []string            `yaml:"args"    toml:"args"`
	Port   int                 `yaml:"port"    toml:"port"`
	Health RuntimeHealthConfig `yaml:"health"  toml:"health"`
	Env    map[string]string   `yaml:"env"     toml:"env"`
}

// RuntimeHealthConfig defines health check settings for a runtime.
type RuntimeHealthConfig struct {
	Path     string `yaml:"path"     toml:"path"`
	Interval string `yaml:"interval" toml:"interval"`
	Timeout  string `yaml:"timeout"  toml:"timeout"`
}

// DetectResult describes a detected runtime on the system.
type DetectResult struct {
	Type       RuntimeType
	Binary     string
	Path       string // full absolute path resolved by exec.LookPath
	Found      bool   // compatibility: true when the runtime is installed and capable
	Installed  bool
	Capable    bool
	ReasonCode string
	Message    string
	Version    string // locally installed version
}

// ValidateType checks if the given string is a valid runtime type.
// It accepts "claude-code" as a backward-compatible alias for "claude", and
// "builtin" as the AgentTeam-managed OpenCode runtime.
func ValidateType(t string) (RuntimeType, error) {
	rt := NormalizeType(t)
	if rt == TypeBuiltin {
		return rt, nil
	}
	for _, allowed := range AllRuntimeTypes {
		if rt == allowed {
			return rt, nil
		}
	}
	return "", fmt.Errorf("unsupported runtime type %q (valid types: %s)", t, formatTypes())
}

// NormalizeType maps legacy runtime type aliases to their normalized values.
func NormalizeType(t string) RuntimeType {
	t = strings.TrimSpace(t)
	if t == "claude-code" {
		return TypeClaudeCode
	}
	return RuntimeType(t)
}

// EffectiveRuntimeType returns the concrete runtime adapter implementation for a
// user-facing runtime value. Builtin uses OpenCode's protocol and driver while
// retaining its own runtime identity for binary resolution.
func EffectiveRuntimeType(t RuntimeType) RuntimeType {
	if NormalizeType(string(t)) == TypeBuiltin {
		return TypeOpenCode
	}
	return NormalizeType(string(t))
}

// EffectiveRuntimeBinary returns the binary marker to resolve for a runtime
// selection. Legacy builtin configs may still persist old builtin or Claude
// fallback binary names; those now launch through the managed OpenCode resolver.
func EffectiveRuntimeBinary(t RuntimeType, binary string) string {
	normalized := NormalizeType(string(t))
	effective := EffectiveRuntimeType(normalized)
	binary = strings.TrimSpace(binary)
	if binary == "" || (normalized == TypeBuiltin && isLegacyBuiltinRuntimeBinary(binary)) {
		return DefaultBinary(effective)
	}
	return binary
}

func isLegacyBuiltinRuntimeBinary(binary string) bool {
	switch strings.TrimSpace(binary) {
	case "builtin", "claude":
		return true
	default:
		return false
	}
}

// IsBuiltinRuntime reports whether the runtime is managed by AgentTeam Desktop and
// does not require an external package-manager install.
func IsBuiltinRuntime(t RuntimeType) bool {
	return NormalizeType(string(t)) == TypeBuiltin
}

// RequiresFullAccessPermissionMode reports whether the runtime must use full
// filesystem access. Agent-scoped workspace-write is no longer offered.
func RequiresFullAccessPermissionMode(t RuntimeType) bool {
	_ = t
	return true
}

// ValidateRuntime checks a runtime record for correctness.
func ValidateRuntime(rt Runtime) error {
	var errs []string

	if _, err := ValidateType(string(rt.Type)); err != nil {
		errs = append(errs, err.Error())
	}

	if strings.TrimSpace(rt.Name) == "" {
		errs = append(errs, "runtime name must not be empty")
	}

	if rt.Port < 0 {
		errs = append(errs, "runtime port must be >= 0")
	}

	if len(errs) > 0 {
		return fmt.Errorf("validation failed: %s", strings.Join(errs, "; "))
	}

	return nil
}

// NormalizeRuntime trims whitespace, fills in type-specific defaults, and validates.
func NormalizeRuntime(rt Runtime) (Runtime, error) {
	rt.Name = strings.TrimSpace(rt.Name)
	rt.Binary = strings.TrimSpace(rt.Binary)
	rt.Health.Path = strings.TrimSpace(rt.Health.Path)
	rt.Health.Interval = strings.TrimSpace(rt.Health.Interval)
	rt.Health.Timeout = strings.TrimSpace(rt.Health.Timeout)
	if NormalizeType(string(rt.Type)) == TypeBuiltin && isLegacyBuiltinRuntimeBinary(rt.Binary) {
		rt.Binary = ""
	}

	if rt.Name == "" && rt.Type != "" {
		rt.Name = string(rt.Type)
	}

	defaults := defaultConfig(rt.Type)
	if rt.Binary == "" {
		rt.Binary = defaults.Binary
	}
	if rt.Args == nil {
		rt.Args = append([]string(nil), defaults.Args...)
	}
	if rt.Health.Path == "" {
		rt.Health.Path = defaults.Health.Path
	}
	if rt.Health.Interval == "" {
		rt.Health.Interval = defaults.Health.Interval
	}
	if rt.Health.Timeout == "" {
		rt.Health.Timeout = defaults.Health.Timeout
	}
	if rt.Env == nil {
		rt.Env = defaults.Env
	}

	if err := ValidateRuntime(rt); err != nil {
		return Runtime{}, err
	}

	return rt, nil
}

// DefaultBinary returns the default binary name for a runtime type.
func DefaultBinary(t RuntimeType) string {
	cfg := defaultConfig(t)
	return cfg.Binary
}

// defaultConfig returns the default Runtime configuration for the given type.
func defaultConfig(t RuntimeType) Runtime {
	t = EffectiveRuntimeType(t)
	name := string(t)
	switch t {
	case TypeOpenCode:
		return Runtime{
			Name:   name,
			Type:   t,
			Binary: "opencode",
			Args:   []string{"serve"},
			Port:   0,
			Health: RuntimeHealthConfig{
				Path:     "/global/health",
				Interval: "10s",
				Timeout:  "5s",
			},
			Env: map[string]string{
				"OPENCODE_CLIENT":                      "agentteam",
				"OPENCODE_EXPERIMENTAL_ICON_DISCOVERY": "true",
				"OPENCODE_EXPERIMENTAL_FILEWATCHER":    "true",
			},
		}
	case TypeClaudeCode:
		return Runtime{
			Name:   name,
			Type:   t,
			Binary: "claude",
			Args:   []string{},
			Port:   0,
			Health: RuntimeHealthConfig{},
			Env:    map[string]string{},
		}
	case TypeCodex:
		return Runtime{
			Name:   name,
			Type:   t,
			Binary: "codex",
			Args:   []string{},
			Port:   0,
			Health: RuntimeHealthConfig{},
			Env:    map[string]string{},
		}
	case TypeKimi:
		return Runtime{
			Name:   name,
			Type:   t,
			Binary: "kimi",
			Args:   []string{},
			Port:   0,
			Health: RuntimeHealthConfig{},
			Env:    map[string]string{},
		}
	case TypePi:
		return Runtime{
			Name:   name,
			Type:   t,
			Binary: "pi",
			Args:   []string{"--mode", "rpc"},
			Port:   0,
			Health: RuntimeHealthConfig{},
			Env:    map[string]string{},
		}
	default:
		return Runtime{Type: t}
	}
}

func formatTypes() string {
	names := make([]string, 0, len(AllRuntimeTypes))
	for _, t := range AllRuntimeTypes {
		names = append(names, string(t))
	}
	return joinQuotes(names)
}

func joinQuotes(ss []string) string {
	if len(ss) == 0 {
		return ""
	}
	result := `"` + ss[0] + `"`
	for _, s := range ss[1:] {
		result += `, "` + s + `"`
	}
	return result
}

// BuiltinRuntimeBinary returns the OpenCode binary marker used by builtin
// runtime selections before ResolveRuntimeBinary maps it to the managed path.
func BuiltinRuntimeBinary() string {
	return DefaultBinary(TypeBuiltin)
}
