package api

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/api/mcp"
	"github.com/tidbcloud/AgentTeam/desktop/internal/messagesend"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"github.com/tidbcloud/AgentTeam/desktop/internal/streamregistry"
	"github.com/tidbcloud/AgentTeam/desktop/internal/token"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
	"github.com/tidbcloud/AgentTeam/desktop/internal/uisession"
	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
)

type routerTestChannels struct{}

func (c routerTestChannels) ServerConnection(id string) (types.ServerConnection, bool) {
	if id != "agentteam-main" {
		return nil, false
	}
	return routerTestConnection{}, true
}
func (routerTestChannels) ServerConnections() []types.ServerConnection { return nil }
func (routerTestChannels) Enable(string) error                         { return nil }
func (routerTestChannels) Disable(string) error                        { return nil }

type routerTestConnection struct{}

func (c routerTestConnection) ID() string                  { return "agentteam-main" }
func (c routerTestConnection) Enabled() bool               { return true }
func (c routerTestConnection) Start(context.Context) error { return nil }
func (c routerTestConnection) Stop(context.Context) error  { return nil }
func (c routerTestConnection) Send(context.Context, types.Metadata, types.OutboundMessage) error {
	return nil
}
func (c routerTestConnection) APIClient(...string) *apiclient.Client {
	return &apiclient.Client{}
}

type routerTestSessionStore struct{}

func (routerTestSessionStore) Session(string) (types.SessionSnapshot, bool) {
	return types.SessionSnapshot{}, false
}
func (routerTestSessionStore) Sessions() []types.SessionSnapshot { return nil }
func (routerTestSessionStore) History(string, int, int64, int64) ([]types.HistoryMessage, error) {
	return nil, nil
}

type routerTestTrajectoryStore struct{}

func (routerTestTrajectoryStore) Session(string) (types.SessionSnapshot, bool) {
	return types.SessionSnapshot{}, false
}
func (routerTestTrajectoryStore) Sessions() []types.SessionSnapshot { return nil }
func (routerTestTrajectoryStore) SubscribeTrajectory(string) <-chan types.TrajectoryLogItem {
	ch := make(chan types.TrajectoryLogItem)
	close(ch)
	return ch
}
func (routerTestTrajectoryStore) SubscribeLiveTrajectory(string) <-chan types.TrajectoryLogItem {
	ch := make(chan types.TrajectoryLogItem)
	close(ch)
	return ch
}
func (routerTestTrajectoryStore) SubscribeAllLiveTrajectory() <-chan types.TrajectoryLogItem {
	ch := make(chan types.TrajectoryLogItem)
	close(ch)
	return ch
}
func (routerTestTrajectoryStore) UnsubscribeTrajectory(string, <-chan types.TrajectoryLogItem) {}
func (routerTestTrajectoryStore) UnsubscribeAllTrajectory(<-chan types.TrajectoryLogItem)      {}
func (routerTestTrajectoryStore) TrajectoryHistory(string, int, int64) []types.TrajectoryLogItem {
	return nil
}
func (routerTestTrajectoryStore) LatestTrajectoryByAgent([]string) map[string]types.TrajectoryLogItem {
	return nil
}

func newTestRouter() http.Handler {
	return newTestGinRouter()
}

func newTestGinRouter() *gin.Engine {
	handler, err := NewRouterWithOptions(
		routerTestChannels{},
		routerTestSessionStore{},
		routerTestTrajectoryStore{},
		uisession.NewStore(uisession.Options{}),
		streamregistry.New(),
		RouterOptions{MessageService: mcp.NewMessageService()},
	)
	if err != nil {
		panic(err)
	}
	return handler.(*gin.Engine)
}

func TestRouterOptionsRequiresMessageService(t *testing.T) {
	_, err := NewRouterWithOptions(routerTestChannels{}, routerTestSessionStore{}, routerTestTrajectoryStore{}, uisession.NewStore(uisession.Options{}), streamregistry.New(), RouterOptions{})
	require.EqualError(t, err, "api: RouterOptions.MessageService is required")
}

