package mcp

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/muyue/muyue/internal/config"
)

type MCPServer struct {
	Name        string            `json:"name"`
	Command     string            `json:"command"`
	Args        []string          `json:"args"`
	Env         map[string]string `json:"env,omitempty"`
	Installed   bool              `json:"installed"`
	Category    string            `json:"category"`
	Description string            `json:"description,omitempty"`
	Version     string            `json:"version,omitempty"`
	Status      string            `json:"status,omitempty"`
}

type mcpEntry struct {
	name string
	cmd  string
	args []string
	env  map[string]string
}

var knownMCPServers = []MCPServer{
	{Name: "filesystem", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-filesystem"}, Category: "core"},
	{Name: "github", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-github"}, Env: map[string]string{"GITHUB_PERSONAL_ACCESS_TOKEN": ""}, Category: "vcs"},
	{Name: "git", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-git"}, Category: "vcs"},
	{Name: "fetch", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-fetch"}, Category: "web"},
	{Name: "memory", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-memory"}, Category: "core"},
	{Name: "sequential-thinking", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-sequential-thinking"}, Category: "ai"},
	{Name: "brave-search", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-brave-search"}, Env: map[string]string{"BRAVE_API_KEY": ""}, Category: "web"},
	{Name: "sqlite", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-sqlite"}, Category: "database"},
	{Name: "postgres", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-postgres"}, Category: "database"},
	{Name: "docker", Command: "npx", Args: []string{"-y", "@modelcontextprotocol/server-docker"}, Category: "devops"},
	{Name: "minimax-web-search", Command: "npx", Args: []string{"-y", "@minimax/mcp-web-search"}, Env: map[string]string{"MINIMAX_API_KEY": ""}, Category: "ai"},
	{Name: "minimax-image", Command: "npx", Args: []string{"-y", "@minimax/mcp-image-understanding"}, Env: map[string]string{"MINIMAX_API_KEY": ""}, Category: "ai"},
}

func ScanServers() []MCPServer {
	servers := make([]MCPServer, len(knownMCPServers))
	for i, s := range knownMCPServers {
		servers[i] = s
		_, err := exec.LookPath(s.Command)
		servers[i].Installed = err == nil
		servers[i].Version = GetInstalledVersion(s.Name)
	}

	regServers, err := scanRegistryServers()
	if err == nil {
		servers = append(servers, regServers...)
	}

	return servers
}

func scanRegistryServers() ([]MCPServer, error) {
	reg, err := LoadRegistry()
	if err != nil {
		return nil, err
	}

	knownNames := map[string]bool{}
	for _, s := range knownMCPServers {
		knownNames[s.Name] = true
	}

	var servers []MCPServer
	for _, rs := range reg.Servers {
		if knownNames[rs.Name] {
			continue
		}
		servers = append(servers, MCPServer{
			Name:        rs.Name,
			Command:     rs.Command,
			Args:        rs.Args,
			Env:         rs.Env,
			Category:    rs.Category,
			Description: rs.Description,
			Installed:   isCommandAvailable(rs.Command),
			Version:     GetInstalledVersion(rs.Name),
		})
	}
	return servers, nil
}

func isCommandAvailable(cmd string) bool {
	_, err := exec.LookPath(cmd)
	return err == nil
}

func getCoreEntries(homeDir string) []mcpEntry {
	return []mcpEntry{
		{"filesystem", "npx", []string{"-y", "@modelcontextprotocol/server-filesystem", filepath.Join(homeDir, "projects")}, nil},
		{"fetch", "npx", []string{"-y", "@modelcontextprotocol/server-fetch"}, nil},
		{"memory", "npx", []string{"-y", "@modelcontextprotocol/server-memory"}, nil},
	}
}

func withProviderEntries(base []mcpEntry, cfg *config.MuyueConfig, extraEntries []mcpEntry) []mcpEntry {
	entries := make([]mcpEntry, len(base))
	copy(entries, base)
	entries = append(entries, extraEntries...)

	if cfg != nil {
		for _, p := range cfg.AI.Providers {
			if p.Name == "minimax" && p.APIKey != "" {
				entries = append(entries,
					mcpEntry{"minimax-web-search", "npx", []string{"-y", "@minimax/mcp-web-search"}, map[string]string{"MINIMAX_API_KEY": p.APIKey}},
					mcpEntry{"minimax-image", "npx", []string{"-y", "@minimax/mcp-image-understanding"}, map[string]string{"MINIMAX_API_KEY": p.APIKey}},
				)
			}
		}
	}
	return entries
}

func writeMCPConfig(configPath string, mcpKey string, entries []mcpEntry) error {
	configDir := filepath.Dir(configPath)
	if err := os.MkdirAll(configDir, 0700); err != nil {
		return fmt.Errorf("create config dir: %w", err)
	}

	existing := map[string]interface{}{}
	data, err := os.ReadFile(configPath)
	if err == nil {
		if err := json.Unmarshal(data, &existing); err != nil {
			return fmt.Errorf("parse existing config: %w", err)
		}
	}

	mcpMap := map[string]interface{}{}
	for _, e := range entries {
		entry := map[string]interface{}{
			"command": e.cmd,
			"args":    e.args,
		}
		if len(e.env) > 0 {
			resolved := ResolveEnv(e.env, nil)
			entry["env"] = resolved
		}
		mcpMap[e.name] = entry
	}

	existing[mcpKey] = mcpMap

	out, err := json.MarshalIndent(existing, "", "  ")
	if err != nil {
		return err
	}

	if err := os.WriteFile(configPath, out, 0600); err != nil {
		return err
	}

	return ValidateConfig(configPath)
}

func writeMCPConfigForEditor(editor EditorConfig, entries []mcpEntry) error {
	configDir := filepath.Dir(editor.ConfigPath)
	if err := os.MkdirAll(configDir, 0700); err != nil {
		return fmt.Errorf("create config dir %s: %w", editor.Name, err)
	}

	existing := map[string]interface{}{}
	data, err := os.ReadFile(editor.ConfigPath)
	if err == nil {
		_ = json.Unmarshal(data, &existing)
	}

	mcpMap := map[string]interface{}{}
	for _, e := range entries {
		if editor.TransformCommand != nil {
			mcpMap[e.name] = editor.TransformCommand(e)
		} else {
			entry := map[string]interface{}{
				"command": e.cmd,
				"args":    e.args,
			}
			if len(e.env) > 0 {
				entry["env"] = e.env
			}
			mcpMap[e.name] = entry
		}
	}

	existing[editor.ConfigKey] = mcpMap

	out, err := json.MarshalIndent(existing, "", "  ")
	if err != nil {
		return err
	}

	return os.WriteFile(editor.ConfigPath, out, 0600)
}

func GenerateCrushMCPConfig(cfg *config.MuyueConfig, homeDir string) error {
	if homeDir == "" {
		home, _ := os.UserHomeDir()
		homeDir = home
	}

	core := getCoreEntries(homeDir)
	entries := withProviderEntries(core, cfg, nil)
	configPath := filepath.Join(homeDir, ".config", "crush", "crush.json")
	return writeMCPConfig(configPath, "mcps", entries)
}

func GenerateClaudeMCPConfig(cfg *config.MuyueConfig, homeDir string) error {
	if homeDir == "" {
		home, _ := os.UserHomeDir()
		homeDir = home
	}

	core := getCoreEntries(homeDir)
	extra := []mcpEntry{
		{"sequential-thinking", "npx", []string{"-y", "@modelcontextprotocol/server-sequential-thinking"}, nil},
	}
	entries := withProviderEntries(core, cfg, extra)
	configPath := filepath.Join(homeDir, ".claude.json")
	return writeMCPConfig(configPath, "mcpServers", entries)
}

func GenerateCursorMCPConfig(cfg *config.MuyueConfig, homeDir string) error {
	if homeDir == "" {
		home, _ := os.UserHomeDir()
		homeDir = home
	}

	core := getCoreEntries(homeDir)
	entries := withProviderEntries(core, cfg, nil)
	editor := EditorConfig{
		Name:       "cursor",
		ConfigPath: filepath.Join(homeDir, ".cursor", "mcp.json"),
		ConfigKey:  "mcpServers",
		Format:     "json",
		TransformCommand: func(e mcpEntry) interface{} {
			m := map[string]interface{}{
				"type":    "stdio",
				"command": e.cmd,
				"args":    e.args,
			}
			if len(e.env) > 0 {
				m["env"] = e.env
			}
			return m
		},
	}
	return writeMCPConfigForEditor(editor, entries)
}

func GenerateVSCodeMCPConfig(cfg *config.MuyueConfig, homeDir string) error {
	if homeDir == "" {
		home, _ := os.UserHomeDir()
		homeDir = home
	}

	core := getCoreEntries(homeDir)
	entries := withProviderEntries(core, cfg, nil)
	editor := EditorConfig{
		Name:       "vscode",
		ConfigPath: filepath.Join(homeDir, ".vscode", "mcp.json"),
		ConfigKey:  "servers",
		Format:     "json",
	}
	return writeMCPConfigForEditor(editor, entries)
}

func GenerateWindsurfMCPConfig(cfg *config.MuyueConfig, homeDir string) error {
	if homeDir == "" {
		home, _ := os.UserHomeDir()
		homeDir = home
	}

	core := getCoreEntries(homeDir)
	entries := withProviderEntries(core, cfg, nil)
	editor := EditorConfig{
		Name:       "windsurf",
		ConfigPath: filepath.Join(homeDir, ".windsurf", "mcp.json"),
		ConfigKey:  "mcpServers",
		Format:     "json",
	}
	return writeMCPConfigForEditor(editor, entries)
}

func ConfigureAll(cfg *config.MuyueConfig) error {
	home, err := os.UserHomeDir()
	if err != nil {
		return fmt.Errorf("get home dir: %w", err)
	}

	editors := []struct {
		name string
		fn   func(*config.MuyueConfig, string) error
	}{
		{"crush", GenerateCrushMCPConfig},
		{"claude", GenerateClaudeMCPConfig},
		{"cursor", GenerateCursorMCPConfig},
		{"vscode", GenerateVSCodeMCPConfig},
		{"windsurf", GenerateWindsurfMCPConfig},
	}

	var errs []string
	for _, e := range editors {
		if err := e.fn(cfg, home); err != nil {
			errs = append(errs, fmt.Sprintf("%s: %s", e.name, err))
		}
	}

	SaveReceipt("all", time.Now().Format("2006-01-02"))

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

	return nil
}

func ConfigureForEditor(cfg *config.MuyueConfig, editorName string) error {
	home, err := os.UserHomeDir()
	if err != nil {
		return fmt.Errorf("get home dir: %w", err)
	}

	switch editorName {
	case "crush":
		return GenerateCrushMCPConfig(cfg, home)
	case "claude", "claude-code":
		return GenerateClaudeMCPConfig(cfg, home)
	case "cursor":
		return GenerateCursorMCPConfig(cfg, home)
	case "vscode", "code":
		return GenerateVSCodeMCPConfig(cfg, home)
	case "windsurf":
		return GenerateWindsurfMCPConfig(cfg, home)
	default:
		return fmt.Errorf("unknown editor: %s (supported: crush, claude-code, cursor, vscode, windsurf)", editorName)
	}
}

func DetectInstalledEditors(homeDir string) []string {
	if homeDir == "" {
		home, _ := os.UserHomeDir()
		homeDir = home
	}

	editors := []struct {
		name string
		path string
	}{
		{"crush", filepath.Join(homeDir, ".config", "crush", "crush.json")},
		{"claude-code", filepath.Join(homeDir, ".claude.json")},
		{"cursor", filepath.Join(homeDir, ".cursor")},
		{"vscode", filepath.Join(homeDir, ".vscode")},
		{"windsurf", filepath.Join(homeDir, ".windsurf")},
	}

	var detected []string
	for _, e := range editors {
		if _, err := os.Stat(e.path); err == nil {
			detected = append(detected, e.name)
		}
	}
	return detected
}

func GetAllStatuses() []MCPStatus {
	servers := ScanServers()
	statuses := make([]MCPStatus, len(servers))
	for i, s := range servers {
		statuses[i] = CheckServerStatus(s.Name)
	}
	return statuses
}
