package onboard

import (
	"fmt"
	"net/url"
	"strconv"
	"strings"
	"sync"

	"github.com/gitlink-org/gitlink-cli/shortcuts/common"
)

// marker is a unique string embedded in welcome comments to detect existing ones.
const marker = "<!-- gitlink-cli:onboard -->"

// tagCache caches tag name→id mappings per owner/repo.
var tagCache sync.Map

// Shortcuts returns the onboarding shortcut group.
func Shortcuts() []*common.Shortcut {
	return []*common.Shortcut{
		{
			Name:        "welcome",
			Description: "Add welcome comments to specific issues or tag-matched issues",
			Flags: []common.Flag{
				{Name: "issues", Short: "i", Usage: "Comma-separated issue numbers (e.g. 1,3,7)"},
				{Name: "tag", Short: "t", Usage: "Tag name to match (comma-separated)", Default: "good first issue,help wanted"},
				{Name: "template", Usage: "Custom welcome message template ({login}, {number}, {subject}, {description})"},
				{Name: "force", Short: "f", Usage: "Force re-add even if already commented", Bool: true},
			},
			DryRun:     true,
			DryRunHint: dryRunHint,
			Run:        runWelcome,
		},
	}
}

func dryRunHint(ctx *common.RuntimeContext) (string, error) {
	if issues := ctx.Arg("issues"); issues != "" {
		return fmt.Sprintf("将为 issue #%s 添加新人引导评论", issues), nil
	}
	tag := ctx.Arg("tag")
	if tag == "" {
		tag = "good first issue,help wanted"
	}
	return fmt.Sprintf("将为所有 [%s] 标签的 issue 添加新人引导评论", tag), nil
}

func runWelcome(ctx *common.RuntimeContext) error {
	if err := ctx.ResolveOwnerRepo(); err != nil {
		return err
	}

	// --issues takes priority over --tag
	if issueArg := ctx.Arg("issues"); issueArg != "" {
		return runWelcomeByIssueNumbers(ctx, issueArg)
	}

	tagNames := ctx.Arg("tag")
	if tagNames == "" {
		tagNames = "good first issue,help wanted"
	}

	// resolve tag names → ids
	tagMap, err := resolveTags(ctx)
	if err != nil {
		return err
	}

	var tagIDs []string
	for _, name := range strings.Split(tagNames, ",") {
		name = strings.TrimSpace(name)
		if id, ok := tagMap[name]; ok {
			tagIDs = append(tagIDs, strconv.Itoa(id))
		}
	}
	if len(tagIDs) == 0 {
		return fmt.Errorf("未找到匹配的标签: %s (可用: %v)", tagNames, tagNamesList(tagMap))
	}

	// fetch open issues with these tags
	issues, err := fetchTaggedIssues(ctx, tagIDs)
	if err != nil {
		return err
	}

	return processIssues(ctx, issues)
}

// runWelcomeByIssueNumbers directly processes specified issue numbers,
// skipping tag resolution and tag-based issue fetching.
func runWelcomeByIssueNumbers(ctx *common.RuntimeContext, issueArg string) error {
	issueNums, err := parseIssueNumbers(issueArg)
	if err != nil {
		return err
	}

	var issues []issueInfo
	for _, num := range issueNums {
		info, err := fetchIssueDetail(ctx, num)
		if err != nil {
			return fmt.Errorf("获取 issue #%d 失败: %w", num, err)
		}
		issues = append(issues, info)
	}

	return processIssues(ctx, issues)
}

// parseIssueNumbers parses a comma-separated string of issue numbers.
func parseIssueNumbers(s string) ([]int, error) {
	parts := strings.Split(s, ",")
	var nums []int
	for _, p := range parts {
		p = strings.TrimSpace(p)
		if p == "" {
			continue
		}
		n, err := strconv.Atoi(p)
		if err != nil {
			return nil, fmt.Errorf("无效的 issue 编号: %q (必须是数字)", p)
		}
		nums = append(nums, n)
	}
	if len(nums) == 0 {
		return nil, fmt.Errorf("--issues 参数为空")
	}
	return nums, nil
}

