package discovery

import (
	"sort"
	"strings"
	"unicode"
)

func normalizeDiscoverySearchText(value string) string {
	var b strings.Builder
	b.Grow(len(value))
	for _, r := range strings.ToLower(strings.TrimSpace(value)) {
		if unicode.IsMark(r) {
			continue
		}
		b.WriteRune(r)
	}
	return b.String()
}

func discoveryQueryTokens(query string) []string {
	n := normalizeDiscoverySearchText(query)
	if n == "" {
		return nil
	}
	return strings.Fields(n)
}

func discoveryFieldMatchScore(haystack, needle string) float64 {
	h := normalizeDiscoverySearchText(haystack)
	n := normalizeDiscoverySearchText(needle)
	if n == "" || !strings.Contains(h, n) {
		return 0
	}
	if h == n {
		return 1
	}
	if strings.HasPrefix(h, n) {
		return 0.95 + 0.05*float64(len(n))/float64(max(len(h), 1))
	}
	for _, word := range strings.FieldsFunc(h, func(r rune) bool {
		return r == ' ' || r == '@' || r == '.' || r == '_' || r == '+' || r == '-'
	}) {
		if word == "" {
			continue
		}
		if strings.HasPrefix(word, n) {
			return 0.88 + 0.07*float64(len(n))/float64(max(len(word), 1))
		}
	}
	idx := strings.Index(h, n)
	positionBonus := 1 - float64(idx)/float64(max(len(h), 1))*0.35
	lengthBonus := float64(len(n)) / float64(max(len(h), 1))
	return 0.42 + 0.28*positionBonus + 0.22*lengthBonus
}

func appendNonEmptyFields(fields []string, values ...string) []string {
	for _, v := range values {
		if strings.TrimSpace(v) != "" {
			fields = append(fields, v)
		}
	}
	return fields
}

func profileSearchFields(p Profile) []string {
	fields := appendNonEmptyFields(nil, p.DisplayName, p.PrimaryEmail)
	if p.EnrichedData != nil {
		d := p.EnrichedData
		fields = appendNonEmptyFields(fields,
			d.FirstName, d.LastName, d.Company, d.Department, d.JobTitle, d.Website, d.Notes,
		)
		for _, e := range d.Emails {
			fields = appendNonEmptyFields(fields, e.Value)
		}
		for _, ph := range d.Phones {
			fields = appendNonEmptyFields(fields, ph.Value)
		}
		for _, a := range d.Addresses {
			fields = appendNonEmptyFields(fields, a.Street, a.City, a.Region, a.PostalCode, a.Country)
		}
	}
	for _, e := range p.AllEmails {
		fields = appendNonEmptyFields(fields, e.Email, e.DisplayName)
	}
	for _, a := range p.DetectedInAccounts {
		fields = appendNonEmptyFields(fields, a.AccountEmail, a.AccountName)
	}
	return fields
}

func profileGroupSearchFields(group ProfileGroup) []string {
	fields := appendNonEmptyFields(nil, group.DisplayName, group.PrimaryEmail)
	for _, p := range group.Profiles {
		fields = append(fields, profileSearchFields(p)...)
	}
	if len(group.Profiles) == 0 {
		fields = append(fields, profileSearchFields(group.Profile)...)
	}
	seen := make(map[string]struct{}, len(fields))
	unique := make([]string, 0, len(fields))
	for _, f := range fields {
		key := normalizeDiscoverySearchText(f)
		if key == "" {
			continue
		}
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		unique = append(unique, f)
	}
	return unique
}

func profileGroupQueryMatchScore(group ProfileGroup, query string) float64 {
	fields := profileGroupSearchFields(group)
	tokens := discoveryQueryTokens(query)
	if len(tokens) == 0 {
		return 0
	}

	var total float64
	for _, token := range tokens {
		best := 0.0
		for _, field := range fields {
			best = max(best, discoveryFieldMatchScore(field, token))
			best = max(best, discoveryFieldMatchScore(field, query))
		}
		if best == 0 {
			return 0
		}
		total += best
	}
	return total / float64(len(tokens))
}

func rankProfileGroupsByQuery(groups []ProfileGroup, query string) []ProfileGroup {
	query = strings.TrimSpace(query)
	if query == "" {
		return groups
	}

	type scored struct {
		group ProfileGroup
		score float64
	}
	matches := make([]scored, 0, len(groups))
	for _, g := range groups {
		if score := profileGroupQueryMatchScore(g, query); score > 0 {
			matches = append(matches, scored{group: g, score: score})
		}
	}

	sort.SliceStable(matches, func(i, j int) bool {
		if matches[i].score != matches[j].score {
			return matches[i].score > matches[j].score
		}
		return strings.ToLower(matches[i].group.DisplayName) < strings.ToLower(matches[j].group.DisplayName)
	})

	out := make([]ProfileGroup, len(matches))
	for i, m := range matches {
		out[i] = m.group
	}
	return out
}
