package skills

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

	"gopkg.in/yaml.v3"
)

type SkillDependency struct {
	Type     string `yaml:"type,omitempty" json:"type,omitempty"`
	Name     string `yaml:"name,omitempty" json:"name,omitempty"`
	Version  string `yaml:"version,omitempty" json:"version,omitempty"`
	Required bool   `yaml:"required,omitempty" json:"required,omitempty"`
}

type Skill struct {
	Name              string            `yaml:"name" json:"name"`
	Description       string            `yaml:"description" json:"description"`
	Content           string            `yaml:"content" json:"content"`
	Author            string            `yaml:"author" json:"author"`
	Version           string            `yaml:"version" json:"version"`
	CreatedAt         time.Time         `yaml:"created_at" json:"created_at"`
	UpdatedAt         time.Time         `yaml:"updated_at" json:"updated_at"`
	Tags              []string          `yaml:"tags" json:"tags"`
	Target            string            `yaml:"target" json:"target"`
	FilePath          string            `yaml:"-" json:"-"`
	Dependencies      []SkillDependency `yaml:"dependencies,omitempty" json:"dependencies,omitempty"`
	Languages         []string          `yaml:"languages,omitempty" json:"languages,omitempty"`
	Category          string            `yaml:"category,omitempty" json:"category,omitempty"`
	Deployed          bool              `yaml:"-" json:"deployed,omitempty"`
	RequiresTools     []string          `yaml:"requires_tools,omitempty" json:"requires_tools,omitempty"`
	FallbackForTools  []string          `yaml:"fallback_for_tools,omitempty" json:"fallback_for_tools,omitempty"`
	AutoImprove       bool              `yaml:"auto_improve,omitempty" json:"auto_improve,omitempty"`
	CreatedFrom       string            `yaml:"created_from,omitempty" json:"created_from,omitempty"`
	ImprovementCount  int               `yaml:"improvement_count,omitempty" json:"improvement_count,omitempty"`
	LastImprovedAt    *time.Time        `yaml:"last_improved_at,omitempty" json:"last_improved_at,omitempty"`
}

type ValidationError struct {
	Field   string `json:"field"`
	Message string `json:"message"`
}

func (v ValidationError) Error() string {
	return fmt.Sprintf("%s: %s", v.Field, v.Message)
}

type SkillTestResult struct {
	Name    string `json:"name"`
	Passed  bool   `json:"passed"`
	Message string `json:"message"`
}

func SkillsDir() (string, error) {
	home, err := os.UserHomeDir()
	if err != nil {
		return "", err
	}
	dir := filepath.Join(home, ".muyue", "skills")
	return dir, nil
}

func List() ([]Skill, error) {
	dir, err := SkillsDir()
	if err != nil {
		return nil, err
	}

	entries, err := os.ReadDir(dir)
	if err != nil {
		if os.IsNotExist(err) {
			return []Skill{}, nil
		}
		return nil, err
	}

	var skills []Skill
	for _, e := range entries {
		if !e.IsDir() {
			continue
		}
		skillPath := filepath.Join(dir, e.Name(), "SKILL.md")
		data, err := os.ReadFile(skillPath)
		if err != nil {
			continue
		}
		skill, err := parseSkill(data)
		if err != nil {
			continue
		}
		skill.FilePath = skillPath
		skill.Name = e.Name()
		skills = append(skills, *skill)
	}

	return skills, nil
}

func Get(name string) (*Skill, error) {
	dir, err := SkillsDir()
	if err != nil {
		return nil, err
	}

	skillPath := filepath.Join(dir, name, "SKILL.md")
	data, err := os.ReadFile(skillPath)
	if err != nil {
		return nil, fmt.Errorf("skill '%s' not found", name)
	}

	skill, err := parseSkill(data)
	if err != nil {
		return nil, err
	}
	skill.FilePath = skillPath
	skill.Name = name
	return skill, nil
}

func Create(skill *Skill) error {
	if errs := Validate(skill); len(errs) > 0 {
		return fmt.Errorf("validation failed: %v", errs)
	}

	dir, err := SkillsDir()
	if err != nil {
		return err
	}

	skillDir := filepath.Join(dir, skill.Name)
	if err := os.MkdirAll(skillDir, 0755); err != nil {
		return err
	}

	skillPath := filepath.Join(skillDir, "SKILL.md")
	content := renderSkill(skill)
	if err := os.WriteFile(skillPath, []byte(content), 0644); err != nil {
		return err
	}

	return Deploy(skill)
}

