package message

import (
	"fmt"
	"strings"

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

const (
	scopeFanoutPayloadLegacyVersion   = 1
	scopeFanoutPayloadVersion         = 2
	scopeDeliveryIntentLegacyVersion  = 1
	scopeDeliveryIntentPayloadVersion = 2
)

type ScopeFanoutPayload struct {
	Version    int             `json:"version"`
	AppMessage AppMessage      `json:"appMessage"`
	Delivery   MessageDelivery `json:"delivery"`
}

type ScopeDeliveryIntentPayload struct {
	Version                 int                             `json:"version"`
	SourceRef               DeliverySourceRef               `json:"sourceRef"`
	Message                 ReceiveMessage                  `json:"message"`
	Recipients              []AgentRecipient                `json:"recipients,omitempty"`
	WakeRecipients          []AgentRecipient                `json:"wakeRecipients,omitempty"`
	HumanRecipientMemberIDs []string                        `json:"humanRecipientMemberIds,omitempty"`
	HumanRecipientsTargeted bool                            `json:"humanRecipientsTargeted,omitempty"`
	Conversation            *DeliveryConversationProjection `json:"conversation,omitempty"`
	RealtimePolicy          string                          `json:"realtimePolicy,omitempty"`
	AttentionPolicy         string                          `json:"attentionPolicy,omitempty"`
}

func MarshalScopeFanoutPayload(appMessage AppMessage, delivery MessageDelivery) ([]byte, error) {
	if strings.TrimSpace(appMessage.ID) == "" || strings.TrimSpace(delivery.MessageID) == "" {
		return nil, fmt.Errorf("scope fanout payload message id is required")
	}
	if strings.TrimSpace(appMessage.ID) != strings.TrimSpace(delivery.MessageID) {
		return nil, fmt.Errorf("scope fanout payload message id mismatch")
	}
	deliveryCopy := delivery
	deliveryCopy.AppMessage = nil
	deliveryCopy.FollowupDeliveries = nil
	deliveryCopy.IdempotentReplay = false
	deliveryCopy.HumanRecipientMemberIDs = normalizeNonEmptyStrings(deliveryCopy.HumanRecipientMemberIDs)
	deliveryCopy.Conversation = copyDeliveryConversationProjection(deliveryCopy.Conversation)
	version := scopeFanoutPayloadLegacyVersion
	if deliveryCopy.HumanRecipientsTargeted || deliveryCopy.RecoverableStreamProjection || deliveryCopy.RequireLiveHandoff {
		version = scopeFanoutPayloadVersion
	}
	if err := validateScopeFanoutPayloadVersion(version, deliveryCopy); err != nil {
		return nil, err
	}
	payload := ScopeFanoutPayload{
		Version:    version,
		AppMessage: appMessage,
		Delivery:   deliveryCopy,
	}
	return model.MarshalJSONObject("scope fanout payload", payload)
}

func UnmarshalScopeFanoutPayload(raw []byte) (ScopeFanoutPayload, error) {
	if len(raw) == 0 {
		return ScopeFanoutPayload{}, fmt.Errorf("scope fanout payload is empty")
	}
	var payload ScopeFanoutPayload
	if err := model.UnmarshalJSONObject("scope fanout payload", raw, &payload); err != nil {
		return ScopeFanoutPayload{}, err
	}
	if strings.TrimSpace(payload.AppMessage.ID) == "" || strings.TrimSpace(payload.Delivery.MessageID) == "" {
		return ScopeFanoutPayload{}, fmt.Errorf("scope fanout payload message id is required")
	}
	if strings.TrimSpace(payload.AppMessage.ID) != strings.TrimSpace(payload.Delivery.MessageID) {
		return ScopeFanoutPayload{}, fmt.Errorf("scope fanout payload message id mismatch")
	}
	payload.Delivery.HumanRecipientMemberIDs = normalizeNonEmptyStrings(payload.Delivery.HumanRecipientMemberIDs)
	payload.Delivery.Conversation = copyDeliveryConversationProjection(payload.Delivery.Conversation)
	if err := validateScopeFanoutPayloadVersion(payload.Version, payload.Delivery); err != nil {
		return ScopeFanoutPayload{}, err
	}
	return payload, nil
}

func validateScopeFanoutPayloadVersion(version int, delivery MessageDelivery) error {
	switch version {
	case scopeFanoutPayloadLegacyVersion:
		if delivery.HumanRecipientsTargeted || delivery.RecoverableStreamProjection || delivery.RequireLiveHandoff || len(delivery.HumanRecipientMemberIDs) > 0 || delivery.Conversation != nil {
			return fmt.Errorf("scope fanout targeted Human delivery requires payload version %d", scopeFanoutPayloadVersion)
		}
	case scopeFanoutPayloadVersion:
		if !delivery.HumanRecipientsTargeted && !delivery.RecoverableStreamProjection && !delivery.RequireLiveHandoff {
			return fmt.Errorf("scope fanout payload version %d requires targeted Human delivery, recoverable stream projection, or live handoff", scopeFanoutPayloadVersion)
		}
	default:
		return fmt.Errorf("unsupported scope fanout payload version %d", version)
	}
	return nil
}

func refreshScopeFanoutPayloadForMessage(raw []byte, record ScopeMessageRecord) ([]byte, error) {
	payload, err := UnmarshalScopeFanoutPayload(raw)
	if err != nil {
		return nil, fmt.Errorf("refresh scope fanout payload: %w", err)
	}
	messageID := strings.TrimSpace(record.ID)
	if strings.TrimSpace(payload.AppMessage.ID) != messageID || strings.TrimSpace(payload.Delivery.MessageID) != messageID {
		return nil, fmt.Errorf("refresh scope fanout payload message id mismatch")
	}
	if channelID := strings.TrimSpace(payload.AppMessage.ChannelID); channelID != "" && channelID != strings.TrimSpace(record.ScopeID) {
		return nil, fmt.Errorf("refresh scope fanout payload scope id mismatch")
	}
	metadata, err := DecodeMessageMetadata(record.Metadata)
	if err != nil {
		return nil, err
	}
	payload.AppMessage.Content = record.Content
	payload.AppMessage.Metadata = CopyMessageMetadata(metadata)
	payload.AppMessage.EditedAt = record.EditedAt
	payload.AppMessage.EditedByMemberID = strings.TrimSpace(record.EditedByMemberID)
	payload.AppMessage.EditRevision = record.EditRevision
	payload.AppMessage.MessageType = strings.TrimSpace(record.MessageType)
	payload.Delivery.Message.Content = record.Content
	payload.Delivery.Message.Metadata = CopyMessageMetadata(metadata)
	payload.Delivery.Message.MessageType = strings.TrimSpace(record.MessageType)
	return MarshalScopeFanoutPayload(payload.AppMessage, payload.Delivery)
}

func NewScopeDeliveryIntentPayload(intent DeliveryIntent) ScopeDeliveryIntentPayload {
	version := scopeDeliveryIntentLegacyVersion
	if intent.HumanRecipientsTargeted {
		version = scopeDeliveryIntentPayloadVersion
	}
	return ScopeDeliveryIntentPayload{
		Version:                 version,
		SourceRef:               intent.SourceRef,
		Message:                 intent.ReceiveMessage,
		Recipients:              append([]AgentRecipient(nil), intent.Recipients...),
		WakeRecipients:          append([]AgentRecipient(nil), intent.WakeRecipients...),
		HumanRecipientMemberIDs: normalizeNonEmptyStrings(intent.HumanRecipientMemberIDs),
		HumanRecipientsTargeted: intent.HumanRecipientsTargeted,
		Conversation:            copyDeliveryConversationProjection(intent.Conversation),
		RealtimePolicy:          strings.TrimSpace(intent.RealtimePolicy),
		AttentionPolicy:         strings.TrimSpace(intent.AttentionPolicy),
	}
}

func MarshalScopeDeliveryIntentPayload(payload ScopeDeliveryIntentPayload) ([]byte, error) {
	if payload.Version == 0 {
		payload.Version = scopeDeliveryIntentLegacyVersion
		if payload.HumanRecipientsTargeted {
			payload.Version = scopeDeliveryIntentPayloadVersion
		}
	}
	sourceRef, _, err := normalizeDeliverySourceRef(payload.SourceRef)
	if err != nil {
		return nil, err
	}
	payload.SourceRef = sourceRef
	payload.Recipients = append([]AgentRecipient(nil), payload.Recipients...)
	payload.WakeRecipients = append([]AgentRecipient(nil), payload.WakeRecipients...)
	payload.HumanRecipientMemberIDs = normalizeNonEmptyStrings(payload.HumanRecipientMemberIDs)
	payload.Conversation = copyDeliveryConversationProjection(payload.Conversation)
	if err := validateScopeDeliveryIntentPayloadVersion(payload); err != nil {
		return nil, err
	}
	return model.MarshalJSONObject("scope delivery intent payload", payload)
}

func UnmarshalScopeDeliveryIntentPayload(raw []byte) (ScopeDeliveryIntentPayload, error) {
	if len(raw) == 0 {
		return ScopeDeliveryIntentPayload{}, fmt.Errorf("scope delivery intent payload is empty")
	}
	var payload ScopeDeliveryIntentPayload
	if err := model.UnmarshalJSONObject("scope delivery intent payload", raw, &payload); err != nil {
		return ScopeDeliveryIntentPayload{}, err
	}
	sourceRef, _, err := normalizeDeliverySourceRef(payload.SourceRef)
	if err != nil {
		return ScopeDeliveryIntentPayload{}, err
	}
	payload.SourceRef = sourceRef
	payload.Recipients = append([]AgentRecipient(nil), payload.Recipients...)
	payload.WakeRecipients = append([]AgentRecipient(nil), payload.WakeRecipients...)
	payload.HumanRecipientMemberIDs = normalizeNonEmptyStrings(payload.HumanRecipientMemberIDs)
	payload.Conversation = copyDeliveryConversationProjection(payload.Conversation)
	if err := validateScopeDeliveryIntentPayloadVersion(payload); err != nil {
		return ScopeDeliveryIntentPayload{}, err
	}
	return payload, nil
}

func validateScopeDeliveryIntentPayloadVersion(payload ScopeDeliveryIntentPayload) error {
	switch payload.Version {
	case scopeDeliveryIntentLegacyVersion:
		if payload.HumanRecipientsTargeted || len(payload.HumanRecipientMemberIDs) > 0 || payload.Conversation != nil {
			return fmt.Errorf("scope delivery intent targeted Human delivery requires payload version %d", scopeDeliveryIntentPayloadVersion)
		}
	case scopeDeliveryIntentPayloadVersion:
		if !payload.HumanRecipientsTargeted {
			return fmt.Errorf("scope delivery intent payload version %d requires targeted Human delivery", scopeDeliveryIntentPayloadVersion)
		}
	default:
		return fmt.Errorf("unsupported scope delivery intent payload version %d", payload.Version)
	}
	return nil
}

func copyDeliveryConversationProjection(value *DeliveryConversationProjection) *DeliveryConversationProjection {
	if value == nil {
		return nil
	}
	copied := *value
	if value.Projection != nil {
		copied.Projection = make(map[string]any, len(value.Projection))
		for key, item := range value.Projection {
			copied.Projection[key] = item
		}
	}
	return &copied
}
