package agent

import (
	"testing"

	"github.com/stretchr/testify/require"

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

func newTurnDeliveryProcess(t *testing.T) *Process {
	t.Helper()
	ap := &Process{}
	t.Cleanup(func() {})
	return ap
}

func TestTurnDeliveryTargetSingleTargetYieldsDefault(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#agentteam")
	ap.mu.Unlock()
	require.Equal(t, "#agentteam", ap.CurrentTurnDeliveryTarget())
}

func TestTurnDeliveryTargetFailsClosedWithoutDelivery(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	require.Equal(t, "", ap.CurrentTurnDeliveryTarget())
}

func TestTurnDeliveryTargetFailsClosedOnMultipleTargets(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#agentteam")
	ap.addTurnDeliveryLocked("#other:0234da66")
	ap.mu.Unlock()
	require.Equal(t, "", ap.CurrentTurnDeliveryTarget(),
		"a turn that delivered several distinct targets must not pick one")
}

func TestTurnDeliveryTargetSameTargetDeduplicates(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#agentteam:0234da66")
	ap.addTurnDeliveryLocked("#agentteam:0234da66")
	ap.mu.Unlock()
	require.Equal(t, "#agentteam:0234da66", ap.CurrentTurnDeliveryTarget())
}

// The state machine reports trusted targets kind-agnostically; whether a
// kind may seed a default is the consumer's decision (see the exec handler
// tests for the fail-closed branches).
func TestTurnDeliveryTargetReportsRawKind(t *testing.T) {
	for _, target := range []string{"dm:@user", "#tsk_abc123", "#team_abcdefghijklmn", "workflow:plan1"} {
		ap := newTurnDeliveryProcess(t)
		ap.mu.Lock()
		ap.addTurnDeliveryLocked(target)
		ap.mu.Unlock()
		require.Equal(t, target, ap.CurrentTurnDeliveryTarget(), target)
	}
}

// A trusted delivery with a blank target introduces an unattributable
// destination: the turn can no longer prove exactly one target, so it must
// poison even when a valid target was already recorded.
func TestTurnDeliveryBlankTargetPoisonsAfterValidTarget(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#agentteam")
	ap.addTurnDeliveryLocked("")
	ap.mu.Unlock()
	require.Equal(t, "", ap.CurrentTurnDeliveryTarget())
}

func TestTurnDeliveryTargetPoisonedByUnparseableDelivery(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#agentteam")
	ap.addTurnDeliveryLocked("#Bad_Slug!")
	ap.mu.Unlock()
	require.Equal(t, "", ap.CurrentTurnDeliveryTarget(),
		"an unparseable trusted delivery poisons the turn even after normal targets")
}

func TestTurnDeliveryTargetUnavailableDuringReplaceHandover(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#agentteam")
	ap.mu.Unlock()
	require.Equal(t, "#agentteam", ap.CurrentTurnDeliveryTarget())

	ap.mu.Lock()
	saved := ap.beginTurnDeliveryHandoverLocked("owner-1")
	ap.mu.Unlock()
	require.Equal(t, "", ap.CurrentTurnDeliveryTarget(),
		"pre-ACK handover window must not expose either turn's target")

	ap.mu.Lock()
	ap.restoreTurnDeliveryLocked(saved)
	ap.mu.Unlock()
	require.Equal(t, "#agentteam", ap.CurrentTurnDeliveryTarget(),
		"failed handover rolls back to the previous turn")
}

func TestTurnDeliveryAckCommitsReplaceHandover(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#agentteam")
	ap.beginTurnDeliveryHandoverLocked("owner-1")
	ap.mu.Unlock()

	ap.mu.Lock()
	ap.commitTurnDeliveryAckLocked("owner-1", "#other:0234da66")
	ap.mu.Unlock()
	require.Equal(t, "#other:0234da66", ap.CurrentTurnDeliveryTarget())
}

func TestTurnDeliveryStaleRestoreCannotAbortNewOwnerHandover(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#old")
	staleSaved := ap.beginTurnDeliveryHandoverLocked("owner-1")
	ap.commitTurnDeliveryAckLocked("owner-1", "#current")
	currentSaved := ap.beginTurnDeliveryHandoverLocked("owner-2")
	ap.restoreTurnDeliveryLocked(staleSaved)
	require.Equal(t, "owner-2", ap.turnDelivery.handover.ownerID)
	ap.restoreTurnDeliveryLocked(currentSaved)
	ap.mu.Unlock()

	require.Equal(t, "#current", ap.CurrentTurnDeliveryTarget(),
		"a stale abort may neither finish another owner's handover nor restore an older turn")
}

func TestTurnDeliveryLateForeignAckNeverEndsHandover(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.beginTurnDeliveryHandoverLocked("owner-1")
	ap.commitTurnDeliveryAckLocked("owner-2", "#other:0234da66")
	ap.mu.Unlock()
	require.Equal(t, "", ap.CurrentTurnDeliveryTarget(),
		"a late ack for a different pending input never finishes the transaction")
}

func TestTurnDeliveryResetClearsTargets(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#agentteam")
	ap.resetTurnDeliveryLocked()
	ap.mu.Unlock()
	require.Equal(t, "", ap.CurrentTurnDeliveryTarget(),
		"authoritative turn end clears the set")
}

func TestTurnDeliveryAppendKeepsTargetsThroughAck(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.mu.Lock()
	ap.addTurnDeliveryLocked("#agentteam")
	ap.beginTurnDeliveryAppendLocked("owner-1")
	ap.mu.Unlock()
	require.Equal(t, "#agentteam", ap.CurrentTurnDeliveryTarget(),
		"same-turn append has no unavailable window")

	ap.mu.Lock()
	ap.commitTurnDeliveryAckLocked("owner-1", "#agentteam")
	ap.mu.Unlock()
	require.Equal(t, "#agentteam", ap.CurrentTurnDeliveryTarget())

	ap.mu.Lock()
	ap.commitTurnDeliveryAckLocked("owner-1", "#other")
	ap.mu.Unlock()
	require.Equal(t, "", ap.CurrentTurnDeliveryTarget(),
		"a second distinct target in the same turn fails closed")
}

func TestTurnDeliveryInitialSeedFromLaunchPrompt(t *testing.T) {
	ap := newTurnDeliveryProcess(t)
	ap.seedInitialTurnDeliveryLocked(&types.InboxMessage{Target: "#agentteam:0234da66"})
	require.Equal(t, "#agentteam:0234da66", ap.CurrentTurnDeliveryTarget(),
		"the launch prompt's initial delivery is trusted evidence from turn one")
	ap.seedInitialTurnDeliveryLocked(nil)
}

func TestTurnDeliveryNilProcessFailsClosed(t *testing.T) {
	var ap *Process
	require.Equal(t, "", ap.CurrentTurnDeliveryTarget())
}