func newRuntimeTestRouter(t *testing.T) http.Handler {
	t.Helper()
	uiSessions := uisession.NewStore(uisession.Options{})
	servers := profile.Default().Servers()
	require.NoError(t, servers.PrepareRuntimeMode())
	require.NoError(t, servers.ActivateRuntimeMode(
		NewServerRuntimeParticipant(routerTestChannels{}, uiSessions),
	))
	t.Cleanup(servers.DeactivateRuntimeMode)
	return newTestRouterWithUISessions(
		routerTestChannels{},
		routerTestSessionStore{},
		routerTestTrajectoryStore{},
		uiSessions,
		mcp.NewMessageService(),
	)
}

func TestRouterMCPRoutesExposeCurrentAndLegacyAgentTeamPaths(t *testing.T) {
	engine := newTestGinRouter()

	postRoutes := map[string]bool{}
	for _, route := range engine.Routes() {
		if route.Method == http.MethodPost {
			postRoutes[route.Path] = true
		}
	}

	for _, path := range []string{
		"/api/internal/mcp/servers/:serverId/agents/:agentId",
		"/mcp/agentteam/:serverId/:agentId",
	} {
		require.Truef(t, postRoutes[path], "%s should remain a registered MCP route", path)
	}

	legacyMemoryRoute := "/mcp/" + "memory/:agentId"
	legacyKnowledgeRoute := "/mcp/" + "knowledge/:serverId/:agentId"
	legacyProviderAppRoute := "/mcp/" + "provider-app/:provider/:agentId"
	for _, path := range []string{
		legacyMemoryRoute,
		legacyKnowledgeRoute,
		legacyProviderAppRoute,
	} {
		require.Falsef(t, postRoutes[path], "%s must not remain a first-party MCP route", path)
	}
}

func TestRouterExposesOnlyUnifiedExecRuntimeRoutes(t *testing.T) {
	engine := newTestGinRouter()
	routes := make(map[string]bool)
	for _, route := range engine.Routes() {
		routes[route.Method+" "+route.Path] = true
	}
	require.True(t, routes["GET /api/internal/exec/servers/:serverID/agents/:agentID/catalog"])
	require.True(t, routes["POST /api/internal/exec/servers/:serverID/agents/:agentID/:domain/:action"])
}

// routerTestFullSessions satisfies both the router's session.SessionStore and
// the MessageRuntime+KBRouteContext that resolveAgentTeamHandler type-asserts, so
// the resolved route can build the full AgentTeam inventory for an ordinary token.
type routerTestFullSessions struct {
	routerTestSessionStore
	agentteamMCPTestMessageRuntime
}

// Session disambiguates the method embedded from both stores.
func (routerTestFullSessions) Session(string) (types.SessionSnapshot, bool) {
	return types.SessionSnapshot{}, true
}

func TestMCPRouteExcludesExecutionTools(t *testing.T) {
	handler := newTestRouterWithUISessions(
		routerTestChannels{},
		routerTestFullSessions{},
		routerTestTrajectoryStore{},
		uisession.NewStore(uisession.Options{}),
		mcp.NewMessageService(),
	)

	manager := token.Default()
	ordinaryToken, err := manager.GenerateAgentTokenForServer("agent-1", "agentteam-main")
	require.NoError(t, err)
	analysisToken, err := manager.GenerateTokenAnalysisAgentToken("agent-1")
	require.NoError(t, err)

	postToolsList := func(path, tokenValue string) *httptest.ResponseRecorder {
		req := httptest.NewRequest(http.MethodPost, path, strings.NewReader(`{"jsonrpc":"2.0","id":1,"method":"tools/list"}`))
		req.Header.Set("Content-Type", "application/json")
		req.Header.Set("Authorization", "Bearer "+tokenValue)
		rec := httptest.NewRecorder()
		handler.ServeHTTP(rec, req)
		return rec
	}
	resolved := "/api/internal/mcp/servers/agentteam-main/agents/agent-1"

	rec := postToolsList(resolved, ordinaryToken)
	require.Equalf(t, http.StatusOK, rec.Code, "ordinary tools/list status: %s", rec.Body.String())
	require.Contains(t, rec.Body.String(), "message_send", "ordinary token should expose message_send on the resolved route")

	rec = postToolsList(resolved, analysisToken)
	require.Equal(t, http.StatusForbidden, rec.Code, "token_analysis must be rejected on the resolved route")
}

