package mcp

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/require"

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

func TestAgentTeamMCPToolGroupsDefineStableDomains(t *testing.T) {
	h := &mcpHandler{}
	groups := mcpToolGroups(h)

	gotGroups := make([]string, 0, len(groups))
	gotTools := make([]string, 0)
	seen := map[string]string{}
	for _, group := range groups {
		gotGroups = append(gotGroups, group.Name)
		for _, registration := range group.Tools {
			require.NotNil(t, registration.call, "tool %s in group %s should have MCP handler", registration.Def.Name, group.Name)
			require.NotNil(t, registration.callWithAPI, "tool %s in group %s should have migrated handler", registration.Def.Name, group.Name)
			if previous, ok := seen[registration.Def.Name]; ok {
				t.Fatalf("tool %s registered in both %s and %s", registration.Def.Name, previous, group.Name)
			}
			seen[registration.Def.Name] = group.Name
			gotTools = append(gotTools, registration.Def.Name)
		}
	}

	require.Equal(t, []string{"message"}, gotGroups)
	require.Equal(t, expectedMCPToolNames(), gotTools)

	tools := h.Tools()
	toolNames := make([]string, 0, len(tools))
	for _, def := range tools {
		toolNames = append(toolNames, def.Name)
	}
	require.Equal(t, gotTools, toolNames)
}

func TestAgentTeamMCPToolDefsUseEnumsForClosedSets(t *testing.T) {
	h := &mcpHandler{}
	defs := map[string]map[string]any{}
	for _, def := range h.Tools() {
		props := map[string]any{}
		for name, prop := range def.InputSchema.Properties {
			if len(prop.Enum) > 0 {
				props[name] = prop.Enum
			}
		}
		defs[def.Name] = props
	}
	require.Equal(t, []any{"add", "remove"}, defs["message_reaction_set"]["op"])
}

func TestAgentTeamMCPToolDefinitionsMessageSendTargetSchema(t *testing.T) {
	t.Parallel()

	var send types.MCPToolDef
	for _, def := range (&mcpHandler{}).Tools() {
		if def.Name == "message_send" {
			send = def
			break
		}
	}
	serialized, err := json.Marshal(send)
	require.NoError(t, err)
	require.LessOrEqual(t, len(serialized), 1200, "message_send definition exceeded its persistent tool-surface budget")
	require.Contains(t, send.Description, "Plain or final output is not posted")
	require.NotContains(t, send.Description, "visible")
	require.NotContains(t, send.Description, "visual")
	require.Equal(t, []string{"target", "content"}, send.InputSchema.Required)
	require.NotNil(t, send.InputSchema.AdditionalProperties)
	require.False(t, *send.InputSchema.AdditionalProperties)
	require.Contains(t, send.InputSchema.Properties, "target")
	require.NotContains(t, send.InputSchema.Properties, "routing_kind")
	require.NotContains(t, send.InputSchema.Properties, "legacy_selector")
	require.Contains(t, send.InputSchema.Properties["target"].Description, "Exact destination")
	require.NotContains(t, send.InputSchema.Properties["target"].Description, "active targets exist")
	require.Contains(t, send.InputSchema.Properties["content"].Description, "`[@name](/mentions/agent/<agent-id>)`")
	require.Contains(t, send.InputSchema.Properties["content"].Description, "plain `@name` may be ambiguous")
	require.NotContains(t, send.InputSchema.Properties["content"].Description, "visible")
	require.NotContains(t, send.InputSchema.Properties["content"].Description, "visual")
	require.Contains(t, send.InputSchema.Properties["team_invoke_team_id"].Description, "hidden Team Context")
	require.Contains(t, send.InputSchema.Properties["team_invoke_team_id"].Description, "ordinary direct mentions")

	for _, legacy := range []string{"routing_mode", "routing_intent", "routing_kind", "legacy_selector", "channel", "dm_to"} {
		require.NotContains(t, send.InputSchema.Properties, legacy)
	}
}

func TestAgentTeamMCPToolDefinitionsUseRuntimeCompatibleRootSchemas(t *testing.T) {
	t.Parallel()

	defs := append([]types.MCPToolDef{}, (&mcpHandler{}).Tools()...)
	defs = append(defs, memorySearchToolDef())
	defs = append(defs, kbAgentTeamToolDefs()...)
	for _, def := range defs {
		data, err := json.Marshal(def.InputSchema)
		require.NoError(t, err, def.Name)

		var schema map[string]any
		require.NoError(t, json.Unmarshal(data, &schema), def.Name)
		require.Equal(t, "object", schema["type"], def.Name)
		require.IsType(t, map[string]any{}, schema["properties"], def.Name)
		require.NotContains(t, schema, "oneOf", def.Name)
		require.NotContains(t, schema, "anyOf", def.Name)
		require.NotContains(t, schema, "allOf", def.Name)
	}
}

func TestAgentTeamMCPToolDefinitionsGolden(t *testing.T) {
	h := &mcpHandler{}
	defs := h.Tools()
	data, err := json.MarshalIndent(defs, "", "  ")
	require.NoError(t, err)
	data = append(data, '\n')

	path := filepath.Join("testdata", "agentteam_mcp_tool_defs.golden.json")
	if os.Getenv("UPDATE_GOLDEN") != "" {
		require.NoError(t, os.MkdirAll(filepath.Dir(path), 0o755))
		require.NoError(t, os.WriteFile(path, data, 0o644))
	}
	want, err := os.ReadFile(path)
	require.NoError(t, err)
	require.Equal(t, string(want), string(data))
}

func TestAgentTeamMCPToolRegistryDispatchesOnlyRegisteredMCP(t *testing.T) {
	h := &mcpHandler{}

	mcpResult := h.CallTool(context.Background(), "agent", "message_history_read", json.RawMessage(`{}`))
	require.True(t, mcpResult.IsError)
	require.Contains(t, mcpResult.Content[0].Text, "agentteam API not available for this session")

	unknown := h.CallTool(context.Background(), "agent", "missing_tool", json.RawMessage(`{}`))
	require.True(t, unknown.IsError)
	require.Contains(t, unknown.Content[0].Text, "unknown tool: missing_tool")
}

func TestAgentTeamMCPToolRegistryExcludesExecOwnedTools(t *testing.T) {
	h := &mcpHandler{}
	registered := make(map[string]struct{}, len(h.Tools()))
	for _, def := range h.Tools() {
		registered[def.Name] = struct{}{}
	}

	execOwned := []string{
		"review_add", "review_list", "review_delete",
		"project_list", "project_create", "project_delete",
		"scheduled_message_list", "scheduled_message_get", "scheduled_message_validate",
		"scheduled_message_create", "scheduled_message_update", "scheduled_message_pause",
		"scheduled_message_resume", "scheduled_message_delete", "scheduled_message_runs",
		"workflow_plan",
		"workflow_binding",
		"workflow_invocation",
		"workflow_run",
		"workflow_set_variable",
		"task_list", "task_get", "task_create", "task_update", "task_set_claim", "task_checkpoint",
	}
	for _, name := range execOwned {
		_, ok := registered[name]
		require.Falsef(t, ok, "Exec-owned tool %q must not be registered as MCP", name)

		result := h.CallTool(context.Background(), "agent", name, json.RawMessage(`{}`))
		require.True(t, result.IsError, name)
		require.Contains(t, result.Content[0].Text, "unknown tool: "+name)
	}
}

func expectedMCPToolNames() []string {
	return []string{
		"message_send", "message_history_read", "message_search",
		"message_attachment_upload", "message_attachment_view",
		"message_reaction_set", "message_reaction_list",
	}
}
