package presets

import (
	"crypto/sha256"
	"embed"
	"encoding/hex"
	"errors"
	"fmt"
	"io/fs"
	"path"
	"sort"
	"strings"
	"testing/fstest"

	"gopkg.in/yaml.v3"
)

// SkillLibraryInfo is the resolved metadata for one AgentTeam-maintained official
// Skill library.
type SkillLibraryInfo struct {
	Slug              string
	Name              string
	Description       string
	SystemSubject     string
	Visibility        string
	ArchiveName       string
	TemplateLocale    string
	SkillSlugs        []string
	AutoAgentAccess   bool
	SourceRepo        string
	SourceCommit      string
	License           string
	Notice            string
	SkippedSkillSlugs []string
	SourceFingerprint string
}

// SkillContentInfo is the resolved content and frontmatter metadata for one
// embedded official Skill.
type SkillContentInfo struct {
	LibrarySlug string
	SkillSlug   string
	Description string
	Content     string
}

type skillLibraryDefinition struct {
	Slug              string            `yaml:"slug"`
	Name              map[string]string `yaml:"name"`
	Description       map[string]string `yaml:"description"`
	SystemSubject     string            `yaml:"system_subject"`
	Visibility        string            `yaml:"visibility"`
	ArchiveName       string            `yaml:"archive_name"`
	SkillSlugs        []string          `yaml:"skill_slugs"`
	AutoAgentAccess   *bool             `yaml:"auto_agent_access"`
	SourceRepo        string            `yaml:"source_repo"`
	SourceCommit      string            `yaml:"source_commit"`
	License           string            `yaml:"license"`
	Notice            string            `yaml:"notice"`
	SkippedSkillSlugs []string          `yaml:"skipped_skill_slugs"`
	AssetFingerprint  string            `yaml:"-"`
}

type skillLibraryPreset struct {
	root string
}

//go:embed assets/skills
var skillPresetFS embed.FS

var officialSkillLibraryPresets = []skillLibraryPreset{
	{root: "assets/skills/workflow-plan-author"},
	{root: "assets/skills/superpowers"},
	{root: "assets/skills/addy-agent-skills"},
}

// OfficialSkillLibraries returns every AgentTeam-maintained official Skill library
// in stable display order, resolved for the requested locale.
func OfficialSkillLibraries(locale string) ([]SkillLibraryInfo, error) {
	out := make([]SkillLibraryInfo, 0, len(officialSkillLibraryPresets))
	for _, preset := range officialSkillLibraryPresets {
		def, err := loadSkillLibraryDefinition(preset)
		if err != nil {
			return nil, err
		}
		out = append(out, resolveSkillLibraryInfo(def, locale))
	}
	return out, nil
}

// OfficialSkillLibrary returns one AgentTeam-maintained official Skill library by
// its stable slug, resolved for the requested locale.
func OfficialSkillLibrary(slug, locale string) (SkillLibraryInfo, error) {
	_, def, err := findSkillLibraryDefinition(slug)
	if err != nil {
		return SkillLibraryInfo{}, err
	}
	return resolveSkillLibraryInfo(def, locale), nil
}

// OfficialSkillLibraryFS returns a virtual filesystem containing the localized
// files for one official Skill library, rooted at "skills/".
func OfficialSkillLibraryFS(slug, locale string) (fs.FS, error) {
	preset, def, err := findSkillLibraryDefinition(slug)
	if err != nil {
		return nil, err
	}
	resolved := NormalizeLocale(locale)
	wantDir := localeDir(resolved)
	root := path.Join(preset.root, "skills")
	out := fstest.MapFS{}
	priorities := map[string]int{}
	walkErr := fs.WalkDir(skillPresetFS, root, func(p string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}
		rel := strings.TrimPrefix(p, root+"/")
		parts := strings.SplitN(rel, "/", 3)
		if len(parts) < 3 {
			return nil
		}
		skillSlug, fileLocale, rest := parts[0], parts[1], parts[2]
		if !containsString(def.SkillSlugs, skillSlug) {
			return nil
		}
		priority := 0
		switch fileLocale {
		case wantDir:
			priority = 2
		case "en":
			priority = 1
		}
		if priority == 0 {
			return nil
		}
		outPath := "skills/" + skillSlug + "/" + rest
		if previous := priorities[outPath]; previous >= priority {
			return nil
		}
		content, err := skillPresetFS.ReadFile(p)
		if err != nil {
			return err
		}
		priorities[outPath] = priority
		out[outPath] = &fstest.MapFile{Data: content}
		return nil
	})
	if walkErr != nil {
		return nil, walkErr
	}
	if len(out) == 0 {
		return nil, errors.New("no skill files embedded for locale " + resolved)
	}
	for _, slug := range def.SkillSlugs {
		entry := "skills/" + slug + "/SKILL.md"
		if _, ok := out[entry]; !ok {
			return nil, fmt.Errorf("skill file %q is not embedded for locale %q", entry, resolved)
		}
	}
	return out, nil
}

