package lsp

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

type LSPServer struct {
	Name         string `json:"name"`
	Language     string `json:"language"`
	Command      string `json:"command"`
	InstallCmd   string `json:"install_cmd"`
	Installed    bool   `json:"installed"`
	Version      string `json:"version,omitempty"`
	Healthy      bool   `json:"healthy,omitempty"`
	Description  string `json:"description,omitempty"`
	Category     string `json:"category,omitempty"`
}

var knownServers = []LSPServer{
	{Name: "gopls", Language: "go", Command: "gopls", InstallCmd: "go install golang.org/x/tools/gopls@latest"},
	{Name: "pyright", Language: "python", Command: "pyright", InstallCmd: "npm install -g pyright"},
	{Name: "typescript-language-server", Language: "typescript", Command: "typescript-language-server", InstallCmd: "npm install -g typescript-language-server typescript"},
	{Name: "vscode-json-language-server", Language: "json", Command: "vscode-json-language-server", InstallCmd: "npm install -g vscode-langservers-extracted"},
	{Name: "vscode-html-language-server", Language: "html", Command: "vscode-html-language-server", InstallCmd: "npm install -g vscode-langservers-extracted"},
	{Name: "vscode-css-language-server", Language: "css", Command: "vscode-css-language-server", InstallCmd: "npm install -g vscode-langservers-extracted"},
	{Name: "yaml-language-server", Language: "yaml", Command: "yaml-language-server", InstallCmd: "npm install -g yaml-language-server"},
	{Name: "bash-language-server", Language: "bash", Command: "bash-language-server", InstallCmd: "npm install -g bash-language-server"},
	{Name: "rust-analyzer", Language: "rust", Command: "rust-analyzer", InstallCmd: "rustup component add rust-analyzer"},
	{Name: "clangd", Language: "c/c++", Command: "clangd", InstallCmd: ""},
	{Name: "lua-language-server", Language: "lua", Command: "lua-language-server", InstallCmd: "npm install -g lua-language-server"},
	{Name: "dockerfile-language-server", Language: "dockerfile", Command: "docker-langserver", InstallCmd: "npm install -g dockerfile-language-server-nodejs"},
	{Name: "tailwindcss-language-server", Language: "tailwind", Command: "tailwindcss-language-server", InstallCmd: "npm install -g @tailwindcss/language-server"},
	{Name: "svelte-language-server", Language: "svelte", Command: "svelteserver", InstallCmd: "npm install -g svelte-language-server"},
	{Name: "vue-language-server", Language: "vue", Command: "vue-language-server", InstallCmd: "npm install -g @vue/language-server"},
	{Name: "golangci-lint-langserver", Language: "go-lint", Command: "golangci-lint-langserver", InstallCmd: "go install github.com/nametake/golangci-lint-langserver@latest"},
}

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

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

	return servers
}

func scanLSPRegistryServers() ([]LSPServer, error) {
	reg, err := LoadLSPRegistry()
	if err != nil {
		return nil, err
	}

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

	var servers []LSPServer
	for _, rs := range reg.Servers {
		if knownNames[rs.Name] {
			continue
		}
		servers = append(servers, LSPServer{
			Name:        rs.Name,
			Language:    rs.Language,
			Command:     rs.Command,
			InstallCmd:  rs.InstallCmd,
			Installed:   isLSPCommandAvailable(rs.Command),
			Description: rs.Description,
			Category:    rs.Category,
			Version:     getInstalledLSPVersion(rs.Name),
		})
	}
	return servers, nil
}

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

func getInstalledLSPVersion(name string) string {
	home, _ := os.UserHomeDir()
	if home == "" {
		return ""
	}
	receiptPath := filepath.Join(home, ".muyue", "receipts", "lsp", name+".json")
	data, err := os.ReadFile(receiptPath)
	if err != nil {
		return ""
	}
	var receipt struct {
		Version string `json:"version"`
	}
	if json.Unmarshal(data, &receipt) == nil {
		return receipt.Version
	}
	return ""
}

func saveLSPReceipt(name, version string) error {
	home, _ := os.UserHomeDir()
	if home == "" {
		return nil
	}
	receiptDir := filepath.Join(home, ".muyue", "receipts", "lsp")
	os.MkdirAll(receiptDir, 0755)

	receipt := struct {
		Name      string `json:"name"`
		Version   string `json:"version"`
		UpdatedAt string `json:"updated_at"`
	}{
		Name:      name,
		Version:   version,
		UpdatedAt: time.Now().Format(time.RFC3339),
	}

	data, _ := json.MarshalIndent(receipt, "", "  ")
	return os.WriteFile(filepath.Join(receiptDir, name+".json"), data, 0644)
}

func InstallServer(name string) error {
	for _, s := range knownServers {
		if s.Name == name {
			return doInstallLSP(s)
		}
	}

	reg, err := LoadLSPRegistry()
	if err == nil {
		for _, s := range reg.Servers {
			if s.Name == name {
				return doInstallLSP(LSPServer{
					Name:        s.Name,
					Language:    s.Language,
					Command:     s.Command,
					InstallCmd:  s.InstallCmd,
				})
			}
		}
	}

	return fmt.Errorf("unknown LSP server: %s", name)
}

