package message

import (
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

func TestScopeFanoutPayloadRoundTrip(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 5, 18, 8, 45, 0, 0, time.UTC)
	appMessage := AppMessage{
		ID:            "msg_scope_payload",
		Seq:           12,
		ChannelID:     "chn_scope_payload",
		AuthorProfile: AuthorProfile{ActorType: AuthorActorTypeHuman, DisplayName: "Alice"},
		Content:       "persisted payload",
		CreatedAt:     now,
	}
	delivery := MessageDelivery{
		MessageID:       "msg_scope_payload",
		Seq:             12,
		InterruptPolicy: AgentInterruptPolicyCurrentRun,
		Message: ReceiveMessage{
			MessageID:       "msg_scope_payload",
			Content:         "persisted payload",
			InterruptPolicy: AgentInterruptPolicyCurrentRun,
		},
		AppMessage: &appMessage,
		WakeRecipients: []AgentRecipient{{
			AgentID: "agt_scope_payload",
			TeamContexts: []TeamDeliveryContext{{
				TeamID:       "team_scope_payload",
				TeamMemberID: "wtm_scope_recipient",
				MemberRoster: []TeamDeliveryRosterMember{{
					TeamMemberID: "wtm_scope_roster",
					ActorKind:    "agent",
					ActorID:      "mem_scope_payload",
				}},
			}},
		}},
		FollowupDeliveries: []MessageDelivery{{
			MessageID: "msg_followup_payload",
		}},
		HumanRecipientMemberIDs: []string{" mem_human_2 ", "mem_human_1", "mem_human_1"},
		HumanRecipientsTargeted: true,
		Conversation: &DeliveryConversationProjection{
			ConversationID: "task:tsk_scope_payload",
			ScopeID:        "tsk_scope_payload",
			Kind:           ConversationKindTask,
			SortAt:         now,
			Projection:     map[string]any{"kind": "task", "title": "#t12 Payload"},
		},
		IdempotentReplay: true,
	}

	raw, err := MarshalScopeFanoutPayload(appMessage, delivery)
	require.NoError(t, err)

	payload, err := UnmarshalScopeFanoutPayload(raw)
	require.NoError(t, err)
	require.Equal(t, scopeFanoutPayloadVersion, payload.Version)
	require.Equal(t, appMessage.ID, payload.AppMessage.ID)
	require.Equal(t, delivery.MessageID, payload.Delivery.MessageID)
	require.Nil(t, payload.Delivery.AppMessage)
	require.Empty(t, payload.Delivery.FollowupDeliveries)
	require.False(t, payload.Delivery.IdempotentReplay)
	require.Equal(t, AgentInterruptPolicyCurrentRun, payload.Delivery.InterruptPolicy)
	require.Equal(t, AgentInterruptPolicyCurrentRun, payload.Delivery.Message.InterruptPolicy)
	require.Equal(t, delivery.WakeRecipients, payload.Delivery.WakeRecipients)
	require.Equal(t, "wtm_scope_roster", payload.Delivery.WakeRecipients[0].TeamContexts[0].MemberRoster[0].TeamMemberID)
	require.Equal(t, []string{"mem_human_2", "mem_human_1"}, payload.Delivery.HumanRecipientMemberIDs)
	require.True(t, payload.Delivery.HumanRecipientsTargeted)
	require.Equal(t, "task:tsk_scope_payload", payload.Delivery.Conversation.ConversationID)
	require.Equal(t, "#t12 Payload", payload.Delivery.Conversation.Projection["title"])
}

func TestScopeFanoutPayloadRejectsMessageIDMismatch(t *testing.T) {
	t.Parallel()

	_, err := MarshalScopeFanoutPayload(AppMessage{ID: "msg_app"}, MessageDelivery{MessageID: "msg_delivery"})
	require.Error(t, err)
	require.Contains(t, err.Error(), "mismatch")
}

func TestScopeFanoutPayloadKeepsOrdinaryMessagesOnLegacyVersion(t *testing.T) {
	t.Parallel()

	raw, err := MarshalScopeFanoutPayload(
		AppMessage{ID: "msg_ordinary"},
		MessageDelivery{MessageID: "msg_ordinary"},
	)
	require.NoError(t, err)
	payload, err := UnmarshalScopeFanoutPayload(raw)
	require.NoError(t, err)
	require.Equal(t, scopeFanoutPayloadLegacyVersion, payload.Version)
}

func TestScopeFanoutPayloadUsesVersionTwoForLiveHandoff(t *testing.T) {
	t.Parallel()

	raw, err := MarshalScopeFanoutPayload(
		AppMessage{ID: "msg_live_handoff"},
		MessageDelivery{
			MessageID:          "msg_live_handoff",
			RequireLiveHandoff: true,
			Message:            ReceiveMessage{RequireLiveHandoff: true},
		},
	)
	require.NoError(t, err)
	payload, err := UnmarshalScopeFanoutPayload(raw)
	require.NoError(t, err)
	require.Equal(t, scopeFanoutPayloadVersion, payload.Version)
	require.True(t, payload.Delivery.RequireLiveHandoff)
	require.True(t, payload.Delivery.Message.RequireLiveHandoff)
}

