// Package workspace contains browser-facing workspace administration routes.
package workspace

import (
	"errors"
	"log/slog"
	"net/http"

	"github.com/gin-gonic/gin"

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/mailer"
	"github.com/tidbcloud/AgentTeam/internal/runtimecontrol"
	"github.com/tidbcloud/AgentTeam/internal/versioning"
	"github.com/tidbcloud/AgentTeam/internal/workspace/owner"
)

func handleWorkspaceAuth(handler api.AuthHandler) gin.HandlerFunc {
	return api.HandleAuth(handler, api.WithErrorClassifier(classifyWorkspaceRuntimeError))
}

func handleWorkspaceAuthJSON[T any](handler func(api.AuthenticatedJSON[T]) (api.Response, error)) gin.HandlerFunc {
	return api.HandleAuthJSON[T](handler, api.WithErrorClassifier(classifyWorkspaceRuntimeError))
}

func handleWorkspaceScoped(handler api.WorkspaceScopedHandler) gin.HandlerFunc {
	return api.HandleWorkspaceScoped(handler, api.WithErrorClassifier(classifyWorkspaceRuntimeError))
}

func handleWorkspaceScopedJSON[T any](handler func(api.WorkspaceScopedJSON[T]) (api.Response, error)) gin.HandlerFunc {
	return api.HandleWorkspaceScopedJSON[T](handler, api.WithErrorClassifier(classifyWorkspaceRuntimeError))
}

func classifyWorkspaceRuntimeError(err error) (api.ErrorResponse, bool) {
	switch {
	case errors.Is(err, runtimecontrol.ErrMachineDirectoryInvalid):
		return api.ErrorResponse{Status: http.StatusBadRequest, Code: "machine_directory_invalid", Message: "machine directory path or cursor is invalid"}, true
	case errors.Is(err, runtimecontrol.ErrMachineDirectoryUnreadable):
		return api.ErrorResponse{Status: http.StatusUnprocessableEntity, Code: "machine_directory_unreadable", Message: "machine directory could not be read"}, true
	case errors.Is(err, runtimecontrol.ErrMachineDirectoryUnsupported):
		return api.ErrorResponse{Status: http.StatusNotImplemented, Code: "machine_directory_unsupported", Message: "machine directory browsing is not supported"}, true
	case errors.Is(err, runtimecontrol.ErrMachineDirectoryUnavailable):
		return api.ErrorResponse{Status: http.StatusServiceUnavailable, Code: "machine_directory_unavailable", Message: "machine directory browsing is unavailable"}, true
	case errors.Is(err, runtimecontrol.ErrMachineDirectoryBusy):
		return api.ErrorResponse{Status: http.StatusServiceUnavailable, Code: "machine_directory_busy", Message: "machine directory browsing is busy"}, true
	case errors.Is(err, runtimecontrol.ErrMachineDashboardInvalid):
		return api.ErrorResponse{Status: http.StatusBadRequest, Code: "machine_dashboard_invalid", Message: "machine dashboard request is invalid"}, true
	case errors.Is(err, runtimecontrol.ErrMachineDashboardUnsupported):
		return api.ErrorResponse{Status: http.StatusNotImplemented, Code: "machine_dashboard_unsupported", Message: "machine dashboard is not supported"}, true
	case errors.Is(err, runtimecontrol.ErrMachineDashboardUnavailable):
		return api.ErrorResponse{Status: http.StatusServiceUnavailable, Code: "machine_dashboard_unavailable", Message: "machine dashboard is unavailable"}, true
	case errors.Is(err, runtimecontrol.ErrMachineSettingsInvalid):
		return api.ErrorResponse{Status: http.StatusBadRequest, Code: "machine_settings_invalid", Message: "machine settings request is invalid"}, true
	case errors.Is(err, runtimecontrol.ErrMachineSettingsUnsupported):
		return api.ErrorResponse{Status: http.StatusNotImplemented, Code: "machine_settings_unsupported", Message: "machine settings are not supported"}, true
	case errors.Is(err, runtimecontrol.ErrMachineSettingsUnavailable):
		return api.ErrorResponse{Status: http.StatusServiceUnavailable, Code: "machine_settings_unavailable", Message: "machine settings are unavailable"}, true
	case errors.Is(err, runtimecontrol.ErrMachineRuntimesInvalid):
		return api.ErrorResponse{Status: http.StatusBadRequest, Code: "machine_runtimes_invalid", Message: "machine runtimes request is invalid"}, true
	case errors.Is(err, runtimecontrol.ErrMachineRuntimesUnsupported):
		return api.ErrorResponse{Status: http.StatusNotImplemented, Code: "machine_runtimes_unsupported", Message: "machine runtimes endpoint is not supported"}, true
	case errors.Is(err, runtimecontrol.ErrMachineRuntimesUnavailable):
		return api.ErrorResponse{Status: http.StatusServiceUnavailable, Code: "machine_runtimes_unavailable", Message: "machine runtimes endpoint is unavailable"}, true
	case errors.Is(err, runtimecontrol.ErrMachineMCPServersInvalid):
		return api.ErrorResponse{Status: http.StatusBadRequest, Code: "machine_mcp_servers_invalid", Message: "machine mcp server request is invalid"}, true
	case errors.Is(err, runtimecontrol.ErrMachineMCPServersUnsupported):
		return api.ErrorResponse{Status: http.StatusNotImplemented, Code: "machine_mcp_servers_unsupported", Message: "machine mcp servers endpoint is not supported"}, true
	case errors.Is(err, runtimecontrol.ErrMachineMCPServersUnavailable):
		return api.ErrorResponse{Status: http.StatusServiceUnavailable, Code: "machine_mcp_servers_unavailable", Message: "machine mcp servers endpoint is unavailable"}, true
	case errors.Is(err, runtimecontrol.ErrAgentStillRunning):
		return api.ErrorResponse{Status: http.StatusConflict, Message: err.Error()}, true
	case errors.Is(err, runtimecontrol.ErrDaemonWorkspaceTargetPathUpgradeRequired):
		return api.ErrorResponse{Status: http.StatusConflict, Message: err.Error()}, true
	case errors.Is(err, runtimecontrol.ErrDaemonPermissionModeUpgradeRequired):
		return api.ErrorResponse{Status: http.StatusConflict, Message: err.Error()}, true
	case errors.Is(err, runtimecontrol.ErrDaemonMaaSDirectUpgradeRequired):
		return api.ErrorResponse{Status: http.StatusConflict, Message: err.Error()}, true
	case errors.Is(err, runtimecontrol.ErrMachineWorkspaceListUnavailable):
		return api.ErrorResponse{Status: http.StatusServiceUnavailable, Message: err.Error()}, true
	case errors.Is(err, runtimecontrol.ErrMachineOffline):
		return api.ErrorResponse{Status: http.StatusServiceUnavailable, Message: err.Error()}, true
	default:
		return api.ErrorResponse{}, false
	}
}

