package app

import (
	"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/workspace/owner"
)

func handleAppWorkspace(handler api.WorkspaceScopedHandler) gin.HandlerFunc {
	return api.HandleWorkspaceScoped(handler)
}

func handleAppWorkspaceJSON[T any](
	handler func(api.WorkspaceScopedJSON[T]) (api.Response, error),
	opts ...api.HandlerOption,
) gin.HandlerFunc {
	return api.HandleWorkspaceScopedJSON[T](handler, opts...)
}

type Dependencies struct {
	SessionAuth                     api.SessionAuthenticator
	WorkspaceStore                  api.WorkspaceResolver
	AppStore                        Store
	AppInstallationService          AppInstallationService
	AppRuntimeStore                 AppRuntimeStore
	AppRunnerService                AppRunnerService
	SearchEngineConnections         SearchEngineConnectionRuntimeStore
	SearchEngineTester              SearchEngineConnectionTester
	ExtensionProviders              ExtensionProviderIndex
	FollowupDeliverer               FollowupDeliverer
	ServiceEventSubscriptionBlocker ServiceEventSubscriptionBlocker
	InstallationRequestProjector    AppInstallationRequestProjector
	Realtime                        WorkspaceAppRealtime
	EmailVerificationRequired       bool
	Logger                          *slog.Logger
	ManagedWorkspaces               owner.ManagedWorkspaceSet
}

func RegisterRoutes(router gin.IRouter, dependencies Dependencies) {
	RegisterWorkspaceRoutes(router, dependencies)
	RegisterIngressRoutes(router, dependencies)
}

// RegisterWorkspaceRoutes mounts workspace app management routes.
func RegisterWorkspaceRoutes(router gin.IRouter, dependencies Dependencies) {
	if router == nil {
		return
	}
	registerWorkspaceAppRoutes(router, dependencies)
}

// RegisterIngressRoutes mounts provider webhook and App runtime ingress routes.
func RegisterIngressRoutes(router gin.IRouter, dependencies Dependencies) {
	if router == nil {
		return
	}
	registerAppWebhookRoute(router, dependencies)
	registerAppRuntimeRoutes(router, dependencies)
	registerAppNotificationInternalRoutes(router, dependencies)
}