// OfficialSkillContent returns one embedded official Skill as a portable
// content item. The manifest item name is always the library skill slug rather
// than any upstream frontmatter alias.
func OfficialSkillContent(librarySlug, skillSlug, locale string) (SkillContentInfo, error) {
	librarySlug = strings.TrimSpace(librarySlug)
	skillSlug = strings.TrimSpace(skillSlug)
	if librarySlug == "" || skillSlug == "" {
		return SkillContentInfo{}, errors.New("official skill library and skill slug are required")
	}
	slugs, err := OfficialSkillSlugs(librarySlug)
	if err != nil {
		return SkillContentInfo{}, err
	}
	if !containsString(slugs, skillSlug) {
		return SkillContentInfo{}, fmt.Errorf("official skill %q is not defined in library %q", skillSlug, librarySlug)
	}
	skillFS, err := OfficialSkillLibraryFS(librarySlug, locale)
	if err != nil {
		return SkillContentInfo{}, err
	}
	raw, err := fs.ReadFile(skillFS, "skills/"+skillSlug+"/SKILL.md")
	if err != nil {
		return SkillContentInfo{}, err
	}
	description, err := skillFrontmatterDescription(string(raw))
	if err != nil {
		return SkillContentInfo{}, fmt.Errorf("read official skill %q frontmatter: %w", skillSlug, err)
	}
	return SkillContentInfo{
		LibrarySlug: librarySlug,
		SkillSlug:   skillSlug,
		Description: description,
		Content:     string(raw),
	}, nil
}

// OfficialSkillSlugs returns the canonical Skill slugs for one official
// library in manifest order.
func OfficialSkillSlugs(slug string) ([]string, error) {
	_, def, err := findSkillLibraryDefinition(slug)
	if err != nil {
		return nil, err
	}
	return append([]string(nil), def.SkillSlugs...), nil
}

func findSkillLibraryDefinition(slug string) (skillLibraryPreset, *skillLibraryDefinition, error) {
	for _, preset := range officialSkillLibraryPresets {
		def, err := loadSkillLibraryDefinition(preset)
		if err != nil {
			return skillLibraryPreset{}, nil, err
		}
		if def.Slug == slug {
			return preset, def, nil
		}
	}
	return skillLibraryPreset{}, nil, fmt.Errorf("official skill library %q is not defined", slug)
}

func loadSkillLibraryDefinition(preset skillLibraryPreset) (*skillLibraryDefinition, error) {
	raw, err := skillPresetFS.ReadFile(path.Join(preset.root, "library.yaml"))
	if err != nil {
		return nil, err
	}
	var def skillLibraryDefinition
	if err := yaml.Unmarshal(raw, &def); err != nil {
		return nil, err
	}
	if def.Slug == "" {
		return nil, errors.New("skill library slug is required")
	}
	fingerprint, err := skillLibraryAssetFingerprint(preset, &def)
	if err != nil {
		return nil, err
	}
	def.AssetFingerprint = fingerprint
	return &def, nil
}

