package api

import (
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"strings"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/desktop/internal/api/auth"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/control"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/dashboard"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/exec"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/localruntime"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/localsecurity"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/machine"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/mcp"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/onboarding"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/session"
	"github.com/tidbcloud/AgentTeam/desktop/internal/api/system"
	tokenanalysisapi "github.com/tidbcloud/AgentTeam/desktop/internal/api/tokenanalysis"
	"github.com/tidbcloud/AgentTeam/desktop/internal/daemoncontrol"
	"github.com/tidbcloud/AgentTeam/desktop/internal/diagnosticbundle"
	"github.com/tidbcloud/AgentTeam/desktop/internal/profile"
	"github.com/tidbcloud/AgentTeam/desktop/internal/token"
	"github.com/tidbcloud/AgentTeam/desktop/internal/tokenanalysis"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
	"github.com/tidbcloud/AgentTeam/desktop/internal/uisession"
	"github.com/tidbcloud/AgentTeam/desktop/internal/userauth"
	"github.com/tidbcloud/AgentTeam/internal/observability/debughttp"
)

type DaemonControlOptions struct {
	Controller      *daemoncontrol.Controller
	RequestShutdown func()
	UISessions      control.UISessionControl
	LocalSecurity   *localsecurity.Policy
}

func NewDaemonUISessionControl(
	instanceID string,
	selections *uisession.Store,
) control.UISessionControl {
	return control.NewUISessionControl(instanceID, selections)
}

// RouterOptions supplies process-owned dependencies to the daemon HTTP router.
type RouterOptions struct {
	Exec           exec.RouteDependencies
	MessageService *mcp.MessageService
	DaemonControl  DaemonControlOptions
	TokenAnalysis  *tokenanalysis.Service
}

