package agent

import (
	"encoding/json"
	"fmt"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

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

func TestCodexCollabToolEventNameCoversProtocolActions(t *testing.T) {
	tests := map[string]string{
		"spawnAgent":  "collaboration.spawn_agent",
		"sendInput":   "collaboration.send_input",
		"resumeAgent": "collaboration.resume_agent",
		"wait":        "collaboration.wait",
		"closeAgent":  "collaboration.close_agent",
		"futureTool":  "collaboration.agent_tool",
	}
	for tool, want := range tests {
		require.Equal(t, want, codexCollabToolEventName(tool), tool)
	}
}

func TestCodexCollabActivityPayloadsAreBounded(t *testing.T) {
	item := appServerItem{
		Tool:         "spawnAgent",
		Prompt:       strings.Repeat("界", maxCodexCollabPromptRunes+50),
		AgentsStates: make(map[string]appServerCollabAgentState),
	}
	for i := 0; i < maxCodexCollabReceivers+10; i++ {
		threadID := fmt.Sprintf("thread-%03d", i)
		item.ReceiverThreadIDs = append(item.ReceiverThreadIDs, threadID)
		item.AgentsStates[threadID] = appServerCollabAgentState{
			Status:  "completed",
			Message: strings.Repeat("完", maxCodexCollabStateMessageRunes+20),
		}
	}

	var input map[string]any
	require.NoError(t, json.Unmarshal([]byte(codexCollabToolInput(item)), &input))
	require.LessOrEqual(t, len([]rune(input["prompt"].(string))), maxCodexCollabPromptRunes)
	require.Len(t, input["receiverThreadIds"], maxCodexCollabReceivers)

	var output map[string]any
	require.NoError(t, json.Unmarshal([]byte(codexCollabToolOutput(item)), &output))
	require.Len(t, output["receiverThreadIds"], maxCodexCollabReceivers)
	states := output["agentsStates"].(map[string]any)
	require.Len(t, states, maxCodexCollabReceivers)
	for _, raw := range states {
		state := raw.(map[string]any)
		require.LessOrEqual(t, len([]rune(state["message"].(string))), maxCodexCollabStateMessageRunes)
	}
}

func TestCodexAppServerCollabAgentToolCallPreservesLifecycleAndIdentity(t *testing.T) {
	driver, _ := newTestCodexDriver(t, nil)
	driver.translatedLines = make(chan string, 4)
	bindCodexPrimaryActiveThreadForIsolationTest(driver)

	started := appServerItem{
		Type:              "collabAgentToolCall",
		ID:                "collab-call-1",
		Tool:              "spawnAgent",
		Status:            "inProgress",
		SenderThreadID:    "thread-primary",
		ReceiverThreadIDs: nil,
		Prompt:            "review the activity implementation",
		Model:             "gpt-5.4",
		ReasoningEffort:   "high",
	}
	driver.handleNotification(appServerEnvelope{
		Method: "item/started",
		Params: rawRPCResult(t, appServerItemNotification{
			ThreadID: "thread-primary",
			TurnID:   "turn-primary",
			Item:     started,
		}),
	})

	completed := started
	completed.Status = "completed"
	completed.ReceiverThreadIDs = []string{"thread-child"}
	completed.AgentsStates = map[string]appServerCollabAgentState{
		"thread-child": {Status: "running", Message: "ready"},
	}
	driver.handleNotification(appServerEnvelope{
		Method: "item/completed",
		Params: rawRPCResult(t, appServerItemNotification{
			ThreadID: "thread-primary",
			TurnID:   "turn-primary",
			Item:     completed,
		}),
	})

	require.Len(t, driver.translatedLines, 2)
	startEvents := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, startEvents, 1)
	startEvent := startEvents[0]
	require.Equal(t, consts.ParsedEventKindToolCall, startEvent.Kind)
	require.Equal(t, "collaboration.spawn_agent", startEvent.ToolName)
	require.Equal(t, "collab-call-1", startEvent.ToolCallID)
	require.Equal(t, "thread-primary", startEvent.SessionID)
	require.Equal(t, "spawnAgent", startEvent.Metadata["collabTool"])
	require.Equal(t, "inProgress", startEvent.Metadata["collabStatus"])

	var input map[string]any
	require.NoError(t, json.Unmarshal([]byte(startEvent.ToolInput.(string)), &input))
	require.Equal(t, "spawnAgent", input["action"])
	require.Equal(t, "thread-primary", input["senderThreadId"])
	require.Equal(t, "review the activity implementation", input["prompt"])
	require.Equal(t, "gpt-5.4", input["model"])
	require.Equal(t, "high", input["reasoningEffort"])

	completionEvents := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, completionEvents, 1)
	completionEvent := completionEvents[0]
	require.Equal(t, consts.ParsedEventKindToolOutput, completionEvent.Kind)
	require.Equal(t, startEvent.ToolName, completionEvent.ToolName)
	require.Equal(t, startEvent.ToolCallID, completionEvent.ToolCallID)
	require.Equal(t, "thread-primary", completionEvent.SessionID)
	require.Equal(t, "completed", completionEvent.Metadata["collabStatus"])

	var output map[string]any
	require.NoError(t, json.Unmarshal([]byte(completionEvent.Text), &output))
	require.Equal(t, "completed", output["status"])
	require.Equal(t, []any{"thread-child"}, output["receiverThreadIds"])
	states := output["agentsStates"].(map[string]any)
	require.Equal(t, "running", states["thread-child"].(map[string]any)["status"])
	require.Equal(t, "ready", states["thread-child"].(map[string]any)["message"])

	driver.mu.Lock()
	require.Equal(t, "gpt-5.4", driver.modelByThread["thread-child"])
	driver.mu.Unlock()
}

