package router_test

import (
	"sort"
	"strings"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/router"
)

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

	centralRouter, workspaceRouter := edgeContractRouters(t)
	contract, err := router.BuildEdgeRouteContractForRouters(centralRouter, workspaceRouter)
	require.NoError(t, err)

	require.Equal(t, expectedNonV1RouteMigrationInventory(), nonV1RouteMigrationInventory(contract))
}

func nonV1RouteMigrationInventory(contract []router.EdgeRouteContract) []string {
	inventory := make([]string, 0)
	for _, route := range contract {
		category, ok := nonV1RouteMigrationCategory(route)
		if !ok {
			continue
		}
		inventory = append(inventory, category+" "+cleanPathRouteFamily(route))
	}
	sort.Strings(inventory)
	inventory = compactSortedStrings(inventory)
	return inventory
}

func nonV1RouteMigrationCategory(route router.EdgeRouteContract) (string, bool) {
	path := route.Path
	if route.Policy == router.EdgePolicyInternalOnly || isAlreadyVersionedAgentTeamAPI(path) {
		return "", false
	}

	switch router.Profile(route.Profile) {
	case router.ProfileCentral:
		return centralNonV1RouteMigrationCategory(route.Method, path)
	case router.ProfileWorkspace:
		return workspaceNonV1RouteMigrationCategory(route.Policy, path)
	default:
		return "unclassified-profile", true
	}
}

func centralNonV1RouteMigrationCategory(method string, path string) (string, bool) {
	switch {
	case isMeshXOrProtocolForwardingRoute(path):
		return "retain meshx-protocol", true
	case isOAuthOrProviderIngress(path):
		return "compat external-ingress", true
	case isBillingExternalIngress(path):
		return "compat external-ingress", true
	case isDeprecatedAvatarObjectRoute(method, path):
		return "compat historical-avatar-object", true
	case isDeprecatedAttachmentObjectRoute(method, path):
		return "compat historical-attachment-object", true
	case isDirectBrowserServerREST(path):
		return "direct browser-rest", true
	case strings.HasPrefix(path, "/api/"):
		return "unclassified central-api", true
	default:
		return "", false
	}
}

func workspaceNonV1RouteMigrationCategory(policy router.EdgePolicy, path string) (string, bool) {
	switch policy {
	case router.EdgePolicyWorkspaceREST:
		if strings.HasPrefix(path, "/api/v1/workspaces/:workspaceId/") {
			return "", false
		}
		if isMeshXOrProtocolForwardingRoute(path) {
			return "retain meshx-protocol", true
		}
		return "unclassified workspace-rest", true
	case router.EdgePolicyWorkspaceExplicit:
		switch {
		case strings.HasPrefix(path, "/api/llm/"):
			return "retain llm-proxy", true
		case path == "/api/apps/github/webhook", path == "/api/apps/gitea/webhook":
			return "compat external-ingress", true
		default:
			return "unclassified workspace-explicit", true
		}
	case router.EdgePolicyLegacyRuntime:
		return "compat legacy-runtime", true
	case router.EdgePolicyUnsupported:
		return "defer split-protocol", true
	default:
		return "", false
	}
}

func isAlreadyVersionedAgentTeamAPI(path string) bool {
	return strings.HasPrefix(path, "/api/v1/")
}

func isMeshXOrProtocolForwardingRoute(path string) bool {
	return path == "/api/graphql" ||
		strings.HasPrefix(path, "/api/ext/v1/") ||
		strings.HasPrefix(path, "/api/v3/")
}

func isOAuthOrProviderIngress(path string) bool {
	switch path {
	case "/api/github/callback", "/api/github/setup", "/api/github/setup/start":
		return true
	default:
		return false
	}
}

func isBillingExternalIngress(path string) bool {
	return strings.Contains(path, "/alipay/notify") ||
		strings.Contains(path, "/alipay/return")
}