func TestScopeFanoutPayloadKeepsRecoverableStreamProjectionSeparateFromTargetedUnread(t *testing.T) {
	t.Parallel()

	raw, err := MarshalScopeFanoutPayload(
		AppMessage{ID: "msg_recoverable_stream"},
		MessageDelivery{
			MessageID:                   "msg_recoverable_stream",
			RecoverableStreamProjection: true,
		},
	)
	require.NoError(t, err)
	payload, err := UnmarshalScopeFanoutPayload(raw)
	require.NoError(t, err)
	require.Equal(t, scopeFanoutPayloadVersion, payload.Version)
	require.True(t, payload.Delivery.RecoverableStreamProjection)
	require.False(t, payload.Delivery.HumanRecipientsTargeted)
}

func TestScopeFanoutPayloadRejectsTargetedFieldsOnLegacyVersion(t *testing.T) {
	t.Parallel()

	_, err := UnmarshalScopeFanoutPayload([]byte(`{
		"version": 1,
		"appMessage": {"id": "msg_targeted"},
		"delivery": {
			"messageId": "msg_targeted",
			"humanRecipientsTargeted": true,
			"humanRecipientMemberIds": ["mem_targeted"]
		}
	}`))
	require.ErrorContains(t, err, "requires payload version 2")
}

func TestScopeDeliveryIntentPayloadRoundTrip(t *testing.T) {
	t.Parallel()

	intent := DeliveryIntent{
		SourceRef: DeliverySourceRef{
			Type: DeliverySourceRefActivityEvent,
			ID:   "act_payload",
		},
		ReceiveMessage: ReceiveMessage{
			MessageID: "act_payload",
			TargetKey: "tsk_payload",
			TargetSeq: 12,
			Content:   "activity wake",
		},
		Recipients:              []AgentRecipient{{AgentID: "agt_payload", AgentMemberID: "mem_payload"}},
		WakeRecipients:          []AgentRecipient{{AgentID: "agt_payload", MachineID: "mach_payload"}},
		HumanRecipientMemberIDs: []string{"mem_human_payload"},
		HumanRecipientsTargeted: true,
		Conversation: &DeliveryConversationProjection{
			ConversationID: "task:tsk_payload",
			ScopeID:        "tsk_payload",
			Kind:           ConversationKindTask,
			Projection:     map[string]any{"kind": "task"},
		},
		RealtimePolicy:  DeliveryRealtimeNone,
		AttentionPolicy: DeliveryAttentionWake,
	}

	raw, err := MarshalScopeDeliveryIntentPayload(NewScopeDeliveryIntentPayload(intent))
	require.NoError(t, err)

	payload, err := UnmarshalScopeDeliveryIntentPayload(raw)
	require.NoError(t, err)
	require.Equal(t, scopeDeliveryIntentPayloadVersion, payload.Version)
	require.Equal(t, DeliverySourceRefActivityEvent, payload.SourceRef.Type)
	require.Equal(t, "act_payload", payload.SourceRef.ID)
	require.Empty(t, payload.SourceRef.MessageID)
	require.Equal(t, "activity wake", payload.Message.Content)
	require.Equal(t, []AgentRecipient{{AgentID: "agt_payload", AgentMemberID: "mem_payload"}}, payload.Recipients)
	require.Equal(t, []AgentRecipient{{AgentID: "agt_payload", MachineID: "mach_payload"}}, payload.WakeRecipients)
	require.Equal(t, []string{"mem_human_payload"}, payload.HumanRecipientMemberIDs)
	require.True(t, payload.HumanRecipientsTargeted)
	require.Equal(t, "task:tsk_payload", payload.Conversation.ConversationID)
	require.Equal(t, DeliveryRealtimeNone, payload.RealtimePolicy)
	require.Equal(t, DeliveryAttentionWake, payload.AttentionPolicy)
}

func TestScopeDeliveryIntentPayloadRejectsNonMessageMessageID(t *testing.T) {
	t.Parallel()

	_, err := MarshalScopeDeliveryIntentPayload(ScopeDeliveryIntentPayload{
		SourceRef: DeliverySourceRef{
			Type:      DeliverySourceRefActivityEvent,
			ID:        "act_payload",
			MessageID: "msg_not_allowed",
		},
	})

	require.ErrorContains(t, err, "must not include message id")
}

func TestScopeDeliveryIntentPayloadRejectsRetiredWorkflowCommandSource(t *testing.T) {
	t.Parallel()

	_, err := MarshalScopeDeliveryIntentPayload(ScopeDeliveryIntentPayload{
		SourceRef: DeliverySourceRef{
			Type: "workflow_command",
			ID:   "cmd_retired",
		},
	})

	require.ErrorContains(t, err, "unsupported delivery source ref type")
}

func TestScopeDeliveryIntentPayloadKeepsOrdinaryIntentOnLegacyVersion(t *testing.T) {
	t.Parallel()

	raw, err := MarshalScopeDeliveryIntentPayload(NewScopeDeliveryIntentPayload(DeliveryIntent{
		SourceRef: DeliverySourceRef{Type: DeliverySourceRefActivityEvent, ID: "act_ordinary"},
	}))
	require.NoError(t, err)
	payload, err := UnmarshalScopeDeliveryIntentPayload(raw)
	require.NoError(t, err)
	require.Equal(t, scopeDeliveryIntentLegacyVersion, payload.Version)
}

func TestScopeDeliveryIntentPayloadRejectsTargetedFieldsOnLegacyVersion(t *testing.T) {
	t.Parallel()

	_, err := UnmarshalScopeDeliveryIntentPayload([]byte(`{
		"version": 1,
		"sourceRef": {"type": "activity_event", "id": "act_targeted"},
		"humanRecipientsTargeted": true,
		"humanRecipientMemberIds": ["mem_targeted"]
	}`))
	require.ErrorContains(t, err, "requires payload version 2")
}