// Dependencies contains collaborators and config for workspace-admin routes.
type Dependencies struct {
	AuthStore                 SessionStore
	WorkspaceService          WorkspaceService
	PlanStore                 PlanStore
	Organizations             OrganizationProvider
	HostedAvailability        HostedAvailabilityClient
	HostedPricing             HostedPricingClient
	Mailer                    mailer.Mailer
	PublicBaseURL             string
	EmailVerificationRequired bool
	MachineRuntime            MachineRuntime
	Realtime                  WorkspaceRealtime
	LatestDaemonVersion       versioning.Provider
	Logger                    *slog.Logger
	ManagedWorkspaces         owner.ManagedWorkspaceSet
	UnreadRuntime             WorkspaceUnreadReader
	// MeshXEnabled advertises the AgentTeam Memory subsystem on workspace
	// responses so Desktop can decide whether to initiate Memory-dependent
	// flows. Today this is deployment-level; per-workspace toggles may land
	// later without reshaping the contract.
	MeshXEnabled bool
}

// RegisterRoutes mounts workspace-admin routes.
func RegisterRoutes(router gin.IRouter, dependencies Dependencies) {
	RegisterControlRoutes(router, dependencies)
	RegisterWorkspaceRoutes(router, dependencies)
}

// RegisterControlRoutes mounts workspace and machine management routes.
func RegisterControlRoutes(router gin.IRouter, dependencies Dependencies) {
	if router == nil {
		return
	}
	apiGroup := router.Group("/api")
	registerMachineExchangeRoute(apiGroup, dependencies)
	workspaceScoped := apiGroup.Group(
		"",
		api.RequireAuthenticatedUser(dependencies.AuthStore),
		api.RequireVerifiedIdentity(dependencies.EmailVerificationRequired),
	)
	registerWorkspaceRoutes(workspaceScoped, dependencies)
}

// RegisterWorkspaceRoutes mounts workspace-scoped data-plane routes.
func RegisterWorkspaceRoutes(router gin.IRouter, dependencies Dependencies) {
	if router == nil {
		return
	}
	apiGroup := router.Group("/api")
	workspaceScoped := apiGroup.Group(
		"/v1/workspaces/:workspaceId",
		api.RequireAuthenticatedUser(dependencies.AuthStore),
		api.RequireVerifiedIdentity(dependencies.EmailVerificationRequired),
		api.RequireRouteWorkspace(api.NewOwnedWorkspaceResolver(dependencies.WorkspaceService, dependencies.ManagedWorkspaces)),
	)
	registerWorkspaceDataPlaneRoutes(workspaceScoped, dependencies)

	machineScoped := apiGroup.Group(
		"",
		api.RequireAuthenticatedUser(dependencies.AuthStore),
		api.RequireVerifiedIdentity(dependencies.EmailVerificationRequired),
		api.RequireRouteWorkspace(api.NewOwnedWorkspaceResolver(dependencies.WorkspaceService, dependencies.ManagedWorkspaces)),
	)
	registerMachineRoutes(machineScoped, dependencies)
	registerMiniappPairingRoutes(machineScoped, dependencies)
}

