package agent

import (
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/messagesend"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
)

func TestAddDeliveryTargetsCollectsCarriedTargets(t *testing.T) {
	ap := &Process{}

	ap.addSendTargetsLocked(types.InboxMessage{
		MessageID: "m1",
		Target:    "#agentteam-in-agentteam:52a2a221",
	})
	ap.addSendTargetsLocked(types.InboxMessage{
		MessageID: "m2",
		Target:    "#agentteam-in-agentteam:52a2a221",
	})
	ap.addSendTargetsLocked(types.InboxMessage{
		MessageID:   "m3",
		Target:      "#team",
		MessageType: "task_dispatch",
		Metadata: map[string]any{
			"task": map[string]any{
				"channelRef": "#team",
				"taskNumber": 12,
				"taskScope":  "#tsk_abc",
				"title":      "T",
			},
		},
	})

	scope, live := ap.SendScope()
	require.True(t, live)
	// task_dispatch is a legacy read-only envelope: it must not derive an
	// implicit task scope target. Only its own conversation target joins the
	// scope; sending to the task target requires explicit allow_cross_target.
	require.Equal(t, []messagesend.Target{
		{Target: "#agentteam-in-agentteam:52a2a221", Source: sendScopeSourceConversation},
		{Target: "#team", Source: sendScopeSourceConversation},
	}, scope.Targets)
}

func TestResetSendScopeClearsAndAdvancesGeneration(t *testing.T) {
	ap := &Process{}
	ap.addSendTargetsLocked(types.InboxMessage{
		MessageID: "m1",
		Target:    "#agentteam-in-agentteam:52a2a221",
	})

	ap.resetSendScopeLocked()

	scope, live := ap.SendScope()
	require.True(t, live)
	require.Empty(t, scope.Targets)
	require.Equal(t, uint64(1), ap.sendScope.generation)
}

func TestLateAckFromRetiredGenerationDoesNotAdd(t *testing.T) {
	ap := &Process{}
	ap.addSendTargetsLocked(types.InboxMessage{
		MessageID: "m1",
		Target:    "#a",
	})
	staleGeneration := ap.sendScope.generation

	// The authoritative request ends and a replacement turn surfaces B.
	ap.resetSendScopeLocked()
	liveGeneration := ap.sendScope.generation
	pendingB := pendingRuntimeInput{
		Message:      types.InboxMessage{MessageID: "m2", Target: "#b"},
		sendScopeGen: liveGeneration,
	}
	pendingA := pendingRuntimeInput{
		Message:      types.InboxMessage{MessageID: "m1", Target: "#a"},
		sendScopeGen: staleGeneration,
	}

	ap.addSendTargetsLocked(pendingB.Message)
	if pendingA.sendScopeGen == ap.sendScope.generation {
		ap.addSendTargetsLocked(pendingA.Message)
	}

	scope, live := ap.SendScope()
	require.True(t, live)
	require.Equal(t, []messagesend.Target{
		{Target: "#b", Source: sendScopeSourceConversation},
	}, scope.Targets)
}

func TestInitialDeliverySeedsWakeTarget(t *testing.T) {
	ap := &Process{}
	ap.addSendTargetLocked("#agentteam-overnight:38bf4096", sendScopeSourceConversation)

	scope, live := ap.SendScope()
	require.True(t, live)
	require.Equal(t, []messagesend.Target{
		{Target: "#agentteam-overnight:38bf4096", Source: sendScopeSourceConversation},
	}, scope.Targets)
}

func TestSendScopeUnavailableWhenStopped(t *testing.T) {
	ap := &Process{}
	ap.addSendTargetsLocked(types.InboxMessage{
		MessageID: "m1",
		Target:    "#agentteam-in-agentteam:52a2a221",
	})
	ap.stopping = true

	scope, live := ap.SendScope()
	require.False(t, live)
	require.Empty(t, scope.Targets)

	ap.stopping = false
	ap.state = processStateStopped
	scope, live = ap.SendScope()
	require.False(t, live)
	require.Empty(t, scope.Targets)

	ap.state = processStateRunning
	scope, live = ap.SendScope()
	require.True(t, live)
	require.Len(t, scope.Targets, 1)
}