func TestRouterDoesNotExposeGoOwnedNativeUIRoutes(t *testing.T) {
	engine := newTestGinRouter()

	routes := make(map[string]bool)
	for _, route := range engine.Routes() {
		routes[route.Method+" "+route.Path] = true
	}

	for _, route := range []string{
		"POST /api/internal/auth/login",
		"POST /api/local/window/show",
		"POST /api/internal/tray/unread",
		"POST /api/internal/window/open",
		"GET /api/internal/window/state",
		"POST /api/internal/window/toggle-maximise",
		"POST /api/internal/window/minimise",
		"POST /api/internal/window/close",
		"POST /api/internal/window/layout",
		"POST /api/internal/window/location",
		"POST /api/internal/window/open-browser",
		"GET /api/internal/upgrade/check",
		"GET /api/internal/upgrade/log",
		"POST /api/internal/upgrade/perform",
		"POST /api/internal/upgrade/restart",
	} {
		require.Falsef(t, routes[route], "%s must be owned by Electron or the headless CLI, not the daemon HTTP API", route)
	}
}

func TestRouterHealthEndpointReturnsJSON(t *testing.T) {
	router := newTestRouter()

	req := httptest.NewRequest(http.MethodGet, "/api/health", nil)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusOK, rec.Code)
	require.Contains(t, rec.Header().Get("Content-Type"), "application/json")
	require.Contains(t, rec.Body.String(), `"ok":true`)
}

func TestRouterUnknownAPIPathDoesNotFallBackToSPA(t *testing.T) {
	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())
	router := newTestRouter()
	sessionToken := token.CreateSession()

	req := httptest.NewRequest(http.MethodGet, "/api/does-not-exist", nil)
	req.Header.Set("Authorization", "Bearer "+sessionToken)
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusConflict, rec.Code)
	require.Contains(t, rec.Header().Get("Content-Type"), "application/json")
	require.Contains(t, rec.Body.String(), `"code":"ACTIVE_SERVER_REQUIRED"`)
}

func TestRouterRegistersServerConfigRoutes(t *testing.T) {
	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())
	router := newTestRouter()
	sessionToken := token.CreateSession()

	tests := []struct {
		name     string
		method   string
		path     string
		body     string
		want     int
		wantBody string
		wantJSON bool
	}{
		{
			name:     "server list",
			method:   http.MethodGet,
			path:     "/api/internal/servers",
			want:     http.StatusOK,
			wantBody: "[]",
			wantJSON: true,
		},
		{
			name:     "server create",
			method:   http.MethodPost,
			path:     "/api/internal/servers",
			body:     "invalid json{{{",
			want:     http.StatusBadRequest,
			wantBody: "invalid request body",
			wantJSON: true,
		},
		{
			name:     "server update",
			method:   http.MethodPut,
			path:     "/api/internal/servers/test",
			body:     "invalid json{{{",
			want:     http.StatusBadRequest,
			wantBody: "invalid request body",
			wantJSON: true,
		},
		{
			name:     "server delete",
			method:   http.MethodDelete,
			path:     "/api/internal/servers/test",
			want:     http.StatusNotFound,
			wantBody: `server \"test\" not found`,
			wantJSON: true,
		},
		{
			name:     "server tools",
			method:   http.MethodGet,
			path:     "/api/internal/servers/test/tools",
			want:     http.StatusNotFound,
			wantBody: "not found or not running",
			wantJSON: true,
		},
		{
			name:     "server bundle",
			method:   http.MethodGet,
			path:     "/api/internal/bundles/servers",
			want:     http.StatusOK,
			wantBody: `"servers":[]`,
			wantJSON: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := httptest.NewRequest(tt.method, tt.path, strings.NewReader(tt.body))
			req.Header.Set("Authorization", "Bearer "+sessionToken)
			if tt.body != "" {
				req.Header.Set("Content-Type", "application/json")
			}
			rec := httptest.NewRecorder()
			router.ServeHTTP(rec, req)

			require.Equal(t, tt.want, rec.Code)
			if tt.wantJSON {
				require.Contains(t, rec.Header().Get("Content-Type"), "application/json")
			}
			require.Contains(t, rec.Body.String(), tt.wantBody)
		})
	}
}

