package message

import (
	"context"
	"testing"
	"time"

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

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

	ctx := context.Background()
	db := startMessagingTestPool(t, ctx)
	now := time.Date(2026, 5, 16, 10, 20, 0, 0, time.UTC)

	const (
		workspaceID        = "wsp_diag_broadcast_owned"
		ownerUserID        = "usr_diag_broadcast_owner"
		otherUserID        = "usr_diag_broadcast_other"
		authorMemberID     = "mem_diag_broadcast_author"
		otherMemberID      = "mem_diag_broadcast_other"
		channelID          = "chn_diag_broadcast_owned"
		messageID          = "msg_diag_broadcast_owned"
		ownedOnlineMember  = "mem_diag_broadcast_owned_online"
		ownedOfflineMember = "mem_diag_broadcast_owned_offline"
		otherOnlineMember  = "mem_diag_broadcast_other_online"
	)

	insertMessagingWorkspace(t, ctx, db, workspaceID, now)
	insertMessagingUser(t, ctx, db, ownerUserID, "diag-broadcast-owner@example.com", now)
	insertMessagingUser(t, ctx, db, otherUserID, "diag-broadcast-other@example.com", now)
	insertMessagingHumanMember(t, ctx, db, authorMemberID, workspaceID, ownerUserID, now)
	insertMessagingHumanMember(t, ctx, db, otherMemberID, workspaceID, otherUserID, now)
	insertMessagingChannel(t, ctx, db, channelID, workspaceID, "diag-broadcast-owned", authorMemberID, "", now)
	insertBroadcastAgent(t, ctx, db, workspaceID, ownerUserID, channelID, authorMemberID, ownedOnlineMember, "public", true, false, now)
	insertBroadcastAgent(t, ctx, db, workspaceID, ownerUserID, channelID, authorMemberID, ownedOfflineMember, "public", false, false, now)
	insertBroadcastAgent(t, ctx, db, workspaceID, otherUserID, channelID, otherMemberID, otherOnlineMember, "public", true, false, now)
	insertMessagingHumanScopeMessage(t, ctx, db, messageID, workspaceID, channelID, "", authorMemberID, "@all run diagnostics", 1, 1, now)

	diagnostics, err := New(db, nil, 0, Options{}).GetMessageDeliveryDiagnosticsForUser(ctx, ownerUserID, workspaceID, messageID)
	require.NoError(t, err)
	require.Equal(t, MessageDeliveryRoutingModeBroadcastAll, diagnostics.Message.RoutingMode)

	agentsByMember := make(map[string]MessageDeliveryDiagnosticsAgent, len(diagnostics.Agents))
	for _, agent := range diagnostics.Agents {
		agentsByMember[agent.MemberID] = agent
	}

	require.Equal(t, MessageDeliveryStatusTargeted, agentsByMember[ownedOnlineMember].DeliveryStatus)
	require.Equal(t, AgentDeliveryReasonChannelBroadcastAll, agentsByMember[ownedOnlineMember].DeliveryReason)
	require.Equal(t, MessageDeliveryWakeWouldStartAgent, agentsByMember[ownedOnlineMember].WakeStatus)

	require.Equal(t, MessageDeliveryStatusTargeted, agentsByMember[ownedOfflineMember].DeliveryStatus)
	require.Equal(t, AgentDeliveryReasonChannelBroadcastAll, agentsByMember[ownedOfflineMember].DeliveryReason)
	require.Equal(t, MessageDeliveryWakeWouldStartAgent, agentsByMember[ownedOfflineMember].WakeStatus)

	require.Equal(t, MessageDeliveryStatusExcluded, agentsByMember[otherOnlineMember].DeliveryStatus)
	require.Equal(t, MessageDeliveryExclusionNotAuthorOwnedBroadcastAgent, agentsByMember[otherOnlineMember].DeliveryReason)
	require.Equal(t, MessageDeliveryDiagnosticsSummary{
		TargetedAgentCount: 2,
		ExcludedAgentCount: 1,
		WakeStartCount:     2,
	}, diagnostics.Summary)
}

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

	ctx := context.Background()
	db := startMessagingTestPool(t, ctx)
	repos := NewScopeRepositories(db)
	fixture := seedScopeStoreFixture(t, ctx, db, "diagnostics_no_resurrection")
	messageOne := scopeSpecialtyMessageForTest(fixture, "msg_diagnostics_no_resurrection_keep", 1, 100)
	messageTwo := scopeSpecialtyMessageForTest(fixture, "msg_diagnostics_no_resurrection_rewound", 2, 200)
	require.NoError(t, repos.Messages.Insert(ctx, messageOne))
	require.NoError(t, repos.Messages.Insert(ctx, messageTwo))
	require.NoError(t, repos.Scopes.UpsertScope(ctx, ScopeRecord{
		ID:                fixture.scopeID,
		WorkspaceID:       fixture.workspaceID,
		Type:              "channel",
		IdentityKey:       "channel:scope-store:diagnostics_no_resurrection",
		LatestSeq:         messageTwo.Seq,
		LatestGlobalSeq:   messageTwo.WorkspaceGlobalSeq,
		LatestMessageAt:   &messageTwo.CreatedAt,
		Status:            "active",
		CreatedByMemberID: fixture.ownerMemberID,
		CreatedAt:         fixture.now,
		UpdatedAt:         fixture.now,
	}))

	diagnostics, err := New(db, nil, 0, Options{}).GetMessageDeliveryDiagnosticsForUser(ctx, fixture.ownerUserID, fixture.workspaceID, messageTwo.ID)
	require.NoError(t, err)
	require.Equal(t, messageTwo.ID, diagnostics.Message.ID)

	activity := scopeActivityEventForTest(fixture, messageTwo.ID, "act_diagnostics_no_resurrection", messageTwo.WorkspaceGlobalSeq+1, nil)
	require.NoError(t, repos.ActivityEvents.Insert(ctx, activity))
	pendingOutbox := scopeOutboxForTest(fixture, messageTwo.ID, "out_diagnostics_no_resurrection_pending", "dedupe:diagnostics-no-resurrection-pending", fixture.now, "pending")
	require.NoError(t, repos.Outbox.Insert(ctx, pendingOutbox))
	deliveredOutbox := scopeOutboxForTest(fixture, messageTwo.ID, "out_diagnostics_no_resurrection_delivered", "dedupe:diagnostics-no-resurrection-delivered", fixture.now, "delivered")
	require.NoError(t, repos.Outbox.Insert(ctx, deliveredOutbox))

	result, err := New(db, nil, 0, Options{}).RewindScopeSuffix(ctx, ScopeRewindSuffixRequest{
		WorkspaceID:       fixture.workspaceID,
		ScopeID:           fixture.scopeID,
		FromSeq:           messageTwo.Seq,
		RewoundAt:         fixture.now.Add(time.Hour),
		SuppressionReason: "test_rewind",
	})
	require.NoError(t, err)
	require.Equal(t, []string{messageTwo.ID}, result.RewoundMessageIDs)
	require.Equal(t, int64(1), result.SuppressedActivityCount)
	require.Equal(t, int64(1), result.SuppressedOutboxCount)

	require.Equal(t, int64(1), countScopeRows(t, ctx, db, "scope_messages", "id = $1", messageTwo.ID))
	var rewoundContent string
	var rewoundMetadata []byte
	require.NoError(t, db.QueryRow(ctx, `SELECT content, metadata FROM scope_messages WHERE id = $1`, messageTwo.ID).Scan(&rewoundContent, &rewoundMetadata))
	require.Empty(t, rewoundContent)
	decodedRewoundMetadata, err := DecodeMessageMetadata(rewoundMetadata)
	require.NoError(t, err)
	require.True(t, MessageMetadataIsRewound(decodedRewoundMetadata))
	require.Equal(t, int64(1), countScopeRows(t, ctx, db, "scope_activity_events", "id = $1 AND suppressed_at IS NOT NULL", activity.ID))
	require.Equal(t, int64(1), countScopeRows(t, ctx, db, "scope_fanout_outbox", "id = $1 AND status = 'suppressed' AND suppression_reason = 'test_rewind'", pendingOutbox.ID))
	require.Equal(t, int64(1), countScopeRows(t, ctx, db, "scope_fanout_outbox", "id = $1 AND status = 'delivered' AND suppressed_at IS NULL", deliveredOutbox.ID))

	_, err = New(db, nil, 0, Options{}).GetMessageDeliveryDiagnosticsForUser(ctx, fixture.ownerUserID, fixture.workspaceID, messageTwo.ID)
	require.ErrorIs(t, err, ErrNotFound)
}