func registerWorkspaceAppRoutes(router gin.IRouter, dependencies Dependencies) {
	apiGroup := router.Group("/api")

	workspaceScoped := apiGroup.Group(
		"/v1/workspaces/:workspaceId",
		api.RequireAuthenticatedUser(dependencies.SessionAuth),
		api.RequireVerifiedIdentity(dependencies.EmailVerificationRequired),
		api.RequireRouteWorkspace(dependencies.WorkspaceStore),
	)

	searchEngineConnections := dependencies.SearchEngineConnections
	if searchEngineConnections == nil {
		searchEngineConnections, _ = dependencies.AppStore.(SearchEngineConnectionRuntimeStore)
	}
	apps := &WorkspaceAppHandler{
		installations:                   dependencies.AppInstallationService,
		store:                           dependencies.AppStore,
		searchEngineConnections:         searchEngineConnections,
		searchEngineTester:              dependencies.SearchEngineTester,
		extensionProviders:              dependencies.ExtensionProviders,
		serviceEventSubscriptionBlocker: dependencies.ServiceEventSubscriptionBlocker,
		installationRequestProjector:    dependencies.InstallationRequestProjector,
		realtime:                        dependencies.Realtime,
		logger:                          dependencies.Logger,
	}

	workspaceScoped.GET("/apps", handleAppWorkspace(apps.List))
	workspaceScoped.GET("/app-installations", handleAppWorkspace(apps.InstallationsList))
	workspaceScoped.GET("/app-installations/:installationId/channels", handleAppWorkspace(apps.InstallationChannelsList))
	workspaceScoped.GET("/app-installations/:installationId/extensions", handleAppWorkspace(apps.InstallationExtensionsList))
	workspaceScoped.POST("/app-installations/:installationId/extensions", handleAppWorkspaceJSON[dto.WorkspaceInstallationExtensionCreateRequest](apps.InstallationExtensionCreate))
	workspaceScoped.PATCH("/app-installations/:installationId/extensions/:selectionId", handleAppWorkspaceJSON[dto.WorkspaceInstallationExtensionUpdateRequest](apps.InstallationExtensionUpdate))
	workspaceScoped.DELETE("/app-installations/:installationId/extensions/:selectionId", handleAppWorkspace(apps.InstallationExtensionDelete))
	workspaceScoped.GET("/app-installations/:installationId/search-engine-connection", handleAppWorkspace(apps.SearchEngineConnectionGet))
	workspaceScoped.PUT(
		"/app-installations/:installationId/search-engine-connection",
		limitSearchEngineConnectionBody(),
		handleAppWorkspaceJSON[dto.SearchEngineConnectionPutRequest](
			apps.SearchEngineConnectionPut,
		),
	)
	workspaceScoped.DELETE("/app-installations/:installationId/search-engine-connection", handleAppWorkspace(apps.SearchEngineConnectionDelete))
	workspaceScoped.POST("/app-installations/:installationId/search-engine-connection:test", handleAppWorkspace(apps.SearchEngineConnectionTest))
	workspaceScoped.GET("/app-installations/:installationId/extension-connection", handleAppWorkspace(apps.ExtensionProviderConnectionGet))
	workspaceScoped.PUT(
		"/app-installations/:installationId/extension-connection",
		limitExtensionProviderConnectionBody(),
		handleAppWorkspaceJSON[dto.ExtensionProviderConnectionPutRequest](
			apps.ExtensionProviderConnectionPut,
			api.WithStrictJSON(),
		),
	)
	workspaceScoped.DELETE("/app-installations/:installationId/extension-connection", handleAppWorkspace(apps.ExtensionProviderConnectionDelete))
	workspaceScoped.POST("/app-installations/:installationId/extension-connection:test", handleAppWorkspace(apps.ExtensionProviderConnectionTest))
	workspaceScoped.POST("/app-installations", handleAppWorkspaceJSON[dto.WorkspaceAppInstallationCreateRequest](apps.InstallationCreate))
	workspaceScoped.GET("/app-installations/:installationId/access-grants", handleAppWorkspace(apps.InstallationAccessGrantsList))
	workspaceScoped.POST("/app-installations/:installationId/access-grants", handleAppWorkspaceJSON[dto.InstallationAccessGrantCreateRequest](apps.InstallationAccessGrantCreate))
	workspaceScoped.DELETE("/app-installations/:installationId/access-grants/:authorizationId", handleAppWorkspace(apps.InstallationAccessGrantDelete))
	workspaceScoped.PATCH("/app-installations/:installationId", handleAppWorkspaceJSON[dto.WorkspaceAppInstallationConfigureRequest](apps.InstallationConfigure))
	workspaceScoped.DELETE("/app-installations/:installationId", handleAppWorkspace(apps.InstallationUninstall))
	workspaceScoped.PATCH("/app-installations/:installationId/display-name", handleAppWorkspaceJSON[dto.WorkspaceAppDisplayNameUpdateRequest](apps.AppDisplayNameUpdate))
	workspaceScoped.PATCH("/app-installations/:installationId/catalog-visibility", handleAppWorkspaceJSON[dto.WorkspaceAppVisibilityUpdateRequest](apps.AppVisibilityUpdate))
	workspaceScoped.GET("/app-installations/:installationId/permissions", handleAppWorkspace(apps.AppPermissionsList))
	workspaceScoped.PATCH("/app-installations/:installationId/permissions", handleAppWorkspaceJSON[dto.WorkspaceAppPermissionsUpdateRequest](apps.AppPermissionsUpdate))
	workspaceScoped.GET("/app-installations/:installationId/runner-credentials", handleAppWorkspace(apps.AppRunnerCredentialsList))
	workspaceScoped.POST("/app-installations/:installationId/runner-credentials", handleAppWorkspaceJSON[dto.AppRunnerCredentialCreateRequest](apps.AppRunnerCredentialCreate))
	workspaceScoped.POST("/app-installations/:installationId/callback-secret:rotate", handleAppWorkspace(apps.AppCallbackSecretRotate))
	workspaceScoped.GET("/app-installations/:installationId/initial-credentials", handleAppWorkspace(apps.AppInitialCredentialsStatus))
	workspaceScoped.POST("/app-installations/:installationId/initial-credentials/claim", handleAppWorkspace(apps.AppInitialCredentialsClaim))
	workspaceScoped.PATCH("/app-installations/:installationId/avatar", handleAppWorkspaceJSON[dto.WorkspaceAppAvatarUpdateRequest](apps.AppAvatarUpdate))
	workspaceScoped.GET("/app-installations/:installationId/managers", handleAppWorkspace(apps.AppManagersList))
	workspaceScoped.POST("/app-installations/:installationId/managers", handleAppWorkspaceJSON[dto.WorkspaceAppManagerAddRequest](apps.AppManagerAdd))
	workspaceScoped.DELETE("/app-installations/:installationId/managers/:managerId", handleAppWorkspace(apps.AppManagerRemove))
	workspaceScoped.POST("/custom-app-requests", handleAppWorkspaceJSON[dto.WorkspaceCustomAppRequestCreateRequest](apps.AppInstallationRequestCreate))
	workspaceScoped.POST("/custom-apps", handleAppWorkspaceJSON[dto.WorkspaceCustomAppCreateRequest](apps.AppInstallationCreate))
	workspaceScoped.GET("/app-installation-requests", handleAppWorkspace(apps.InstallationRequestsList))
	workspaceScoped.POST("/app-installation-requests", handleAppWorkspaceJSON[dto.WorkspaceAppInstallationRequestCreateRequest](apps.InstallationRequestCreate))
	workspaceScoped.POST("/app-installation-requests/:requestAction", handleAppWorkspaceJSON[dto.WorkspaceAppInstallationRequestDecisionRequest](apps.InstallationRequestDecide))
	workspaceScoped.GET("/messages/:messageId/app-notification-card", handleAppWorkspace(apps.AppNotificationCardGet))
	workspaceScoped.POST(
		"/messages/:messageId/app-notification-actions/:actionKey",
		limitAppNotificationActionBody(),
		handleAppWorkspaceJSON[dto.AppNotificationActionRequest](apps.AppNotificationActionCreate),
	)
	workspaceScoped.GET("/app-installations/:installationId/service-event-subscriptions", handleAppWorkspace(apps.ServiceEventSubscriptionsList))
	workspaceScoped.POST("/app-installations/:installationId/service-event-subscriptions", handleAppWorkspaceJSON[dto.ServiceEventSubscriptionCreateRequest](apps.ServiceEventSubscriptionCreate))
	workspaceScoped.GET("/app-installations/:installationId/provider-watches", handleAppWorkspace(apps.ProviderWatchesList))
	workspaceScoped.POST("/app-installations/:installationId/provider-watches", handleAppWorkspaceJSON[dto.ProviderWatchCreateRequest](apps.ProviderWatchCreate))
	workspaceScoped.GET("/app-webhook-deliveries", handleAppWorkspace(apps.WebhookDeliveriesList))
	workspaceScoped.POST("/service-event-subscriptions/:subscriptionAction", handleAppWorkspace(apps.ServiceEventSubscriptionAction))
	workspaceScoped.DELETE("/service-event-subscriptions/:subscriptionId", handleAppWorkspace(apps.ServiceEventSubscriptionDelete))
	workspaceScoped.POST("/provider-watches/:watchAction", handleAppWorkspace(apps.ProviderWatchAction))
	workspaceScoped.DELETE("/provider-watches/:watchId", handleAppWorkspace(apps.ProviderWatchDelete))
	workspaceScoped.DELETE("/runner-credentials/:credentialId", handleAppWorkspace(apps.AppRunnerCredentialDelete))
	workspaceScoped.GET("/agents/:agentId/app-authorizations", handleAppWorkspace(apps.AgentAppAuthorizationsList))
	workspaceScoped.POST("/agents/:agentId/app-authorizations", handleAppWorkspaceJSON[dto.AgentAppAuthorizationRequest](apps.AgentAppAuthorizationEnable))
	workspaceScoped.DELETE("/agents/:agentId/app-authorizations/:appInstallationId", handleAppWorkspace(apps.AgentAppAuthorizationDisable))
}