func TestRouterDesktopBootstrapRoutesUseAuthenticatedSession(t *testing.T) {
	dir := t.TempDir()
	t.Setenv(profile.ConfigDirEnvKey, dir)
	server := profile.AgentTeamServer{
		ID:          "server-a",
		Enabled:     true,
		Name:        "Production",
		Description: "Primary server",
		ServerURL:   "https://secret.example.test",
		APIKey:      "machine-secret",
		AuthToken:   "user-secret",
		WorkspaceID: "wsp-a",
		MachineID:   "mac-a",
	}
	require.NoError(t, profile.New(dir).Servers().Add(&server))
	router := newRuntimeTestRouter(t)
	windowA := token.CreateSession()
	windowB := token.CreateSession()

	runtimeReq := httptest.NewRequest(http.MethodGet, "/api/internal/runtime", nil)
	runtimeReq.Header.Set("Authorization", "Bearer "+windowA)
	runtimeRec := httptest.NewRecorder()
	router.ServeHTTP(runtimeRec, runtimeReq)
	require.Equal(t, http.StatusOK, runtimeRec.Code, runtimeRec.Body.String())
	require.Contains(t, runtimeRec.Body.String(), `"kind":"desktop"`)
	require.Contains(t, runtimeRec.Body.String(), `"nativeWindow":false`)

	missingBReq := httptest.NewRequest(http.MethodGet, "/api/internal/servers/active", nil)
	missingBReq.Header.Set("Authorization", "Bearer "+windowB)
	missingBRec := httptest.NewRecorder()
	router.ServeHTTP(missingBRec, missingBReq)
	require.Equal(t, http.StatusConflict, missingBRec.Code)
	require.Contains(t, missingBRec.Body.String(), `"code":"ACTIVE_SERVER_REQUIRED"`)

	selectServer := func(sessionToken string) *httptest.ResponseRecorder {
		req := httptest.NewRequest(http.MethodPut, "/api/internal/servers/active", strings.NewReader(`{"serverId":"server-a"}`))
		req.Header.Set("Authorization", "Bearer "+sessionToken)
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)
		return rec
	}

	selectedA := selectServer(windowA)
	require.Equal(t, http.StatusOK, selectedA.Code, selectedA.Body.String())
	require.Contains(t, selectedA.Body.String(), `"generation":1`)
	require.NotContains(t, selectedA.Body.String(), "machine-secret")
	require.NotContains(t, selectedA.Body.String(), "user-secret")
	require.NotContains(t, selectedA.Body.String(), "secret.example.test")

	selectedB := selectServer(windowB)
	require.Equal(t, http.StatusOK, selectedB.Code, selectedB.Body.String())
	require.Contains(t, selectedB.Body.String(), `"generation":1`)

	logoutReq := httptest.NewRequest(http.MethodPost, "/api/internal/auth/logout", nil)
	logoutReq.Header.Set("Authorization", "Bearer "+windowA)
	logoutRec := httptest.NewRecorder()
	router.ServeHTTP(logoutRec, logoutReq)
	require.Equal(t, http.StatusOK, logoutRec.Code)
	require.False(t, token.ValidateSession(windowA))

	afterLogoutReq := httptest.NewRequest(http.MethodGet, "/api/internal/servers/active", nil)
	afterLogoutReq.Header.Set("Authorization", "Bearer "+windowA)
	afterLogoutRec := httptest.NewRecorder()
	router.ServeHTTP(afterLogoutRec, afterLogoutReq)
	require.Equal(t, http.StatusUnauthorized, afterLogoutRec.Code)
}

