package issue

import (
	"encoding/csv"
	"fmt"
	"net/url"
	"os"
	"strconv"
	"strings"
	"sync"

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

var issueTagCache sync.Map

// resolveIssueTags fetches the project's issue tags and returns a name→id mapping.
// Results are cached per owner/repo.
func resolveIssueTags(ctx *common.RuntimeContext) (map[string]int, error) {
	key := ctx.Owner + "/" + ctx.Repo
	if cached, ok := issueTagCache.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, ok := env.Data.(map[string]interface{})
	if !ok {
		return nil, fmt.Errorf("标签列表响应格式异常")
	}

	rawTags, ok := data["issue_tags"].([]interface{})
	if !ok {
		return nil, fmt.Errorf("标签列表响应缺少 issue_tags 字段")
	}

	tags := make(map[string]int, len(rawTags))
	for _, item := range rawTags {
		tag, ok := item.(map[string]interface{})
		if !ok {
			continue
		}
		name, _ := tag["name"].(string)
		if name == "" {
			continue
		}
		var id int
		switch v := tag["id"].(type) {
		case float64:
			id = int(v)
		case int:
			id = v
		default:
			id, _ = strconv.Atoi(fmt.Sprintf("%v", v))
		}
		if id == 0 {
			continue
		}
		tags[name] = id
	}

	if len(tags) == 0 {
		return nil, fmt.Errorf("项目没有配置任何标签，请先在 GitLink 网页端创建标签")
	}

	issueTagCache.Store(key, tags)
	return tags, nil
}

func newBatchCreateShortcut() *common.Shortcut {
	return &common.Shortcut{
		Name:        "batch-create",
		Description: "Create multiple issues from CLI flags or a CSV file",
		Flags: []common.Flag{
			{Name: "titles", Usage: "Comma-separated issue titles, e.g. 标题1,标题2"},
			{Name: "priority", Short: "p", Usage: "Priority: low, normal, high, urgent (default: normal)"},
			{Name: "label", Short: "l", Usage: "Label name, e.g. 缺陷"},
			{Name: "assignee", Short: "a", Usage: "Assignee login name"},
			{Name: "state", Short: "s", Usage: "Initial state: new, in-progress, resolved, closed, rejected (default: new)", Default: "new"},
			{Name: "from", Usage: "CSV file path"},
			{Name: "template", Short: "t", Usage: "Template: bug or feature (only with --from)"},
			{Name: "dry-run", Usage: "Preview without creating issues", Bool: true, Default: "false"},
		},
		Run: runBatchCreate,
	}
}

type createIssueInput struct {
	Title    string
	Body     string
	Priority string
	Label    string
	Assignee string
	Status   string
	// template-specific fields
	Version    string
	Severity   string
	Steps      string
	Expected   string
	Actual     string
	UserStory  string
	Acceptance string
}

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

	tags, err := resolveIssueTags(ctx)
	if err != nil {
		return err
	}

	dryRun := parseBool(ctx.Arg("dry-run"))
	template := strings.ToLower(strings.TrimSpace(ctx.Arg("template")))

	// Collect inputs from --titles and/or --from
	var inputs []createIssueInput
	if titlesStr := ctx.Arg("titles"); titlesStr != "" {
		inputs = append(inputs, parseTitles(titlesStr, ctx)...)
	}
	if csvPath := ctx.Arg("from"); csvPath != "" {
		csvInputs, err := readCreateInputsFromCSV(csvPath, template)
		if err != nil {
			return err
		}
		inputs = append(inputs, csvInputs...)
	}
	if len(inputs) == 0 {
		return fmt.Errorf("no issue titles provided; use --titles 标题1,标题2 or --from issues.csv")
	}

	// Apply CLI --state as fallback for inputs without an explicit status
	cliState := ctx.Arg("state")
	for i := range inputs {
		if inputs[i].Status == "" {
			inputs[i].Status = cliState
		}
	}

	summary := BatchSummary{
		Repository: fmt.Sprintf("%s/%s", ctx.Owner, ctx.Repo),
		Action:     "create",
		Value:      template,
		DryRun:     dryRun,
		Total:      len(inputs),
		Results:    make([]BatchResult, 0, len(inputs)),
	}

	for i, input := range inputs {
		label := fmt.Sprintf("#%d", i+1)
		if input.Title != "" {
			label = truncate(input.Title, 40)
		}
		result := BatchResult{Number: label, Action: "create"}

		if dryRun {
			result.Status = "planned"
			summary.Succeeded++
			summary.Results = append(summary.Results, result)
			continue
		}

		body := buildCreateBody(ctx, input, template, tags)
		env, err := ctx.CallAPI("POST", v1RepoPath(ctx)+"/issues", body)
		if err != nil {
			result.Status = "failed"
			result.Error = err.Error()
			summary.Failed++
		} else {
			result.Status = "created"
			if data, ok := env.Data.(map[string]interface{}); ok {
				if num, ok := data["project_issues_index"]; ok {
					result.Number = fmt.Sprintf("%v", num)
				}
			}
			summary.Succeeded++
		}
		summary.Results = append(summary.Results, result)
	}

	if err := ctx.OutputData(summary); err != nil {
		return err
	}
	if summary.Failed > 0 {
		return fmt.Errorf("%d of %d issue(s) failed to create", summary.Failed, summary.Total)
	}
	return nil
}