func Delete(name string) error {
	dir, err := SkillsDir()
	if err != nil {
		return err
	}

	skillDir := filepath.Join(dir, name)
	if err := os.RemoveAll(skillDir); err != nil {
		return err
	}

	undeployFromTargets(name)
	return nil
}

func IsDeployed(name string) bool {
	home, err := os.UserHomeDir()
	if err != nil {
		return false
	}
	crushPath := filepath.Join(home, ".config", "crush", "skills", name, "SKILL.md")
	claudePath := filepath.Join(home, ".claude", "skills", name, "SKILL.md")
	_, crushErr := os.Stat(crushPath)
	_, claudeErr := os.Stat(claudePath)
	return crushErr == nil || claudeErr == nil
}

func Undeploy(name string) error {
	skill, err := Get(name)
	if err != nil {
		return err
	}
	undeployFromTargets(skill.Name)
	return nil
}

func Update(skill *Skill) error {
	if errs := Validate(skill); len(errs) > 0 {
		return fmt.Errorf("validation failed: %v", errs)
	}

	dir, err := SkillsDir()
	if err != nil {
		return err
	}

	skillDir := filepath.Join(dir, skill.Name)
	skillPath := filepath.Join(skillDir, "SKILL.md")

	skill.UpdatedAt = time.Now()
	content := renderSkill(skill)
	if err := os.WriteFile(skillPath, []byte(content), 0644); err != nil {
		return err
	}

	return Deploy(skill)
}

func Deploy(skill *Skill) error {
	home, err := os.UserHomeDir()
	if err != nil {
		return fmt.Errorf("get home dir: %w", err)
	}

	if skill.Target == "crush" || skill.Target == "both" {
		crushSkillsDir := filepath.Join(home, ".config", "crush", "skills")
		if err := os.MkdirAll(crushSkillsDir, 0755); err != nil {
			return fmt.Errorf("create crush skills dir: %w", err)
		}
		target := filepath.Join(crushSkillsDir, skill.Name)
		if err := os.MkdirAll(target, 0755); err != nil {
			return fmt.Errorf("create skill dir: %w", err)
		}
		content := renderSkill(skill)
		if err := os.WriteFile(filepath.Join(target, "SKILL.md"), []byte(content), 0644); err != nil {
			return fmt.Errorf("write crush skill: %w", err)
		}
	}

	if skill.Target == "claude" || skill.Target == "both" {
		claudeSkillsDir := filepath.Join(home, ".claude", "skills")
		if err := os.MkdirAll(claudeSkillsDir, 0755); err != nil {
			return fmt.Errorf("create claude skills dir: %w", err)
		}
		target := filepath.Join(claudeSkillsDir, skill.Name)
		if err := os.MkdirAll(target, 0755); err != nil {
			return fmt.Errorf("create skill dir: %w", err)
		}
		content := renderSkill(skill)
		if err := os.WriteFile(filepath.Join(target, "SKILL.md"), []byte(content), 0644); err != nil {
			return fmt.Errorf("write claude skill: %w", err)
		}
	}

	return nil
}

func DeployAll() error {
	skills, err := List()
	if err != nil {
		return err
	}
	for _, s := range skills {
		if err := Deploy(&s); err != nil {
			return fmt.Errorf("deploy %s: %w", s.Name, err)
		}
	}
	return nil
}

func undeployFromTargets(name string) {
	home, _ := os.UserHomeDir()

	os.RemoveAll(filepath.Join(home, ".config", "crush", "skills", name))
	os.RemoveAll(filepath.Join(home, ".claude", "skills", name))
}

