package skills

import (
	"fmt"
	"sort"
	"strings"
)

type Candidate struct {
	ID          string
	Label       string
	Description string
	Installed   bool
}

type Finder interface {
	Find(prompt string) []Candidate
}

type Installer interface {
	Install(candidates []Candidate) ([]Candidate, error)
}

type ResolveRequest struct {
	Prompt            string
	ExplicitSkills    []string
	AvailableSkills   []Candidate
	AllowSkillInstall bool
	InstallApproval   InstallApproval
}

type InstallApproval struct {
	RequestID         string
	ApprovedSkillKeys []string
}

type ResolveResult struct {
	ResolvedSkills   []string
	Candidates       []Candidate
	Source           string
	NeedsInstall     bool
	InstallRequestID string
}

type StaticFinder struct{}

func (StaticFinder) Find(prompt string) []Candidate {
	haystack := normalize(prompt)
	candidates := make([]Candidate, 0, 4)
	for _, entry := range builtinCatalog {
		if !containsAny(haystack, entry.keywords) {
			continue
		}
		candidates = append(candidates, Candidate{
			ID:        entry.id,
			Label:     entry.label,
			Installed: false,
		})
	}
	return dedupeCandidates(candidates)
}

func Resolve(req ResolveRequest, finder Finder, installer Installer) ResolveResult {
	available := dedupeCandidates(req.AvailableSkills)
	explicit := normalizeList(req.ExplicitSkills)
	if len(explicit) > 0 {
		return ResolveResult{
			ResolvedSkills: explicit,
			Source:         "local_match",
		}
	}

	localMatches := matchLocalSkills(req.Prompt, available)
	if len(localMatches) > 0 {
		return ResolveResult{
			ResolvedSkills: localMatches,
			Source:         "local_match",
		}
	}

	if finder == nil {
		return ResolveResult{Source: "none"}
	}

	fallback := dedupeCandidates(finder.Find(req.Prompt))
	if len(fallback) == 0 {
		return ResolveResult{Source: "none"}
	}

	installed := make([]string, 0, len(fallback))
	uninstalled := make([]Candidate, 0, len(fallback))
	for _, candidate := range fallback {
		if matched := findInstalledMatch(candidate, available); matched != "" {
			installed = append(installed, matched)
			continue
		}
		uninstalled = append(uninstalled, candidate)
	}

	if len(installed) > 0 {
		return ResolveResult{
			ResolvedSkills: dedupeStrings(installed),
			Candidates:     fallback,
			Source:         "find_skills",
		}
	}

	installRequestID := buildInstallRequestID(uninstalled)
	if shouldInstallApprovedCandidates(req, installRequestID) &&
		installer != nil &&
		len(uninstalled) > 0 {
		approvedCandidates := filterApprovedCandidates(
			uninstalled,
			req.InstallApproval.ApprovedSkillKeys,
		)
		if len(approvedCandidates) == 0 {
			return ResolveResult{
				Candidates:       fallback,
				Source:           "find_skills",
				NeedsInstall:     true,
				InstallRequestID: installRequestID,
			}
		}
		installedCandidates, err := installer.Install(approvedCandidates)
		if err == nil && len(installedCandidates) > 0 {
			mergedAvailable := dedupeCandidates(
				append(append([]Candidate(nil), available...), installedCandidates...),
			)
			if resolved := installedMatches(fallback, mergedAvailable); len(resolved) > 0 {
				return ResolveResult{
					ResolvedSkills: resolved,
					Candidates: dedupeCandidates(
						append(append([]Candidate(nil), fallback...), installedCandidates...),
					),
					Source: "find_skills",
				}
			}
		}
	}

	return ResolveResult{
		Candidates:       fallback,
		Source:           "find_skills",
		NeedsInstall:     len(uninstalled) > 0,
		InstallRequestID: installRequestID,
	}
}

func shouldInstallApprovedCandidates(
	req ResolveRequest,
	expectedRequestID string,
) bool {
	if !req.AllowSkillInstall || expectedRequestID == "" {
		return false
	}
	if strings.TrimSpace(req.InstallApproval.RequestID) != expectedRequestID {
		return false
	}
	return len(dedupeStrings(req.InstallApproval.ApprovedSkillKeys)) > 0
}

func filterApprovedCandidates(
	candidates []Candidate,
	approvedSkillKeys []string,
) []Candidate {
	if len(candidates) == 0 {
		return nil
	}
	approved := make(map[string]struct{}, len(approvedSkillKeys))
	for _, key := range approvedSkillKeys {
		normalized := normalize(key)
		if normalized == "" {
			continue
		}
		approved[normalized] = struct{}{}
	}
	filtered := make([]Candidate, 0, len(candidates))
	for _, candidate := range candidates {
		if _, ok := approved[normalize(candidate.ID)]; ok {
			filtered = append(filtered, candidate)
		}
	}
	return filtered
}