func doInstallLSP(s LSPServer) error {
	if s.InstallCmd == "" {
		return fmt.Errorf("%s has no auto-install command, install manually", s.Name)
	}
	cmd := exec.Command("bash", "-c", s.InstallCmd)
	cmd.Env = os.Environ()
	if output, err := cmd.CombinedOutput(); err != nil {
		return fmt.Errorf("install %s: %s: %w", s.Name, string(output), err)
	}

	saveLSPReceipt(s.Name, "latest")
	return nil
}

func InstallForLanguages(languages []string) []LSPServer {
	langMap := map[string][]string{
		"go":         {"gopls"},
		"typescript": {"typescript-language-server", "vscode-json-language-server", "vscode-html-language-server", "vscode-css-language-server"},
		"javascript": {"typescript-language-server", "vscode-json-language-server", "vscode-html-language-server", "vscode-css-language-server"},
		"python":     {"pyright"},
		"rust":       {"rust-analyzer"},
		"c":          {"clangd"},
		"cpp":        {"clangd"},
		"json":       {"vscode-json-language-server"},
		"yaml":       {"yaml-language-server"},
		"bash":       {"bash-language-server"},
		"html":       {"vscode-html-language-server"},
		"css":        {"vscode-css-language-server"},
		"lua":        {"lua-language-server"},
		"docker":     {"dockerfile-language-server"},
		"svelte":     {"svelte-language-server"},
		"vue":        {"vue-language-server"},
	}

	installed := map[string]bool{}
	var results []LSPServer

	for _, lang := range languages {
		if servers, ok := langMap[lang]; ok {
			for _, srv := range servers {
				if installed[srv] {
					continue
				}
				installed[srv] = true
				if err := InstallServer(srv); err != nil {
					results = append(results, LSPServer{Name: srv, Language: lang, Installed: false})
				} else {
					results = append(results, LSPServer{Name: srv, Language: lang, Installed: true})
				}
			}
		}
	}

	return results
}

func AutoInstallForProject(projectDir string) ([]LSPServer, error) {
	languages := DetectProjectLanguages(projectDir)
	if len(languages) == 0 {
		return nil, nil
	}
	results := InstallForLanguages(languages)
	return results, nil
}

func HealthCheck(name string) (bool, string) {
	for _, s := range knownServers {
		if s.Name == name {
			return healthCheckServer(s)
		}
	}
	return false, "unknown server"
}

func healthCheckServer(s LSPServer) (bool, string) {
	path, err := exec.LookPath(s.Command)
	if err != nil {
		return false, fmt.Sprintf("command %q not found in PATH", s.Command)
	}

	versionArgs := map[string][]string{
		"gopls":                        {"version"},
		"pyright":                      {"--version"},
		"typescript-language-server":   {"--version"},
		"rust-analyzer":                {"--version"},
		"clangd":                       {"--version"},
		"lua-language-server":          {"--version"},
		"bash-language-server":         {"--version"},
		"yaml-language-server":         {"--version"},
	}

	if args, ok := versionArgs[s.Command]; ok {
		cmd := exec.Command(path, args...)
		output, err := cmd.CombinedOutput()
		if err != nil {
			return true, fmt.Sprintf("installed at %s but version check failed", path)
		}
		version := strings.TrimSpace(string(output))
		if idx := strings.Index(version, "\n"); idx > 0 {
			version = version[:idx]
		}
		saveLSPReceipt(s.Name, version)
		return true, version
	}

	return true, fmt.Sprintf("installed at %s", path)
}

func GenerateEditorConfigs(servers []LSPServer, editor string, homeDir string) (string, error) {
	if homeDir == "" {
		home, _ := os.UserHomeDir()
		homeDir = home
	}

	reg, err := LoadLSPRegistry()
	if err != nil {
		return "", err
	}

	regMap := map[string]RegistryEntry{}
	for _, s := range reg.Servers {
		regMap[s.Name] = s
	}

	var regEntries []RegistryEntry
	for _, s := range servers {
		if re, ok := regMap[s.Name]; ok {
			regEntries = append(regEntries, re)
		}
	}

	switch editor {
	case "neovim", "nvim":
		return GenerateNeovimConfig(regEntries), nil
	case "helix", "hx":
		return GenerateHelixConfig(regEntries), nil
	case "vscode", "code", "cursor":
		exts := GenerateVSCodeRecommendations(regEntries)
		var b strings.Builder
		b.WriteString("{\n  \"recommendations\": [\n")
		for i, ext := range exts {
			if i > 0 {
				b.WriteString(",\n")
			}
			b.WriteString("    \"" + ext + "\"")
		}
		b.WriteString("\n  ]\n}")
		return b.String(), nil
	default:
		return "", fmt.Errorf("unsupported editor: %s", editor)
	}
}