func TestRouterServerDeleteInvalidatesAllWindowSelections(t *testing.T) {
	dir := t.TempDir()
	t.Setenv(profile.ConfigDirEnvKey, dir)
	server := profile.AgentTeamServer{
		ID:          "server-a",
		Enabled:     true,
		Name:        "Production",
		WorkspaceID: "wsp-a",
		MachineID:   "mac-a",
	}
	require.NoError(t, profile.New(dir).Servers().Add(&server))
	router := newTestRouter()
	windowA := token.CreateSession()
	windowB := token.CreateSession()

	for _, sessionToken := range []string{windowA, windowB} {
		body, err := json.Marshal(map[string]string{"serverId": "server-a"})
		require.NoError(t, err)
		req := httptest.NewRequest(http.MethodPut, "/api/internal/servers/active", strings.NewReader(string(body)))
		req.Header.Set("Authorization", "Bearer "+sessionToken)
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)
		require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
	}

	deleteReq := httptest.NewRequest(http.MethodDelete, "/api/internal/servers/server-a", nil)
	deleteReq.Header.Set("Authorization", "Bearer "+windowA)
	deleteRec := httptest.NewRecorder()
	router.ServeHTTP(deleteRec, deleteReq)
	require.Equal(t, http.StatusOK, deleteRec.Code, deleteRec.Body.String())

	for _, sessionToken := range []string{windowA, windowB} {
		req := httptest.NewRequest(http.MethodGet, "/api/internal/servers/active", nil)
		req.Header.Set("Authorization", "Bearer "+sessionToken)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)
		require.Equal(t, http.StatusConflict, rec.Code, rec.Body.String())
		require.Contains(t, rec.Body.String(), `"code":"ACTIVE_SERVER_REQUIRED"`)
	}
}

func TestRouterServerDisableInvalidatesAllWindowSelections(t *testing.T) {
	dir := t.TempDir()
	t.Setenv(profile.ConfigDirEnvKey, dir)
	server := profile.AgentTeamServer{
		ID:          "server-a",
		Enabled:     true,
		Name:        "Production",
		WorkspaceID: "wsp-a",
		MachineID:   "mac-a",
	}
	require.NoError(t, profile.New(dir).Servers().Add(&server))
	router := newTestRouter()
	windowA := token.CreateSession()
	windowB := token.CreateSession()

	for _, sessionToken := range []string{windowA, windowB} {
		req := httptest.NewRequest(
			http.MethodPut,
			"/api/internal/servers/active",
			strings.NewReader(`{"serverId":"server-a"}`),
		)
		req.Header.Set("Authorization", "Bearer "+sessionToken)
		req.Header.Set("Content-Type", "application/json")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)
		require.Equal(t, http.StatusOK, rec.Code, rec.Body.String())
	}

	disableReq := httptest.NewRequest(
		http.MethodPut,
		"/api/internal/servers/server-a",
		strings.NewReader(`{"enabled":false}`),
	)
	disableReq.Header.Set("Authorization", "Bearer "+windowA)
	disableReq.Header.Set("Content-Type", "application/json")
	disableRec := httptest.NewRecorder()
	router.ServeHTTP(disableRec, disableReq)
	require.Equal(t, http.StatusOK, disableRec.Code, disableRec.Body.String())

	for _, sessionToken := range []string{windowA, windowB} {
		req := httptest.NewRequest(http.MethodGet, "/api/internal/servers/active", nil)
		req.Header.Set("Authorization", "Bearer "+sessionToken)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)
		require.Equal(t, http.StatusConflict, rec.Code, rec.Body.String())
		require.Contains(t, rec.Body.String(), `"code":"ACTIVE_SERVER_REQUIRED"`)
	}
}

