package app

import (
	"context"
	"fmt"
	"log/slog"
	"net"
	"net/http"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"google.golang.org/grpc"

	"github.com/codex-k8s/kodex/libs/go/crypto/tokencrypt"
	"github.com/codex-k8s/kodex/libs/go/postgres"
	"github.com/codex-k8s/kodex/libs/go/registry"
	repoprovider "github.com/codex-k8s/kodex/libs/go/repo/provider"
	githubprovider "github.com/codex-k8s/kodex/libs/go/repo/provider/github"
	sharedsystemsettings "github.com/codex-k8s/kodex/libs/go/systemsettings"
	controlplanev1 "github.com/codex-k8s/kodex/proto/gen/go/kodex/controlplane/v1"
	githubclient "github.com/codex-k8s/kodex/services/internal/control-plane/internal/clients/github"
	githubmgmtclient "github.com/codex-k8s/kodex/services/internal/control-plane/internal/clients/githubmgmt"
	kubernetesclient "github.com/codex-k8s/kodex/services/internal/control-plane/internal/clients/kubernetes"
	postgresadminclient "github.com/codex-k8s/kodex/services/internal/control-plane/internal/clients/postgresadmin"
	agentcallbackdomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/agentcallback"
	changegovernancedomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/changegovernance"
	codexauthdomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/codexauth"
	githubratelimitdomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/githubratelimit"
	mcpdomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/mcp"
	missioncontroldomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/missioncontrol"
	missioncontrolworkerdomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/missioncontrolworker"
	runstatusdomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/runstatus"
	runtimedeploydomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/runtimedeploy"
	runtimeerrordomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/runtimeerror"
	"github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/staff"
	systemsettingsdomain "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/systemsettings"
	valuetypes "github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/types/value"
	"github.com/codex-k8s/kodex/services/internal/control-plane/internal/domain/webhook"
	"github.com/codex-k8s/kodex/services/internal/control-plane/internal/observability"
	agentrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/agent"
	agentrunrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/agentrun"
	agentsessionrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/agentsession"
	changegovernancerepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/changegovernance"
	floweventrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/flowevent"
	githubratelimitwaitrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/githubratelimitwait"
	interactionrequestrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/interactionrequest"
	learningfeedbackrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/learningfeedback"
	mcpactionrequestrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/mcpactionrequest"
	missioncontrolrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/missioncontrol"
	platformtokenrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/platformtoken"
	projectrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/project"
	projectdatabaserepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/projectdatabase"
	projectmemberrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/projectmember"
	projecttokenrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/projecttoken"
	repocfgrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/repocfg"
	runtimedeploytaskrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/runtimedeploytask"
	runtimeerrorrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/runtimeerror"
	staffrunrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/staffrun"
	systemsettingrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/systemsetting"
	userrepo "github.com/codex-k8s/kodex/services/internal/control-plane/internal/repository/postgres/user"
	grpctransport "github.com/codex-k8s/kodex/services/internal/control-plane/internal/transport/grpc"
	mcptransport "github.com/codex-k8s/kodex/services/internal/control-plane/internal/transport/mcp"
)