func Validate(skill *Skill) []ValidationError {
	var errs []ValidationError

	if skill.Name == "" {
		errs = append(errs, ValidationError{Field: "name", Message: "name is required"})
	}

	if skill.Name != "" {
		if matched, _ := regexp.MatchString(`^[a-z0-9][a-z0-9-]*$`, skill.Name); !matched {
			errs = append(errs, ValidationError{Field: "name", Message: "name must be lowercase alphanumeric with dashes"})
		}
	}

	if skill.Description == "" {
		errs = append(errs, ValidationError{Field: "description", Message: "description is required"})
	}

	if skill.Content == "" {
		errs = append(errs, ValidationError{Field: "content", Message: "content is required"})
	}

	if skill.Target != "" && skill.Target != "crush" && skill.Target != "claude" && skill.Target != "both" {
		errs = append(errs, ValidationError{Field: "target", Message: "target must be crush, claude, or both"})
	}

	if skill.Version != "" {
		if matched, _ := regexp.MatchString(`^\d+\.\d+\.\d+$`, skill.Version); !matched {
			errs = append(errs, ValidationError{Field: "version", Message: "version must be semver (e.g. 1.0.0)"})
		}
	}

	for i, dep := range skill.Dependencies {
		if dep.Type != "mcp_server" && dep.Type != "lsp" && dep.Type != "tool" && dep.Type != "runtime" && dep.Type != "" {
			errs = append(errs, ValidationError{
				Field:   fmt.Sprintf("dependencies[%d].type", i),
				Message: "dependency type must be mcp_server, lsp, tool, or runtime",
			})
		}
		if dep.Name == "" {
			errs = append(errs, ValidationError{
				Field:   fmt.Sprintf("dependencies[%d].name", i),
				Message: "dependency name is required",
			})
		}
	}

	return errs
}

func CheckDependencies(skill *Skill) []SkillDependency {
	var missing []SkillDependency
	for _, dep := range skill.Dependencies {
		switch dep.Type {
		case "mcp_server":
			if !isMCPServerAvailable(dep.Name) {
				missing = append(missing, dep)
			}
		case "lsp", "tool", "runtime":
			if !isToolAvailable(dep.Name) {
				missing = append(missing, dep)
			}
		}
	}
	return missing
}

func isToolAvailable(name string) bool {
	_, err := lookPath(name)
	return err == nil
}

func lookPath(name string) (string, error) {
	pathEnv := os.Getenv("PATH")
	home, _ := os.UserHomeDir()
	if home != "" {
		pathEnv = home + "/.local/bin:" + home + "/go/bin:" + pathEnv
	}
	for _, dir := range filepath.SplitList(pathEnv) {
		candidate := filepath.Join(dir, name)
		if info, err := os.Stat(candidate); err == nil && !info.IsDir() {
			return candidate, nil
		}
	}
	return "", fmt.Errorf("%s not found", name)
}

func isMCPServerAvailable(name string) bool {
	home, _ := os.UserHomeDir()
	if home == "" {
		return false
	}
	configPath := filepath.Join(home, ".config", "crush", "crush.json")
	data, err := os.ReadFile(configPath)
	if err != nil {
		return false
	}
	var cfg map[string]interface{}
	if err := json.Unmarshal(data, &cfg); err != nil {
		return false
	}
	mcps, ok := cfg["mcps"].(map[string]interface{})
	if !ok {
		return false
	}
	_, exists := mcps[name]
	return exists
}

func Export(name string, exportPath string) error {
	skill, err := Get(name)
	if err != nil {
		return err
	}

	if err := os.MkdirAll(filepath.Dir(exportPath), 0755); err != nil {
		return err
	}

	content := renderSkill(skill)
	return os.WriteFile(exportPath, []byte(content), 0644)
}

func Import(exportPath string) (*Skill, error) {
	data, err := os.ReadFile(exportPath)
	if err != nil {
		return nil, fmt.Errorf("read export file: %w", err)
	}

	skill, err := parseSkill(data)
	if err != nil {
		return nil, err
	}

	name := filepath.Base(filepath.Dir(exportPath))
	if skill.Name == "" {
		skill.Name = strings.TrimSuffix(filepath.Base(exportPath), ".md")
		if skill.Name == "SKILL" {
			skill.Name = filepath.Base(filepath.Dir(exportPath))
		}
	}

	_ = name
	if errs := Validate(skill); len(errs) > 0 {
		return nil, fmt.Errorf("validation failed: %v", errs)
	}

	return skill, nil
}

func DryRun(name string, sampleTask string) SkillTestResult {
	skill, err := Get(name)
	if err != nil {
		return SkillTestResult{Name: name, Passed: false, Message: fmt.Sprintf("skill not found: %s", err)}
	}

	if skill.Content == "" {
		return SkillTestResult{Name: name, Passed: false, Message: "skill has no content"}
	}

	if len(skill.Dependencies) > 0 {
		missing := CheckDependencies(skill)
		if len(missing) > 0 {
			var names []string
			for _, d := range missing {
				names = append(names, d.Name)
			}
			return SkillTestResult{
				Name:    name,
				Passed:  false,
				Message: fmt.Sprintf("missing dependencies: %s", strings.Join(names, ", ")),
			}
		}
	}

	if sampleTask != "" {
		tags := skill.Tags
		taskLower := strings.ToLower(sampleTask)
		matched := false
		for _, tag := range tags {
			if strings.Contains(taskLower, strings.ToLower(tag)) {
				matched = true
				break
			}
		}
		if len(tags) > 0 && !matched {
			return SkillTestResult{
				Name:    name,
				Passed:  true,
				Message: "skill loaded but sample task does not match skill tags",
			}
		}
	}

	return SkillTestResult{
		Name:    name,
		Passed:  true,
		Message: "skill validated successfully",
	}
}