func limitAppNotificationActionBody() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, appNotificationActionBodyLimit)
		ctx.Next()
	}
}

func limitExtensionProviderConnectionBody() gin.HandlerFunc {
	const maxExtensionProviderConnectionBodyBytes int64 = 64 * 1024
	return func(ctx *gin.Context) {
		ctx.Request.Body = http.MaxBytesReader(
			ctx.Writer,
			ctx.Request.Body,
			maxExtensionProviderConnectionBodyBytes,
		)
		ctx.Next()
	}
}

func limitSearchEngineConnectionBody() gin.HandlerFunc {
	const maxSearchEngineConnectionBodyBytes int64 = 64 * 1024
	return func(ctx *gin.Context) {
		ctx.Request.Body = http.MaxBytesReader(
			ctx.Writer,
			ctx.Request.Body,
			maxSearchEngineConnectionBodyBytes,
		)
		ctx.Next()
	}
}

func registerAppWebhookRoute(router gin.IRouter, dependencies Dependencies) {
	apiGroup := router.Group("/api")
	apiGroup.POST("/apps/github/webhook", handlerForWebhook(dependencies, "github"))
	apiGroup.POST("/apps/gitea/webhook", handlerForWebhook(dependencies, "gitea"))
}

func registerAppRuntimeRoutes(router gin.IRouter, dependencies Dependencies) {
	runtimeStore := dependencies.AppRuntimeStore
	if runtimeStore == nil {
		runtimeStore, _ = dependencies.AppStore.(AppRuntimeStore)
	}
	handler := &AppRuntimeHandler{store: runtimeStore, runner: dependencies.AppRunnerService, managedWorkspaces: dependencies.ManagedWorkspaces}
	register := func(runtime *gin.RouterGroup) {
		runtime.Use(limitAppRuntimeBody())
		runtime.POST("/notifications", handler.NotificationCreate)
		runtime.GET("/notifications/:notificationId", handler.NotificationGet)
		runtime.PATCH("/notifications/:notificationId", handler.NotificationUpdate)
		runtime.POST("/notifications/:notificationAction", handler.NotificationAction)
		runtime.GET("/stream", handler.Stream)
		runtime.POST("/heartbeat", handler.Heartbeat)
		runtime.POST("/events:claim", handler.Claim)
		runtime.GET("/deliveries/:deliveryId/context", handler.Context)
		runtime.POST("/deliveries/:deliveryId/reply", handler.Reply)
		runtime.POST("/deliveries/:deliveryId/ack", handler.Ack)
		runtime.POST("/deliveries/:deliveryId/fail", handler.Fail)
		runtime.POST("/sessions/:sessionId/offline", handler.SessionOffline)
	}

	register(router.Group("/api/v1/workspaces/:workspaceId/app-runtime"))
}

func limitAppRuntimeBody() gin.HandlerFunc {
	const appRuntimeBodyLimit = 512 * 1024
	return func(ctx *gin.Context) {
		ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, appRuntimeBodyLimit)
		ctx.Next()
	}
}

func registerAppNotificationInternalRoutes(router gin.IRouter, dependencies Dependencies) {
	if router == nil {
		return
	}
	handler := &AppNotificationInternalHandler{store: dependencies.AppStore, managedWorkspaces: dependencies.ManagedWorkspaces}
	internal := router.Group("/internal/workspaces/:workspaceId/app-notifications")
	internal.POST("", handler.Create)
	internal.GET("/:notificationId", handler.Get)
	internal.PATCH("/:notificationId", handler.Update)
	internal.POST("/:notificationAction", handler.Action)
}

func handlerForWebhook(dependencies Dependencies, providerSlug string) gin.HandlerFunc {
	w := &WebhookHandler{
		store:             dependencies.AppStore,
		providerSlug:      providerSlug,
		followupDeliverer: dependencies.FollowupDeliverer,
		managedWorkspaces: dependencies.ManagedWorkspaces,
	}
	return w.Handle
}
