package schedule

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/message"
)

const scheduleMessageOptionsMetadataKey = "agentteamMessageOptions"

type scheduleMessageOptionsMetadata struct {
	Version         int                          `json:"version"`
	AlsoStartThread bool                         `json:"alsoStartThread,omitempty"`
	Mentions        []message.MessageMentionHint `json:"mentions,omitempty"`
}

// MessageOptions stores schedule delivery options that are owned by the
// schedule definition/run metadata rather than by the target or source shape.
type MessageOptions struct {
	AlsoStartThread bool
	MentionHints    []message.MessageMentionHint
}

// MessageOptionsFromMetadata decodes optional schedule message delivery
// metadata. Malformed or missing metadata behaves as the zero option set so
// older schedules keep their original delivery semantics.
func MessageOptionsFromMetadata(metadata []byte) MessageOptions {
	if len(metadata) == 0 {
		return MessageOptions{}
	}
	var object map[string]json.RawMessage
	if err := json.Unmarshal(metadata, &object); err != nil {
		return MessageOptions{}
	}
	raw := object[scheduleMessageOptionsMetadataKey]
	if len(raw) == 0 {
		return MessageOptions{}
	}
	var stored scheduleMessageOptionsMetadata
	if err := json.Unmarshal(raw, &stored); err != nil {
		return MessageOptions{}
	}
	return MessageOptions{
		AlsoStartThread: stored.AlsoStartThread,
		MentionHints:    normalizeMessageOptionMentionHints(stored.Mentions),
	}
}

// PutMessageOptionsMetadata updates a decoded schedule metadata object with the
// optional message delivery settings. The key is omitted for zero-value options
// so unchanged schedules do not carry extra metadata.
func PutMessageOptionsMetadata(object map[string]any, options MessageOptions) map[string]any {
	if object == nil {
		object = map[string]any{}
	}
	options.MentionHints = normalizeMessageOptionMentionHints(options.MentionHints)
	if options.AlsoStartThread || len(options.MentionHints) > 0 {
		object[scheduleMessageOptionsMetadataKey] = scheduleMessageOptionsMetadata{
			Version:         1,
			AlsoStartThread: options.AlsoStartThread,
			Mentions:        options.MentionHints,
		}
		return object
	}
	delete(object, scheduleMessageOptionsMetadataKey)
	return object
}

func normalizeMessageOptionMentionHints(hints []message.MessageMentionHint) []message.MessageMentionHint {
	if len(hints) == 0 {
		return nil
	}
	normalized := make([]message.MessageMentionHint, 0, min(len(hints), message.MaxPersistedMentionHints))
	seen := make(map[string]struct{}, len(hints))
	for _, hint := range hints {
		mentionType := strings.ToLower(strings.TrimSpace(hint.Type))
		id := strings.TrimSpace(hint.ID)
		token := strings.TrimSpace(hint.Token)
		if mentionType == "" || id == "" || token == "" {
			continue
		}
		key := mentionType + "\x00" + id + "\x00" + strings.ToLower(token)
		if _, ok := seen[key]; ok {
			continue
		}
		seen[key] = struct{}{}
		normalized = append(normalized, message.MessageMentionHint{
			Type:     mentionType,
			ID:       id,
			Token:    token,
			Metadata: hint.Metadata,
		})
		if len(normalized) >= message.MaxPersistedMentionHints {
			break
		}
	}
	return normalized
}

// marshalMessageOptionsMetadata returns a standalone schedule metadata payload
// containing only non-zero message options. Tests and narrow callers can use it
// without depending on the management-layer idempotency metadata helpers.
func marshalMessageOptionsMetadata(options MessageOptions) ([]byte, error) {
	object := PutMessageOptionsMetadata(nil, options)
	if len(object) == 0 {
		return nil, nil
	}
	data, err := json.Marshal(object)
	if err != nil {
		return nil, fmt.Errorf("marshal schedule message options metadata: %w", err)
	}
	return data, nil
}