func TestRouterEnabledServerIdentityChangeAdvancesGenerationWithoutClearingSelection(t *testing.T) {
	dir := t.TempDir()
	t.Setenv(profile.ConfigDirEnvKey, dir)
	server := profile.AgentTeamServer{
		ID:          "server-a",
		Enabled:     true,
		Name:        "Production",
		ServerURL:   "https://old.example.test",
		APIKey:      "old-key",
		WorkspaceID: "wsp-old",
		MachineID:   "mac-old",
	}
	require.NoError(t, profile.New(dir).Servers().Add(&server))
	router := newRuntimeTestRouter(t)
	sessionToken := token.CreateSession()

	selectReq := httptest.NewRequest(
		http.MethodPut,
		"/api/internal/servers/active",
		strings.NewReader(`{"serverId":"server-a"}`),
	)
	selectReq.Header.Set("Authorization", "Bearer "+sessionToken)
	selectReq.Header.Set("Content-Type", "application/json")
	selectRec := httptest.NewRecorder()
	router.ServeHTTP(selectRec, selectReq)
	require.Equal(t, http.StatusOK, selectRec.Code, selectRec.Body.String())
	require.Contains(t, selectRec.Body.String(), `"generation":1`)

	updateReq := httptest.NewRequest(
		http.MethodPut,
		"/api/internal/servers/server-a",
		strings.NewReader(`{
			"enabled": true,
			"config": {
				"server_url": "https://new.example.test",
				"api_key": "new-key",
				"workspace_id": "wsp-new",
				"machine_id": "mac-new"
			}
		}`),
	)
	updateReq.Header.Set("Authorization", "Bearer "+sessionToken)
	updateReq.Header.Set("Content-Type", "application/json")
	updateRec := httptest.NewRecorder()
	router.ServeHTTP(updateRec, updateReq)
	require.Equal(t, http.StatusOK, updateRec.Code, updateRec.Body.String())

	activeReq := httptest.NewRequest(http.MethodGet, "/api/internal/servers/active", nil)
	activeReq.Header.Set("Authorization", "Bearer "+sessionToken)
	activeRec := httptest.NewRecorder()
	router.ServeHTTP(activeRec, activeReq)
	require.Equal(t, http.StatusOK, activeRec.Code, activeRec.Body.String())
	require.Contains(t, activeRec.Body.String(), `"id":"server-a"`)
	require.Contains(t, activeRec.Body.String(), `"generation":3`)
	require.Contains(t, activeRec.Body.String(), `"workspaceId":"wsp-new"`)
	require.Contains(t, activeRec.Body.String(), `"machineId":"mac-new"`)
}

func TestRouterDoesNotRegisterLegacyInternalChannelRoutes(t *testing.T) {
	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())
	router := newTestRouter()
	sessionToken := token.CreateSession()

	tests := []struct {
		method string
		path   string
	}{
		{http.MethodGet, "/api/internal/channels"},
		{http.MethodPost, "/api/internal/channels"},
		{http.MethodPut, "/api/internal/channels/test"},
		{http.MethodDelete, "/api/internal/channels/test"},
		{http.MethodGet, "/api/internal/channels/test/tools"},
		{http.MethodGet, "/api/internal/bundles/channels"},
	}

	for _, tt := range tests {
		t.Run(tt.method+" "+tt.path, func(t *testing.T) {
			req := httptest.NewRequest(tt.method, tt.path, nil)
			req.Header.Set("Authorization", "Bearer "+sessionToken)
			rec := httptest.NewRecorder()
			router.ServeHTTP(rec, req)

			require.Equal(t, http.StatusNotFound, rec.Code)
		})
	}
}

func TestRouterFrontendPathDoesNotServeSPA(t *testing.T) {
	router := newTestRouter()

	req := httptest.NewRequest(http.MethodGet, "/apps", nil)
	req.Header.Set("Accept", "text/html")
	req.Header.Set("Sec-Fetch-Mode", "navigate")
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)

	require.Equal(t, http.StatusNotFound, rec.Code)
	require.Contains(t, rec.Header().Get("Content-Type"), "application/json")
	require.JSONEq(t, `{"error":"Not Found"}`, rec.Body.String())
}

