package viral

import (
	"context"
	"fmt"
	"strings"

	"haixun-backend/internal/library/placement"
)

const (
	defaultLimitPerKeyword = 15
	maxKeywords            = 6
	maxMergedPosts         = 60
)

type DiscoverInput struct {
	Keywords   []string
	Exclusions []string
	Member     placement.MemberContext
	Crawler    placement.CrawlerSearchFn
	Limit      int // per keyword; 0 = default
}

type ProgressFn func(message string, pct int)

// RunDiscover searches Threads for viral candidates across keywords, ranked by engagement.
func RunDiscover(ctx context.Context, input DiscoverInput, progress ProgressFn) ([]placement.ScanCandidate, error) {
	keywords := normalizeKeywords(input.Keywords)
	if len(keywords) == 0 {
		return nil, fmt.Errorf("請提供至少一個爆款掃描關鍵字")
	}
	perKeyword := input.Limit
	if perKeyword <= 0 {
		perKeyword = defaultLimitPerKeyword
	}

	merged := map[string]placement.ScanCandidate{}
	total := len(keywords)
	for i, keyword := range keywords {
		if progress != nil {
			pct := 10 + (i*70)/total
			progress(fmt.Sprintf("掃描關鍵字「%s」…", keyword), pct)
		}
		posts, _, err := placement.Discover(ctx, placement.DiscoverRequest{
			Query:   keyword,
			Keyword: keyword,
			Limit:   perKeyword,
			Member:  input.Member,
			Crawler: input.Crawler,
		})
		if err != nil {
			return nil, fmt.Errorf("關鍵字「%s」：%w", keyword, err)
		}
		for _, post := range posts {
			key := strings.TrimSpace(post.Permalink)
			if key == "" {
				key = strings.TrimSpace(post.ExternalID)
			}
			if key == "" {
				continue
			}
			score := ScorePost(post.LikeCount, post.ReplyCount)
			if !PassesViralCandidate(post.Text, post.LikeCount, post.ReplyCount, score, input.Exclusions) {
				continue
			}
			candidate := placement.ScanCandidate{
				Permalink:       post.Permalink,
				ExternalID:      post.ExternalID,
				Author:          post.Author,
				Text:            post.Text,
				SearchTag:       keyword,
				Source:          post.Source,
				LikeCount:       post.LikeCount,
				ReplyCount:      post.ReplyCount,
				EngagementScore: score,
				PlacementScore:  score,
				Priority:        PriorityLabel(score),
			}
			if prev, ok := merged[key]; !ok || candidate.EngagementScore > prev.EngagementScore {
				merged[key] = candidate
			}
		}
	}

	out := make([]placement.ScanCandidate, 0, len(merged))
	for _, item := range merged {
		out = append(out, item)
	}
	sortByEngagement(out)
	if len(out) > maxMergedPosts {
		out = out[:maxMergedPosts]
	}
	if progress != nil {
		progress(fmt.Sprintf("合併 %d 篇爆款候選", len(out)), 85)
	}
	return out, nil
}

func normalizeKeywords(raw []string) []string {
	seen := map[string]struct{}{}
	out := make([]string, 0, len(raw))
	for _, item := range raw {
		kw := strings.TrimSpace(item)
		if kw == "" {
			continue
		}
		if _, ok := seen[kw]; ok {
			continue
		}
		seen[kw] = struct{}{}
		out = append(out, kw)
		if len(out) >= maxKeywords {
			break
		}
	}
	return out
}

func sortByEngagement(items []placement.ScanCandidate) {
	for i := 0; i < len(items); i++ {
		for j := i + 1; j < len(items); j++ {
			if items[j].EngagementScore > items[i].EngagementScore {
				items[i], items[j] = items[j], items[i]
			}
		}
	}
}
