package presets

import (
	"embed"
	"errors"
	"fmt"
	"io/fs"
	"path"
	"sort"
	"strings"

	"gopkg.in/yaml.v3"
)

// TeamTemplate is one localized built-in Team template.
type TeamTemplate struct {
	ID          string
	Slug        string
	Name        string
	Description string
	Visibility  string
	Scope       string
	Members     []TeamTemplateMember
}

// TeamTemplateMember is one localized member slot in a Team template.
type TeamTemplateMember struct {
	ID                    string
	ActorKind             string
	Role                  string
	RoleLabel             string
	RoleOverlayPrompt     string
	PresetID              string
	PresetVersion         string
	RoleRequired          bool
	RoleRealizationPolicy string
	WakeOnTeamMention     bool
	SortOrder             int64
}

type teamTemplateMeta struct {
	ID         string                  `yaml:"id"`
	Slug       string                  `yaml:"slug"`
	Visibility string                  `yaml:"visibility"`
	Scope      string                  `yaml:"scope"`
	Members    []teamTemplateMemberDef `yaml:"members"`
}

type teamTemplateMemberDef struct {
	ID                    string `yaml:"id"`
	File                  string `yaml:"file"`
	ActorKind             string `yaml:"actor_kind"`
	Role                  string `yaml:"role"`
	PresetID              string `yaml:"preset_id"`
	PresetVersion         string `yaml:"preset_version"`
	RoleRequired          *bool  `yaml:"role_required"`
	RoleRealizationPolicy string `yaml:"role_realization_policy"`
	WakeOnTeamMention     *bool  `yaml:"wake_on_team_mention"`
	SortOrder             int64  `yaml:"sort_order"`
}

type teamTemplateLocaleDoc struct {
	Name        string `yaml:"name"`
	Description string `yaml:"description"`
}

type teamMemberLocaleDoc struct {
	RoleLabel string `yaml:"role_label"`
}

//go:embed assets/team
var teamPresetFS embed.FS

const (
	teamPresetRoot = "assets/team"

	teamTemplateFile = "template.md"
	teamMembersDir   = "members"
	teamMetaFile     = "meta.yaml"
	teamDefaultScope = "workspace"
	teamDefaultVis   = "workspace"

	teamRoleRealizationCreateAgent      = "create_agent"
	teamRoleRealizationBindExisting     = "bind_existing"
	teamRoleRealizationOptionalExisting = "optional_existing"
)

// LoadTeamTemplates returns the localized built-in Team templates, sorted by
// slug. Locale is normalized via NormalizeLocale; missing localized fields
// fall back to English, then to the first available locale.
func LoadTeamTemplates(locale string) ([]TeamTemplate, error) {
	resolved := NormalizeLocale(locale)
	slugs, err := listTeamTemplateSlugs()
	if err != nil {
		return nil, err
	}
	out := make([]TeamTemplate, 0, len(slugs))
	for _, slug := range slugs {
		template, err := loadTeamTemplate(slug, resolved)
		if err != nil {
			return nil, fmt.Errorf("load team template %q: %w", slug, err)
		}
		out = append(out, template)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].Slug < out[j].Slug })
	return out, nil
}

// TeamTemplateByID returns one localized built-in Team template by its ID.
func TeamTemplateByID(id, locale string) (TeamTemplate, error) {
	resolved := NormalizeLocale(locale)
	slugs, err := listTeamTemplateSlugs()
	if err != nil {
		return TeamTemplate{}, err
	}
	for _, slug := range slugs {
		meta, err := loadTeamTemplateMeta(slug)
		if err != nil {
			return TeamTemplate{}, err
		}
		if meta.ID != id {
			continue
		}
		return loadTeamTemplate(slug, resolved)
	}
	return TeamTemplate{}, fmt.Errorf("team template %q not found", id)
}

// TeamTemplateSlugs returns the canonical template slug list in definition
// order. This is locale-independent.
func TeamTemplateSlugs() ([]string, error) {
	return listTeamTemplateSlugs()
}