func registerWorkspaceRoutes(routes gin.IRouter, dependencies Dependencies) {
	handler := &WorkspaceHandler{
		workspaceService: dependencies.WorkspaceService,
		planStore:        dependencies.PlanStore,
		organizations:    dependencies.Organizations,
		mailer:           dependencies.Mailer,
		publicBaseURL:    dependencies.PublicBaseURL,
		machineRuntime:   dependencies.MachineRuntime,
		realtime:         dependencies.Realtime,
		unreadRuntime:    dependencies.UnreadRuntime,
		meshXEnabled:     dependencies.MeshXEnabled,
		logger:           dependencies.Logger,
	}
	routes.GET("/v1/workspaces", handleWorkspaceAuth(handler.List))
	routes.POST("/v1/workspaces", handleWorkspaceAuthJSON(handler.Create))

	workspaceRoutes := routes.Group("/v1/workspaces/:workspaceId")
	workspaceRoutes.PATCH("", handleWorkspaceAuthJSON(handler.Update))
	workspaceRoutes.GET("/organization", handleWorkspaceAuth(handler.Organization))
	workspaceRoutes.GET("/members", handleWorkspaceAuth(handler.Members))
	workspaceRoutes.POST("/members/search", handleWorkspaceAuthJSON(handler.MembersSearch))
	workspaceRoutes.POST("/members:batchLookup", handleWorkspaceAuthJSON(handler.MembersLookup))
	workspaceRoutes.GET("/members/by-user/:userId/offboarding-impact", handleWorkspaceAuth(handler.MemberOffboardingImpact))
	workspaceRoutes.GET("/members/:memberId", handleWorkspaceAuth(handler.Member))
	workspaceRoutes.PATCH("/members/:memberId", handleWorkspaceAuthJSON(handler.MembersUpdate))
	workspaceRoutes.DELETE("/members/:memberId", handleWorkspaceAuth(handler.MembersDelete))
	workspaceRoutes.GET("/invites", handleWorkspaceAuth(handler.InvitesList))
	workspaceRoutes.POST("/invites", handleWorkspaceAuthJSON(handler.InvitesCreate))
	workspaceRoutes.DELETE("/invites/:inviteId", handleWorkspaceAuth(handler.InvitesDelete))
	workspaceRoutes.GET("/usage", handleWorkspaceAuth(handler.Usage))

	routes.GET("/v1/me/behavior-settings", handleWorkspaceAuth(handler.AccountBehaviorSettings))
	routes.PATCH("/v1/me/behavior-settings", handleWorkspaceAuthJSON[dto.AccountBehaviorSettingsUpdateRequest](handler.UpdateAccountBehaviorSettings))
}

