package main

import (
	"context"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"
	"time"

	"clawreef/internal/aigateway"
	"clawreef/internal/config"
	"clawreef/internal/db"
	"clawreef/internal/handlers"
	"clawreef/internal/middleware"
	"clawreef/internal/models"
	"clawreef/internal/repository"
	"clawreef/internal/services"
	"clawreef/internal/services/k8s"
	"clawreef/internal/services/leader"

	"github.com/gin-gonic/gin"
)

func main() {
	// Load configuration
	cfg, err := config.Load()
	if err != nil {
		log.Fatalf("Failed to load config: %v", err)
	}

	// Initialize database
	database, err := db.Initialize(cfg.Database)
	if err != nil {
		log.Fatalf("Failed to initialize database: %v", err)
	}
	defer db.Close()

	// Initialize Kubernetes client
	log.Printf("K8s StorageClass config: %s", cfg.GetStorageClass())
	if err := k8s.Initialize(cfg); err != nil {
		log.Printf("Warning: Failed to initialize Kubernetes client: %v", err)
		log.Println("Instance management features will not work without K8s connectivity")
	} else {
		client := k8s.GetClient()
		log.Printf("Kubernetes client initialized successfully (mode: %s, storageClass: %s)",
			client.GetConnectionMode(), client.StorageClass)
	}

	// Initialize repositories
	userRepo := repository.NewUserRepository(database)
	quotaRepo := repository.NewQuotaRepository(database)
	instanceRepo := repository.NewInstanceRepository(database)
	systemImageSettingRepo := repository.NewSystemImageSettingRepository(database)
	llmModelRepo := repository.NewLLMModelRepository(database)
	modelInvocationRepo := repository.NewModelInvocationRepository(database)
	auditEventRepo := repository.NewAuditEventRepository(database)
	costRecordRepo := repository.NewCostRecordRepository(database)
	chatSessionRepo := repository.NewChatSessionRepository(database)
	chatMessageRepo := repository.NewChatMessageRepository(database)
	riskRuleRepo := repository.NewRiskRuleRepository(database)
	riskHitRepo := repository.NewRiskHitRepository(database)
	openClawConfigRepo := repository.NewOpenClawConfigRepository(database)
	instanceAgentRepo := repository.NewInstanceAgentRepository(database)
	instanceRuntimeStatusRepo := repository.NewInstanceRuntimeStatusRepository(database)
	instanceDesiredStateRepo := repository.NewInstanceDesiredStateRepository(database)
	instanceCommandRepo := repository.NewInstanceCommandRepository(database)
	instanceConfigRevisionRepo := repository.NewInstanceConfigRevisionRepository(database)
	runtimePodRepo := repository.NewRuntimePodRepository(database)
	bindingRepo := repository.NewInstanceRuntimeBindingRepository(database)
	rolloutRepo := repository.NewRuntimeRolloutRepository(database)
	workspaceFileAuditRepo := repository.NewWorkspaceFileAuditRepository(database)
	teamRepo := repository.NewTeamRepository(database)
	skillRepo := repository.NewSkillRepository(database)
	securityScanRepo := repository.NewSecurityScanRepository(database)
	instanceExternalAccessRepo := repository.NewInstanceExternalAccessRepository(database)

	if repaired, repairErr := services.RepairSeededAdminPassword(userRepo); repairErr != nil {
		log.Printf("Warning: failed to repair seeded admin password: %v", repairErr)
	} else if repaired {
		log.Printf("Repaired seeded admin password hash for default admin account")
	}

	// Initialize services
	authService := services.NewAuthService(userRepo, cfg.JWT)
	quotaService := services.NewQuotaService(quotaRepo)
	userService := services.NewUserService(userRepo, quotaRepo)
	systemImageSettingService := services.NewSystemImageSettingService(systemImageSettingRepo)
	llmModelService := services.NewLLMModelService(llmModelRepo)
	modelInvocationService := services.NewModelInvocationService(modelInvocationRepo)
	auditEventService := services.NewAuditEventService(auditEventRepo)
	costRecordService := services.NewCostRecordService(costRecordRepo)
	chatSessionService := services.NewChatSessionService(chatSessionRepo)
	chatMessageService := services.NewChatMessageService(chatMessageRepo)
	riskDetectionService := services.NewRiskDetectionService(riskRuleRepo)
	riskHitService := services.NewRiskHitService(riskHitRepo)
	riskRuleService := services.NewRiskRuleService(riskRuleRepo)
	openClawConfigService := services.NewOpenClawConfigService(openClawConfigRepo, skillRepo)
	objectStorageService, err := services.NewObjectStorageService(cfg.ObjectStorage)
	if err != nil {
		log.Fatalf("Failed to initialize object storage: %v", err)
	}
	skillScannerClient := services.NewSkillScannerClient(cfg.SkillScanner)
	aiObservabilityService := services.NewAIObservabilityService(modelInvocationRepo, auditEventRepo, costRecordRepo, riskHitRepo, chatMessageRepo, llmModelRepo, instanceRepo, userRepo)
	clusterResourceService := services.NewClusterResourceService(instanceRepo)
	services.SetRuntimeImageSettingsProvider(systemImageSettingService)
	services.SetOpenClawTransferRuntimeRepositories(instanceRepo, bindingRepo, runtimePodRepo)
	runtimeAgentClient := services.NewRuntimeAgentClient(cfg.Runtime.AgentControlToken)
	instanceService := services.NewInstanceService(
		instanceRepo,
		quotaRepo,
		llmModelRepo,
		openClawConfigService,
		services.WithPrivilegedInstancePods(cfg.Kubernetes.Runtime.Pod.Privileged),
		services.WithV2RuntimeLifecycle(runtimePodRepo, bindingRepo, runtimeAgentClient, cfg.Runtime.WorkspaceRoot),
	)
	instanceAgentService := services.NewInstanceAgentService(instanceRepo, instanceAgentRepo, instanceDesiredStateRepo, instanceRuntimeStatusRepo, instanceCommandRepo)
	instanceRuntimeStatusService := services.NewInstanceRuntimeStatusService(instanceRuntimeStatusRepo, instanceAgentRepo, instanceDesiredStateRepo)
	instanceCommandService := services.NewInstanceCommandService(instanceCommandRepo, instanceRuntimeStatusRepo, instanceDesiredStateRepo)
	instanceConfigRevisionService := services.NewInstanceConfigRevisionService(instanceConfigRevisionRepo)
	teamService := services.NewTeamService(teamRepo, instanceService, services.WithTeamRuntimeWorkspaceRoot(cfg.Runtime.WorkspaceRoot))
	var platformRedis services.PlatformRedisClient
	if redisURL := strings.TrimSpace(cfg.Runtime.RedisURL); redisURL != "" {
		var redisErr error
		platformRedis, redisErr = services.NewPlatformRedisClient(redisURL)
		if redisErr != nil {
			log.Printf("platform redis disabled: %v", redisErr)
		}
	} else {
		log.Printf("platform redis disabled: redis url is empty")
	}
	runtimeEvents := services.NewRuntimeEventService(platformRedis)
	workspaceFileService := services.NewWorkspaceFileService(workspaceFileAuditRepo)
	runtimeWorkspaceFileService := services.NewRuntimeWorkspaceFileService(workspaceFileAuditRepo)
	skillService := services.NewSkillService(skillRepo, instanceRepo, instanceCommandService, objectStorageService, skillScannerClient)
	securityScanService := services.NewSecurityScanService(securityScanRepo, skillRepo, objectStorageService, skillScannerClient)
	externalAccessService := services.NewInstanceExternalAccessService(instanceExternalAccessRepo)
	aiGatewayService := aigateway.NewService(llmModelRepo, modelInvocationService, auditEventService, costRecordService, riskDetectionService, riskHitService, chatSessionService, chatMessageService)

	// Initialize handlers
	authHandler := handlers.NewAuthHandler(authService)
	userHandler := handlers.NewUserHandler(userService, quotaService)
	instanceHandler := handlers.NewInstanceHandler(
		instanceService,
		instanceAgentService,
		instanceRuntimeStatusService,
		instanceCommandService,
		instanceConfigRevisionService,
		openClawConfigService,
		skillService,
		externalAccessService,
		services.WithInstanceProxyRuntimeRepositories(instanceRepo, runtimePodRepo, bindingRepo),
	)
	systemSettingsHandler := handlers.NewSystemSettingsHandler(systemImageSettingService)
	llmModelHandler := handlers.NewLLMModelHandler(llmModelService)
	aiGatewayHandler := handlers.NewAIGatewayHandler(aiGatewayService)
	aiObservabilityHandler := handlers.NewAIObservabilityHandler(aiObservabilityService)
	riskRuleHandler := handlers.NewRiskRuleHandler(riskRuleService)
	clusterResourceHandler := handlers.NewClusterResourceHandler(clusterResourceService)
	egressProxyHandler := handlers.NewEgressProxyHandler()
	openClawConfigHandler := handlers.NewOpenClawConfigHandler(openClawConfigService)
	skillHandler := handlers.NewSkillHandler(skillService, instanceService)
	securityHandler := handlers.NewSecurityHandler(securityScanService)
	agentHandler := handlers.NewAgentHandler(instanceAgentService, instanceCommandService, instanceRuntimeStatusService, instanceConfigRevisionService, skillService)
	teamHandler := handlers.NewTeamHandler(teamService)
	workspaceFileHandler := handlers.NewWorkspaceFileHandler(instanceService, workspaceFileService, runtimeWorkspaceFileService)
	runtimeAgentHandler := handlers.NewRuntimeAgentHandler(cfg.Runtime, runtimePodRepo, bindingRepo, runtimeEvents)

	// Initialize WebSocket hub and handler
	wsHub := services.GetHub()
	wsHandler := handlers.NewWebSocketHandler(wsHub)
	var runtimeAdminEventBridgeCancel context.CancelFunc
	if platformRedis != nil {
		var bridgeCtx context.Context
		bridgeCtx, runtimeAdminEventBridgeCancel = context.WithCancel(context.Background())
		services.StartRuntimeAdminEventBridge(bridgeCtx, runtimeEvents, wsHub)
	}

	// Control-plane singleton background loops. The HTTP API and the in-pod
	// nginx desktop data plane run on every replica, but these loops must run
	// on exactly one replica. With leader election enabled they only run on the
	// elected leader and migrate on failover; with it disabled (single-replica
	// deployments) they run directly.
	syncService := services.NewSyncService(instanceRepo, instanceRuntimeStatusService)
	var runtimeSchedulerCancel context.CancelFunc
	var runtimeSchedulerMu sync.Mutex
	var runtimeScheduler *services.RuntimeScheduler
	if cfg.Runtime.SchedulerEnabled {
		k8sClient := k8s.GetClient()
		if k8sClient == nil || k8sClient.Clientset == nil {
			log.Printf("runtime scheduler disabled: k8s client is unavailable")
		} else {
			runtimeLeader := services.NewRuntimeLeaderService(k8sClient.Clientset, cfg.Runtime.Namespace, cfg.Runtime.BackendReplicaID)
			runtimeDeployments := k8s.NewRuntimeDeploymentService(k8sClient.Clientset)
			runtimeSchedulerOptions := []services.RuntimeSchedulerOption{
				services.WithRuntimeSchedulerWorkspaceRoot(cfg.Runtime.WorkspaceRoot),
				services.WithRuntimeSchedulerNamespace(cfg.Runtime.Namespace),
				services.WithRuntimeSchedulerGatewayPortRange(cfg.Runtime.GatewayPortStart, cfg.Runtime.GatewayPortEnd),
				services.WithRuntimeSchedulerHeartbeatTimeout(cfg.Runtime.HeartbeatTimeout),
				services.WithRuntimeSchedulerMaxGatewaysPerPod(cfg.Runtime.MaxGatewaysPerPod),
			}
			if gatewayEnvProvider, ok := instanceService.(interface {
				BuildGatewayEnv(*models.Instance) (map[string]string, error)
			}); ok {
				runtimeSchedulerOptions = append(runtimeSchedulerOptions, services.WithRuntimeSchedulerGatewayEnvBuilder(gatewayEnvProvider.BuildGatewayEnv))
			}
			runtimeScheduler = services.NewRuntimeScheduler(
				instanceRepo,
				runtimePodRepo,
				bindingRepo,
				rolloutRepo,
				runtimeAgentClient,
				runtimeEvents,
				runtimeLeader,
				runtimeDeployments,
				cfg.Runtime.SchedulerTick,
				runtimeSchedulerOptions...,
			)
			log.Printf("runtime scheduler initialized")
		}
	} else {
		log.Printf("runtime scheduler disabled by configuration")
	}
	runtimePoolHandler := handlers.NewRuntimePoolHandler(runtimePodRepo, bindingRepo, rolloutRepo, runtimeScheduler, runtimeEvents)

	leaderCtx, leaderCancel := context.WithCancel(context.Background())
	defer leaderCancel()

	startBackground := func(ctx context.Context) {
		log.Printf("Starting leader-only background loops (identity=%s)", cfg.LeaderElection.Identity)
		syncService.Start()
		teamService.StartBackground(ctx)
		if runtimeScheduler != nil {
			runtimeSchedulerMu.Lock()
			if runtimeSchedulerCancel == nil {
				var schedulerCtx context.Context
				schedulerCtx, runtimeSchedulerCancel = context.WithCancel(ctx)
				runtimeScheduler.Start(schedulerCtx)
				log.Printf("runtime scheduler started")
			}
			runtimeSchedulerMu.Unlock()
		}
	}
	stopBackground := func() {
		log.Printf("Stopping leader-only background loops (identity=%s)", cfg.LeaderElection.Identity)
		runtimeSchedulerMu.Lock()
		if runtimeSchedulerCancel != nil {
			runtimeSchedulerCancel()
			runtimeSchedulerCancel = nil
		}
		runtimeSchedulerMu.Unlock()
		teamService.StopBackground()
		syncService.Stop()
	}

	if cfg.LeaderElection.Enabled && k8s.GetClient() != nil && k8s.GetClient().Clientset != nil {
		go leader.Run(leaderCtx, k8s.GetClient().Clientset, leader.Config{
			Namespace:     cfg.LeaderElection.Namespace,
			LeaseName:     cfg.LeaderElection.LeaseName,
			Identity:      cfg.LeaderElection.Identity,
			LeaseDuration: time.Duration(cfg.LeaderElection.LeaseDuration) * time.Second,
			RenewDeadline: time.Duration(cfg.LeaderElection.RenewDeadline) * time.Second,
			RetryPeriod:   time.Duration(cfg.LeaderElection.RetryPeriod) * time.Second,
		}, leader.Callbacks{
			OnStartedLeading: startBackground,
			OnStoppedLeading: stopBackground,
		})
	} else {
		log.Println("Leader election disabled or K8s unavailable; running control-plane background loops directly")
		startBackground(leaderCtx)
	}

	// Setup router
	r := gin.Default()

	// Middleware
	r.Use(middleware.CORS())
	r.Use(middleware.ErrorHandler())
	r.NoRoute(egressProxyHandler.Handle)
	r.NoMethod(egressProxyHandler.Handle)

	// Routes
	r.Any("/s/:code", instanceHandler.OpenShortExternalAccess)
	r.Any("/s/:code/*path", instanceHandler.OpenShortExternalAccess)

	api := r.Group("/api/v1")
	{
		runtimeAgent := api.Group("/runtime-agent")
		{
			runtimeAgent.POST("/register", runtimeAgentHandler.Register)
			runtimeAgent.POST("/heartbeat", runtimeAgentHandler.Heartbeat)
			runtimeAgent.POST("/metrics/report", runtimeAgentHandler.ReportMetrics)
			runtimeAgent.POST("/gateways/report", runtimeAgentHandler.ReportGateways)
			runtimeAgent.POST("/skills/report", runtimeAgentHandler.ReportSkills)
		}

		// Auth routes
		auth := api.Group("/auth")
		{
			auth.POST("/register", authHandler.Register)
			auth.POST("/login", authHandler.Login)
			auth.POST("/refresh", authHandler.RefreshToken)
			auth.POST("/logout", authHandler.Logout)
			auth.GET("/me", middleware.Auth(), middleware.SetUserInfo(userRepo), authHandler.GetCurrentUser)
			auth.POST("/change-password", middleware.Auth(), authHandler.ChangePassword)
		}

		// User routes (authenticated)
		users := api.Group("/users")
		users.Use(middleware.Auth())
		users.Use(middleware.SetUserInfo(userRepo))
		{
			// Admin only routes
			adminOnly := users.Group("")
			adminOnly.Use(middleware.NewAdminAuth(userRepo))
			{
				adminOnly.GET("", userHandler.ListUsers)
				adminOnly.POST("", userHandler.CreateUser)
				adminOnly.POST("/import", userHandler.ImportUsers)
				adminOnly.DELETE("/:id", userHandler.DeleteUser)
				adminOnly.PUT("/:id/role", userHandler.UpdateRole)
				adminOnly.PUT("/:id/quota", userHandler.UpdateUserQuota)
			}

			// User or admin routes
			users.GET("/:id", userHandler.GetUser)
			users.PUT("/:id", userHandler.UpdateUser)
			users.GET("/:id/quota", userHandler.GetUserQuota)
		}

		// Instance routes (authenticated)
		instances := api.Group("/instances")
		instances.Use(middleware.Auth())
		instances.Use(middleware.SetUserInfo(userRepo))
		{
			instances.GET("", instanceHandler.ListInstances)
			instances.POST("", instanceHandler.CreateInstance)
			instances.GET("/:id", instanceHandler.GetInstance)
			instances.PUT("/:id", instanceHandler.UpdateInstance)
			instances.DELETE("/:id", instanceHandler.DeleteInstance)
			instances.POST("/:id/start", instanceHandler.StartInstance)
			instances.POST("/:id/stop", instanceHandler.StopInstance)
			instances.POST("/:id/restart", instanceHandler.RestartInstance)
			instances.GET("/:id/status", instanceHandler.GetInstanceStatus)
			instances.GET("/:id/runtime", instanceHandler.GetRuntimeDetails)
			instances.POST("/:id/runtime/:command", instanceHandler.CreateRuntimeCommand)
			instances.GET("/:id/config/revisions", instanceHandler.ListConfigRevisions)
			instances.POST("/:id/config/revisions/publish", instanceHandler.PublishConfigRevision)
			instances.POST("/:id/access", instanceHandler.GenerateAccessToken)
			instances.GET("/:id/access", instanceHandler.AccessInstance)
			instances.GET("/:id/shell", instanceHandler.StreamShell)
			instances.POST("/:id/sync", instanceHandler.ForceSync)
			instances.GET("/:id/openclaw/export", instanceHandler.ExportOpenClaw)
			instances.POST("/:id/openclaw/import", instanceHandler.ImportOpenClaw)
			instances.GET("/:id/hermes/export", instanceHandler.ExportHermes)
			instances.POST("/:id/hermes/import", instanceHandler.ImportHermes)
			instances.GET("/:id/external-access", instanceHandler.GetExternalAccess)
			instances.POST("/:id/external-access/share-link", instanceHandler.EnableShareLink)
			instances.POST("/:id/external-access/password", instanceHandler.CreateExternalAccessPassword)
			instances.DELETE("/:id/external-access", instanceHandler.DisableExternalAccess)
			instances.GET("/:id/workspace/files", workspaceFileHandler.List)
			instances.GET("/:id/workspace/preview", workspaceFileHandler.Preview)
			instances.GET("/:id/workspace/download", workspaceFileHandler.Download)
			instances.POST("/:id/workspace/upload", workspaceFileHandler.Upload)
			instances.POST("/:id/workspace/folders", workspaceFileHandler.Mkdir)
			instances.PATCH("/:id/workspace/entries", workspaceFileHandler.Rename)
			instances.DELETE("/:id/workspace/entries", workspaceFileHandler.Delete)
			instances.GET("/:id/skills", skillHandler.ListInstanceSkills)
			instances.POST("/:id/skills", skillHandler.AttachSkillToInstance)
			instances.DELETE("/:id/skills/:skillId", skillHandler.RemoveSkillFromInstance)
		}

		// Admin console: cross-user instance listing. Gated by admin
		// middleware — non-admin callers get 403. The workspace
		// /instances endpoint above stays caller-scoped regardless of
		// role; admin status only unlocks this dedicated surface.
		adminInstances := api.Group("/admin/instances")
		adminInstances.Use(middleware.Auth())
		adminInstances.Use(middleware.SetUserInfo(userRepo))
		adminInstances.Use(middleware.NewAdminAuth(userRepo))
		{
			adminInstances.GET("", instanceHandler.ListAllInstances)
		}

		adminRuntime := api.Group("/admin")
		adminRuntime.Use(middleware.Auth())
		adminRuntime.Use(middleware.SetUserInfo(userRepo))
		adminRuntime.Use(middleware.NewAdminAuth(userRepo))
		{
			adminRuntime.GET("/runtime-pods", runtimePoolHandler.ListPods)
			adminRuntime.GET("/runtime-pods/:id/gateways", runtimePoolHandler.GetPodGateways)
			adminRuntime.POST("/runtime-pods/:id/drain", runtimePoolHandler.DrainPod)
			adminRuntime.POST("/runtime-rollouts", runtimePoolHandler.StartRollout)
		}

		teams := api.Group("/teams")
		teams.Use(middleware.Auth())
		teams.Use(middleware.SetUserInfo(userRepo))
		{
			teams.GET("", teamHandler.ListTeams)
			teams.POST("", teamHandler.CreateTeam)
			teams.GET("/:id", teamHandler.GetTeam)
			teams.DELETE("/:id", teamHandler.DeleteTeam)
			teams.GET("/:id/tasks", teamHandler.ListTasks)
			teams.POST("/:id/tasks", teamHandler.DispatchTask)
			teams.GET("/:id/events", teamHandler.ListEvents)
			teams.DELETE("/:id/members/:memberID", teamHandler.DeleteMember)
		}

		openClawConfigs := api.Group("/openclaw-configs")
		openClawConfigs.Use(middleware.Auth())
		openClawConfigs.Use(middleware.SetUserInfo(userRepo))
		{
			openClawConfigs.GET("/resources", openClawConfigHandler.ListResources)
			openClawConfigs.POST("/resources", openClawConfigHandler.CreateResource)
			openClawConfigs.POST("/resources/validate", openClawConfigHandler.ValidateResource)
			openClawConfigs.GET("/resources/:id", openClawConfigHandler.GetResource)
			openClawConfigs.PUT("/resources/:id", openClawConfigHandler.UpdateResource)
			openClawConfigs.DELETE("/resources/:id", openClawConfigHandler.DeleteResource)
			openClawConfigs.POST("/resources/:id/clone", openClawConfigHandler.CloneResource)

			openClawConfigs.GET("/bundles", openClawConfigHandler.ListBundles)
			openClawConfigs.POST("/bundles", openClawConfigHandler.CreateBundle)
			openClawConfigs.GET("/bundles/:id", openClawConfigHandler.GetBundle)
			openClawConfigs.PUT("/bundles/:id", openClawConfigHandler.UpdateBundle)
			openClawConfigs.DELETE("/bundles/:id", openClawConfigHandler.DeleteBundle)
			openClawConfigs.POST("/bundles/:id/clone", openClawConfigHandler.CloneBundle)

			openClawConfigs.POST("/compile-preview", openClawConfigHandler.CompilePreview)
			openClawConfigs.GET("/injections", openClawConfigHandler.ListSnapshots)
			openClawConfigs.GET("/injections/:id", openClawConfigHandler.GetSnapshot)
		}

		skills := api.Group("/skills")
		skills.Use(middleware.Auth())
		skills.Use(middleware.SetUserInfo(userRepo))
		{
			skills.GET("", skillHandler.ListSkills)
			skills.POST("/import", skillHandler.ImportSkills)
			skills.GET("/:id", skillHandler.GetSkill)
			skills.PUT("/:id", skillHandler.UpdateSkill)
			skills.DELETE("/:id", skillHandler.DeleteSkill)
			skills.GET("/:id/download", skillHandler.DownloadSkill)
			skills.GET("/:id/versions", skillHandler.ListVersions)
			skills.GET("/:id/scan-results", skillHandler.ListScanResults)
		}

		systemSettings := api.Group("/system-settings")
		systemSettings.Use(middleware.Auth())
		systemSettings.Use(middleware.SetUserInfo(userRepo))
		{
			systemSettings.GET("/images", systemSettingsHandler.ListSystemImageSettings)
		}

		adminSystemSettings := api.Group("/system-settings")
		adminSystemSettings.Use(middleware.Auth())
		adminSystemSettings.Use(middleware.SetUserInfo(userRepo))
		adminSystemSettings.Use(middleware.NewAdminAuth(userRepo))
		{
			adminSystemSettings.PUT("/images", systemSettingsHandler.UpsertSystemImageSetting)
			adminSystemSettings.DELETE("/images/:instanceType", systemSettingsHandler.DeleteSystemImageSetting)
			adminSystemSettings.GET("/cluster-resources", clusterResourceHandler.GetOverview)
		}

		adminModels := api.Group("/admin/models")
		adminModels.Use(middleware.Auth())
		adminModels.Use(middleware.SetUserInfo(userRepo))
		adminModels.Use(middleware.NewAdminAuth(userRepo))
		{
			adminModels.GET("", llmModelHandler.ListModels)
			adminModels.POST("/discover", llmModelHandler.DiscoverModels)
			adminModels.PUT("", llmModelHandler.UpsertModel)
			adminModels.DELETE("/:id", llmModelHandler.DeleteModel)
		}

		adminAIAudit := api.Group("/admin/ai-audit")
		adminAIAudit.Use(middleware.Auth())
		adminAIAudit.Use(middleware.SetUserInfo(userRepo))
		adminAIAudit.Use(middleware.NewAdminAuth(userRepo))
		{
			adminAIAudit.GET("", aiObservabilityHandler.ListAuditItems)
			adminAIAudit.GET("/:traceId", aiObservabilityHandler.GetTraceDetail)
		}

		adminCosts := api.Group("/admin/costs")
		adminCosts.Use(middleware.Auth())
		adminCosts.Use(middleware.SetUserInfo(userRepo))
		adminCosts.Use(middleware.NewAdminAuth(userRepo))
		{
			adminCosts.GET("", aiObservabilityHandler.GetCostOverview)
		}

		adminRiskRules := api.Group("/admin/risk-rules")
		adminRiskRules.Use(middleware.Auth())
		adminRiskRules.Use(middleware.SetUserInfo(userRepo))
		adminRiskRules.Use(middleware.NewAdminAuth(userRepo))
		{
			adminRiskRules.GET("", riskRuleHandler.ListRules)
			adminRiskRules.POST("/test", riskRuleHandler.TestRules)
			adminRiskRules.POST("/bulk-status", riskRuleHandler.BulkUpdateStatus)
			adminRiskRules.PUT("", riskRuleHandler.UpsertRule)
			adminRiskRules.DELETE("/:ruleId", riskRuleHandler.DeleteRule)
		}

		adminSkills := api.Group("/admin/skills")
		adminSkills.Use(middleware.Auth())
		adminSkills.Use(middleware.SetUserInfo(userRepo))
		adminSkills.Use(middleware.NewAdminAuth(userRepo))
		{
			adminSkills.GET("", skillHandler.ListAllSkills)
		}

		adminSecurity := api.Group("/admin/security")
		adminSecurity.Use(middleware.Auth())
		adminSecurity.Use(middleware.SetUserInfo(userRepo))
		adminSecurity.Use(middleware.NewAdminAuth(userRepo))
		{
			adminSecurity.GET("/config", securityHandler.GetConfig)
			adminSecurity.PUT("/config", securityHandler.SaveConfig)
			adminSecurity.POST("/scan-jobs", securityHandler.StartScan)
			adminSecurity.POST("/skills/:id/rescan", securityHandler.RescanSkill)
			adminSecurity.GET("/scan-jobs", securityHandler.ListJobs)
			adminSecurity.GET("/scan-jobs/:id", securityHandler.GetJob)
		}

		gatewayLLM := api.Group("/gateway/llm")
		gatewayLLM.Use(middleware.GatewayAuth(instanceRepo, bindingRepo))
		{
			gatewayLLM.GET("/models", aiGatewayHandler.ListModels)
			gatewayLLM.POST("/chat/completions", aiGatewayHandler.ChatCompletions)
		}

		agent := api.Group("/agent")
		{
			agent.POST("/register", agentHandler.Register)
			agent.POST("/heartbeat", agentHandler.Heartbeat)
			agent.GET("/commands/next", agentHandler.NextCommand)
			agent.POST("/commands/:id/start", agentHandler.StartCommand)
			agent.POST("/commands/:id/finish", agentHandler.FinishCommand)
			agent.POST("/state/report", agentHandler.ReportState)
			agent.POST("/skills/inventory", agentHandler.ReportSkillInventory)
			agent.POST("/skills/upload", agentHandler.UploadSkillPackage)
			agent.GET("/skills/versions/:skillVersion/download", skillHandler.DownloadSkillVersionForAgent)
			agent.GET("/config/revisions/:id", agentHandler.GetConfigRevision)
		}

		// Instance proxy routes (token-based auth, no session required)
		// These routes proxy requests to the actual instance pods
		api.Any("/instances/:id/proxy", instanceHandler.ProxyInstance)
		api.Any("/instances/:id/proxy/*path", instanceHandler.ProxyInstance)

		// WebSocket routes
		ws := api.Group("/ws")
		ws.Use(middleware.Auth())
		ws.Use(middleware.SetUserInfo(userRepo))
		{
			ws.GET("", wsHandler.HandleWebSocket)
			ws.GET("/stats", wsHandler.GetConnectionCount)
		}
	}

	// Start server with graceful shutdown
	srv := &http.Server{
		Addr:    cfg.Server.Address,
		Handler: r,
	}

	go func() {
		log.Printf("Server starting on %s", cfg.Server.Address)
		if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("Failed to start server: %v", err)
		}
	}()

	// Wait for interrupt signal
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	sig := <-quit
	log.Printf("Received signal %v, shutting down gracefully...", sig)

	// Give active requests up to 10 seconds to finish
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	if err := srv.Shutdown(ctx); err != nil {
		log.Printf("HTTP server forced to shutdown: %v", err)
	}

	// Stop background services. Cancelling leaderCtx releases the lease (and,
	// if we were leader, triggers stopBackground); the explicit stopBackground
	// call is idempotent and covers the leader-election-disabled path.
	leaderCancel()
	stopBackground()
	if runtimeAdminEventBridgeCancel != nil {
		runtimeAdminEventBridgeCancel()
	}
	wsHub.Stop()
	instanceHandler.Shutdown()

	log.Println("Server exited cleanly")
}