// NewRouterWithOptions constructs the AgentTeam daemon's authenticated business,
// gateway, MCP, session, and control HTTP routes. It does not serve renderer
// assets or expose native-window APIs.
func NewRouterWithOptions(
	chMgr types.ServerConnectionAccessor,
	sessions session.SessionStore,
	trajectories session.TrajectoryStore,
	uiSessions *uisession.Store,
	registry gatewayOperationRegistry,
	options RouterOptions,
) (http.Handler, error) {
	if options.MessageService == nil {
		return nil, fmt.Errorf("api: RouterOptions.MessageService is required")
	}
	if os.Getenv("AGENTTEAM_DEV") != "" {
		gin.SetMode(gin.DebugMode)
	} else {
		gin.SetMode(gin.ReleaseMode)
	}
	r := gin.New()
	r.Use(gin.Recovery())
	if options.DaemonControl.LocalSecurity != nil {
		r.Use(options.DaemonControl.LocalSecurity.Middleware())
	}
	r.Use(traceMCPHTTP())
	debughttp.RegisterPprofRoutes(r)
	controlRootRegistered := false
	if options.DaemonControl.Controller != nil {
		controlRootRegistered = true
		control.RegisterRoutes(r, control.Dependencies{
			Controller:      options.DaemonControl.Controller,
			RequestShutdown: options.DaemonControl.RequestShutdown,
			UISessions:      options.DaemonControl.UISessions,
		})
	}
	if !controlRootRegistered {
		// Reserve the fixed private namespace even in test/embedded routers so
		// an unknown control path never falls through to business auth or SPA.
		r.Any(control.RootPath+"/*path", func(c *gin.Context) {
			c.AbortWithStatus(http.StatusNotFound)
		})
	}
	if uiSessions == nil {
		uiSessions = uisession.NewStore(uisession.Options{})
	}
	profileManager := profile.Default()
	if _, err := token.NewKeyManager(profile.Path()); err != nil {
		slog.Warn("initialize execution encryption keys", "error", err)
	}
	userAuth, userAuthErr := userauth.New(profile.ConfigDir())
	if userAuthErr != nil {
		slog.Warn("load Desktop authentication", "error", userAuthErr)
	}
	targets := desktopServerTargets{
		persisted: profileManager.Servers(),
		userAuth:  userAuth,
	}

	// MCP clients probe these well-known paths before or during the initial
	// HTTP handshake. Register them ahead of the SPA fallback so they always
	// return explicit JSON responses.
	mcp.RegisterDiscoveryRoutes(r)

	// Health remains unauthenticated for local discovery.
	var healthIdentity func() daemoncontrol.Identity
	if options.DaemonControl.Controller != nil {
		healthIdentity = options.DaemonControl.Controller.PublicIdentity
	}
	r.GET("/api/health", system.NewHealthHandler(healthIdentity))

	if options.Exec.Agents == nil {
		options.Exec.Agents = execAgentReader{sessions: sessions}
	}
	exec.RegisterRoutes(r.Group("/api/internal/exec"), options.Exec)

	localRuntimeDependencies := localruntime.Dependencies{
		NativeWindow:     func() bool { return controlRootRegistered },
		Servers:          targets,
		ServerCandidates: profileManager.Servers(),
		Sessions:         uiSessions,
	}
	if userAuth != nil {
		if controlRootRegistered {
			localRuntimeDependencies.TargetHistory = electronActiveTargetHistory{manager: userAuth}
		} else {
			localRuntimeDependencies.TargetHistory = userAuth
		}
	}
	machineBindings := machine.NewService(machine.Dependencies{
		Auth:                userAuth,
		Agents:              profileManager.Agents(),
		ConnectionReadiness: connectionReadiness(chMgr),
		DiagnosticPaths: diagnosticbundle.Paths{
			ConfigDir:     profileManager.ConfigDir(),
			DaemonLogPath: profileManager.Config().LogFile(),
		},
		Servers: profileManager.Servers(),
	})
	localRuntimeDependencies.MachineSetup = machineBindings
	localruntime.RegisterActiveSelectionStreamRoute(r, localRuntimeDependencies)
	machine.RegisterDiagnosticDownloadRoute(r, machine.Dependencies{
		Auth:     userAuth,
		Agents:   profileManager.Agents(),
		Service:  machineBindings,
		Servers:  profileManager.Servers(),
		Sessions: uiSessions,
	})

	internalApi := r.Group("/api/internal", auth.Middleware())
	{
		tokenanalysisapi.RegisterHumanRoutes(
			internalApi.Group("/token-analysis/v2"),
			tokenanalysisapi.Dependencies{Service: options.TokenAnalysis},
		)
		internalApi.POST("/auth/logout", auth.LogoutHandlerWithLease(
			token.SessionStore{},
			uiSessions.Revoke,
		))
		localruntime.RegisterRoutes(internalApi, localRuntimeDependencies)
		onboarding.RegisterRoutes(internalApi, onboarding.Dependencies{
			Auth:            userAuth,
			MachineBindings: machineBindings,
			Servers:         profileManager.Servers(),
			Sessions:        uiSessions,
		})
		machine.RegisterRoutes(internalApi, machine.Dependencies{
			Auth:          userAuth,
			Agents:        profileManager.Agents(),
			Service:       machineBindings,
			Servers:       profileManager.Servers(),
			Sessions:      uiSessions,
			AgentSessions: sessions,
		})

		internalApi.GET("", dashboard.NewOverviewHandler())
		internalApi.GET("/", dashboard.NewOverviewHandler())
		internalApi.GET("/overview", dashboard.NewOverviewHandler())

		internalApi.GET("/agents", dashboard.NewListAgentsHandler(sessions))
		internalApi.GET("/agents/:id", dashboard.NewGetAgentHandler(sessions))
		internalApi.GET("/agents/:id/docs/:kind", dashboard.NewGetAgentDocHandler())
		internalApi.PUT("/agents/:id/docs/:kind", dashboard.NewUpdateAgentDocHandler())
		internalApi.GET("/agents/:id/workspace/tree", dashboard.NewAgentWorkspaceTreeHandler())
		internalApi.GET("/agents/:id/workspace/file", dashboard.NewAgentWorkspaceFileHandler())

		internalApi.GET("/runtimes", dashboard.NewListRuntimesHandler())
		internalApi.GET("/runtimes/detect", dashboard.NewDetectRuntimesHandler())
		internalApi.GET("/runtimes/:name/available", dashboard.NewCheckRuntimeHandler())
		internalApi.GET("/runtimes/:name/config", dashboard.NewGetRuntimeConfigHandler())
		internalApi.PUT("/runtimes/:name/config", dashboard.NewUpdateRuntimeConfigHandler())
		internalApi.POST("/runtimes/:name/config/validate", dashboard.NewValidateRuntimeConfigHandler())
		internalApi.GET("/runtimes/:name", dashboard.NewGetRuntimeHandler())

		internalApi.GET("/mcp-servers", dashboard.NewListMCPServersHandler())
		internalApi.POST("/mcp-servers", dashboard.NewCreateMCPServerHandler())
		internalApi.POST("/mcp-servers/test", dashboard.NewTestUnsavedMCPServerHandler())
		internalApi.GET("/mcp-servers/:id", dashboard.NewGetMCPServerHandler())
		internalApi.PUT("/mcp-servers/:id", dashboard.NewUpdateMCPServerHandler())
		internalApi.DELETE("/mcp-servers/:id", dashboard.NewDeleteMCPServerHandler())
		internalApi.POST("/mcp-servers/:id/test", dashboard.NewTestMCPServerHandler())

		internalApi.GET("/toolchain/detect", dashboard.NewToolchainDetectHandler())
		internalApi.GET("/toolchain/check-updates", dashboard.NewToolchainCheckUpdatesHandler())
		internalApi.POST("/toolchain/:name/install", dashboard.NewToolchainInstallHandler())
		internalApi.POST("/toolchain/:name/upgrade", dashboard.NewToolchainUpgradeHandler())

		registerServerConfigRoutes(
			internalApi,
			"/servers",
			chMgr,
			profile.Default().Servers(),
		)

		internalApi.GET("/sessions", session.NewSessionsHandler(sessions))
		sessionRestarter, _ := sessions.(session.SessionRestartStore)
		internalApi.POST("/sessions/:agentId/restart", session.NewRestartHandler(sessionRestarter))
		memoryMaintainer, _ := sessions.(session.MemorySearchMaintenanceStore)
		internalApi.POST("/agents/:agentId/memory-search/rebuild", session.NewRebuildMemorySearchHandler(memoryMaintainer))
		internalApi.DELETE("/agents/:agentId/memory-search", session.NewClearMemorySearchHandler(memoryMaintainer))
		memorySync, _ := sessions.(session.MemorySyncStore)
		// Gin requires each method's /agents wildcard name to match the routes
		// already registered in that method tree (GET uses :id; POST uses :agentId).
		internalApi.GET("/agents/:id/memory-sync", session.NewMemorySyncStatusHandler(memorySync))
		internalApi.POST("/agents/:agentId/memory-sync/retry", session.NewMemorySyncRetryHandler(memorySync))
		internalApi.GET("/trajectory/latest", session.NewTrajectoryLatestHandler(trajectories))
		internalApi.GET("/trajectory/live", session.NewTrajectoryLiveHandler(trajectories))
		internalApi.GET("/sessions/:agentId/trajectory/history", session.NewTrajectoryHistoryHandler(trajectories))
		internalApi.GET("/sessions/:agentId/trajectory", session.NewTrajectoryHandler(trajectories))

		internalApi.GET("/skills", dashboard.NewListSkillsHandler())
		internalApi.POST("/skills/import", dashboard.NewImportSkillHandler())
		internalApi.POST("/skills/import-stream", dashboard.NewImportSkillStreamHandler())
		internalApi.POST("/skills/import-batch", dashboard.NewImportBatchSkillHandler())
		internalApi.GET("/skills/:id/files/tree", dashboard.NewSkillFilesTreeHandler())
		internalApi.GET("/skills/:id/files/file", dashboard.NewSkillFileHandler())
		internalApi.GET("/skills/:id", dashboard.NewGetSkillHandler())
		internalApi.PUT("/skills/:id", dashboard.NewUpdateSkillHandler())
		internalApi.DELETE("/skills/:id", dashboard.NewDeleteSkillHandler())

		internalApi.GET("/dbaas", dashboard.NewListDBaaSHandler())
		internalApi.POST("/dbaas", dashboard.NewCreateDBaaSHandler())
		internalApi.GET("/dbaas/:id", dashboard.NewGetDBaaSHandler())
		internalApi.PUT("/dbaas/:id", dashboard.NewUpdateDBaaSHandler())
		internalApi.DELETE("/dbaas/:id", dashboard.NewDeleteDBaaSHandler())

		internalApi.GET("/databases", dashboard.NewListDatabasesHandler())
		internalApi.POST("/databases", dashboard.NewCreateDatabaseHandler())
		internalApi.GET("/databases/vendors", dashboard.NewListVendorsHandler())
		internalApi.GET("/databases/:id", dashboard.NewGetDatabaseHandler())
		internalApi.PATCH("/databases/:id", dashboard.NewUpdateDatabaseHandler())
		internalApi.DELETE("/databases/:id", dashboard.NewDeleteDatabaseHandler())

		internalApi.GET("/settings", dashboard.NewGetSettingsHandler())
		internalApi.PUT("/settings", dashboard.NewUpdateSettingsHandler())

		internalApi.GET("/help-docs", dashboard.NewHelpDocsHandler())

		internalApi.GET("/doctor", system.NewDoctorHandler())
		internalApi.GET("/logs/execution", system.NewDaemonLogHandler())
		internalApi.GET("/system-info", system.NewSystemInfoHandler())

		internalApi.GET("/stats/servers", system.NewServerMessageStatsHandler())
		internalApi.GET("/stats/servers/timeseries", system.NewServerMessageTimeSeriesHandler())
		internalApi.GET("/stats/servers/directional-timeseries", system.NewServerMessageDirectionalTimeSeriesHandler())
		internalApi.GET("/stats/servers/window-summary", system.NewServerMessageWindowSummaryHandler())
		internalApi.GET("/stats/agents/:agentId/messages-summary", system.NewAgentMessageSummaryHandler())
		internalApi.GET("/stats/agents/:agentId/messages-directional-timeseries", system.NewAgentMessageDirectionalTimeSeriesHandler())
		internalApi.GET("/stats/agents/:agentId/messages-bar", system.NewAgentMessageBarSeriesHandler())
		internalApi.GET("/stats/agents/:agentId/tokens-summary", system.NewAgentTokenSummaryHandler())
		internalApi.GET("/stats/agents/:agentId/tokens-timeseries", system.NewAgentTokenTimeSeriesHandler())
		internalApi.GET("/stats/agents/:agentId/tokens-bar", system.NewAgentModelTokenBarSeriesHandler())

		internalApi.GET("/stats/llm/summary", system.NewLLMStatsSummaryHandler())
		internalApi.GET("/stats/llm/detail", system.NewLLMStatsDetailHandler())
		internalApi.GET("/stats/llm/timeseries", system.NewLLMStatsTimeSeriesHandler())
		internalApi.GET("/stats/llm/status", system.NewLLMServiceStatusHandler())
		internalApi.GET("/stats/llm/errors/summary", system.NewLLMErrorSummaryHandler())
		internalApi.GET("/stats/llm/errors/timeseries", system.NewLLMErrorTimeSeriesHandler())

		// Aggregate bundle endpoints used by the dashboard web UI to reduce
		// HTTP round-trips. Additive — per-resource endpoints above remain
		// the source of truth for mutations and third-party callers.
		internalApi.GET("/bundles/home", dashboard.NewHomeBundleHandler())
		internalApi.GET("/bundles/agents", dashboard.NewAgentsBundleHandler())
		internalApi.GET("/bundles/agents/:agentId/stats", dashboard.NewAgentStatsBundleHandler())
		internalApi.GET("/bundles/servers", dashboard.NewServersBundleHandler())
		internalApi.GET("/bundles/runtimes", dashboard.NewRuntimesBundleHandler())
		internalApi.GET("/bundles/settings", dashboard.NewSettingsBundleHandler())
	}

	tokenanalysisapi.RegisterAgentRoutes(
		r.Group("/api/internal/token-analysis/v2/agents/:agentId"),
		tokenanalysisapi.Dependencies{Service: options.TokenAnalysis},
	)

	// --- MCP Routes ---
	// Dynamic per-server MCP endpoints resolved at request time by server ID.
	// Registered outside internalApi group so that dashboard auth.Middleware()
	// does not block agent requests — MCP uses its own access-control middleware.
	{
		mcpService := options.MessageService
		resolveAgentTeamHandler := func(c *gin.Context, serverID string) (types.MCPHandler, bool) {
			memory, _ := sessions.(mcp.MemorySearcher)
			runtime, ok := sessions.(interface {
				mcp.MessageRuntime
				mcp.KBRouteContext
			})
			if !ok {
				return nil, false
			}
			return newAgentTeamMCPHandlerForRoute(mcpService, chMgr, runtime, memory, serverID)
		}
		agentteamGroup := r.Group("/api/internal/mcp/servers/:serverId/agents/:agentId", mcp.AccessControl())
		mcp.RegisterContextHandlerRoutes(agentteamGroup, "serverId", resolveAgentTeamHandler)
		legacyAgentTeamGroup := r.Group("/mcp/agentteam/:serverId/:agentId", mcp.AccessControl())
		mcp.RegisterContextHandlerRoutes(legacyAgentTeamGroup, "serverId", resolveAgentTeamHandler)
	}

	gateway := newDesktopGateway(chMgr, uiSessions, registry)
	gateway.servers = targets
	gateway.userAuth = userAuth
	r.NoRoute(func(c *gin.Context) {
		if strings.HasPrefix(c.Request.URL.Path, control.RootPath+"/") ||
			c.Request.URL.Path == control.RootPath {
			c.AbortWithStatus(http.StatusNotFound)
			return
		}
		gateway.handle(c)
	})

	return r, nil
}

