package agent_test

import (
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"

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

	"github.com/tidbcloud/AgentTeam/internal/api/agent"
	"github.com/tidbcloud/AgentTeam/internal/service/channeladmin"
)

func TestRegisterRoutesOwnsAgentRuntimeRoutes(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	agent.RegisterRoutes(router, agent.Dependencies{})

	actual := map[string]struct{}{}
	for _, route := range router.Routes() {
		if !strings.HasPrefix(route.Path, "/api/v1/workspaces/:workspaceId/agents/:agentId/runtime") {
			continue
		}
		actual[fmt.Sprintf("%s %s", route.Method, route.Path)] = struct{}{}
	}

	routeSuffixes := []string{
		"POST /send",
		"POST /resolve-channel",
		"GET /triggers",
		"POST /triggers",
		"POST /triggers/validate",
		"GET /triggers/:triggerId",
		"PATCH /triggers/:triggerId",
		"POST /triggers/:triggerId/pause",
		"POST /triggers/:triggerId/resume",
		"DELETE /triggers/:triggerId",
		"GET /triggers/:triggerId/runs",
		"GET /schedules",
		"POST /schedules",
		"POST /schedules/validate",
		"GET /schedules/:scheduleId",
		"PATCH /schedules/:scheduleId",
		"POST /schedules/:scheduleId/pause",
		"POST /schedules/:scheduleId/resume",
		"DELETE /schedules/:scheduleId",
		"GET /schedules/:scheduleId/runs",
		"POST /llm-proxy-session",
		"POST /llm-proxy-session/refresh",
		"POST /agent-thread/attestation",
		"POST /agent-thread/upstream",
		"GET /threads/:threadChannelId/review-context",
		"PUT /threads/:threadChannelId/review-context",
		"DELETE /threads/:threadChannelId/review-context",
		"GET /threads/:threadChannelId/review-contexts",
		"DELETE /review-contexts/:contextId",
		"GET /tasks/:taskScopeId/review-contexts",
		"PUT /tasks/:taskScopeId/review-context",
		"POST /upload",
		"POST /avatar",
		"GET /workspace",
		"GET /history",
		"GET /messages/:messageId/read-check",
		"GET /scopes/:scopeId/members",
		"POST /reactions/get",
		"POST /reactions/add",
		"POST /reactions/remove",
		"GET /search",
		"POST /tasks/get",
		"POST /tasks/update",
		"POST /tasks/add-checkpoint",
		"GET /tasks",
		"POST /tasks",
	}

	expected := make([]string, 0, len(routeSuffixes))
	for _, route := range routeSuffixes {
		method, suffix, ok := strings.Cut(route, " ")
		require.True(t, ok, "route suffix must include method and path: %s", route)
		expected = append(expected, method+" /api/v1/workspaces/:workspaceId/agents/:agentId/runtime"+suffix)
	}
	expected = append(expected, "POST /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/tasks/set-claim")
	expected = append(expected, "POST /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/tasks/delete")
	expected = append(expected,
		"GET /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/teams",
		"POST /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/teams/get",
		"POST /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/teams/update",
	)
	for _, route := range expected {
		require.Contains(t, actual, route)
	}
	require.NotContains(
		t,
		actual,
		"GET /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/provider-apps/"+inlineProviderConfigRoute(),
	)
	require.NotContains(
		t,
		actual,
		"POST /api/v1/workspaces/:workspaceId/agents/:agentId/runtime/provider-apps/:provider/"+inlineProviderToolRoute(),
	)
	require.Len(t, actual, len(expected))
}

func TestRetiredProviderAppRuntimeRoutesReturnNotFound(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	agent.RegisterRoutes(router, agent.Dependencies{})

	for _, testCase := range []struct {
		method string
		path   string
	}{
		{
			method: http.MethodGet,
			path:   "/api/v1/workspaces/workspace-1/agents/agent-1/runtime/provider-apps/" + inlineProviderConfigRoute(),
		},
		{
			method: http.MethodPost,
			path:   "/api/v1/workspaces/workspace-1/agents/agent-1/runtime/provider-apps/github/" + inlineProviderToolRoute(),
		},
	} {
		request := httptest.NewRequest(testCase.method, testCase.path, nil)
		response := httptest.NewRecorder()
		router.ServeHTTP(response, request)
		require.Equal(t, http.StatusNotFound, response.Code, testCase.path)
	}
}

func inlineProviderConfigRoute() string { return "mcp-" + "configs" }

func inlineProviderToolRoute() string { return "tools/" + "call" }

func TestRegisterRoutesMountsChannelManagementOnlyOnRuntime(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	agent.RegisterRoutes(router, agent.Dependencies{
		Channels: channeladmin.New(channeladmin.Dependencies{}),
	})

	const agentBase = "/api/v1/workspaces/:workspaceId/agents/:agentId/runtime"
	expectedRoutes := map[string]struct{}{
		"GET " + agentBase + "/channels":               {},
		"POST " + agentBase + "/channels/get":          {},
		"POST " + agentBase + "/channels/members":      {},
		"POST " + agentBase + "/channels/create":       {},
		"POST " + agentBase + "/channels/update":       {},
		"POST " + agentBase + "/channels/agent-add":    {},
		"POST " + agentBase + "/channels/agent-remove": {},
	}
	actualRoutes := make(map[string]struct{}, len(expectedRoutes))
	for _, route := range router.Routes() {
		key := route.Method + " " + route.Path
		if strings.HasPrefix(route.Path, agentBase+"/channels") {
			actualRoutes[key] = struct{}{}
		}
		require.False(t, strings.HasPrefix(route.Path, "/internal/agent/:agentId/channels"),
			"channel management route leaked onto frozen legacy Agent API: %s", key)
	}
	require.Equal(t, expectedRoutes, actualRoutes)
}

func TestRegisterRoutesOwnsAgentTaskRoutes(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	agent.RegisterRoutes(router, agent.Dependencies{})

	allowedTaskActions := map[string]struct{}{
		"get":            {},
		"update":         {},
		"add-checkpoint": {},
		"set-claim":      {},
		"delete":         {},
		":taskScopeId":   {},
	}
	for _, route := range router.Routes() {
		path := route.Path
		if !strings.HasPrefix(path, "/api/v1/workspaces/:workspaceId/agents/:agentId/runtime") {
			continue
		}
		idx := strings.Index(path, "/tasks")
		if idx < 0 {
			continue
		}
		suffix := path[idx:]
		if suffix == "/tasks" {
			require.True(t,
				route.Method == "GET" || route.Method == "POST",
				"unexpected method %s on agent task base path %s",
				route.Method, path)
			continue
		}
		trimmed := strings.TrimPrefix(suffix, "/tasks/")
		action := trimmed
		if i := strings.Index(trimmed, "/"); i >= 0 {
			action = trimmed[:i]
		}
		_, ok := allowedTaskActions[action]
		require.True(t, ok,
			"agent task action %q is not in the allowed action set — add it here as part of the same change that introduces the route (%s %s)",
			action, route.Method, path)
	}
}

func TestRegisterRoutesExposesOnlyAgentRuntimeTaskRoutes(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()
	agent.RegisterRoutes(router, agent.Dependencies{})

	expectedCount := 0
	for _, route := range router.Routes() {
		require.False(t, strings.HasPrefix(route.Path, "/internal/agent/:agentId/"), route.Method+" "+route.Path)
		if strings.HasPrefix(route.Path, "/api/v1/workspaces/:workspaceId/agents/:agentId/runtime/tasks") {
			expectedCount++
		}
	}
	require.Positive(t, expectedCount)
}