func parseSkill(data []byte) (*Skill, error) {
	content := string(data)

	if !strings.HasPrefix(content, "---") {
		return &Skill{Content: content}, nil
	}

	end := strings.Index(content[3:], "---")
	if end == -1 {
		return &Skill{Content: content}, nil
	}

	frontmatter := content[3 : end+3]
	body := strings.TrimSpace(content[end+6:])

	skill := &Skill{Content: body}

	if err := yaml.Unmarshal([]byte(frontmatter), skill); err != nil {
		return &Skill{Content: content}, nil
	}

	return skill, nil
}

func renderSkill(skill *Skill) string {
	var b strings.Builder

	b.WriteString("---\n")
	b.WriteString(fmt.Sprintf("name: %s\n", skill.Name))
	b.WriteString(fmt.Sprintf("description: %s\n", skill.Description))
	if skill.Author != "" {
		b.WriteString(fmt.Sprintf("author: %s\n", skill.Author))
	}
	if skill.Version != "" {
		b.WriteString(fmt.Sprintf("version: %s\n", skill.Version))
	}
	if skill.Target != "" {
		b.WriteString(fmt.Sprintf("target: %s\n", skill.Target))
	}
	if skill.Category != "" {
		b.WriteString(fmt.Sprintf("category: %s\n", skill.Category))
	}
	if len(skill.Tags) > 0 {
		b.WriteString(fmt.Sprintf("tags: [%s]\n", strings.Join(skill.Tags, ", ")))
	}
	if len(skill.Languages) > 0 {
		b.WriteString(fmt.Sprintf("languages: [%s]\n", strings.Join(skill.Languages, ", ")))
	}
	if len(skill.Dependencies) > 0 {
		b.WriteString("dependencies:\n")
		for _, dep := range skill.Dependencies {
			req := ""
			if dep.Required {
				req = ", required: true"
			}
			b.WriteString(fmt.Sprintf("  - type: %s, name: %s%s\n", dep.Type, dep.Name, req))
		}
	}
	if len(skill.RequiresTools) > 0 {
		b.WriteString(fmt.Sprintf("requires_tools: [%s]\n", strings.Join(skill.RequiresTools, ", ")))
	}
	if len(skill.FallbackForTools) > 0 {
		b.WriteString(fmt.Sprintf("fallback_for_tools: [%s]\n", strings.Join(skill.FallbackForTools, ", ")))
	}
	if skill.AutoImprove {
		b.WriteString("auto_improve: true\n")
	}
	if skill.CreatedFrom != "" {
		b.WriteString(fmt.Sprintf("created_from: %s\n", skill.CreatedFrom))
	}
	if skill.ImprovementCount > 0 {
		b.WriteString(fmt.Sprintf("improvement_count: %d\n", skill.ImprovementCount))
	}
	if skill.LastImprovedAt != nil {
		b.WriteString(fmt.Sprintf("last_improved_at: %s\n", skill.LastImprovedAt.Format(time.RFC3339)))
	}
	b.WriteString("---\n\n")
	b.WriteString(skill.Content)
	b.WriteString("\n")

	return b.String()
}

func BuildAIGeneratePrompt(name, description, target string) string {
	return fmt.Sprintf(`Generate a skill file for an AI coding assistant.

SKILL NAME: %s
DESCRIPTION: %s
TARGET: %s (crush = Crush with GLM, claude = Claude Code, both = both tools)

The skill must follow this EXACT format:
1. YAML frontmatter with: name, description, tags, dependencies (if needed)
2. Markdown body with detailed instructions

The skill should be practical, specific, and actionable.
Include:
- When to activate this skill
- Step-by-step instructions
- Examples where relevant
- Error handling guidance

If the skill requires specific tools, MCP servers, or LSP servers, declare them as dependencies:
  - type: mcp_server, name: <server-name>
  - type: lsp, name: <language-server-name>
  - type: tool, name: <tool-name>

Output ONLY the skill file content, starting with ---`, name, description, target)
}