// fetchIssueDetail fetches a single issue's subject and description by number.
func fetchIssueDetail(ctx *common.RuntimeContext, issueNumber int) (issueInfo, error) {
	path := fmt.Sprintf("/v1/%s/%s/issues/%d", ctx.Owner, ctx.Repo, issueNumber)
	env, err := ctx.CallAPIWithQuery("GET", path, nil)
	if err != nil {
		return issueInfo{}, err
	}
	data, _ := env.Data.(map[string]interface{})
	subj := getString(data, "subject")
	if subj == "" {
		subj = fmt.Sprintf("issue #%d", issueNumber)
	}
	desc := getString(data, "description")
	return issueInfo{number: issueNumber, subject: subj, description: desc}, nil
}

// processIssues handles the common issue processing loop used by both
// --issues and --tag paths.
func processIssues(ctx *common.RuntimeContext, issues []issueInfo) error {
	tmpl := ctx.Arg("template")
	if tmpl == "" {
		tmpl = ""
	}

	type result struct {
		num    int
		action string
		msg    string
	}
	var results []result

	for _, issue := range issues {
		force := ctx.Arg("force") == "true"
		if !force && hasWelcomeComment(ctx, issue.number) {
			results = append(results, result{issue.number, "skipped", fmt.Sprintf("#%d \"%s\" — 已有引导评论，跳过", issue.number, issue.subject)})
			continue
		}

		// Render per-issue message with issue-specific variables.
		body := renderComment(ctx, issue, tmpl)

		if ctx.IsDryRun() {
			fmt.Printf("\n--- 预览 #%d \"%s\" ---\n%s\n---\n", issue.number, issue.subject, body)
			proceed, err := common.ConfirmAction(ctx)
			if err != nil {
				return err
			}
			if !proceed {
				results = append(results, result{issue.number, "skipped", "用户取消"})
				continue
			}
		}

		if err := addComment(ctx, issue.number, body); err != nil {
			results = append(results, result{issue.number, "error", err.Error()})
		} else {
			results = append(results, result{issue.number, "added", fmt.Sprintf("#%d \"%s\" — 已添加引导评论", issue.number, issue.subject)})
		}
	}

	// output
	added := 0
	skipped := 0
	errors := 0
	for _, r := range results {
		switch r.action {
		case "added":
			added++
		case "skipped":
			skipped++
		case "error":
			errors++
		}
	}
	fmt.Printf("\n完成: 添加 %d, 跳过 %d, 错误 %d\n", added, skipped, errors)
	for _, r := range results {
		fmt.Printf("  [%s] %s\n", r.action, r.msg)
	}

	return nil
}

func resolveTags(ctx *common.RuntimeContext) (map[string]int, error) {
	key := ctx.Owner + "/" + ctx.Repo
	if cached, ok := tagCache.Load(key); ok {
		return cached.(map[string]int), nil
	}

	path := fmt.Sprintf("/v1/%s/%s/issue_tags", ctx.Owner, ctx.Repo)
	q := url.Values{}
	q.Set("only_name", "true")
	env, err := ctx.CallAPIWithQuery("GET", path, q)
	if err != nil {
		return nil, fmt.Errorf("获取标签列表失败: %w", err)
	}

	data, _ := env.Data.(map[string]interface{})
	raw, _ := data["issue_tags"].([]interface{})
	tags := make(map[string]int)
	for _, item := range raw {
		if t, ok := item.(map[string]interface{}); ok {
			if name, ok := t["name"].(string); ok && name != "" {
				switch v := t["id"].(type) {
				case float64:
					tags[name] = int(v)
				case int:
					tags[name] = v
				}
			}
		}
	}
	if len(tags) == 0 {
		return nil, fmt.Errorf("项目没有配置任务标签，请先在 GitLink 网页端创建")
	}
	tagCache.Store(key, tags)
	return tags, nil
}

func tagNamesList(tags map[string]int) []string {
	var names []string
	for n := range tags {
		names = append(names, n)
	}
	return names
}

type issueInfo struct {
	number      int
	subject     string
	description string
}

