package message

import (
	"context"
	"sort"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

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

	ctx := context.Background()
	pool := startMessagingTestPool(t, ctx)
	now := time.Date(2026, 5, 16, 8, 5, 0, 0, time.UTC)

	const (
		workspaceID    = "wsp_broadcast_channel_scope"
		ownerUserID    = "usr_broadcast_owner"
		otherUserID    = "usr_broadcast_other"
		senderMemberID = "mem_broadcast_sender"
		otherMemberID  = "mem_broadcast_other"
		channelID      = "chn_broadcast_scope"
	)
	insertMessagingWorkspace(t, ctx, pool, workspaceID, now)
	insertMessagingUser(t, ctx, pool, ownerUserID, "broadcast-owner@example.com", now)
	insertMessagingUser(t, ctx, pool, otherUserID, "broadcast-other@example.com", now)
	insertMessagingHumanMember(t, ctx, pool, senderMemberID, workspaceID, ownerUserID, now)
	insertMessagingHumanMember(t, ctx, pool, otherMemberID, workspaceID, otherUserID, now)
	insertMessagingChannel(t, ctx, pool, channelID, workspaceID, "broadcast-scope", senderMemberID, "", now)

	const (
		ownedPublicMember       = "mem_broadcast_owned_public"
		ownedPrivateMember      = "mem_broadcast_owned_private"
		ownedDisconnectedMember = "mem_broadcast_owned_disconnected"
		otherPublicMember       = "mem_broadcast_other_public"
		otherPrivateMember      = "mem_broadcast_other_private"
		offChannelMember        = "mem_broadcast_off_channel"
	)
	insertBroadcastAgent(t, ctx, pool, workspaceID, ownerUserID, channelID, senderMemberID, ownedPublicMember, "public", true, false, now)
	insertBroadcastAgent(t, ctx, pool, workspaceID, ownerUserID, channelID, senderMemberID, ownedPrivateMember, "private", true, false, now)
	insertBroadcastAgent(t, ctx, pool, workspaceID, ownerUserID, channelID, senderMemberID, ownedDisconnectedMember, "private", false, false, now)
	insertBroadcastAgent(t, ctx, pool, workspaceID, otherUserID, channelID, otherMemberID, otherPublicMember, "public", true, false, now)
	insertBroadcastAgent(t, ctx, pool, workspaceID, otherUserID, channelID, otherMemberID, otherPrivateMember, "private", true, false, now)
	insertBroadcastAgent(t, ctx, pool, workspaceID, ownerUserID, "", senderMemberID, offChannelMember, "public", true, false, now)

	var plan AgentRoutingPlan
	require.NoError(t, coredb.WithTx(ctx, pool, func(tx coredb.Tx) error {
		var err error
		plan, err = LoadOwnedScopeBroadcastRoutingPlan(ctx, tx, ownerUserID, channelID, senderMemberID, "@all assemble")
		return err
	}))
	require.Equal(t, []string{ownedDisconnectedMember, ownedPrivateMember, ownedPublicMember}, recipientMemberIDs(plan.DurableRecipients))
	require.Equal(t, []string{ownedDisconnectedMember, ownedPrivateMember, ownedPublicMember}, recipientMemberIDs(plan.WakeRecipients))
	require.Equal(t, AgentDeliveryReasonChannelBroadcastAll, recipientReasonByMember(plan.DurableRecipients)[ownedPrivateMember])
	require.Equal(t, AgentDeliveryReasonChannelBroadcastAll, recipientReasonByMember(plan.WakeRecipients)[ownedPublicMember])

	var onlinePlan AgentRoutingPlan
	require.NoError(t, coredb.WithTx(ctx, pool, func(tx coredb.Tx) error {
		var err error
		onlinePlan, err = LoadOwnedScopeBroadcastRoutingPlan(ctx, tx, ownerUserID, channelID, senderMemberID, "@online assemble")
		return err
	}))
	require.Equal(t, []string{ownedDisconnectedMember, ownedPrivateMember, ownedPublicMember}, recipientMemberIDs(onlinePlan.DurableRecipients))
	require.Equal(t, []string{ownedDisconnectedMember, ownedPrivateMember, ownedPublicMember}, recipientMemberIDs(onlinePlan.WakeRecipients))
	require.Equal(t, AgentDeliveryReasonChannelBroadcastOnline, recipientReasonByMember(onlinePlan.WakeRecipients)[ownedPrivateMember])
}

func insertBroadcastAgent(t *testing.T, ctx context.Context, pool coredb.QueryExecer, workspaceID string, userID string, channelID string, ownerMemberID string, memberID string, visibility string, legacyConnected bool, legacyActive bool, now time.Time) {
	t.Helper()

	machineID := "mach_" + memberID
	agentID := "agt_" + memberID
	mustExecMessaging(t, ctx, pool, `
INSERT INTO machines (id, workspace_id, created_by_user_id, name, api_key, created_at, updated_at)
VALUES ($1, $2, $3, $1, $4, $5, $5)
`, machineID, workspaceID, userID, machineID+"-key", now)
	insertMessagingAgentMember(t, ctx, pool, memberID, workspaceID, now)
	insertMessagingAgent(t, ctx, pool, agentID, workspaceID, machineID, memberID, memberID, now)

	status := "inactive"
	if legacyActive {
		status = "active"
	}
	mustExecMessaging(t, ctx, pool, `
UPDATE agents
SET status = $2, updated_at = $3
WHERE id = $1
`, agentID, status, now)
	if legacyConnected {
		mustExecMessaging(t, ctx, pool, `
UPDATE machines
SET connected_at = $2, disconnected_at = NULL, updated_at = $2
WHERE id = $1
`, machineID, now)
	}
	if channelID == "" {
		return
	}
	membershipOwner := ""
	if visibility == "private" {
		membershipOwner = ownerMemberID
	}
	require.NoError(t, EnsureScopeMembership(ctx, pool, channelID, memberID, visibility, membershipOwner, "active", 0, 0, now))
}

func recipientMemberIDs(recipients []AgentRecipient) []string {
	ids := make([]string, 0, len(recipients))
	for _, recipient := range recipients {
		ids = append(ids, recipient.AgentMemberID)
	}
	sort.Strings(ids)
	return ids
}

func recipientReasonByMember(recipients []AgentRecipient) map[string]string {
	reasons := make(map[string]string, len(recipients))
	for _, recipient := range recipients {
		reasons[recipient.AgentMemberID] = recipient.DeliveryReason
	}
	return reasons
}