func registerMachineRoutes(routes gin.IRoutes, dependencies Dependencies) {
	handler := &MachineHandler{
		workspaceService:    dependencies.WorkspaceService,
		runtime:             dependencies.MachineRuntime,
		latestDaemonVersion: dependencies.LatestDaemonVersion,
		logger:              dependencies.Logger,
	}
	routes.GET("/v1/workspaces/:workspaceId/machines", handleWorkspaceAuth(handler.List))
	routes.POST("/v1/workspaces/:workspaceId/machines", handleWorkspaceAuthJSON(handler.Create))
	routes.PATCH("/v1/workspaces/:workspaceId/machines/:machineId", handleWorkspaceAuthJSON(handler.Rename))
	routes.POST("/v1/workspaces/:workspaceId/machines/:machineId/rename", handleWorkspaceAuthJSON(handler.Rename))
	routes.DELETE("/v1/workspaces/:workspaceId/machines/:machineId", handleWorkspaceAuth(handler.Delete))
	routes.POST("/v1/workspaces/:workspaceId/machines/:machineId/api-key-rotations", handleWorkspaceAuth(handler.RotateKey))
	routes.POST("/v1/workspaces/:workspaceId/machines/:machineId/connect-tokens", handleWorkspaceAuthJSON(handler.ConnectToken))
	routes.POST("/v1/workspaces/:workspaceId/machines/:machineId/evictions", handleWorkspaceAuth(handler.Evict))
	routes.GET("/v1/workspaces/:workspaceId/machines/:machineId/agents", handleWorkspaceAuth(handler.Agents))
	routes.GET("/v1/workspaces/:workspaceId/machines/:machineId/dashboard", handleWorkspaceAuth(handler.Dashboard))
	routes.GET("/v1/workspaces/:workspaceId/machines/:machineId/settings", handleWorkspaceAuth(handler.GetSettings))
	routes.PUT("/v1/workspaces/:workspaceId/machines/:machineId/settings", handleWorkspaceAuth(handler.UpdateSettings))
	routes.GET("/v1/workspaces/:workspaceId/machines/:machineId/runtimes", handleWorkspaceAuth(handler.ListRuntimes))
	routes.POST("/v1/workspaces/:workspaceId/machines/:machineId/runtimes/detect", handleWorkspaceAuth(handler.DetectRuntimes))
	routes.GET("/v1/workspaces/:workspaceId/machines/:machineId/runtimes/:name/config", handleWorkspaceAuth(handler.GetRuntimeConfig))
	routes.PUT("/v1/workspaces/:workspaceId/machines/:machineId/runtimes/:name/config", handleWorkspaceAuth(handler.UpdateRuntimeConfig))
	routes.POST("/v1/workspaces/:workspaceId/machines/:machineId/runtimes/:name/config/validate", handleWorkspaceAuth(handler.ValidateRuntimeConfig))
	routes.GET("/v1/workspaces/:workspaceId/machines/:machineId/mcp-servers", handleWorkspaceAuth(handler.ListMCPServers))
	routes.POST("/v1/workspaces/:workspaceId/machines/:machineId/mcp-servers", handleWorkspaceAuth(handler.UpsertMCPServer))
	routes.DELETE("/v1/workspaces/:workspaceId/machines/:machineId/mcp-servers/:name", handleWorkspaceAuth(handler.DeleteMCPServer))
	routes.POST("/v1/workspaces/:workspaceId/machines/:machineId/mcp-servers/:name/test", handleWorkspaceAuth(handler.TestMCPServer))
	routes.GET("/v1/workspaces/:workspaceId/machines/:machineId/workspace-directories", handleWorkspaceAuth(handler.ListWorkspaceDirectories))
	routes.DELETE("/v1/workspaces/:workspaceId/machines/:machineId/workspace-directories/:directoryName", handleWorkspaceAuth(handler.DeleteWorkspaceDirectory))
	routes.POST(
		"/v1/workspaces/:workspaceId/machines/:machineId/directories:list",
		limitMachineDirectoryListBody,
		handleWorkspaceAuthJSON[dto.MachineDirectoryListRequest](handler.ListDirectories),
	)
}

func registerMiniappPairingRoutes(routes gin.IRoutes, dependencies Dependencies) {
	handler := &MiniappPairingHandler{
		workspaceService: dependencies.WorkspaceService,
	}
	routes.POST("/v1/workspaces/:workspaceId/miniapp-pairing-codes", handleWorkspaceAuthJSON(handler.Issue))
}

func registerWorkspaceDataPlaneRoutes(routes gin.IRoutes, dependencies Dependencies) {
	handler := &WorkspaceHandler{
		workspaceService: dependencies.WorkspaceService,
		planStore:        dependencies.PlanStore,
		organizations:    dependencies.Organizations,
		mailer:           dependencies.Mailer,
		machineRuntime:   dependencies.MachineRuntime,
		realtime:         dependencies.Realtime,
		hostedModels:     dependencies.HostedAvailability,
		hostedPricing:    dependencies.HostedPricing,
		logger:           dependencies.Logger,
	}
	routes.GET("/providers", handler.VisibleProviders)
	routes.GET("/selectable-models", handleWorkspaceScoped(handler.SelectableModels))
	routes.GET("/sidebar/channel-sections", handleWorkspaceScoped(handler.ChannelSidebarPreference))
	routes.PUT(
		"/sidebar/channel-sections",
		limitChannelSidebarPreferenceBody,
		handleWorkspaceScopedJSON[dto.WorkspaceChannelSidebarPreferenceUpdateRequest](handler.PutChannelSidebarPreference),
	)
}

const maxChannelSidebarPreferenceBodyBytes int64 = 2 * 1024 * 1024

func limitChannelSidebarPreferenceBody(ctx *gin.Context) {
	ctx.Request.Body = http.MaxBytesReader(
		ctx.Writer,
		ctx.Request.Body,
		maxChannelSidebarPreferenceBodyBytes,
	)
	ctx.Next()
}

func registerMachineExchangeRoute(apiGroup gin.IRoutes, dependencies Dependencies) {
	handler := &MachineHandler{workspaceService: dependencies.WorkspaceService}
	apiGroup.POST("/v1/machine-connect-exchanges", handler.ConnectExchange)
	apiGroup.POST("/v1/machine-auth-resolutions", handler.ResolveAuth)
}