func fetchTaggedIssues(ctx *common.RuntimeContext, tagIDs []string) ([]issueInfo, error) {
	var all []issueInfo
	page := 1

	for {
		q := url.Values{}
		q.Set("state", "open")
		q.Set("page", strconv.Itoa(page))
		q.Set("limit", "100")
		q.Set("issue_tag_ids", strings.Join(tagIDs, ","))

		env, err := ctx.CallAPIWithQuery("GET", fmt.Sprintf("/v1/%s/%s/issues", ctx.Owner, ctx.Repo), q)
		if err != nil {
			return nil, err
		}

		data, _ := env.Data.(map[string]interface{})
		issues, _ := data["issues"].([]interface{})
		if len(issues) == 0 {
			break
		}

		for _, item := range issues {
			if issue, ok := item.(map[string]interface{}); ok {
				all = append(all, issueInfo{
					number:  getInt(issue, "project_issues_index"),
					subject: getString(issue, "subject"),
				})
			}
		}

		total := getInt(data, "total_count")
		if page*100 >= total {
			break
		}
		page++
	}

	return all, nil
}

func hasWelcomeComment(ctx *common.RuntimeContext, issueNumber int) bool {
	path := fmt.Sprintf("/v1/%s/%s/issues/%d/journals", ctx.Owner, ctx.Repo, issueNumber)
	q := url.Values{}
	q.Set("limit", "100")
	env, err := ctx.CallAPIWithQuery("GET", path, q)
	if err != nil {
		return false
	}
	data, _ := env.Data.(map[string]interface{})
	journals, _ := data["journals"].([]interface{})
	for _, j := range journals {
		if jm, ok := j.(map[string]interface{}); ok {
			if notes := getString(jm, "notes"); strings.Contains(notes, marker) {
				return true
			}
		}
	}
	return false
}

func addComment(ctx *common.RuntimeContext, issueNumber int, body string) error {
	path := fmt.Sprintf("/v1/%s/%s/issues/%d/journals", ctx.Owner, ctx.Repo, issueNumber)
	payload := map[string]interface{}{"notes": marker + "\n\n" + body}
	_, err := ctx.CallAPI("POST", path, payload)
	return err
}

// renderComment renders the comment body for a specific issue.
// It uses the --template if provided, otherwise generates an issue-aware default.
func renderComment(ctx *common.RuntimeContext, issue issueInfo, customTmpl string) string {
	tmpl := customTmpl
	if tmpl == "" {
		tmpl = defaultTemplate(ctx.Owner, ctx.Repo, issue)
	}
	body := strings.NewReplacer(
		"{login}", ctx.Owner,
		"{number}", strconv.Itoa(issue.number),
		"{subject}", issue.subject,
		"{description}", issue.description,
	).Replace(tmpl)
	return body
}

// defaultTemplate returns an issue-aware onboarding message.
func defaultTemplate(owner, repo string, issue issueInfo) string {
	summary := issue.subject
	if len(issue.description) > 200 {
		summary = issue.description[:200] + "..."
	} else if issue.description != "" {
		summary = issue.description
	}
	return fmt.Sprintf(`## 欢迎贡献！:wave:

感谢你对 [%s/%s](https://www.gitlink.org.cn/%s/%s) 的关注。

### :bulb: 关于本 Issue：{subject}

%s

### :rocket: 参与步骤
1. **Fork 仓库** 并克隆到本地
2. 创建新分支：` + "`git checkout -b fix/issue-{number}`" + `
3. 参照上方 issue 描述修改代码
4. 推送到你的 Fork 后创建 Pull Request

### :memo: 注意事项
- 请先阅读 [CONTRIBUTING.md](https://www.gitlink.org.cn/%s/%s/src/master/CONTRIBUTING.md)（如有）
- 如有疑问，欢迎在评论区留言讨论

期待你的 PR！`, owner, repo, owner, repo, summary, owner, repo)
}

func getString(m map[string]interface{}, key string) string {
	if v, ok := m[key]; ok {
		if s, ok := v.(string); ok {
			return s
		}
	}
	return ""
}

func getInt(m map[string]interface{}, key string) int {
	switch v := m[key].(type) {
	case float64:
		return int(v)
	case int:
		return v
	}
	return 0
}
