package promptcompiler

import (
	"context"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

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

// TestDeliveryToolNameRoutingPerRuntime verifies that, for each runtime, the
// delivery path renders AgentTeam message tool names with the correct runtime-specific
// prefix across the participant-delivery footer, the active-task sub-block, and
// the task_dispatch / task_discussion routing sub-blocks — and never emits a bare
// (un-prefixed) tool call form.
func TestDeliveryToolNameRoutingPerRuntime(t *testing.T) {
	t.Parallel()

	runtimes := []struct {
		name    string
		runtime string
	}{
		{name: "claude", runtime: "claude"},
		{name: "codex", runtime: "codex"},
		{name: "kimi", runtime: "kimi"},
		{name: "opencode", runtime: "opencode"},
		{name: "builtin", runtime: "builtin"},
	}

	for _, rt := range runtimes {
		rt := rt
		t.Run(rt.name, func(t *testing.T) {
			t.Parallel()

			namer := toolNamePrefix(rt.runtime, "agentteam")
			wantSend := namer("message_send")
			wantRead := namer("message_history_read")

			// claude/codex/kimi use the mcp__agentteam__ prefix; opencode/builtin use mcp_agentteam_.
			expectedPrefix := "mcp__agentteam__"
			if rt.runtime == "opencode" || rt.runtime == "builtin" {
				expectedPrefix = "mcp_agentteam_"
			}
			require.Equal(t, expectedPrefix+"message_send", wantSend)
			require.Equal(t, expectedPrefix+"message_history_read", wantRead)

			compiler := New(DefaultPolicy())

			t.Run("active_task_target", func(t *testing.T) {
				t.Parallel()
				// Plain human message carrying an active-task target. This exercises
				// both formatActiveTaskTargetPrompt (task routing -> message_send)
				// and FormatActiveTaskDirectMessageRoutingPrompt (history read +
				// message_send for the plain-message target).
				env, err := compiler.CompileDeliveryEnvelope(
					context.Background(),
					EventRoutedMessageDelivery,
					"evt-active-"+rt.name,
					CompileInput{
						AgentID: "agt-1",
						Runtime: rt.runtime,
						Delivery: DeliveryCompileInput{
							MessageID:      "msg-active",
							Target:         "#agentteam:00000001",
							ChannelType:    "thread",
							AuthorIdentity: types.AuthorIdentity{ActorType: "human", DisplayName: "alice"},
							Content:        "please ship the feature",
							DeliveryMode:   "turn_direct",
							ActiveTaskTarget: types.ActiveTaskTarget{
								Source:     "human_selected",
								Channel:    "agentteam",
								TaskNumber: 7,
								TaskScope:  "#tsk_7",
							},
						},
					},
				)
				require.NoError(t, err)
				text := renderDeliveryTextSlot(t, compiler, env, rt.runtime)

				// Active-task routing sub-block: task discussion -> prefixed send.
				require.Contains(t, text, wantSend+`(target="#tsk_7", content="...")`)
				// Direct routing sub-block (plain message + active task): prefixed
				// history read and prefixed send for the plain target.
				require.Contains(t, text, wantRead+`(channel="#agentteam:00000001"`)
				require.Contains(t, text, wantSend+`(target="#agentteam:00000001", content="...")`)
				require.NotContains(t, text, "routing_kind")
				requireNotContainsBareToolCall(t, text, "message_send", rt.runtime)
				requireNotContainsBareToolCall(t, text, "message_history_read", rt.runtime)
			})

			t.Run("task_dispatch", func(t *testing.T) {
				t.Parallel()
				// task_dispatch metadata exercises formatTaskDispatchMetadataDetails,
				// whose reply line uses the prefixed message_send tool.
				env, err := compiler.CompileDeliveryEnvelope(
					context.Background(),
					EventRoutedMessageDelivery,
					"evt-dispatch-"+rt.name,
					CompileInput{
						AgentID: "agt-1",
						Runtime: rt.runtime,
						Delivery: DeliveryCompileInput{
							MessageID:      "msg-dispatch",
							Target:         "#tsk_42",
							ChannelType:    "task",
							AuthorIdentity: types.AuthorIdentity{ActorType: "human", DisplayName: "alice"},
							Content:        "see the task brief",
							DeliveryMode:   "turn_direct",
							MessageType:    "task_dispatch",
							Metadata: map[string]any{
								"task": map[string]any{
									"taskNumber": float64(42),
									"title":      "ship the feature",
									"channelRef": "agentteam",
								},
								"taskDiscussion": map[string]any{
									"body":      "How should we sequence the rollout?",
									"createdBy": map[string]any{"name": "alice"},
								},
							},
						},
					},
				)
				require.NoError(t, err)
				text := renderDeliveryTextSlot(t, compiler, env, rt.runtime)

				require.Contains(t, text, `reply: use `+wantSend+` with the exact task scope target`)
				require.Contains(t, text, wantSend+`(target="#tsk_42", content="...")`)
				require.NotContains(t, text, "routing_kind")
				requireNotContainsBareToolCall(t, text, "message_send", rt.runtime)
				requireNotContainsBareToolCall(t, text, "message_history_read", rt.runtime)
			})

			t.Run("task_discussion", func(t *testing.T) {
				t.Parallel()
				// task_discussion message exercises formatTaskScopeDiscussionPromptDetails,
				// whose reply line uses the prefixed message_send tool against the
				// task scope target.
				env, err := compiler.CompileDeliveryEnvelope(
					context.Background(),
					EventRoutedMessageDelivery,
					"evt-discussion-"+rt.name,
					CompileInput{
						AgentID: "agt-1",
						Runtime: rt.runtime,
						Delivery: DeliveryCompileInput{
							MessageID:      "msg-discussion",
							Target:         "#tsk_42",
							ChannelType:    "task",
							AuthorIdentity: types.AuthorIdentity{ActorType: "human", DisplayName: "alice"},
							Content:        "reply to the task comment",
							DeliveryMode:   "turn_direct",
							MessageType:    "task_discussion",
						},
					},
				)
				require.NoError(t, err)
				text := renderDeliveryTextSlot(t, compiler, env, rt.runtime)

				require.Contains(t, text, `reply: use `+wantSend+`(target="#tsk_42", content="...")`)
				require.NotContains(t, text, "routing_kind")
				requireNotContainsBareToolCall(t, text, "message_send", rt.runtime)
				requireNotContainsBareToolCall(t, text, "message_history_read", rt.runtime)
			})
		})
	}
}

// renderDeliveryTextSlot compiles and renders a delivery envelope and returns
// the model-facing deliveryText slot.
func renderDeliveryTextSlot(t *testing.T, compiler *Compiler, env RuntimeInputEnvelope, runtime string) string {
	t.Helper()
	rendered, err := compiler.RenderRuntimeInput(context.Background(), env, runtime, RenderOptions{})
	require.NoError(t, err)
	return ExtractStringSlot(rendered, "deliveryText")
}

// requireNotContainsBareToolCall asserts that text does not contain a bare
// (un-prefixed) tool call like "message_send(". It strips every valid prefixed
// form first, so a leftover occurrence after stripping is a bare call.
func requireNotContainsBareToolCall(t *testing.T, text, toolName, runtime string) {
	t.Helper()
	for _, prefix := range []string{"mcp__agentteam__", "mcp_agentteam_"} {
		text = strings.ReplaceAll(text, prefix+toolName+"(", "")
	}
	require.NotContains(t, text, toolName+"(", "runtime %q text must not contain bare %s( after stripping prefixed forms", runtime, toolName)
}