func buildInstallRequestID(candidates []Candidate) string {
	if len(candidates) == 0 {
		return ""
	}
	keys := make([]string, 0, len(candidates))
	for _, candidate := range candidates {
		key := normalize(candidate.ID)
		if key == "" {
			key = normalize(candidate.Label)
		}
		if key != "" {
			keys = append(keys, key)
		}
	}
	if len(keys) == 0 {
		return ""
	}
	sort.Strings(keys)
	return "skill-install:" + strings.Join(keys, ",")
}

type builtinSkill struct {
	id       string
	label    string
	keywords []string
}

var builtinCatalog = []builtinSkill{
	{id: "pptx", label: "pptx", keywords: []string{"ppt", "pptx", "powerpoint", "slides", "幻灯片", "演示文稿"}},
	{id: "docx", label: "docx", keywords: []string{"docx", "word", "word document", "文档"}},
	{id: "xlsx", label: "xlsx", keywords: []string{"xlsx", "excel", "spreadsheet", "表格", "工作表"}},
	{id: "pdf", label: "pdf", keywords: []string{"pdf", "表单", "merge pdf", "split pdf"}},
	{id: "image-resizer", label: "image-resizer", keywords: []string{"image-resizer", "resize image", "compress image", "crop image", "批量图片"}},
	{id: "image-cog", label: "image-cog", keywords: []string{"image-cog", "文生图", "图生图", "角色一致性"}},
	{id: "image-video-generation-editting", label: "image-video-generation-editting", keywords: []string{"wan", "文生视频", "图生视频", "视频生成", "视频编辑"}},
	{id: "video-translator", label: "video-translator", keywords: []string{"video-translator", "视频翻译", "配音", "字幕翻译", "translate video", "dub video", "subtitles"}},
	{id: "browser-automation", label: "Browser Automation", keywords: []string{"browser", "跨浏览器", "浏览器", "web scraping", "资讯采集", "search", "搜索", "news", "资讯"}},
	{id: "find-skills", label: "find_skills", keywords: []string{"find skills", "find_skills", "技能包", "skill package"}},
}

func matchLocalSkills(prompt string, available []Candidate) []string {
	if len(available) == 0 {
		return nil
	}
	haystack := normalize(prompt)
	if haystack == "" {
		return nil
	}

	matches := make([]string, 0, len(available))
	for _, candidate := range available {
		keywords := candidateKeywords(candidate)
		if containsAny(haystack, keywords) {
			matches = append(matches, candidateLabel(candidate))
		}
	}
	return dedupeStrings(matches)
}

func candidateKeywords(candidate Candidate) []string {
	base := []string{
		normalize(candidate.ID),
		normalize(candidate.Label),
	}
	text := normalize(strings.Join([]string{candidate.ID, candidate.Label}, " "))
	for _, entry := range builtinCatalog {
		if containsAny(text, []string{normalize(entry.id), normalize(entry.label)}) {
			base = append(base, entry.keywords...)
		}
	}
	return dedupeStrings(base)
}

func findInstalledMatch(candidate Candidate, available []Candidate) string {
	want := candidateKeywords(candidate)
	for _, item := range available {
		if containsAny(strings.Join(candidateKeywords(item), " "), want) {
			return candidateLabel(item)
		}
	}
	return ""
}

func installedMatches(candidates []Candidate, available []Candidate) []string {
	resolved := make([]string, 0, len(candidates))
	for _, candidate := range candidates {
		if matched := findInstalledMatch(candidate, available); matched != "" {
			resolved = append(resolved, matched)
		}
	}
	return dedupeStrings(resolved)
}

func candidateLabel(candidate Candidate) string {
	if strings.TrimSpace(candidate.Label) != "" {
		return strings.TrimSpace(candidate.Label)
	}
	return strings.TrimSpace(candidate.ID)
}

func containsAny(haystack string, needles []string) bool {
	for _, needle := range needles {
		if strings.TrimSpace(needle) == "" {
			continue
		}
		if strings.Contains(haystack, normalize(needle)) {
			return true
		}
	}
	return false
}

func normalize(value string) string {
	return strings.ToLower(strings.TrimSpace(value))
}

func normalizeList(values []string) []string {
	result := make([]string, 0, len(values))
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		result = append(result, trimmed)
	}
	return dedupeStrings(result)
}

func dedupeStrings(values []string) []string {
	if len(values) == 0 {
		return nil
	}
	seen := make(map[string]string, len(values))
	ordered := make([]string, 0, len(values))
	for _, value := range values {
		trimmed := strings.TrimSpace(value)
		if trimmed == "" {
			continue
		}
		key := normalize(trimmed)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = trimmed
		ordered = append(ordered, trimmed)
	}
	return ordered
}

func dedupeCandidates(values []Candidate) []Candidate {
	if len(values) == 0 {
		return nil
	}
	seen := make(map[string]struct{}, len(values))
	ordered := make([]Candidate, 0, len(values))
	for _, candidate := range values {
		key := normalize(fmt.Sprintf("%s|%s", candidate.ID, candidate.Label))
		if key == "|" {
			continue
		}
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		ordered = append(ordered, candidate)
	}
	return ordered
}