func listTeamTemplateSlugs() ([]string, error) {
	entries, err := fs.ReadDir(teamPresetFS, teamPresetRoot)
	if err != nil {
		return nil, fmt.Errorf("read team preset root: %w", err)
	}
	var slugs []string
	for _, entry := range entries {
		if !entry.IsDir() {
			continue
		}
		name := entry.Name()
		if strings.HasPrefix(name, ".") {
			continue
		}
		if _, err := teamPresetFS.ReadFile(path.Join(teamPresetRoot, name, teamMetaFile)); err != nil {
			continue
		}
		slugs = append(slugs, name)
	}
	sort.Strings(slugs)
	return slugs, nil
}

func loadTeamTemplate(slug, locale string) (TeamTemplate, error) {
	meta, err := loadTeamTemplateMeta(slug)
	if err != nil {
		return TeamTemplate{}, err
	}
	doc, err := loadTeamTemplateLocaleDoc(slug, locale)
	if err != nil {
		return TeamTemplate{}, err
	}
	members := make([]TeamTemplateMember, 0, len(meta.Members))
	for _, def := range meta.Members {
		member, err := loadTeamTemplateMember(slug, def, locale)
		if err != nil {
			return TeamTemplate{}, err
		}
		members = append(members, member)
	}
	scope := strings.TrimSpace(meta.Scope)
	if scope == "" {
		scope = teamDefaultScope
	}
	visibility := strings.TrimSpace(meta.Visibility)
	if visibility == "" {
		visibility = teamDefaultVis
	}
	name := strings.TrimSpace(doc.Name)
	if name == "" {
		name = meta.Slug
	}
	return TeamTemplate{
		ID:          meta.ID,
		Slug:        meta.Slug,
		Name:        name,
		Description: doc.Description,
		Visibility:  visibility,
		Scope:       scope,
		Members:     members,
	}, nil
}

func loadTeamTemplateMeta(slug string) (*teamTemplateMeta, error) {
	raw, err := teamPresetFS.ReadFile(path.Join(teamPresetRoot, slug, teamMetaFile))
	if err != nil {
		return nil, fmt.Errorf("read team meta: %w", err)
	}
	return parseTeamTemplateMeta(slug, raw)
}

func parseTeamTemplateMeta(slug string, raw []byte) (*teamTemplateMeta, error) {
	var meta teamTemplateMeta
	if err := yaml.Unmarshal(raw, &meta); err != nil {
		return nil, fmt.Errorf("parse team meta: %w", err)
	}
	if strings.TrimSpace(meta.ID) == "" || strings.TrimSpace(meta.Slug) == "" {
		return nil, errors.New("team template id and slug are required")
	}
	for _, member := range meta.Members {
		if err := validateTeamTemplateMemberMeta(slug, member); err != nil {
			return nil, err
		}
	}
	return &meta, nil
}

func validateTeamTemplateMemberMeta(slug string, def teamTemplateMemberDef) error {
	memberID := strings.TrimSpace(def.ID)
	if memberID == "" {
		return fmt.Errorf("team template %q member id is required", slug)
	}
	if strings.TrimSpace(def.PresetID) == "" {
		return fmt.Errorf("team template %q member %q preset_id is required", slug, memberID)
	}
	if strings.TrimSpace(def.PresetVersion) == "" {
		return fmt.Errorf("team template %q member %q preset_version is required", slug, memberID)
	}
	if def.RoleRequired == nil {
		return fmt.Errorf("team template %q member %q role_required is required", slug, memberID)
	}
	switch strings.TrimSpace(def.RoleRealizationPolicy) {
	case teamRoleRealizationCreateAgent, teamRoleRealizationBindExisting, teamRoleRealizationOptionalExisting:
		return nil
	default:
		return fmt.Errorf("team template %q member %q role_realization_policy must be create_agent, bind_existing, or optional_existing", slug, memberID)
	}
}

func loadTeamTemplateLocaleDoc(slug, locale string) (teamTemplateLocaleDoc, error) {
	resolvedDir := localeDir(locale)
	doc, err := readTeamTemplateLocaleDoc(slug, resolvedDir)
	if err != nil && resolvedDir != localeDir(LocaleEnglish) {
		doc, err = readTeamTemplateLocaleDoc(slug, localeDir(LocaleEnglish))
	}
	return doc, err
}