func connectionReadiness(connections types.ServerConnectionAccessor) machine.ConnectionReadiness {
	readiness, _ := connections.(machine.ConnectionReadiness)
	return readiness
}

func registerServerConfigRoutes(
	r gin.IRoutes,
	base string,
	chMgr types.ServerConnectionAccessor,
	servers *profile.ServerManager,
) {
	r.GET(base, dashboard.NewListServersHandler())
	r.POST(base, dashboard.NewCreateServerHandler(servers))
	r.GET(base+"/:id", dashboard.NewGetServerHandler())
	r.PUT(base+"/:id", dashboard.NewUpdateServerHandler(servers))
	r.POST(base+"/:id/repair", dashboard.NewRepairServerHandler(servers))
	r.DELETE(base+"/:id", dashboard.NewDeleteServerHandler(servers))
	r.GET(base+"/:id/tools", dashboard.NewListServerToolsHandler(chMgr, func(connection types.ServerConnection) []types.MCPToolDef {
		if _, ok := connection.(mcp.AgentTeamConnection); !ok {
			return nil
		}
		return mcp.MessageToolDefs()
	}))
}

func traceMCPHTTP() gin.HandlerFunc {
	return func(c *gin.Context) {
		path := c.Request.URL.Path
		if !strings.HasPrefix(path, "/mcp/") && !strings.Contains(path, "/.well-known/") {
			c.Next()
			return
		}

		accept := c.GetHeader("Accept")
		reqContentType := c.GetHeader("Content-Type")
		mcpVersion := c.GetHeader("Mcp-Protocol-Version")
		authPresent := strings.TrimSpace(c.GetHeader("Authorization")) != ""

		c.Next()

		slog.Debug("mcp http",
			"method", c.Request.Method,
			"path", path,
			"status", c.Writer.Status(),
			"accept", accept,
			"request_content_type", reqContentType,
			"response_content_type", c.Writer.Header().Get("Content-Type"),
			"mcp_protocol_version", mcpVersion,
			"auth_present", authPresent,
		)
	}
}