func TestRouterPprofRoutesRequireEnvAndLocalhost(t *testing.T) {
	t.Run("disabled by default", func(t *testing.T) {
		t.Setenv("AGENTTEAM_ENABLE_PPROF", "")
		router := newTestRouter()

		req := httptest.NewRequest(http.MethodGet, "/debug/pprof/", nil)
		req.RemoteAddr = "127.0.0.1:12345"
		req.Header.Set("Accept", "text/html")
		req.Header.Set("Sec-Fetch-Mode", "navigate")
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusNotFound, rec.Code)
		require.Contains(t, rec.Header().Get("Content-Type"), "application/json")
		require.NotContains(t, rec.Body.String(), "Types of profiles available")
	})

	t.Run("enabled for localhost", func(t *testing.T) {
		t.Setenv("AGENTTEAM_ENABLE_PPROF", "1")
		router := newTestRouter()

		req := httptest.NewRequest(http.MethodGet, "/debug/pprof/", nil)
		req.RemoteAddr = "127.0.0.1:12345"
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusOK, rec.Code)
		require.Contains(t, rec.Body.String(), "Types of profiles available")
	})

	t.Run("rejects non-localhost", func(t *testing.T) {
		t.Setenv("AGENTTEAM_ENABLE_PPROF", "1")
		router := newTestRouter()

		req := httptest.NewRequest(http.MethodGet, "/debug/pprof/", nil)
		req.RemoteAddr = "192.0.2.10:12345"
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		require.Equal(t, http.StatusForbidden, rec.Code)
	})
}

func TestRouterAlwaysExposesDesktopBusinessAPIs(t *testing.T) {
	t.Setenv(profile.ConfigDirEnvKey, t.TempDir())
	router, err := NewRouterWithOptions(
		routerTestChannels{},
		routerTestSessionStore{},
		routerTestTrajectoryStore{},
		uisession.NewStore(uisession.Options{}),
		streamregistry.New(),
		RouterOptions{MessageService: mcp.NewMessageService()},
	)
	require.NoError(t, err)

	req := httptest.NewRequest(http.MethodGet, "/api/internal/onboarding/workspaces", nil)
	req.Header.Set("Authorization", "Bearer "+token.CreateSession())
	rec := httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	require.NotEqual(t, http.StatusNotFound, rec.Code)

	req = httptest.NewRequest(http.MethodGet, "/api/internal/servers/active?optional=true", nil)
	req.Header.Set("Authorization", "Bearer "+token.CreateSession())
	rec = httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	require.Equal(t, http.StatusOK, rec.Code)
	require.JSONEq(t, "null", rec.Body.String())

	req = httptest.NewRequest(http.MethodGet, "/api/internal/servers/available", nil)
	req.Header.Set("Authorization", "Bearer "+token.CreateSession())
	rec = httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	require.Equal(t, http.StatusOK, rec.Code)
	require.JSONEq(t, `{"servers":[]}`, rec.Body.String())

	require.NoError(t, os.WriteFile(
		filepath.Join(profile.ConfigDir(), "auth.yaml"),
		[]byte("not: valid: yaml"),
		0o600,
	))
	router, err = NewRouterWithOptions(
		routerTestChannels{},
		routerTestSessionStore{},
		routerTestTrajectoryStore{},
		uisession.NewStore(uisession.Options{}),
		streamregistry.New(),
		RouterOptions{MessageService: mcp.NewMessageService()},
	)
	require.NoError(t, err)
	req = httptest.NewRequest(http.MethodGet, "/api/internal/onboarding/servers", nil)
	req.Header.Set("Authorization", "Bearer "+token.CreateSession())
	rec = httptest.NewRecorder()
	router.ServeHTTP(rec, req)
	require.NotEqual(t, http.StatusNotFound, rec.Code)
}

func (routerTestFullSessions) SendScope(context.Context, string) (messagesend.Scope, bool) {
	return messagesend.Scope{}, true
}