func isDeprecatedAvatarObjectRoute(method string, path string) bool {
	// Deprecated: kept for historical avatar object URLs created before
	// 2026-06-12. Canonical avatar APIs are /api/v1/users/:userId/avatar,
	// /api/v1/me/avatar, and workspace-scoped agent avatar resources.
	return method == "GET" && path == "/api/avatars/:avatarId"
}

func isDeprecatedAttachmentObjectRoute(method string, path string) bool {
	// Deprecated: kept for older daemon MCP view_file clients. New callers use
	// /api/v1/workspaces/:workspaceId/attachments/:attachmentId.
	return method == "GET" && path == "/api/attachments/:attachmentId"
}

func isDirectBrowserServerREST(path string) bool {
	switch {
	case strings.HasPrefix(path, "/api/admin/"),
		strings.HasPrefix(path, "/api/auth/"),
		strings.HasPrefix(path, "/api/billing/"),
		strings.HasPrefix(path, "/api/orgs"),
		strings.HasPrefix(path, "/api/public/"),
		path == "/api/attachments/:attachmentId",
		path == "/api/stickers/:stickerId":
		return true
	default:
		return false
	}
}

func cleanPathRouteFamily(route router.EdgeRouteContract) string {
	return route.Profile + " " + nonV1RouteFamily(route.Path)
}

func nonV1RouteFamily(path string) string {
	switch {
	case strings.HasPrefix(path, "/api/github/setup"):
		return "/api/github/setup"
	case path == "/api/attachments/:attachmentId",
		path == "/api/avatars/:avatarId",
		path == "/api/github/callback",
		path == "/api/graphql",
		path == "/api/stickers/:stickerId",
		path == "/api/apps/github/webhook",
		path == "/api/apps/gitea/webhook":
		return path
	case strings.HasPrefix(path, "/api/admin/"):
		return "/api/admin"
	case strings.HasPrefix(path, "/api/auth/"):
		return "/api/auth"
	case strings.HasPrefix(path, "/api/billing/seat-orders/alipay/"):
		return "/api/billing/seat-orders/alipay"
	case strings.HasPrefix(path, "/api/billing/alipay/"):
		return "/api/billing/alipay"
	case strings.HasPrefix(path, "/api/billing/"):
		return "/api/billing"
	case strings.HasPrefix(path, "/api/orgs"):
		return "/api/orgs"
	case strings.HasPrefix(path, "/api/public/"):
		return "/api/public"
	case strings.HasPrefix(path, "/api/v3/"):
		return "/api/v3"
	case strings.HasPrefix(path, "/daemon"):
		return "/daemon"
	case strings.HasPrefix(path, "/internal/agent/"):
		return "/internal/agent/:agentId"
	case strings.HasPrefix(path, "/internal/app/runtime"):
		return "/internal/app/runtime"
	case strings.HasPrefix(path, "/api/llm/"):
		return "/api/llm"
	case strings.HasPrefix(path, "/w/"):
		return "/w/:workspaceId"
	case strings.HasPrefix(path, "/:owner/:repo/raw/"):
		return "/:owner/:repo/raw"
	default:
		return path
	}
}

func compactSortedStrings(items []string) []string {
	if len(items) == 0 {
		return items
	}
	compacted := items[:0]
	var previous string
	for _, item := range items {
		if item == previous {
			continue
		}
		compacted = append(compacted, item)
		previous = item
	}
	return compacted
}

func expectedNonV1RouteMigrationInventory() []string {
	return []string{
		"compat external-ingress central /api/billing/alipay",
		"compat external-ingress central /api/billing/seat-orders/alipay",
		"compat external-ingress central /api/github/callback",
		"compat external-ingress central /api/github/setup",
		"compat external-ingress workspace /api/apps/gitea/webhook",
		"compat external-ingress workspace /api/apps/github/webhook",
		"compat historical-attachment-object central /api/attachments/:attachmentId",
		"compat historical-avatar-object central /api/avatars/:avatarId",
	}
}