func buildCreateBody(ctx *common.RuntimeContext, input createIssueInput, template string, tags map[string]int) map[string]interface{} {
	statusID := statusNew
	if input.Status != "" {
		if sid, err := parseStatus(input.Status); err == nil {
			statusID = sid
		}
	}
	body := map[string]interface{}{
		"subject":     input.Title,
		"status_id":   statusID,
		"priority_id": priorityNormal,
		"done_ratio":  0,
	}

	if template != "" {
		body["description"] = buildTemplateDescription(input, template)
		if template == "bug" {
			body["issue_tag_ids"] = []interface{}{tags["缺陷"]}
		} else if template == "feature" {
			body["issue_tag_ids"] = []interface{}{tags["功能"]}
		}
	} else if input.Body != "" {
		body["description"] = input.Body
	}

	if input.Priority != "" {
		if pid, err := parsePriority(input.Priority); err == nil {
			body["priority_id"] = pid
		}
	}
	if input.Label != "" {
		if tid, err := parseLabel(input.Label, tags); err == nil {
			body["issue_tag_ids"] = []interface{}{tid}
		}
	}
	if input.Assignee != "" {
		if id, err := resolveUserID(ctx, input.Assignee); err == nil {
			body["assigner_ids"] = []interface{}{id}
		}
	}

	return body
}

func buildTemplateDescription(input createIssueInput, template string) string {
	switch template {
	case "bug":
		return buildBugDescription(input)
	case "feature":
		return buildFeatureDescription(input)
	default:
		return input.Body
	}
}

func buildBugDescription(input createIssueInput) string {
	var b strings.Builder
	b.WriteString("## Bug 描述\n")
	b.WriteString(input.Title)
	b.WriteString("\n")

	if input.Version != "" {
		b.WriteString("\n## 版本\n")
		b.WriteString(input.Version)
		b.WriteString("\n")
	}
	if input.Severity != "" {
		b.WriteString("\n## 严重程度\n")
		b.WriteString(input.Severity)
		b.WriteString("\n")
	}
	if input.Steps != "" {
		b.WriteString("\n## 复现步骤\n")
		b.WriteString(input.Steps)
		b.WriteString("\n")
	}
	if input.Expected != "" {
		b.WriteString("\n## 期望结果\n")
		b.WriteString(input.Expected)
		b.WriteString("\n")
	}
	if input.Actual != "" {
		b.WriteString("\n## 实际结果\n")
		b.WriteString(input.Actual)
		b.WriteString("\n")
	}
	return b.String()
}

func buildFeatureDescription(input createIssueInput) string {
	var b strings.Builder
	b.WriteString("## 用户故事\n")
	if input.UserStory != "" {
		b.WriteString(input.UserStory)
	} else {
		b.WriteString(input.Title)
	}
	b.WriteString("\n")

	if input.Body != "" {
		b.WriteString("\n## 描述\n")
		b.WriteString(input.Body)
		b.WriteString("\n")
	}
	if input.Acceptance != "" {
		b.WriteString("\n## 验收标准\n")
		b.WriteString(input.Acceptance)
		b.WriteString("\n")
	}
	if input.Priority != "" {
		b.WriteString("\n## 优先级\n")
		b.WriteString(input.Priority)
		b.WriteString("\n")
	}
	return b.String()
}

func readCreateInputsFromCSV(path string, template string) ([]createIssueInput, error) {
	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("read CSV: %w", err)
	}
	defer file.Close()

	reader := csv.NewReader(file)
	reader.TrimLeadingSpace = true
	records, err := reader.ReadAll()
	if err != nil {
		return nil, fmt.Errorf("parse CSV: %w", err)
	}
	if len(records) < 2 {
		return nil, fmt.Errorf("CSV must have a header row and at least one data row")
	}

	header := records[0]
	col := make(map[string]int)
	for i, h := range header {
		col[normalizeHeader(h)] = i
	}

	if _, ok := col["title"]; !ok {
		return nil, fmt.Errorf("CSV must have a 'title' column")
	}

	var inputs []createIssueInput
	for _, record := range records[1:] {
		input := createIssueInput{
			Title:    getCol(record, col, "title"),
			Body:     getCol(record, col, "body"),
			Priority: getCol(record, col, "priority"),
			Label:    getCol(record, col, "label"),
			Assignee: getCol(record, col, "assignee"),
				Status:   getCol(record, col, "status"),
			Version:  getCol(record, col, "version"),
			Severity: getCol(record, col, "severity"),
			Steps:    getCol(record, col, "steps"),
			Expected: getCol(record, col, "expected"),
			Actual:   getCol(record, col, "actual"),
			// Support alternate heading for feature template
			UserStory:  getCol(record, col, "user_story"),
			Acceptance: getCol(record, col, "acceptance"),
		}
		if input.UserStory == "" {
			input.UserStory = getCol(record, col, "user story")
		}
		if input.Title == "" {
			continue
		}
		inputs = append(inputs, input)
	}
	return inputs, nil
}

func parseTitles(titlesStr string, ctx *common.RuntimeContext) []createIssueInput {
	parts := strings.Split(titlesStr, ",")
	inputs := make([]createIssueInput, 0, len(parts))
	for _, title := range parts {
		title = strings.TrimSpace(title)
		if title == "" {
			continue
		}
		inputs = append(inputs, createIssueInput{
			Title:    title,
			Priority: ctx.Arg("priority"),
			Label:    ctx.Arg("label"),
			Assignee: ctx.Arg("assignee"),
			Status:   ctx.Arg("state"),
		})
	}
	return inputs
}

func normalizeHeader(h string) string {
	return strings.ToLower(strings.TrimSpace(h))
}

func getCol(record []string, col map[string]int, name string) string {
	if idx, ok := col[name]; ok && idx < len(record) {
		return strings.TrimSpace(record[idx])
	}
	return ""
}

func truncate(s string, n int) string {
	runes := []rune(s)
	if len(runes) <= n {
		return s
	}
	return string(runes[:n]) + "..."
}