// Run starts control-plane servers and blocks until shutdown or fatal error.
func Run() error {
	cfg, err := LoadConfig()
	if err != nil {
		return err
	}

	appCtx := context.Background()
	logger := slog.New(slog.NewJSONHandler(os.Stdout, nil))
	runCtx, stop := signal.NotifyContext(appCtx, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGHUP)
	defer stop()

	// DB readiness is handled by initContainer in deployment; control-plane starts fail-fast.
	dbOpenParams := postgres.OpenParams{
		Host:     cfg.DBHost,
		Port:     cfg.DBPort,
		DBName:   cfg.DBName,
		User:     cfg.DBUser,
		Password: cfg.DBPassword,
		SSLMode:  cfg.DBSSLMode,
	}
	pgxPool, err := postgres.OpenPGXPool(runCtx, dbOpenParams)
	if err != nil {
		return fmt.Errorf("open postgres pgx pool: %w", err)
	}
	defer pgxPool.Close()

	agentRuns := agentrunrepo.NewRepository(pgxPool)
	agents := agentrepo.NewRepository(pgxPool)
	flowEvents := floweventrepo.NewRepository(pgxPool)
	githubRateLimitWaits := githubratelimitwaitrepo.NewRepository(pgxPool)

	users := userrepo.NewRepository(pgxPool)
	projects := projectrepo.NewRepository(pgxPool)
	members := projectmemberrepo.NewRepository(pgxPool)
	runs := staffrunrepo.NewRepository(pgxPool)
	repos := repocfgrepo.NewRepository(pgxPool)
	feedback := learningfeedbackrepo.NewRepository(pgxPool)
	agentSessions := agentsessionrepo.NewRepository(pgxPool)
	platformTokens := platformtokenrepo.NewRepository(pgxPool)
	projectTokens := projecttokenrepo.NewRepository(pgxPool)
	mcpActionRequests := mcpactionrequestrepo.NewRepository(pgxPool)
	missionControlProjection := missioncontrolrepo.NewRepository(pgxPool)
	interactionRequests := interactionrequestrepo.NewRepository(pgxPool)
	projectDatabases := projectdatabaserepo.NewRepository(pgxPool)
	runtimeDeployTasks := runtimedeploytaskrepo.NewRepository(pgxPool)
	runtimeErrors := runtimeerrorrepo.NewRepository(pgxPool)
	systemSettingsRepo := systemsettingrepo.NewRepository(pgxPool)
	changeGovernanceProjection := changegovernancerepo.NewRepository(pgxPool)

	tokenCrypto, err := tokencrypt.NewService(cfg.TokenEncryptionKey)
	if err != nil {
		return fmt.Errorf("init token encryption: %w", err)
	}
	if err := syncGitHubTokens(runCtx, syncGitHubTokensParams{
		PlatformTokenRaw: strings.TrimSpace(cfg.GitHubPAT),
		BotTokenRaw:      strings.TrimSpace(cfg.GitBotToken),
		PlatformTokens:   platformTokens,
		Repos:            repos,
		TokenCrypt:       tokenCrypto,
		Logger:           logger,
	}); err != nil {
		return fmt.Errorf("sync github tokens: %w", err)
	}
	k8sClient, err := kubernetesclient.NewClient(cfg.KubeconfigPath)
	if err != nil {
		return fmt.Errorf("init kubernetes mcp client: %w", err)
	}
	postgresAdminClient, err := postgresadminclient.NewClient(runCtx, postgresadminclient.Config{
		Host:         cfg.ProjectDBAdminHost,
		Port:         cfg.ProjectDBAdminPort,
		User:         cfg.ProjectDBAdminUser,
		Password:     cfg.ProjectDBAdminPassword,
		SSLMode:      cfg.ProjectDBAdminSSLMode,
		AdminDBName:  cfg.ProjectDBAdminDatabase,
		ProtectedDBs: []string{cfg.DBName},
	})
	if err != nil {
		return fmt.Errorf("init postgres admin client: %w", err)
	}
	defer postgresAdminClient.Close()
	githubMCPClient := githubclient.NewClient(nil)
	githubMgmtClient := githubmgmtclient.NewClient(nil)
	githubRepoProvider := githubprovider.NewProvider(nil)

	codexAuthService, err := codexauthdomain.NewService(codexauthdomain.Config{
		PlatformNamespace: strings.TrimSpace(cfg.PlatformNamespace),
	}, k8sClient)
	if err != nil {
		return fmt.Errorf("init codex auth domain service: %w", err)
	}

	mcpTokenTTL, err := time.ParseDuration(cfg.MCPTokenTTL)
	if err != nil {
		return fmt.Errorf("parse KODEX_MCP_TOKEN_TTL=%q: %w", cfg.MCPTokenTTL, err)
	}
	mcpSigningKey := strings.TrimSpace(cfg.MCPTokenSigningKey)
	if mcpSigningKey == "" {
		mcpSigningKey = cfg.TokenEncryptionKey
	}
	mcpService, err := mcpdomain.NewService(mcpdomain.Config{
		TokenSigningKey:              mcpSigningKey,
		PublicBaseURL:                cfg.PublicBaseURL,
		InteractionCallbackBaseURL:   cfg.InteractionCallbackBaseURL,
		InternalMCPBaseURL:           cfg.ControlPlaneMCPBaseURL,
		RepositoryRoot:               cfg.RepositoryRoot,
		ServicesConfigEnv:            cfg.ServicesConfigEnv,
		DefaultTokenTTL:              mcpTokenTTL,
		DatabaseLifecycleAllowedEnvs: cfg.ProjectDBLifecycleAllowedEnvs,
	}, mcpdomain.Dependencies{
		Runs:             agentRuns,
		FlowEvents:       flowEvents,
		Repos:            repos,
		Platform:         platformTokens,
		Actions:          mcpActionRequests,
		Interactions:     interactionRequests,
		Sessions:         agentSessions,
		ProjectDatabases: projectDatabases,
		TokenCrypt:       tokenCrypto,
		GitHub:           githubMCPClient,
		Kubernetes:       k8sClient,
		Database:         postgresAdminClient,
	})
	if err != nil {
		return fmt.Errorf("init mcp domain service: %w", err)
	}
	systemSettingsService, err := systemsettingsdomain.NewService(systemSettingsRepo)
	if err != nil {
		return fmt.Errorf("init system settings domain service: %w", err)
	}
	if err := sharedsystemsettings.StartReloadLoop(
		runCtx,
		sharedsystemsettings.ReloadLoopConfig{
			DSN:         postgres.BuildDSN(dbOpenParams),
			ListenQuery: systemsettingrepo.ListenQuery(),
		},
		logger,
		systemSettingsService.RefreshCache,
	); err != nil {
		return fmt.Errorf("start system settings reload loop: %w", err)
	}
	var githubRateLimitService *githubratelimitdomain.Service
	changeGovernanceService, err := changegovernancedomain.NewService(changegovernancedomain.Config{}, changegovernancedomain.Dependencies{
		Repository:   changeGovernanceProjection,
		RolloutState: systemSettingsService,
	})
	if err != nil {
		return fmt.Errorf("init change governance domain service: %w", err)
	}
	missionControlService, err := missioncontroldomain.NewService(missioncontroldomain.Config{
		RolloutState: valuetypes.MissionControlRolloutState{
			SchemaReady: true,
			DomainReady: true,
		},
		DefaultTimelineLimit: 100,
		NextStepLabels:       buildNextStepLabels(cfg),
	}, missioncontroldomain.Dependencies{
		Repository: missionControlProjection,
		FlowEvents: flowEvents,
	})
	if err != nil {
		return fmt.Errorf("init mission control domain service: %w", err)
	}
	missionControlWorkerService, err := missioncontrolworkerdomain.NewService(missioncontrolworkerdomain.Config{
		ProjectLimit:        50,
		RunLimit:            500,
		TimelineEventLimit:  100,
		StaleAfter:          24 * time.Hour,
		DefaultTimelineText: "Platform event",
	}, missioncontrolworkerdomain.Dependencies{
		Projects:       projects,
		Repositories:   repos,
		AgentRuns:      agentRuns,
		StaffRuns:      runs,
		MissionControl: missionControlService,
		Projection:     missionControlProjection,
	})
	if err != nil {
		return fmt.Errorf("init mission control worker domain service: %w", err)
	}
	runtimeDeployRolloutTimeout, err := time.ParseDuration(cfg.RuntimeDeployRolloutTimeout)
	if err != nil {
		return fmt.Errorf("parse KODEX_RUNTIME_DEPLOY_ROLLOUT_TIMEOUT=%q: %w", cfg.RuntimeDeployRolloutTimeout, err)
	}
	runtimeDeployKanikoTimeout, err := time.ParseDuration(cfg.RuntimeDeployKanikoTimeout)
	if err != nil {
		return fmt.Errorf("parse KODEX_RUNTIME_DEPLOY_KANIKO_TIMEOUT=%q: %w", cfg.RuntimeDeployKanikoTimeout, err)
	}
	runtimeDeployWaitPollInterval, err := time.ParseDuration(cfg.RuntimeDeployWaitPollInterval)
	if err != nil {
		return fmt.Errorf("parse KODEX_RUNTIME_DEPLOY_WAIT_POLL_INTERVAL=%q: %w", cfg.RuntimeDeployWaitPollInterval, err)
	}
	registryHTTPTimeout, err := time.ParseDuration(cfg.RegistryHTTPTimeout)
	if err != nil {
		return fmt.Errorf("parse KODEX_REGISTRY_HTTP_TIMEOUT=%q: %w", cfg.RegistryHTTPTimeout, err)
	}
	runtimeDeployReconcileInterval, err := time.ParseDuration(cfg.RuntimeDeployReconcileInterval)
	if err != nil {
		return fmt.Errorf("parse KODEX_RUNTIME_DEPLOY_RECONCILE_INTERVAL=%q: %w", cfg.RuntimeDeployReconcileInterval, err)
	}
	runtimeDeployLeaseTTL, err := time.ParseDuration(cfg.RuntimeDeployLeaseTTL)
	if err != nil {
		return fmt.Errorf("parse KODEX_RUNTIME_DEPLOY_LEASE_TTL=%q: %w", cfg.RuntimeDeployLeaseTTL, err)
	}
	runtimeDeployWorkerID := strings.TrimSpace(cfg.RuntimeDeployWorkerID)
	if runtimeDeployWorkerID == "" {
		hostname, hostErr := os.Hostname()
		if hostErr != nil || strings.TrimSpace(hostname) == "" {
			runtimeDeployWorkerID = "runtime-deploy-control-plane"
		} else {
			runtimeDeployWorkerID = "runtime-deploy-" + strings.TrimSpace(hostname)
		}
	}
	runtimeDeployWorkersPerPod := cfg.RuntimeDeployWorkersPerPod
	if runtimeDeployWorkersPerPod <= 0 {
		return fmt.Errorf("parse KODEX_RUNTIME_DEPLOY_WORKERS_PER_POD=%d: value must be > 0", cfg.RuntimeDeployWorkersPerPod)
	}
	registryScheme := strings.TrimSpace(cfg.InternalRegistryScheme)
	if registryScheme == "" {
		registryScheme = "http"
	}
	registryBaseURL := registryScheme + "://" + strings.TrimSpace(cfg.InternalRegistryHost)
	registryClient, err := registry.NewClient(registryBaseURL, registryHTTPTimeout)
	if err != nil {
		return fmt.Errorf("init registry client: %w", err)
	}
	runtimeErrorService, err := runtimeerrordomain.NewService(runtimeErrors, logger)
	if err != nil {
		return fmt.Errorf("init runtime error service: %w", err)
	}
	runtimeDeployService, err := runtimedeploydomain.NewService(runtimedeploydomain.Config{
		ServicesConfigPath:      cfg.ServicesConfigPath,
		RepositoryRoot:          cfg.RepositoryRoot,
		RolloutTimeout:          runtimeDeployRolloutTimeout,
		KanikoTimeout:           runtimeDeployKanikoTimeout,
		WaitPollInterval:        runtimeDeployWaitPollInterval,
		KanikoFieldManager:      cfg.RuntimeDeployFieldManager,
		GitHubPAT:               strings.TrimSpace(cfg.GitHubPAT),
		RegistryCleanupKeepTags: cfg.RegistryCleanupKeepTags,
		KanikoJobLogTailLines:   200,
	}, runtimedeploydomain.Dependencies{
		Kubernetes: newRuntimeDeployKubernetesAdapter(k8sClient),
		Tasks:      runtimeDeployTasks,
		Runs:       agentRuns,
		FlowEvents: flowEvents,
		Registry:   registryClient,
		RuntimeErr: runtimeErrorService,
		Logger:     logger,
	})
	if err != nil {
		return fmt.Errorf("init runtime deploy domain service: %w", err)
	}
	runStatusService, err := runstatusdomain.NewService(runstatusdomain.Config{
		PublicBaseURL:    cfg.PublicBaseURL,
		DefaultLocale:    "ru",
		AIDomain:         cfg.AIDomain,
		ProductionDomain: cfg.ProductionDomain,
		NextStepLabels:   buildNextStepLabels(cfg),
	}, runstatusdomain.Dependencies{
		Runs:                 agentRuns,
		Sessions:             agentSessions,
		Platform:             platformTokens,
		TokenCrypt:           tokenCrypto,
		GitHub:               githubMCPClient,
		Kubernetes:           k8sClient,
		FlowEvents:           flowEvents,
		StaffRuns:            runs,
		GitHubRateLimitWaits: githubRateLimitWaits,
		RuntimeDeploy:        runtimeDeployService,
	})
	if err != nil {
		return fmt.Errorf("init runstatus domain service: %w", err)
	}

	learningDefault, err := cfg.LearningModeDefaultBool()
	if err != nil {
		return err
	}
	webhookRuntimeModePolicy := loadWebhookRuntimeModePolicy(cfg, logger)

	webhookService := webhook.NewService(webhook.Config{
		AgentRuns:           agentRuns,
		Agents:              agents,
		FlowEvents:          flowEvents,
		Repos:               repos,
		Projects:            projects,
		Users:               users,
		Members:             members,
		RunStatus:           runStatusService,
		RuntimeErrors:       runtimeErrorService,
		LearningModeDefault: learningDefault,
		TriggerLabels:       buildWebhookTriggerLabels(cfg),
		RuntimeModePolicy:   webhookRuntimeModePolicy,
		PlatformNamespace:   strings.TrimSpace(cfg.PlatformNamespace),
		GitHubToken:         strings.TrimSpace(cfg.GitHubPAT),
		GitBotUsername:      strings.TrimSpace(cfg.GitBotUsername),
		GitHubMgmt:          githubMgmtClient,
		PushMainAutoBump:    true,
	})

	webhookURL := strings.TrimSpace(cfg.GitHubWebhookURL)
	if webhookURL == "" {
		webhookURL = strings.TrimRight(cfg.PublicBaseURL, "/") + "/api/v1/webhooks/github"
	}
	events := make([]string, 0, len(cfg.GitHubWebhookEvents))
	for _, event := range cfg.GitHubWebhookEvents {
		event = strings.TrimSpace(event)
		if event == "" {
			continue
		}
		events = append(events, event)
	}

	bootstrapSeed, err := seedBootstrapProjectsAndRepositories(runCtx, seedBootstrapProjectsAndRepositoriesParams{
		GitHubRepo:             strings.TrimSpace(cfg.GitHubRepo),
		FirstProjectGitHubRepo: strings.TrimSpace(cfg.FirstProjectGitHubRepo),
		LearningModeDefault:    learningDefault,
		GitHubPAT:              strings.TrimSpace(cfg.GitHubPAT),
		TokenCrypt:             tokenCrypto,
		PlatformTokens:         platformTokens,
		Projects:               projects,
		Repos:                  repos,
		GitHub:                 githubRepoProvider,
		Logger:                 logger,
	})
	if err != nil {
		return fmt.Errorf("seed bootstrap projects and repositories: %w", err)
	}
	staffService := staff.NewService(staff.Config{
		LearningModeDefault: learningDefault,
		PromptSeedsDir:      filepath.Join(cfg.RepositoryRoot, "services/jobs/agent-runner/internal/runner/promptseeds"),
		WebhookSpec: repoprovider.WebhookSpec{
			URL:    webhookURL,
			Secret: cfg.GitHubWebhookSecret,
			Events: events,
		},
		ProtectedProjectIDs:    bootstrapSeed.ProtectedProjectIDs,
		ProtectedRepositoryIDs: bootstrapSeed.ProtectedRepositoryIDs,
		NextStepLabels:         buildNextStepLabels(cfg),
	}, staff.Dependencies{
		Users:          users,
		Projects:       projects,
		Members:        members,
		Repos:          repos,
		ProjectTokens:  projectTokens,
		Feedback:       feedback,
		Runs:           runs,
		Tasks:          runtimeDeployTasks,
		RuntimeErrors:  runtimeErrors,
		K8s:            k8sClient,
		Tokencrypt:     tokenCrypto,
		PlatformTokens: platformTokens,
		GitHub:         githubRepoProvider,
		GitHubMgmt:     githubMgmtClient,
		RunStatus:      runStatusService,
		RuntimeDeploy:  runtimeDeployService,
		SystemSettings: systemSettingsService,
	})
	githubRateLimitService, err = githubratelimitdomain.NewService(githubratelimitdomain.Config{
		RolloutState: valuetypes.GitHubRateLimitRolloutState{},
	}, githubratelimitdomain.Dependencies{
		Runs:           agentRuns,
		Waits:          githubRateLimitWaits,
		FlowEvents:     flowEvents,
		RunStatusRetry: runStatusService,
		PlatformReplay: staffService,
		RolloutState:   systemSettingsService,
	})
	if err != nil {
		return fmt.Errorf("init github rate-limit domain service with platform replay: %w", err)
	}

	// Ensure bootstrap users exist so that the first login can be matched by email.
	_, err = users.EnsureOwner(runCtx, cfg.BootstrapOwnerEmail)
	if err != nil {
		return fmt.Errorf("ensure bootstrap owner user: %w", err)
	}
	if err := ensureBootstrapAllowedUsers(runCtx, users, cfg.BootstrapOwnerEmail, cfg.BootstrapAllowedEmails, logger); err != nil {
		return fmt.Errorf("ensure bootstrap allowed users: %w", err)
	}
	if err := ensureBootstrapPlatformAdmins(runCtx, users, cfg.BootstrapOwnerEmail, cfg.BootstrapPlatformAdminEmails, logger); err != nil {
		return fmt.Errorf("ensure bootstrap platform admins: %w", err)
	}

	grpcLis, err := net.Listen("tcp", cfg.GRPCAddr)
	if err != nil {
		return fmt.Errorf("listen grpc %s: %w", cfg.GRPCAddr, err)
	}
	defer func() { _ = grpcLis.Close() }()

	agentCallbackService := agentcallbackdomain.NewService(
		agentSessions,
		flowEvents,
		agentRuns,
		agentRuns,
		repos,
		tokenCrypto,
		map[repoprovider.Provider]repoprovider.RepositoryProvider{
			repoprovider.ProviderGitHub: githubRepoProvider,
		},
	)
	retentionDays := cfg.RunHeavyFieldsRetentionDays
	if retentionDays <= 0 {
		retentionDays = cfg.RunAgentLogsRetentionDays
	}
	if err := startRunHeavyFieldsCleanupLoop(runCtx, agentCallbackService, runtimeDeployService, logger, retentionDays); err != nil {
		return fmt.Errorf("start run heavy fields cleanup loop: %w", err)
	}
	if err := startRuntimeDeployReconcilerLoop(
		runCtx,
		runtimeDeployService,
		logger,
		runtimeDeployWorkerID,
		runtimeDeployReconcileInterval,
		runtimeDeployLeaseTTL,
		runtimeDeployWorkersPerPod,
	); err != nil {
		return fmt.Errorf("start runtime deploy reconciler loop: %w", err)
	}

	interactionCollector := observability.NewInteractionCollector(interactionRequests, logger)
	if err := registerOrReplaceCollector(prometheus.DefaultRegisterer, interactionCollector); err != nil {
		return fmt.Errorf("register interaction collector: %w", err)
	}
	defer prometheus.DefaultRegisterer.Unregister(interactionCollector)

	grpcServer := grpc.NewServer()
	controlplanev1.RegisterControlPlaneServiceServer(grpcServer, grpctransport.NewServer(grpctransport.Dependencies{
		Webhook:              webhookService,
		Staff:                staffService,
		AgentCallbacks:       agentCallbackService,
		ChangeGovernance:     changeGovernanceService,
		GitHubRateLimit:      githubRateLimitService,
		MissionControl:       missionControlWorkerService,
		MissionControlDomain: missionControlService,
		RunStatus:            runStatusService,
		Runs:                 agentRuns,
		RuntimeDeploy:        runtimeDeployService,
		RuntimeErrors:        runtimeErrorService,
		MCP:                  mcpService,
		CodexAuth:            codexAuthService,
		Logger:               logger,
	}))

	mcpHandler := mcptransport.NewHandler(mcpService, logger)
	httpMux := http.NewServeMux()
	httpMux.Handle("/mcp", mcpHandler)
	httpMux.Handle("/mcp/", mcpHandler)
	httpMux.Handle("/metrics", promhttp.Handler())
	httpMux.HandleFunc("/health/readyz", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})
	httpMux.HandleFunc("/health/livez", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("alive"))
	})
	// Backward compatibility for existing probes patterns.
	httpMux.HandleFunc("/readyz", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("ok"))
	})
	httpMux.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte("alive"))
	})

	httpServer := &http.Server{Addr: cfg.HTTPAddr, Handler: httpMux}

	errCh := make(chan error, 2)
	go func() {
		logger.Info("control-plane grpc started", "addr", cfg.GRPCAddr)
		errCh <- grpcServer.Serve(grpcLis)
	}()
	go func() {
		logger.Info("control-plane http started", "addr", cfg.HTTPAddr)
		errCh <- httpServer.ListenAndServe()
	}()

	select {
	case <-runCtx.Done():
		logger.Info("shutting down control-plane")

		grpcServer.GracefulStop()

		shutdownCtx, cancel := context.WithTimeout(appCtx, 15*time.Second)
		defer cancel()
		if err := httpServer.Shutdown(shutdownCtx); err != nil {
			return fmt.Errorf("shutdown control-plane http: %w", err)
		}
		return nil
	case err := <-errCh:
		if err == nil {
			return nil
		}
		if err == http.ErrServerClosed {
			return nil
		}
		return fmt.Errorf("control-plane server failed: %w", err)
	}
}
