package message

import (
	"context"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/core/routing"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

type HumanMentionRecipient struct {
	UserID   string
	MemberID string
}

type MentionContext struct {
	ChannelType       string
	ParentChannelID   string
	ParentChannelType string
	ParentMessageID   string
}

// LoadMentionedHumanRecipientsForTarget resolves human direct mentions from
// message content plus validated browser-editor mention hints.
func LoadMentionedHumanRecipientsForTarget(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	authorMemberID string,
	target ThreadAccess,
	content string,
	hints []MessageMentionHint,
) ([]HumanMentionRecipient, error) {
	tokens := ParseDirectMentionTokens(content)
	if len(tokens) == 0 {
		return nil, nil
	}
	hintUserIDs := humanMentionHintUserIDs(hints, tokens)

	mentionChannelID := strings.TrimSpace(target.ChannelID)
	if target.ReplyThread != nil {
		switch target.ThreadType {
		case domain.ThreadTypeChannel:
			parentChannelID := strings.TrimSpace(target.ReplyThread.ParentChannelID)
			if parentChannelID == "" {
				return nil, fmt.Errorf("reply-thread mention parent channel is incomplete")
			}
			mentionChannelID = parentChannelID
		case domain.ThreadTypeDM:
			return loadMentionedDMHumanRecipients(
				ctx,
				tx,
				strings.TrimSpace(workspaceID),
				strings.TrimSpace(authorMemberID),
				strings.TrimSpace(target.PeerMemberID),
				tokens,
				hintUserIDs,
			)
		default:
			return nil, fmt.Errorf("unsupported reply-thread mention parent scope %q", target.ThreadType)
		}
	}

	switch target.ThreadType {
	case domain.ThreadTypeChannel:
		if mentionChannelID == "" {
			return nil, fmt.Errorf("channel mention scope is incomplete")
		}
		return loadMentionedChannelHumanRecipients(
			ctx,
			tx,
			strings.TrimSpace(workspaceID),
			strings.TrimSpace(authorMemberID),
			mentionChannelID,
			tokens,
			hintUserIDs,
		)
	case domain.ThreadTypeDM:
		return loadMentionedDMHumanRecipients(
			ctx,
			tx,
			strings.TrimSpace(workspaceID),
			strings.TrimSpace(authorMemberID),
			strings.TrimSpace(target.PeerMemberID),
			tokens,
			hintUserIDs,
		)
	default:
		return nil, fmt.Errorf("unsupported mention scope %q", target.ThreadType)
	}
}

func loadMentionedChannelHumanRecipients(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	authorMemberID string,
	channelID string,
	tokens []string,
	hintUserIDs []string,
) ([]HumanMentionRecipient, error) {
	mentionedTokensCTE, mentionedTokenArgs, _, err := coredb.BuildStringValuesCTE("mentioned_tokens", "token", normalizeNonEmptyStrings(tokens), 4)
	if err != nil {
		return nil, err
	}

	query := fmt.Sprintf(`
WITH %s
SELECT DISTINCT m.user_id, m.id
FROM members m
JOIN users u ON u.id = m.user_id
JOIN scope_memberships sm
  ON sm.member_id = m.id
 AND sm.scope_id = $1
 AND sm.removed_at IS NULL
WHERE m.workspace_id = $2
  AND m.type = 'human'
  AND m.status = 'active'
  AND m.id <> $3
  AND EXISTS (
      SELECT 1
      FROM mentioned_tokens
      WHERE LOWER(m.id) = mentioned_tokens.token
         OR LOWER(u.name) = mentioned_tokens.token
  )
ORDER BY m.id ASC
`, mentionedTokensCTE)
	args := append([]any{channelID, workspaceID, authorMemberID}, mentionedTokenArgs...)
	rows, err := tx.Query(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("load mentioned channel humans: %w", err)
	}
	defer rows.Close()

	recipients := make([]HumanMentionRecipient, 0, len(tokens))
	for rows.Next() {
		var recipient HumanMentionRecipient
		if err := rows.Scan(&recipient.UserID, &recipient.MemberID); err != nil {
			return nil, fmt.Errorf("scan mentioned channel human: %w", err)
		}
		recipients = append(recipients, recipient)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate mentioned channel humans: %w", err)
	}
	hintRecipients, err := loadMentionedChannelHumanHintRecipients(ctx, tx, workspaceID, authorMemberID, channelID, hintUserIDs)
	if err != nil {
		return nil, err
	}
	return mergeHumanMentionRecipients(recipients, hintRecipients), nil
}

func loadMentionedDMHumanRecipients(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	authorMemberID string,
	peerMemberID string,
	tokens []string,
	hintUserIDs []string,
) ([]HumanMentionRecipient, error) {
	if peerMemberID == "" || peerMemberID == authorMemberID {
		return nil, nil
	}

	tokenSet := make(map[string]struct{}, len(tokens))
	for _, token := range tokens {
		normalized := strings.ToLower(strings.TrimSpace(token))
		if normalized == "" {
			continue
		}
		tokenSet[normalized] = struct{}{}
	}
	if len(tokenSet) == 0 {
		return nil, nil
	}

	row := tx.QueryRow(ctx, `
SELECT m.user_id, m.id, COALESCE(u.name, '')
FROM members m
JOIN users u ON u.id = m.user_id
WHERE m.workspace_id = $1
  AND m.id = $2
  AND m.type = 'human'
  AND m.status = 'active'
`, workspaceID, peerMemberID)

	var recipient HumanMentionRecipient
	var legacyName string
	if err := row.Scan(&recipient.UserID, &recipient.MemberID, &legacyName); err != nil {
		if coredb.IsNotFound(err) {
			return nil, nil
		}
		return nil, fmt.Errorf("load mentioned dm human: %w", err)
	}

	if _, ok := tokenSet[strings.ToLower(strings.TrimSpace(recipient.MemberID))]; ok {
		return []HumanMentionRecipient{recipient}, nil
	}
	if _, ok := tokenSet[strings.ToLower(strings.TrimSpace(legacyName))]; ok {
		return []HumanMentionRecipient{recipient}, nil
	}
	hintRecipients, err := loadMentionedDMHumanHintRecipients(ctx, tx, workspaceID, authorMemberID, peerMemberID, hintUserIDs)
	if err != nil {
		return nil, err
	}
	return hintRecipients, nil
}

func humanMentionHintUserIDs(hints []MessageMentionHint, directTokens []string) []string {
	if len(hints) == 0 || len(directTokens) == 0 {
		return nil
	}

	allowedTokens := make([]string, 0, len(directTokens))
	seenAllowedTokens := make(map[string]struct{}, len(directTokens))
	for _, token := range directTokens {
		normalized := strings.ToLower(strings.TrimSpace(token))
		if normalized == "" {
			continue
		}
		if _, ok := seenAllowedTokens[normalized]; ok {
			continue
		}
		seenAllowedTokens[normalized] = struct{}{}
		allowedTokens = append(allowedTokens, normalized)
	}
	if len(allowedTokens) == 0 {
		return nil
	}

	userIDs := make([]string, 0, min(len(hints), MaxPersistedMentionHints))
	seenUserIDs := make(map[string]struct{}, len(hints))
	seenHintTokens := make(map[string]struct{}, len(hints))
	for _, hint := range hints {
		if strings.ToLower(strings.TrimSpace(hint.Type)) != "human" {
			continue
		}
		userID := strings.TrimSpace(hint.ID)
		if userID == "" {
			continue
		}
		if _, ok := seenUserIDs[userID]; ok {
			continue
		}
		token := strings.ToLower(strings.TrimSpace(hint.Token))
		if token == "" || !mentionTokenMatchesAny(token, allowedTokens) {
			continue
		}
		if _, ok := seenHintTokens[token]; ok {
			continue
		}
		seenHintTokens[token] = struct{}{}
		seenUserIDs[userID] = struct{}{}
		userIDs = append(userIDs, userID)
		if len(userIDs) >= MaxPersistedMentionHints {
			break
		}
	}
	return userIDs
}

func loadMentionedChannelHumanHintRecipients(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	authorMemberID string,
	channelID string,
	userIDs []string,
) ([]HumanMentionRecipient, error) {
	userIDs = normalizeNonEmptyStrings(userIDs)
	if len(userIDs) == 0 {
		return nil, nil
	}

	requestedHumansCTE, requestedHumanArgs, _, err := coredb.BuildStringValuesCTE("requested_humans", "user_id", userIDs, 4)
	if err != nil {
		return nil, err
	}
	query := fmt.Sprintf(`
WITH %s
SELECT DISTINCT m.user_id, m.id
FROM requested_humans
JOIN members m
  ON m.user_id = requested_humans.user_id
JOIN scope_memberships sm
  ON sm.member_id = m.id
 AND sm.scope_id = $1
 AND sm.removed_at IS NULL
WHERE m.workspace_id = $2
  AND m.type = 'human'
  AND m.status = 'active'
  AND m.id <> $3
ORDER BY m.id ASC
`, requestedHumansCTE)
	rows, err := tx.Query(ctx, query, append([]any{channelID, workspaceID, authorMemberID}, requestedHumanArgs...)...)
	if err != nil {
		return nil, fmt.Errorf("load mentioned channel human hints: %w", err)
	}
	defer rows.Close()

	recipients := make([]HumanMentionRecipient, 0, len(userIDs))
	for rows.Next() {
		var recipient HumanMentionRecipient
		if err := rows.Scan(&recipient.UserID, &recipient.MemberID); err != nil {
			return nil, fmt.Errorf("scan mentioned channel human hint: %w", err)
		}
		recipients = append(recipients, recipient)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate mentioned channel human hints: %w", err)
	}
	return recipients, nil
}

func loadMentionedDMHumanHintRecipients(
	ctx context.Context,
	tx coredb.Tx,
	workspaceID string,
	authorMemberID string,
	peerMemberID string,
	userIDs []string,
) ([]HumanMentionRecipient, error) {
	if peerMemberID == "" || peerMemberID == authorMemberID {
		return nil, nil
	}
	userIDs = normalizeNonEmptyStrings(userIDs)
	if len(userIDs) == 0 {
		return nil, nil
	}

	requestedHumansCTE, requestedHumanArgs, _, err := coredb.BuildStringValuesCTE("requested_humans", "user_id", userIDs, 4)
	if err != nil {
		return nil, err
	}
	query := fmt.Sprintf(`
WITH %s
SELECT DISTINCT m.user_id, m.id
FROM requested_humans
JOIN members m
  ON m.user_id = requested_humans.user_id
WHERE m.workspace_id = $1
  AND m.id = $2
  AND m.type = 'human'
  AND m.status = 'active'
  AND m.id <> $3
ORDER BY m.id ASC
`, requestedHumansCTE)
	rows, err := tx.Query(ctx, query, append([]any{workspaceID, peerMemberID, authorMemberID}, requestedHumanArgs...)...)
	if err != nil {
		return nil, fmt.Errorf("load mentioned dm human hints: %w", err)
	}
	defer rows.Close()

	recipients := make([]HumanMentionRecipient, 0, 1)
	for rows.Next() {
		var recipient HumanMentionRecipient
		if err := rows.Scan(&recipient.UserID, &recipient.MemberID); err != nil {
			return nil, fmt.Errorf("scan mentioned dm human hint: %w", err)
		}
		recipients = append(recipients, recipient)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate mentioned dm human hints: %w", err)
	}
	return recipients, nil
}

func mergeHumanMentionRecipients(groups ...[]HumanMentionRecipient) []HumanMentionRecipient {
	total := 0
	for _, group := range groups {
		total += len(group)
	}
	if total == 0 {
		return nil
	}

	recipients := make([]HumanMentionRecipient, 0, total)
	seenMemberIDs := make(map[string]struct{}, total)
	for _, group := range groups {
		for _, recipient := range group {
			memberID := strings.TrimSpace(recipient.MemberID)
			if memberID == "" {
				continue
			}
			if _, ok := seenMemberIDs[memberID]; ok {
				continue
			}
			seenMemberIDs[memberID] = struct{}{}
			recipients = append(recipients, HumanMentionRecipient{
				UserID:   strings.TrimSpace(recipient.UserID),
				MemberID: memberID,
			})
		}
	}
	return recipients
}

func HumanMentionRecipientMemberIDs(recipients []HumanMentionRecipient) []string {
	return routing.HumanMentionRecipientMemberIDs(humanMentionRecipientsToServiceRouting(recipients))
}

func MentionContextForTarget(target ThreadAccess) MentionContext {
	context := routing.MentionContextForTarget(threadAccessToServiceRouting(target))
	return MentionContext{
		ChannelType:       context.ChannelType,
		ParentChannelID:   context.ParentChannelID,
		ParentChannelType: context.ParentChannelType,
		ParentMessageID:   context.ParentMessageID,
	}
}

func humanMentionRecipientsToServiceRouting(recipients []HumanMentionRecipient) []routing.HumanMentionRecipient {
	if len(recipients) == 0 {
		return nil
	}
	converted := make([]routing.HumanMentionRecipient, 0, len(recipients))
	for _, recipient := range recipients {
		converted = append(converted, routing.HumanMentionRecipient{
			UserID:   recipient.UserID,
			MemberID: recipient.MemberID,
		})
	}
	return converted
}

func threadAccessToServiceRouting(target ThreadAccess) routing.ThreadAccess {
	converted := routing.ThreadAccess{
		ThreadType: string(target.ThreadType),
	}
	if target.ReplyThread != nil {
		converted.ReplyThread = &routing.ReplyThreadTarget{
			ParentChannelID: target.ReplyThread.ParentChannelID,
			ParentMessageID: target.ReplyThread.ParentMessageID,
		}
	}
	return converted
}
