package discovery

import (
	"context"
	"sort"
	"strings"

	"github.com/google/uuid"
)

var consumerMailDomains = map[string]struct{}{
	"gmail.com": {}, "googlemail.com": {}, "outlook.com": {}, "hotmail.com": {},
	"live.com": {}, "msn.com": {}, "yahoo.com": {}, "yahoo.fr": {},
	"icloud.com": {}, "me.com": {}, "mac.com": {}, "proton.me": {}, "protonmail.com": {},
	"aol.com": {}, "gmx.com": {}, "gmx.fr": {}, "mail.com": {}, "zoho.com": {},
	"yandex.com": {}, "yandex.ru": {}, "fastmail.com": {},
}

func isConsumerMailDomain(domain string) bool {
	domain = strings.ToLower(strings.TrimSpace(domain))
	if domain == "" {
		return true
	}
	if _, ok := consumerMailDomains[domain]; ok {
		return true
	}
	return false
}

func personGroupKey(p Profile) string {
	if p.EnrichedData != nil {
		fn := strings.TrimSpace(p.EnrichedData.FirstName)
		ln := strings.TrimSpace(p.EnrichedData.LastName)
		if fn != "" || ln != "" {
			return strings.ToLower(strings.TrimSpace(fn + " " + ln))
		}
	}
	dn := strings.TrimSpace(p.DisplayName)
	if dn != "" && !strings.Contains(dn, "@") {
		return strings.ToLower(dn)
	}
	return ""
}

func groupProfiles(profiles []Profile) []ProfileGroup {
	if len(profiles) == 0 {
		return nil
	}
	buckets := map[string][]Profile{}
	var solo []Profile
	for _, p := range profiles {
		key := personGroupKey(p)
		if key == "" {
			solo = append(solo, p)
			continue
		}
		buckets[key] = append(buckets[key], p)
	}

	var groups []ProfileGroup
	for key, list := range buckets {
		if len(list) == 1 {
			solo = append(solo, list[0])
			continue
		}
		groups = append(groups, mergeProfileGroup(key, list))
	}
	for _, p := range solo {
		groups = append(groups, mergeProfileGroup(p.ID, []Profile{p}))
	}

	sort.Slice(groups, func(i, j int) bool {
		return compareProfileGroupsByInteraction(groups[i], groups[j])
	})
	return groups
}

func mergeProfileGroup(key string, profiles []Profile) ProfileGroup {
	sort.Slice(profiles, func(i, j int) bool {
		return compareProfilesByInteraction(profiles[i], profiles[j])
	})
	primary := profiles[0]
	ids := make([]string, 0, len(profiles))
	emailSeen := map[string]struct{}{}
	var allEmails []EmailEntry
	accountMap := map[string]*AccountDetection{}
	totalMessages := 0

	for _, p := range profiles {
		ids = append(ids, p.ID)
		totalMessages += p.MessageCount
		for _, e := range p.AllEmails {
			low := strings.ToLower(strings.TrimSpace(e.Email))
			if low == "" {
				continue
			}
			if _, ok := emailSeen[low]; ok {
				continue
			}
			emailSeen[low] = struct{}{}
			allEmails = append(allEmails, e)
		}
		if _, ok := emailSeen[strings.ToLower(primary.PrimaryEmail)]; !ok {
			emailSeen[strings.ToLower(primary.PrimaryEmail)] = struct{}{}
		}
		for _, a := range p.DetectedInAccounts {
			hit, ok := accountMap[a.AccountID]
			if !ok {
				cp := a
				accountMap[a.AccountID] = &cp
				continue
			}
			hit.MessageCount += a.MessageCount
		}
	}

	var accounts []AccountDetection
	for _, a := range accountMap {
		accounts = append(accounts, *a)
	}
	sort.Slice(accounts, func(i, j int) bool {
		return accounts[i].MessageCount > accounts[j].MessageCount
	})

	merged := primary
	merged.AllEmails = allEmails
	merged.DetectedInAccounts = accounts
	merged.MessageCount = totalMessages

	merged.AllEmails = allEmails
	merged.DetectedInAccounts = accounts
	merged.MessageCount = totalMessages
	return ProfileGroup{
		GroupKey:     key,
		ProfileIDs:   ids,
		Profile:      merged,
		Profiles:     profiles,
		DisplayName:  profileDisplayName(merged),
		PrimaryEmail: merged.PrimaryEmail,
		MessageCount: totalMessages,
	}
}

func profileDisplayName(p Profile) string {
	if p.EnrichedData != nil {
		fn := strings.TrimSpace(p.EnrichedData.FirstName)
		ln := strings.TrimSpace(p.EnrichedData.LastName)
		if fn != "" || ln != "" {
			return strings.TrimSpace(fn + " " + ln)
		}
	}
	if strings.TrimSpace(p.DisplayName) != "" {
		return strings.TrimSpace(p.DisplayName)
	}
	return p.PrimaryEmail
}

func (s *Service) assignPersonGroups(ctx context.Context, externalUserID string) error {
	profiles, err := s.ListProfilesByStatus(ctx, externalUserID, ProfileSuggested)
	if err != nil {
		return err
	}
	groups := groupProfiles(profiles)
	for _, g := range groups {
		if len(g.ProfileIDs) < 2 {
			continue
		}
		groupID := uuid.NewString()
		for _, id := range g.ProfileIDs {
			_, _ = s.db.Exec(ctx, `
				UPDATE contact_discovered_profiles
				SET person_group_id = $3::uuid, updated_at = NOW()
				WHERE id = $2::uuid AND user_id = (SELECT id FROM users WHERE external_id = $1)
			`, externalUserID, id, groupID)
		}
	}
	return nil
}
