package message

import (
	"fmt"
	"strings"
	"time"
	"unicode/utf8"

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

const (
	MaxReactionEmojiKindsPerMessage = 1000
	ReactionTargetPreviewMaxRunes   = 200
	ReactionNotificationPreviewMax  = 140

	ReactionReceiveMessageType = "reaction"
)

type ReactionActorIdentity struct {
	MemberID      string
	ActorType     string
	ActorName     string
	AuthorProfile AuthorProfile
}

type ReactionReceiveMessage struct {
	MessageID         string
	ChannelType       string
	ChannelName       string
	ParentChannelType string
	ParentChannelName string
	ActorType         string
	ActorName         string
	AuthorProfile     AuthorProfile
	MessageType       string
	Content           string
	Metadata          map[string]any
	Timestamp         time.Time
}

func NormalizeReactionToolMessageShortID(messageID string) (string, bool) {
	trimmed := strings.TrimSpace(messageID)
	if len(trimmed) != 8 {
		return "", false
	}
	normalized := strings.ToLower(trimmed)
	for i := 0; i < len(normalized); i++ {
		b := normalized[i]
		switch {
		case b >= '0' && b <= '9':
		case b >= 'a' && b <= 'f':
		default:
			return "", false
		}
	}
	return normalized, true
}

func ValidateReactionEmoji(emoji string) (string, error) {
	trimmed := strings.TrimSpace(emoji)
	if trimmed == "" {
		return "", badRequest("emoji is required")
	}
	if utf8.RuneCountInString(trimmed) > 64 {
		return "", badRequest("emoji is too long")
	}
	return trimmed, nil
}

func TruncateReactionTargetPreview(content string) string {
	trimmed := strings.TrimSpace(content)
	if trimmed == "" {
		return ""
	}
	runes := []rune(trimmed)
	if len(runes) <= ReactionTargetPreviewMaxRunes {
		return trimmed
	}
	return string(runes[:ReactionTargetPreviewMaxRunes]) + "..."
}

func TruncateReactionNotificationPreview(content string) string {
	trimmed := strings.TrimSpace(content)
	if trimmed == "" {
		return ""
	}
	runes := []rune(trimmed)
	if len(runes) <= ReactionNotificationPreviewMax {
		return trimmed
	}
	return string(runes[:ReactionNotificationPreviewMax]) + "..."
}

func ReactionSummaryContent(emoji string) string {
	return fmt.Sprintf("reacted %s to your message", emoji)
}

func SyntheticReactionDeliverySeq(createdAt time.Time) int64 {
	seq := createdAt.UTC().UnixNano()
	if seq <= 0 {
		return 1
	}
	return seq
}

func ReactionMetadata(deliveryID string, reactionID string, targetMessageID string, targetMessagePreview string, emoji string, actor ReactionActorIdentity) map[string]any {
	return map[string]any{
		"reaction": map[string]any{
			"deliveryId":           strings.TrimSpace(deliveryID),
			"reactionId":           strings.TrimSpace(reactionID),
			"event":                "added",
			"targetMessageId":      strings.TrimSpace(targetMessageID),
			"targetMessagePreview": strings.TrimSpace(targetMessagePreview),
			"emoji":                strings.TrimSpace(emoji),
			"reactorMemberId":      strings.TrimSpace(actor.MemberID),
			// Old daemon reaction receive metadata keeps the legacy key names.
			"reactorSenderType": strings.TrimSpace(actor.ActorType),
			"reactorSenderName": strings.TrimSpace(actor.ActorName),
		},
	}
}

func BuildReactionReceiveMessage(target ThreadAccess, actor ReactionActorIdentity, deliveryID string, reactionID string, targetMessageID string, targetMessagePreview string, emoji string, createdAt time.Time) ReactionReceiveMessage {
	message := ReactionReceiveMessage{
		MessageID:     strings.TrimSpace(deliveryID),
		ActorType:     strings.TrimSpace(actor.ActorType),
		ActorName:     strings.TrimSpace(actor.ActorName),
		AuthorProfile: ReactionActorAuthorProfile(actor),
		MessageType:   ReactionReceiveMessageType,
		Content:       ReactionSummaryContent(emoji),
		Metadata:      ReactionMetadata(deliveryID, reactionID, targetMessageID, targetMessagePreview, emoji, actor),
		Timestamp:     createdAt.UTC(),
	}

	if target.ReplyThread != nil {
		message.ChannelType = "thread"
		message.ChannelName = "thread-" + strings.TrimSpace(target.ReplyThread.ShortID)
		message.ParentChannelType = string(target.ThreadType)
		if target.ThreadType == domain.ThreadTypeDM {
			message.ParentChannelName = strings.TrimSpace(actor.ActorName)
		} else {
			message.ParentChannelName = strings.TrimSpace(target.ReplyThread.ParentChannelName)
		}
		return message
	}
	if target.ThreadType == domain.ThreadTypeDM {
		message.ChannelType = string(domain.ThreadTypeDM)
		message.ChannelName = strings.TrimSpace(actor.ActorName)
		return message
	}
	message.ChannelType = string(domain.ThreadTypeChannel)
	message.ChannelName = strings.TrimSpace(target.ChannelName)
	return message
}

func ReactionActorAuthorProfile(actor ReactionActorIdentity) AuthorProfile {
	profile := actor.AuthorProfile.Normalize()
	if profile.Valid() {
		return profile
	}
	return AuthorProfile{
		ActorType:   strings.TrimSpace(actor.ActorType),
		MemberID:    strings.TrimSpace(actor.MemberID),
		TargetID:    strings.TrimSpace(actor.ActorName),
		DisplayName: strings.TrimSpace(actor.ActorName),
	}.Normalize()
}

func ReactionReactorAuthorProfile(memberID string, actorType string, actorName string, displayName string, avatarURL string, serviceKind string, serviceSlug string) AuthorProfile {
	memberID = strings.TrimSpace(memberID)
	actorName = strings.TrimSpace(actorName)
	displayName = strings.TrimSpace(displayName)
	profile := AuthorProfile{
		ActorType:   strings.TrimSpace(actorType),
		MemberID:    memberID,
		TargetID:    firstNonEmptyReaction(actorName, memberID),
		DisplayName: firstNonEmptyReaction(displayName, actorName, memberID),
		AvatarURL:   strings.TrimSpace(avatarURL),
		ServiceKind: strings.TrimSpace(serviceKind),
		ServiceSlug: strings.TrimSpace(serviceSlug),
	}.Normalize()
	if profile.Valid() {
		return profile
	}
	return AuthorProfile{
		ActorType:   AuthorActorTypeHuman,
		MemberID:    memberID,
		TargetID:    firstNonEmptyReaction(actorName, memberID),
		DisplayName: firstNonEmptyReaction(displayName, actorName, memberID),
		AvatarURL:   strings.TrimSpace(avatarURL),
	}.Normalize()
}

func firstNonEmptyReaction(values ...string) string {
	for _, value := range values {
		if trimmed := strings.TrimSpace(value); trimmed != "" {
			return trimmed
		}
	}
	return ""
}
