package scanner

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
	"sync"
	"time"

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

type ToolStatus struct {
	Name        string `yaml:"name"         json:"name"`
	Installed   bool   `yaml:"installed"    json:"installed"`
	Version     string `yaml:"version"      json:"version"`
	Path        string `yaml:"path"         json:"path"`
	Latest      string `yaml:"latest"       json:"latest"`
	NeedsUpdate bool   `yaml:"needs_update" json:"needs_update"`
	Category    string `yaml:"category"     json:"category"`
}

type RuntimeStatus struct {
	Name      string `yaml:"name"      json:"name"`
	Installed bool   `yaml:"installed" json:"installed"`
	Version   string `yaml:"version"   json:"version"`
}

type ScanResult struct {
	System        platform.SystemInfo `yaml:"system"         json:"system"`
	Tools         []ToolStatus        `yaml:"tools"          json:"tools"`
	Runtimes      []RuntimeStatus     `yaml:"runtimes"       json:"runtimes"`
	ShellSetup    bool                `yaml:"shell_setup"    json:"shell_setup"`
	GitConfigured bool                `yaml:"git_configured" json:"git_configured"`
}

var (
	cacheMu     sync.RWMutex
	cacheResult *ScanResult
	cacheTime   time.Time
	cacheTTL    = 5 * time.Minute
)

func ScanSystem() *ScanResult {
	cacheMu.RLock()
	if cacheResult != nil && time.Since(cacheTime) < cacheTTL {
		result := cacheResult
		cacheMu.RUnlock()
		return result
	}
	cacheMu.RUnlock()

	result := doScan()

	cacheMu.Lock()
	cacheResult = result
	cacheTime = time.Now()
	cacheMu.Unlock()

	return result
}

func InvalidateCache() {
	cacheMu.Lock()
	cacheResult = nil
	cacheTime = time.Time{}
	cacheMu.Unlock()
}

func doScan() *ScanResult {
	info := platform.Detect()
	result := &ScanResult{
		System: info,
	}

	result.Tools = scanTools()
	result.Runtimes = scanRuntimes()
	result.ShellSetup = checkShellSetup()
	result.GitConfigured = checkGitConfig()

	return result
}

func scanTools() []ToolStatus {
	tools := []struct {
		name     string
		category string
		version  []string
	}{
		{"crush", "ai", []string{"version", "--short"}},
		{"claude", "ai", []string{"--version"}},
		{"git", "vcs", []string{"--version"}},
		{"node", "runtime", []string{"--version"}},
		{"npm", "runtime", []string{"--version"}},
		{"pnpm", "runtime", []string{"--version"}},
		{"python3", "runtime", []string{"--version"}},
		{"pip3", "runtime", []string{"--version"}},
		{"uv", "runtime", []string{"--version"}},
		{"go", "runtime", []string{"version"}},
		{"docker", "devops", []string{"--version"}},
		{"gh", "devops", []string{"--version"}},
		{"starship", "prompt", []string{"--version"}},
		{"npx", "runtime", []string{"--version"}},
	}

	var statuses []ToolStatus
	for _, t := range tools {
		status := ToolStatus{
			Name:     t.name,
			Category: t.category,
		}

		path, err := exec.LookPath(t.name)
		if err != nil {
			statuses = append(statuses, status)
			continue
		}

		status.Installed = true
		status.Path = path

		if len(t.version) > 0 {
			cmd := exec.Command(t.name, t.version...)
			out, err := cmd.Output()
			if err == nil {
				status.Version = strings.TrimSpace(string(out))
			}
		}

		statuses = append(statuses, status)
	}

	return statuses
}

func scanRuntimes() []RuntimeStatus {
	runtimes := []struct {
		name    string
		command []string
	}{
		{"Go", []string{"go", "version"}},
		{"Node.js", []string{"node", "--version"}},
		{"Python", []string{"python3", "--version"}},
		{"Rust", []string{"rustc", "--version"}},
		{"Java", []string{"java", "--version"}},
		{"Ruby", []string{"ruby", "--version"}},
		{"PHP", []string{"php", "--version"}},
		{"Dotnet", []string{"dotnet", "--version"}},
	}

	var statuses []RuntimeStatus
	for _, r := range runtimes {
		status := RuntimeStatus{Name: r.name}
		cmd := exec.Command(r.command[0], r.command[1:]...)
		out, err := cmd.Output()
		if err == nil {
			status.Installed = true
			status.Version = strings.TrimSpace(string(out))
		}
		statuses = append(statuses, status)
	}

	return statuses
}

func checkShellSetup() bool {
	home, _ := os.UserHomeDir()
	rcFiles := []string{".bashrc", ".zshrc", ".config/fish/config.fish"}
	for _, f := range rcFiles {
		data, err := os.ReadFile(filepath.Join(home, f))
		if err != nil {
			continue
		}
		content := string(data)
		if strings.Contains(content, "starship") ||
			strings.Contains(content, "muyue") {
			return true
		}
	}
	return false
}

func checkGitConfig() bool {
	for _, key := range []string{"user.name", "user.email"} {
		cmd := exec.Command("git", "config", "--global", key)
		if _, err := cmd.Output(); err != nil {
			return false
		}
	}
	return true
}

var editorsList = []struct {
	name    string
	cmd     []string
	version []string
}{
	{"vim", []string{"vim"}, []string{"--version"}},
	{"nvim", []string{"nvim"}, []string{"--version"}},
	{"code", []string{"code"}, []string{"--version"}},
	{"emacs", []string{"emacs"}, []string{"--version"}},
	{"nano", []string{"nano"}, []string{"--version"}},
	{"helix", []string{"hx"}, []string{"--version"}},
	{"subl", []string{"subl"}, []string{"--version"}},
	{"zed", []string{"zed"}, []string{"--version"}},
}

func ScanEditors() []ToolStatus {
	var results []ToolStatus
	for _, e := range editorsList {
		status := ToolStatus{Name: e.name}
		path, err := exec.LookPath(e.name)
		if err != nil {
			continue
		}
		status.Installed = true
		status.Path = path
		if len(e.version) > 0 {
			cmd := exec.Command(e.cmd[0], e.version...)
			out, err := cmd.Output()
			if err == nil {
				status.Version = strings.TrimSpace(string(out))
			}
		}
		results = append(results, status)
	}
	return results
}

var versionRegex = regexp.MustCompile(`\d+\.\d+\.\d+`)

func (s *ScanResult) Summary() string {
	var b strings.Builder

	fmt.Fprintf(&b, "System: %s\n", s.System.String())
	fmt.Fprintf(&b, "\nTools:\n")

	installed := 0
	for _, t := range s.Tools {
		if t.Installed {
			installed++
			fmt.Fprintf(&b, "  [v] %s %s\n", t.Name, versionRegex.FindString(t.Version))
		} else {
			fmt.Fprintf(&b, "  [ ] %s (not installed)\n", t.Name)
		}
	}
	fmt.Fprintf(&b, "\nInstalled: %d/%d\n", installed, len(s.Tools))

	fmt.Fprintf(&b, "\nRuntimes:\n")
	for _, r := range s.Runtimes {
		if r.Installed {
			fmt.Fprintf(&b, "  [v] %s\n", r.Version)
		} else {
			fmt.Fprintf(&b, "  [ ] %s (not installed)\n", r.Name)
		}
	}

	if s.GitConfigured {
		fmt.Fprintf(&b, "\nGit: configured\n")
	} else {
		fmt.Fprintf(&b, "\nGit: not fully configured\n")
	}

	return b.String()
}