func readTeamTemplateLocaleDoc(slug, dir string) (teamTemplateLocaleDoc, error) {
	filePath := path.Join(teamPresetRoot, slug, dir, teamTemplateFile)
	raw, err := teamPresetFS.ReadFile(filePath)
	if err != nil {
		return teamTemplateLocaleDoc{}, fmt.Errorf("read team locale doc: %w", err)
	}
	front, _, err := splitMarkdownFrontMatter(raw)
	if err != nil {
		return teamTemplateLocaleDoc{}, fmt.Errorf("parse team locale doc %q: %w", filePath, err)
	}
	var doc teamTemplateLocaleDoc
	if err := yaml.Unmarshal(front, &doc); err != nil {
		return teamTemplateLocaleDoc{}, fmt.Errorf("parse team locale front matter %q: %w", filePath, err)
	}
	return doc, nil
}

func loadTeamTemplateMember(slug string, def teamTemplateMemberDef, locale string) (TeamTemplateMember, error) {
	if strings.TrimSpace(def.ID) == "" {
		return TeamTemplateMember{}, errors.New("team template member id is required")
	}
	fileSlug := strings.TrimSpace(def.File)
	if fileSlug == "" {
		fileSlug = def.ID
	}
	resolvedDir := localeDir(locale)
	label, body, err := readTeamMemberDoc(slug, fileSlug, resolvedDir)
	if err != nil && resolvedDir != localeDir(LocaleEnglish) {
		label, body, err = readTeamMemberDoc(slug, fileSlug, localeDir(LocaleEnglish))
	}
	if err != nil {
		return TeamTemplateMember{}, fmt.Errorf("member %q: %w", def.ID, err)
	}
	wake := true
	if def.WakeOnTeamMention != nil {
		wake = *def.WakeOnTeamMention
	}
	if def.RoleRequired == nil {
		return TeamTemplateMember{}, fmt.Errorf("member %q role_required is required", def.ID)
	}
	return TeamTemplateMember{
		ID:                    strings.TrimSpace(def.ID),
		ActorKind:             strings.TrimSpace(def.ActorKind),
		Role:                  strings.TrimSpace(def.Role),
		RoleLabel:             label,
		RoleOverlayPrompt:     body,
		PresetID:              strings.TrimSpace(def.PresetID),
		PresetVersion:         strings.TrimSpace(def.PresetVersion),
		RoleRequired:          *def.RoleRequired,
		RoleRealizationPolicy: strings.TrimSpace(def.RoleRealizationPolicy),
		WakeOnTeamMention:     wake,
		SortOrder:             def.SortOrder,
	}, nil
}

func readTeamMemberDoc(slug, memberID, dir string) (string, string, error) {
	filePath := path.Join(teamPresetRoot, slug, dir, teamMembersDir, memberID+".md")
	raw, err := teamPresetFS.ReadFile(filePath)
	if err != nil {
		return "", "", fmt.Errorf("read member doc: %w", err)
	}
	front, body, err := splitMarkdownFrontMatter(raw)
	if err != nil {
		return "", "", fmt.Errorf("parse member doc %q: %w", filePath, err)
	}
	var doc teamMemberLocaleDoc
	if err := yaml.Unmarshal(front, &doc); err != nil {
		return "", "", fmt.Errorf("parse member front matter %q: %w", filePath, err)
	}
	return strings.TrimSpace(doc.RoleLabel), strings.TrimSpace(body), nil
}

// splitMarkdownFrontMatter splits a Markdown file into its YAML front matter
// block and the remaining body. Files without front matter return an empty
// front slice and the full body.
func splitMarkdownFrontMatter(raw []byte) (front []byte, body string, err error) {
	normalized := strings.ReplaceAll(string(raw), "\r\n", "\n")
	if !strings.HasPrefix(normalized, "---\n") {
		return nil, strings.TrimSpace(normalized), nil
	}
	rest := strings.TrimPrefix(normalized, "---\n")
	end := strings.Index(rest, "\n---\n")
	if end < 0 {
		return nil, "", errors.New("unterminated markdown front matter")
	}
	return []byte(rest[:end]), strings.TrimSpace(rest[end+len("\n---\n"):]), nil
}
