package mcp

import (
	"context"
	"strings"
	"testing"

	"github.com/mark3labs/mcp-go/mcp"
)

func TestScrubControlChars(t *testing.T) {
	cases := []struct {
		name string
		in   string
		want string
	}{
		{"plain text unchanged", "a normal description", "a normal description"},
		{"pipes preserved", "Output format: json | gcx | toon", "Output format: json | gcx | toon"},
		{"multibyte runes preserved", "café — résumé — déjà", "café — résumé — déjà"},
		{"newline and tab kept", "line one\n\tindented", "line one\n\tindented"},
		{"NUL dropped", "before\x00after", "beforeafter"},
		{"carriage return dropped", "overwrite\rme", "overwriteme"},
		{"DEL dropped", "a\x7fb", "ab"},
		{"C1 control dropped", "a\u009bb", "ab"},
		{"ANSI color stripped", "\x1b[31mred\x1b[0m text", "red text"},
		{"ANSI cursor-move stripped", "x\x1b[2Ky", "xy"},
		{"bare ESC sequence stripped", "a\x1bMb", "ab"},
		{"empty string", "", ""},
	}
	for _, c := range cases {
		t.Run(c.name, func(t *testing.T) {
			if got := scrubControlChars(c.in); got != c.want {
				t.Errorf("scrubControlChars(%q) = %q, want %q", c.in, got, c.want)
			}
		})
	}
}

func TestScrubToolText(t *testing.T) {
	tool := mcp.NewTool("demo",
		mcp.WithDescription("does a thing\x1b[31m with color\x00 and a NUL"),
		mcp.WithString("arg", mcp.Description("a param\x07 with a bell")),
	)
	scrubToolText(&tool)

	if strings.ContainsAny(tool.Description, "\x00\x1b\x07") {
		t.Errorf("tool description still carries control chars: %q", tool.Description)
	}
	if !strings.Contains(tool.Description, "does a thing") {
		t.Errorf("scrub destroyed legitimate text: %q", tool.Description)
	}
	prop, ok := tool.InputSchema.Properties["arg"].(map[string]any)
	if !ok {
		t.Fatalf("arg property missing or wrong shape: %#v", tool.InputSchema.Properties["arg"])
	}
	desc, _ := prop["description"].(string)
	if strings.ContainsRune(desc, '\x07') {
		t.Errorf("param description still carries a control char: %q", desc)
	}
	if !strings.Contains(desc, "a param") {
		t.Errorf("scrub destroyed legitimate param text: %q", desc)
	}
}

// TestAddTool_ScrubsRegisteredDescription proves the scrub runs on the
// real registration path: a tool registered with a poisoned
// description lands in tools/list clean.
func TestAddTool_ScrubsRegisteredDescription(t *testing.T) {
	t.Setenv("GORTEX_LAZY_TOOLS", "0")
	srv := newFullTestServer(t)
	srv.addTool(
		mcp.NewTool("scrub_probe", mcp.WithDescription("poisoned\x1b[31m\x00description")),
		func(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
			return mcp.NewToolResultText("ok"), nil
		},
	)
	st, ok := srv.mcpServer.ListTools()["scrub_probe"]
	if !ok {
		t.Fatal("scrub_probe was not registered")
	}
	if strings.ContainsAny(st.Tool.Description, "\x00\x1b") {
		t.Errorf("registered description not scrubbed: %q", st.Tool.Description)
	}
}