func TestCodexAppServerSpawnAgentInheritsPrimaryModelWhenNoOverrideIsRequested(t *testing.T) {
	driver, _ := newTestCodexDriver(t, nil)
	driver.translatedLines = make(chan string, 2)
	bindCodexPrimaryActiveThreadForIsolationTest(driver)
	driver.updateEffectiveModel("gpt-5.4")

	driver.handleNotification(appServerEnvelope{
		Method: "item/completed",
		Params: rawRPCResult(t, appServerItemNotification{
			ThreadID: "thread-primary",
			TurnID:   "turn-primary",
			Item: appServerItem{
				Type:              "collabAgentToolCall",
				ID:                "spawn-inherited-model",
				Tool:              "spawnAgent",
				Status:            "completed",
				SenderThreadID:    "thread-primary",
				ReceiverThreadIDs: []string{"thread-child"},
			},
		}),
	})

	require.Len(t, driver.translatedLines, 1)
	_ = driver.ParseLine(<-driver.translatedLines)
	driver.mu.Lock()
	require.Equal(t, "gpt-5.4", driver.modelByThread["thread-child"])
	driver.mu.Unlock()
}

func TestCodexAppServerSubagentActivityProjectsOnlyPrimaryLifecycle(t *testing.T) {
	driver, _ := newTestCodexDriver(t, nil)
	driver.translatedLines = make(chan string, 8)
	bindCodexPrimaryActiveThreadForIsolationTest(driver)

	tests := []struct {
		kind string
		want string
	}{
		{kind: "started", want: "Started /root/reviewer"},
		{kind: "interacted", want: "Interacted with /root/reviewer"},
		{kind: "interrupted", want: "Interrupted /root/reviewer"},
	}
	for i, test := range tests {
		driver.handleNotification(appServerEnvelope{
			Method: "item/completed",
			Params: rawRPCResult(t, appServerItemNotification{
				ThreadID: "thread-primary",
				TurnID:   "turn-primary",
				Item: appServerItem{
					Type:          "subAgentActivity",
					ID:            "activity-" + test.kind,
					Kind:          test.kind,
					AgentThreadID: "thread-child",
					AgentPath:     "/root/reviewer",
				},
			}),
		})

		events := driver.ParseLine(<-driver.translatedLines)
		require.Len(t, events, 1, "case %d", i)
		event := events[0]
		require.Equal(t, consts.ParsedEventKindSubagentActivity, event.Kind)
		require.Equal(t, test.want, event.Message)
		require.Equal(t, "thread-primary", event.SessionID)
		require.Equal(t, test.kind, event.Metadata["activityKind"])
		require.Equal(t, "thread-child", event.Metadata["agentThreadId"])
		require.Equal(t, "/root/reviewer", event.Metadata["agentPath"])
	}

	// Authoritative subAgentActivity is a completed parent item. A speculative
	// started item, malformed identity, or the same shape on a child thread must
	// not create an additional primary activity row.
	for _, notification := range []appServerEnvelope{
		{
			Method: "item/started",
			Params: rawRPCResult(t, appServerItemNotification{
				ThreadID: "thread-primary",
				TurnID:   "turn-primary",
				Item: appServerItem{
					Type:          "subAgentActivity",
					ID:            "activity-speculative",
					Kind:          "started",
					AgentThreadID: "thread-child",
					AgentPath:     "/root/reviewer",
				},
			}),
		},
		{
			Method: "item/completed",
			Params: rawRPCResult(t, appServerItemNotification{
				ThreadID: "thread-primary",
				TurnID:   "turn-primary",
				Item: appServerItem{
					Type:      "subAgentActivity",
					ID:        "activity-malformed",
					Kind:      "started",
					AgentPath: "/root/reviewer",
				},
			}),
		},
		{
			Method: "item/completed",
			Params: rawRPCResult(t, appServerItemNotification{
				ThreadID: "thread-child",
				TurnID:   "turn-child",
				Item: appServerItem{
					Type:          "subAgentActivity",
					ID:            "activity-foreign",
					Kind:          "started",
					AgentThreadID: "thread-grandchild",
					AgentPath:     "/root/reviewer/helper",
				},
			}),
		},
	} {
		driver.handleNotification(notification)
	}
	require.Empty(t, driver.translatedLines)
}
