package viral

import (
	"context"
	"regexp"
	"sort"
	"strings"

	libthreads "haixun-backend/internal/library/threadsapi"
	"haixun-backend/internal/library/websearch"
)

const (
	// Single web-search query per supplement pass — fewer API calls, same recall
	// via a combined site: + seed + brief/pillar hint in one request.
	maxAccountDiscoverQueries = 1
	MaxSimilarAccounts        = 10
	// Skip web-search supplement when scan already surfaced enough reference authors.
	MinSimilarAccountsBeforeWebSupplement = 5
)

var threadsProfileRE = regexp.MustCompile(`(?i)threads\.(?:com|net)/@([a-zA-Z0-9._]+)`)

var reservedUsernames = map[string]struct{}{
	"login": {}, "signup": {}, "search": {}, "explore": {}, "home": {},
	"help": {}, "about": {}, "privacy": {}, "terms": {}, "settings": {},
	"threads": {}, "thread": {}, "instagram": {}, "meta": {}, "www": {},
}

type SimilarAccount struct {
	Username      string   `json:"username"`
	Reason        string   `json:"reason"`
	Source        string   `json:"source"`
	MatchedSource []string `json:"matchedSource,omitempty"`
	Confidence    string   `json:"confidence"`
	ProfileURL    string   `json:"profileUrl"`
}

type DiscoverAccountsInput struct {
	SeedQuery string
	Brief     string
	Pillars   []string
}

type accountCandidate struct {
	username  string
	score     int
	reason    string
	source    string
	permalink string
}

func DiscoverSimilarAccounts(ctx context.Context, client websearch.Client, input DiscoverAccountsInput) ([]SimilarAccount, error) {
	if client == nil || !client.Enabled() {
		return nil, nil
	}
	seed := strings.TrimSpace(input.SeedQuery)
	if seed == "" {
		return nil, nil
	}
	queries := buildAccountDiscoverQueries(seed, input.Brief, input.Pillars)
	if len(queries) == 0 {
		return nil, nil
	}

	seen := map[string]accountCandidate{}
	for _, query := range queries {
		res, err := client.Search(ctx, websearch.SearchOptions{
			Query: query,
			Limit: 12,
			Mode:  websearch.ModeThreadsDiscover,
		})
		if err != nil || res.Status != "success" {
			continue
		}
		for _, item := range res.Results {
			blob := strings.TrimSpace(item.URL + " " + item.Title + " " + item.Snippet)
			for _, username := range extractUsernames(blob) {
				weight := 2
				if strings.Contains(strings.ToLower(item.URL), "/@"+strings.ToLower(username)) {
					weight = 4
				}
				reason := strings.TrimSpace(item.Snippet)
				if reason == "" {
					reason = strings.TrimSpace(item.Title)
				}
				if reason == "" {
					reason = "在「" + seed + "」相關搜尋結果中找到"
				}
				if len([]rune(reason)) > 120 {
					reason = string([]rune(reason)[:120])
				}
				key := strings.ToLower(username)
				prev, ok := seen[key]
				permalink := strings.TrimSpace(item.URL)
				if !ok || weight > prev.score {
					seen[key] = accountCandidate{
						username:  username,
						score:     weight,
						reason:    reason,
						source:    "web",
						permalink: permalink,
					}
				} else if ok {
					prev.score += 1
					if prev.permalink == "" && permalink != "" {
						prev.permalink = permalink
					}
					seen[key] = prev
				}
			}
		}
	}

	out := make([]accountCandidate, 0, len(seen))
	for _, item := range seen {
		out = append(out, item)
	}
	sort.Slice(out, func(i, j int) bool { return out[i].score > out[j].score })
	if len(out) > MaxSimilarAccounts {
		out = out[:MaxSimilarAccounts]
	}

	accounts := make([]SimilarAccount, 0, len(out))
	for _, item := range out {
		profileURL := libthreads.ProfileURLFromPermalink(item.permalink, item.username)
		accounts = append(accounts, SimilarAccount{
			Username:      item.username,
			Reason:        item.reason,
			Source:        item.source,
			MatchedSource: []string{item.source},
			Confidence:    accountConfidence(item.score),
			ProfileURL:    profileURL,
		})
	}
	return accounts, nil
}

func buildAccountDiscoverQueries(seed, brief string, pillars []string) []string {
	seed = strings.TrimSpace(seed)
	if seed == "" {
		return nil
	}
	quoted := `"` + seed + `"`
	query := `site:threads.net ` + quoted
	if hint := strings.TrimSpace(brief); len([]rune(hint)) >= 4 && len([]rune(hint)) <= 24 {
		query += ` ` + hint
	}
	for _, pillar := range pillars {
		pillar = strings.TrimSpace(pillar)
		if len([]rune(pillar)) >= 4 {
			query += ` "` + pillar + `"`
			break
		}
	}
	query = strings.TrimSpace(query)
	if query == "" {
		return nil
	}
	return []string{query}
}

func extractUsernames(blob string) []string {
	matches := threadsProfileRE.FindAllStringSubmatch(blob, -1)
	out := []string{}
	seen := map[string]struct{}{}
	for _, match := range matches {
		if len(match) < 2 {
			continue
		}
		user := strings.TrimSpace(match[1])
		if !isValidUsername(user) {
			continue
		}
		key := strings.ToLower(user)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		out = append(out, user)
	}
	return out
}

func isValidUsername(username string) bool {
	if username == "" || len(username) < 2 || len(username) > 30 {
		return false
	}
	if _, ok := reservedUsernames[strings.ToLower(username)]; ok {
		return false
	}
	for _, r := range username {
		if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '.' || r == '_' {
			continue
		}
		return false
	}
	return true
}

func accountConfidence(score int) string {
	if score >= 5 {
		return "high"
	}
	if score >= 3 {
		return "medium"
	}
	return "low"
}