func resolveSkillLibraryInfo(def *skillLibraryDefinition, locale string) SkillLibraryInfo {
	resolved := NormalizeLocale(locale)
	autoAgentAccess := true
	if def.AutoAgentAccess != nil {
		autoAgentAccess = *def.AutoAgentAccess
	}
	return SkillLibraryInfo{
		Slug:              def.Slug,
		Name:              pickLocale(def.Name, resolved),
		Description:       pickLocale(def.Description, resolved),
		SystemSubject:     def.SystemSubject,
		Visibility:        def.Visibility,
		ArchiveName:       def.ArchiveName,
		TemplateLocale:    resolved,
		SkillSlugs:        append([]string(nil), def.SkillSlugs...),
		AutoAgentAccess:   autoAgentAccess,
		SourceRepo:        strings.TrimSpace(def.SourceRepo),
		SourceCommit:      strings.TrimSpace(def.SourceCommit),
		License:           strings.TrimSpace(def.License),
		Notice:            strings.TrimSpace(def.Notice),
		SkippedSkillSlugs: append([]string(nil), def.SkippedSkillSlugs...),
		SourceFingerprint: skillLibrarySourceFingerprint(def),
	}
}

func skillLibrarySourceFingerprint(def *skillLibraryDefinition) string {
	hash := sha256.New()
	writeHashPart(hash, "slug", def.Slug)
	writeHashPart(hash, "source_repo", def.SourceRepo)
	writeHashPart(hash, "source_commit", def.SourceCommit)
	writeHashPart(hash, "archive_name", def.ArchiveName)
	writeHashPart(hash, "asset_fingerprint", def.AssetFingerprint)
	for _, slug := range def.SkillSlugs {
		writeHashPart(hash, "skill", slug)
	}
	for _, slug := range def.SkippedSkillSlugs {
		writeHashPart(hash, "skipped", slug)
	}
	return "sha256:" + hex.EncodeToString(hash.Sum(nil))
}

func skillLibraryAssetFingerprint(preset skillLibraryPreset, def *skillLibraryDefinition) (string, error) {
	root := path.Join(preset.root, "skills")
	var files []string
	if err := fs.WalkDir(skillPresetFS, root, func(p string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if d.IsDir() {
			return nil
		}
		rel := strings.TrimPrefix(p, root+"/")
		parts := strings.SplitN(rel, "/", 2)
		if len(parts) != 2 || !containsString(def.SkillSlugs, parts[0]) {
			return nil
		}
		files = append(files, p)
		return nil
	}); err != nil {
		return "", err
	}
	sort.Strings(files)
	hash := sha256.New()
	for _, file := range files {
		content, err := skillPresetFS.ReadFile(file)
		if err != nil {
			return "", err
		}
		writeHashPart(hash, "file", file)
		_, _ = hash.Write(content)
		_, _ = hash.Write([]byte{0})
	}
	return hex.EncodeToString(hash.Sum(nil)), nil
}

func writeHashPart(hash interface{ Write([]byte) (int, error) }, key, value string) {
	_, _ = hash.Write([]byte(key))
	_, _ = hash.Write([]byte{0})
	_, _ = hash.Write([]byte(strings.TrimSpace(value)))
	_, _ = hash.Write([]byte{0})
}

func skillFrontmatterDescription(content string) (string, error) {
	normalized := strings.ReplaceAll(strings.ReplaceAll(content, "\r\n", "\n"), "\r", "\n")
	const delimiter = "---\n"
	if !strings.HasPrefix(normalized, delimiter) {
		return "", errors.New("SKILL.md frontmatter is required")
	}
	end := strings.Index(normalized[len(delimiter):], "\n---")
	if end < 0 {
		return "", errors.New("SKILL.md frontmatter terminator is required")
	}
	frontmatter := normalized[len(delimiter) : len(delimiter)+end]
	var parsed struct {
		Description string `yaml:"description"`
	}
	if err := yaml.Unmarshal([]byte(frontmatter), &parsed); err != nil {
		return "", err
	}
	description := strings.TrimSpace(parsed.Description)
	if description == "" {
		return "", errors.New("SKILL.md description is required")
	}
	return description, nil
}

func containsString(values []string, target string) bool {
	for _, v := range values {
		if v == target {
			return true
		}
	}
	return false
}
