package dispatcher

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

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

func newFenceTestProcess(t *testing.T, authorizationID string, target string) *agent.Process {
	t.Helper()
	proc, err := agent.NewProcess(
		context.Background(),
		runningStubDriver{},
		drivercontract.LaunchConfig{
			AgentID: "agentteam1-claude",
			WorkDir: t.TempDir(),
			Exec:    &drivercontract.ExecConfig{AuthorizationID: authorizationID},
		},
		agent.ProcessOptions{
			Wake:            &agent.WakeInfo{MessageID: "msg-fence", Target: target},
			InitialDelivery: &types.InboxMessage{MessageID: "msg-fence", Target: target},
		},
	)
	require.NoError(t, err)
	t.Cleanup(func() {
		proc.Stop()
		require.Eventually(t, func() bool { return proc.State() == "stopped" }, time.Second, 10*time.Millisecond)
	})
	return proc
}

// TestDispatcherCurrentTurnDeliveryTargetFencedToAuthorizationGeneration locks
// the cross-generation fence: after a process replacement, the replaced
// process's still-valid Exec authorization must not read its successor's turn
// target, and only the exact current authorization may.
func TestDispatcherCurrentTurnDeliveryTargetFencedToAuthorizationGeneration(t *testing.T) {
	d := mustNewDispatcher("")
	binding := conversationBinding{
		channelID:       "chn_fence",
		targetScopeType: "channel",
		conversationKey: "#agentteam:fence",
		serverAgentID:   "agentteam1-claude",
	}
	key := sessionKeyForBinding("agentteam1-claude", binding).String()
	sess := &interactiveSession{
		sessionKey: key,
		agentID:    "agentteam1-claude",
		agent:      agent.Config{ID: "agentteam1-claude"},
		binding:    binding,
	}
	d.sessionManager.Set(key, sess)

	sess.mu.Lock()
	sess.process = newFenceTestProcess(t, "auth-old", "#old-chan")
	sess.mu.Unlock()
	require.Equal(t, "#old-chan", d.CurrentTurnDeliveryTarget("agentteam1-claude", "auth-old"))

	sess.mu.Lock()
	sess.process = newFenceTestProcess(t, "auth-new", "#new-chan:0234da66")
	sess.mu.Unlock()

	require.Equal(t, "", d.CurrentTurnDeliveryTarget("agentteam1-claude", "auth-old"),
		"a replaced process's still-valid authorization must not read the successor turn target")
	require.Equal(t, "#new-chan:0234da66", d.CurrentTurnDeliveryTarget("agentteam1-claude", "auth-new"))
}

func TestDispatcherCurrentTurnDeliveryTargetRequiresAuthorizationIdentity(t *testing.T) {
	d := mustNewDispatcher("")
	binding := conversationBinding{
		channelID:       "chn_fence2",
		targetScopeType: "channel",
		conversationKey: "#agentteam:fence2",
		serverAgentID:   "agentteam1-claude",
	}
	key := sessionKeyForBinding("agentteam1-claude", binding).String()
	d.sessionManager.Set(key, &interactiveSession{
		sessionKey: key,
		agentID:    "agentteam1-claude",
		agent:      agent.Config{ID: "agentteam1-claude"},
		binding:    binding,
		process:    newFenceTestProcess(t, "auth-current", "#chan"),
	})
	require.Equal(t, "", d.CurrentTurnDeliveryTarget("agentteam1-claude", ""),
		"an unidentifiable authorization must fail closed")
	require.Equal(t, "", d.CurrentTurnDeliveryTarget("nonexistent-agent", "auth-current"))
}

// TestDispatcherCurrentTurnDeliveryTargetAnswersPendingLaunchWindow locks the
// Spawn-window fence: before publication, an in-flight Process bound to its
// exact Exec authorization answers through the launch-window registry while a
// different (replaced) authorization reads nothing.
func TestDispatcherCurrentTurnDeliveryTargetAnswersPendingLaunchWindow(t *testing.T) {
	d := mustNewDispatcher("")
	inFlight := newFenceTestProcess(t, "auth-inflight", "#launch:0234da66")
	reservation, _, ok := d.sessionManager.BeginStart("fence-pending-key", nil)
	require.True(t, ok)
	d.bindPendingTurnTarget("auth-inflight", inFlight, reservation)

	require.Equal(t, "#launch:0234da66", d.CurrentTurnDeliveryTarget("agentteam1-claude", "auth-inflight"),
		"the in-flight launch process answers first-turn exec calls")
	require.Equal(t, "", d.CurrentTurnDeliveryTarget("agentteam1-claude", "auth-replaced"),
		"a different authorization must not read the pending window")

	d.unbindPendingTurnTarget("auth-inflight")
	require.Equal(t, "", d.CurrentTurnDeliveryTarget("agentteam1-claude", "auth-inflight"),
		"after unbinding, no pending window and no published process means fail closed")
}
