package message

import (
	"bytes"
	"fmt"
	"strings"

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

const maxMessageMetadataBytes = 16 * 1024

const AgentDeliveryMetadataKey = "agentDelivery"
const MessageRecallMetadataKey = "messageRecall"
const SystemNoticeMetadataKey = "systemNotice"

const SystemNoticeKindRewindConversation = "rewind.conversation"

const (
	AgentDeliveryStatusPending   = "pending"
	AgentDeliveryStatusDelivered = "delivered"
	AgentDeliveryStatusFailed    = "failed"
)

const (
	MessageLifecycleStatusRecalled = "recalled"
	MessageLifecycleStatusRewound  = "rewound"
)

// BuildMessageRecallMetadata returns the public tombstone marker stored on a
// recalled message. It intentionally omits prior message metadata so direct
// reads, stream payloads, and projection rebuilds cannot expose original
// mentions, delivery state, source snippets, or attachment hints after recall.
func BuildMessageRecallMetadata(recalledByMemberID string, recalledAt string, placeholder string) map[string]any {
	recall := map[string]any{
		"status":             MessageLifecycleStatusRecalled,
		"recalledByMemberId": strings.TrimSpace(recalledByMemberID),
	}
	if trimmed := strings.TrimSpace(recalledAt); trimmed != "" {
		recall["recalledAt"] = trimmed
	}
	if trimmed := strings.TrimSpace(placeholder); trimmed != "" {
		recall["placeholder"] = trimmed
	}
	return map[string]any{
		MessageRecallMetadataKey: recall,
	}
}

// BuildMessageRewoundMetadata returns the private tombstone marker stored on a
// message hidden by conversation rewind. It intentionally omits prior metadata
// so direct reads, stream payload rebuilds, and sidecar projections cannot
// expose original mentions, delivery state, source snippets, or attachments.
func BuildMessageRewoundMetadata(rewoundAt string) map[string]any {
	rewind := map[string]any{
		"status": MessageLifecycleStatusRewound,
	}
	if trimmed := strings.TrimSpace(rewoundAt); trimmed != "" {
		rewind["rewoundAt"] = trimmed
	}
	return map[string]any{
		MessageRecallMetadataKey: rewind,
	}
}

// BuildRewindSystemNoticeMetadata returns structured UI metadata for the
// human-visible system message appended after a successful conversation
// rewind. The stored Content remains a stable fallback key; clients localize
// this metadata instead of persisting one language in message content.
func BuildRewindSystemNoticeMetadata(actorDisplayName string) map[string]any {
	notice := map[string]any{
		"kind": SystemNoticeKindRewindConversation,
	}
	if trimmed := strings.TrimSpace(actorDisplayName); trimmed != "" {
		notice["actorDisplayName"] = trimmed
	}
	return map[string]any{
		SystemNoticeMetadataKey: notice,
	}
}

func messageRecallStatus(metadata map[string]any) string {
	raw, ok := metadata[MessageRecallMetadataKey]
	if !ok {
		return ""
	}
	recall, ok := raw.(map[string]any)
	if !ok {
		return ""
	}
	status, _ := recall["status"].(string)
	return strings.TrimSpace(status)
}

// MessageMetadataIsRecalled reports whether the decoded metadata marks this
// message as an IM recall tombstone.
func MessageMetadataIsRecalled(metadata map[string]any) bool {
	return messageRecallStatus(metadata) == MessageLifecycleStatusRecalled
}

// MessageMetadataIsRewound reports whether the decoded metadata marks this
// message as hidden by conversation rewind.
func MessageMetadataIsRewound(metadata map[string]any) bool {
	return messageRecallStatus(metadata) == MessageLifecycleStatusRewound
}

func MessageMetadataIsTombstone(metadata map[string]any) bool {
	switch messageRecallStatus(metadata) {
	case MessageLifecycleStatusRecalled, MessageLifecycleStatusRewound:
		return true
	default:
		return false
	}
}

func EncodeMessageMetadata(metadata map[string]any) ([]byte, map[string]any, error) {
	if len(metadata) == 0 {
		return []byte("{}"), nil, nil
	}

	encoded, err := model.MarshalJSONObject("message metadata", metadata)
	if err != nil {
		return nil, nil, badRequest("metadata must be valid JSON")
	}
	if len(encoded) > maxMessageMetadataBytes {
		return nil, nil, badRequest(fmt.Sprintf("metadata is too large (max %d bytes)", maxMessageMetadataBytes))
	}

	decoded, err := DecodeMessageMetadata(encoded)
	if err != nil {
		return nil, nil, badRequest("metadata must be valid JSON")
	}
	if len(decoded) == 0 {
		return []byte("{}"), nil, nil
	}
	return encoded, decoded, nil
}

func DecodeMessageMetadata(raw []byte) (map[string]any, error) {
	trimmed := bytes.TrimSpace(raw)
	if len(trimmed) == 0 || bytes.Equal(trimmed, []byte("null")) || bytes.Equal(trimmed, []byte("{}")) {
		return nil, nil
	}

	var metadata map[string]any
	if err := model.UnmarshalJSONObject("message metadata", trimmed, &metadata); err != nil {
		return nil, fmt.Errorf("decode message metadata: %w", err)
	}
	if len(metadata) == 0 {
		return nil, nil
	}
	return metadata, nil
}

func CopyMessageMetadata(metadata map[string]any) map[string]any {
	if len(metadata) == 0 {
		return nil
	}

	copied := make(map[string]any, len(metadata))
	for key, value := range metadata {
		copied[key] = value
	}
	return copied
}

// MetadataWithAgentDeliveryInterruptPolicy returns metadata with the public
// agent-delivery marker used by browser rows to distinguish urgent messages.
func MetadataWithAgentDeliveryInterruptPolicy(metadata map[string]any, interruptPolicy string) map[string]any {
	return MetadataWithAgentDeliveryStatus(metadata, interruptPolicy, AgentDeliveryStatusDelivered)
}

// MetadataWithAgentDeliveryStatus returns metadata with the public
// agent-delivery marker and status used by browser rows to render urgent
// delivery progress.
func MetadataWithAgentDeliveryStatus(metadata map[string]any, interruptPolicy string, status string) map[string]any {
	normalized, ok := NormalizeAgentInterruptPolicy(interruptPolicy)
	if !ok || normalized == "" {
		return CopyMessageMetadata(metadata)
	}
	normalizedStatus := NormalizeAgentDeliveryStatus(status)
	copied := CopyMessageMetadata(metadata)
	if copied == nil {
		copied = make(map[string]any, 1)
	}
	delivery := map[string]any{
		"interruptPolicy": normalized,
	}
	if normalizedStatus != "" {
		delivery["status"] = normalizedStatus
	}
	if existing, ok := copied[AgentDeliveryMetadataKey].(map[string]any); ok {
		for key, value := range existing {
			if strings.TrimSpace(key) == "" || key == "interruptPolicy" || key == "status" {
				continue
			}
			delivery[key] = value
		}
	}
	copied[AgentDeliveryMetadataKey] = delivery
	return copied
}

func NormalizeAgentDeliveryStatus(status string) string {
	switch strings.TrimSpace(status) {
	case AgentDeliveryStatusPending:
		return AgentDeliveryStatusPending
	case AgentDeliveryStatusDelivered, "":
		return AgentDeliveryStatusDelivered
	case AgentDeliveryStatusFailed:
		return AgentDeliveryStatusFailed
	default:
		return ""
	}
}

// MessageHasAgentDeliveryInterruptPolicy reports whether metadata marks the
// message as an urgent agent delivery.
func MessageHasAgentDeliveryInterruptPolicy(metadata map[string]any, interruptPolicy string) bool {
	normalized, ok := NormalizeAgentInterruptPolicy(interruptPolicy)
	if !ok || normalized == "" {
		return false
	}
	rawDelivery, ok := metadata[AgentDeliveryMetadataKey].(map[string]any)
	if !ok {
		return false
	}
	rawPolicy, _ := rawDelivery["interruptPolicy"].(string)
	return strings.TrimSpace(rawPolicy) == normalized
}
