package daemon

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/workflow/agentruntime"
)

// T1: production-shaped wire-router test — an agent:turn_state frame routed
// through hub.handleMessage (NOT the handler directly) must reach
// handleSessionLinkAgentTurnStateCommand and make Hub.AgentRuntimeTurnState
// known. This is the exact link that rounds 4/5 missed: the command registry
// silently dropped agent:turn_state before the handler, so Hub stayed unknown
// and recovery never fired.
func TestWireRouterRoutesTurnStateToHandler(t *testing.T) {
	ctx := context.Background()
	hub := &Hub{clients: map[string]*clientState{}}
	client := &clientState{
		machineID:             "mac_1",
		runningAgents:         map[string]bool{},
		startingAgents:        map[string]bool{},
		stoppedAgents:         map[string]bool{},
		agentStatuses:         map[string]string{},
		agentActivities:       map[string]agentActivityState{},
		agentSessions:         map[string]string{},
		agentTurnStateRecords: map[string]agentTurnStateRecord{},
		connectedAt:           time.Now().UTC(), // recent connection: within the bounded snapshot grace
	}
	hub.clients["mac_1"] = client
	client.setAgentSession("agt_1", "sess_1")

	reader := agentruntime.NewHubWorkingReader(fakeAgentMachineResolver{machineID: "mac_1"}, hub)
	_, known := reader.RuntimeWorking(ctx, "wsp_1", "agt_1")
	require.False(t, known, "before any routed frame the state is unknown (within the bounded snapshot grace)")

	// Route the exact wire frame through handleMessage (the production router).
	err := hub.handleMessage(ctx, client, []byte(`{"type":"agent:turn_state","agentId":"agt_1","turnState":"idle","sessionId":"sess_1"}`))
	require.NoError(t, err)
	working, known := reader.RuntimeWorking(ctx, "wsp_1", "agt_1")
	require.True(t, known, "a routed agent:turn_state frame must reach the handler and make the Hub state known")
	require.False(t, working, "an idle turn state routed through the wire must be recoverable")

	// A matching active frame updates the state.
	err = hub.handleMessage(ctx, client, []byte(`{"type":"agent:turn_state","agentId":"agt_1","turnState":"active","sessionId":"sess_1"}`))
	require.NoError(t, err)
	working, known = reader.RuntimeWorking(ctx, "wsp_1", "agt_1")
	require.True(t, known)
	require.True(t, working)

	// A stale-session frame routed through the wire is dropped, current state kept.
	err = hub.handleMessage(ctx, client, []byte(`{"type":"agent:turn_state","agentId":"agt_1","turnState":"idle","sessionId":"sess_old"}`))
	require.NoError(t, err)
	working, known = reader.RuntimeWorking(ctx, "wsp_1", "agt_1")
	require.True(t, known, "a stale-session frame must be dropped, preserving the current known state")
	require.True(t, working)
}

// T4: daemon-to-wire end-to-end — the exact JSON frame shape the daemon wire
// emits for each turn state (from emitAgentTeamSessionTelemetrySnapshot, proven by
// the desktop wire L1 test) is routed through handleMessage and lands in the
// Hub overlay consumed by HubWorkingReader.
func TestTurnStateDaemonFrameToHubEndToEnd(t *testing.T) {
	ctx := context.Background()
	hub := &Hub{clients: map[string]*clientState{}}
	client := &clientState{
		machineID:             "mac_1",
		runningAgents:         map[string]bool{},
		startingAgents:        map[string]bool{},
		stoppedAgents:         map[string]bool{},
		agentStatuses:         map[string]string{},
		agentActivities:       map[string]agentActivityState{},
		agentSessions:         map[string]string{},
		agentTurnStateRecords: map[string]agentTurnStateRecord{},
	}
	hub.clients["mac_1"] = client
	client.connectedAt = time.Now().UTC() // recent connection: within the bounded snapshot grace
	reader := agentruntime.NewHubWorkingReader(fakeAgentMachineResolver{machineID: "mac_1"}, hub)

	cases := []struct {
		name      string
		turnState string
		wantWork  bool
		wantKnown bool
	}{
		{"idle live session", "idle", false, true},
		{"active turn", "active", true, true},
		{"real queued turn", "queued", true, true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			// The daemon wire emits agent:session first (so the server learns the
			// sessionId) then agent:turn_state with the same sessionId.
			client.setAgentSession("agt_1", "sess_1")
			require.NoError(t, hub.handleMessage(ctx, client, []byte(`{"type":"agent:turn_state","agentId":"agt_1","turnState":"`+tc.turnState+`","sessionId":"sess_1"}`)))
			working, known := reader.RuntimeWorking(ctx, "wsp_1", "agt_1")
			require.Equal(t, tc.wantKnown, known)
			require.Equal(t, tc.wantWork, working)
		})
	}
}
