package sync

import (
	"context"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// jiraConnector talks to Jira Cloud REST v3 with email+API-token basic auth.
type jiraConnector struct {
	baseURL    string
	projectKey string
	email      string
	apiToken   string
}

func (j *jiraConnector) Authorize(req *http.Request) {
	req.SetBasicAuth(j.email, j.apiToken)
}

func (j *jiraConnector) TestConnection(ctx context.Context) error {
	var me struct {
		AccountID string `json:"accountId"`
	}
	if err := getJSON(ctx, j.Authorize, j.baseURL+"/rest/api/3/myself", &me); err != nil {
		return fmt.Errorf("jira /myself: %w", err)
	}
	return nil
}

type jiraIssue struct {
	Key    string `json:"key"`
	Fields struct {
		Summary     string         `json:"summary"`
		Description map[string]any `json:"description"` // ADF document
		IssueType   struct {
			Name string `json:"name"`
		} `json:"issuetype"`
		Attachment []struct {
			Filename string `json:"filename"`
			Content  string `json:"content"`
			MimeType string `json:"mimeType"`
		} `json:"attachment"`
	} `json:"fields"`
}

func (j *jiraConnector) search(ctx context.Context, jql, fields string) ([]jiraIssue, error) {
	var all []jiraIssue
	for startAt := 0; ; {
		var page struct {
			Issues []jiraIssue `json:"issues"`
			Total  int         `json:"total"`
		}
		u := fmt.Sprintf("%s/rest/api/3/search?jql=%s&fields=%s&maxResults=100&startAt=%d",
			j.baseURL, url.QueryEscape(jql), url.QueryEscape(fields), startAt)
		if err := getJSON(ctx, j.Authorize, u, &page); err != nil {
			return nil, fmt.Errorf("jira search: %w", err)
		}
		all = append(all, page.Issues...)
		startAt += len(page.Issues)
		if len(page.Issues) == 0 || startAt >= page.Total {
			return all, nil
		}
	}
}

func (j *jiraConnector) FetchUpdated(ctx context.Context, identity string, since time.Time) ([]Ticket, error) {
	jql := fmt.Sprintf(`assignee = %q AND project = %q AND updated >= %q ORDER BY updated ASC`,
		identity, j.projectKey, since.UTC().Format("2006/01/02 15:04"))
	issues, err := j.search(ctx, jql, "summary,description,issuetype,attachment")
	if err != nil {
		return nil, err
	}
	out := make([]Ticket, 0, len(issues))
	for _, is := range issues {
		t := Ticket{
			Key:         is.Key,
			URL:         j.baseURL + "/browse/" + is.Key,
			Type:        jiraType(is.Fields.IssueType.Name),
			Title:       is.Fields.Summary,
			Description: adfToText(is.Fields.Description),
			Raw:         map[string]any{"key": is.Key, "issueType": is.Fields.IssueType.Name},
		}
		for _, a := range is.Fields.Attachment {
			t.Attachments = append(t.Attachments, TicketAttachment{Name: a.Filename, URL: a.Content, MimeType: a.MimeType})
		}
		out = append(out, t)
	}
	return out, nil
}

func (j *jiraConnector) ListAssignedKeys(ctx context.Context, identity string) ([]string, error) {
	jql := fmt.Sprintf(`assignee = %q AND project = %q`, identity, j.projectKey)
	issues, err := j.search(ctx, jql, "key")
	if err != nil {
		return nil, err
	}
	keys := make([]string, len(issues))
	for i, is := range issues {
		keys[i] = is.Key
	}
	return keys, nil
}

func jiraType(name string) string {
	switch strings.ToLower(name) {
	case "epic":
		return "epic"
	case "story", "user story":
		return "story"
	default:
		return "task"
	}
}

// adfToText flattens an Atlassian Document Format tree into plain text —
// good enough for atomization input.
func adfToText(node map[string]any) string {
	if node == nil {
		return ""
	}
	var sb strings.Builder
	var walk func(n map[string]any)
	walk = func(n map[string]any) {
		if text, ok := n["text"].(string); ok {
			sb.WriteString(text)
		}
		if t, _ := n["type"].(string); t == "paragraph" || t == "heading" || t == "listItem" {
			defer sb.WriteString("\n")
		}
		if content, ok := n["content"].([]any); ok {
			for _, child := range content {
				if m, ok := child.(map[string]any); ok {
					walk(m)
				}
			}
		}
	}
	walk(node)
	return strings.TrimSpace(sb.String())
}
