package services

import (
	"encoding/json"
	"os"

	"sproutclaw-web/internal/models"
)

// mcp.json is shared with pi-mcp-adapter, which only reads the top-level
// "mcpServers", "imports" and "settings" keys. To disable a server we move its
// full definition into the adapter-ignored "mcpServersDisabled" object, so the
// adapter never starts it; enabling moves it back. All operations work on the
// raw JSON map so unrecognized keys (settings, imports, ...) are preserved.

// ReadMCPServers loads mcp.json and returns the list of servers with their tools.
// Servers under "mcpServers" are reported enabled; those parked under
// "mcpServersDisabled" are reported disabled.
func ReadMCPServers(mcpConfigPath, mcpCachePath string) []models.MCPServer {
	raw := loadRawMCP(mcpConfigPath)
	cache := loadMCPCache(mcpCachePath)

	excludeSet := map[string]bool{}
	for _, t := range toStringSlice(raw["excludeTools"]) {
		excludeSet[t] = true
	}

	var servers []models.MCPServer
	appendServer := func(name string, enabled bool) {
		srv := models.MCPServer{Name: name, Configured: true, Enabled: enabled}
		for _, t := range cache[name] {
			toolEnabled := !(excludeSet[name+"/"+t] || excludeSet[t])
			srv.Tools = append(srv.Tools, models.MCPTool{Server: name, Name: t, Enabled: toolEnabled})
			srv.ToolCount++
			if toolEnabled {
				srv.EnabledToolCount++
			}
		}
		servers = append(servers, srv)
	}

	for name := range asMap(raw["mcpServers"]) {
		appendServer(name, true)
	}
	for name := range asMap(raw["mcpServersDisabled"]) {
		appendServer(name, false)
	}
	return servers
}

// ToggleMCPServer enables/disables a server by moving its full definition between
// "mcpServers" (adapter reads it -> server can start) and "mcpServersDisabled"
// (adapter ignores it -> server never starts).
func ToggleMCPServer(mcpConfigPath, serverName string, enable bool) error {
	raw := loadRawMCP(mcpConfigPath)
	servers := asMap(raw["mcpServers"])
	disabled := asMap(raw["mcpServersDisabled"])

	if enable {
		if def, ok := disabled[serverName]; ok {
			servers[serverName] = def
			delete(disabled, serverName)
		}
	} else {
		if def, ok := servers[serverName]; ok {
			disabled[serverName] = def
			delete(servers, serverName)
		}
	}

	raw["mcpServers"] = servers
	setOrDeleteMap(raw, "mcpServersDisabled", disabled)
	return saveRawMCP(mcpConfigPath, raw)
}

// ToggleMCPTool enables or disables a specific tool via the top-level
// "excludeTools" list (server/tool keys).
func ToggleMCPTool(mcpConfigPath, serverName, toolName string, enable bool) error {
	raw := loadRawMCP(mcpConfigPath)
	key := serverName + "/" + toolName

	set := map[string]bool{}
	for _, t := range toStringSlice(raw["excludeTools"]) {
		set[t] = true
	}
	if enable {
		delete(set, key)
	} else {
		set[key] = true
	}

	list := make([]any, 0, len(set))
	for k := range set {
		list = append(list, k)
	}
	if len(list) > 0 {
		raw["excludeTools"] = list
	} else {
		delete(raw, "excludeTools")
	}
	return saveRawMCP(mcpConfigPath, raw)
}

func loadRawMCP(path string) map[string]any {
	data, err := os.ReadFile(path)
	if err != nil {
		return map[string]any{}
	}
	var raw map[string]any
	if err := json.Unmarshal(data, &raw); err != nil || raw == nil {
		return map[string]any{}
	}
	return raw
}

func saveRawMCP(path string, raw map[string]any) error {
	data, err := json.MarshalIndent(raw, "", "  ")
	if err != nil {
		return err
	}
	return os.WriteFile(path, append(data, '\n'), 0o644)
}

func asMap(v any) map[string]any {
	if m, ok := v.(map[string]any); ok {
		return m
	}
	return map[string]any{}
}

func setOrDeleteMap(raw map[string]any, key string, m map[string]any) {
	if len(m) > 0 {
		raw[key] = m
	} else {
		delete(raw, key)
	}
}

func toStringSlice(v any) []string {
	arr, ok := v.([]any)
	if !ok {
		return nil
	}
	out := make([]string, 0, len(arr))
	for _, e := range arr {
		if s, ok := e.(string); ok {
			out = append(out, s)
		}
	}
	return out
}

func loadMCPCache(path string) map[string][]string {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil
	}
	// cache format: { "serverName": { "tools": [{ "name": "..." }] } }
	var raw map[string]struct {
		Tools []struct {
			Name string `json:"name"`
		} `json:"tools"`
	}
	if err := json.Unmarshal(data, &raw); err != nil {
		return nil
	}
	result := map[string][]string{}
	for srv, info := range raw {
		for _, t := range info.Tools {
			result[srv] = append(result[srv], t.Name)
		}
	}
	return result
}
