package server

import (
	"context"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"log/slog"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	activityservice "github.com/tidbcloud/AgentTeam/internal/service/resource/activity"

	agsauth "github.com/pingcap/agent-git-service/auth"
	agsconfig "github.com/pingcap/agent-git-service/config"
	agsserver "github.com/pingcap/agent-git-service/server"

	"github.com/tidbcloud/AgentTeam/internal/agsbridge"
	"github.com/tidbcloud/AgentTeam/internal/agsbridge/gittransport"
	apiactionsrunner "github.com/tidbcloud/AgentTeam/internal/api/actionsrunner"
	"github.com/tidbcloud/AgentTeam/internal/api/ags"
	apiautomations "github.com/tidbcloud/AgentTeam/internal/api/automation"
	apikb "github.com/tidbcloud/AgentTeam/internal/api/kb"
	apimemory "github.com/tidbcloud/AgentTeam/internal/api/memory"
	apipersonalaccesstokens "github.com/tidbcloud/AgentTeam/internal/api/providertokens"
	apipublicrepocache "github.com/tidbcloud/AgentTeam/internal/api/repocache"
	apirepositoryaccesstokens "github.com/tidbcloud/AgentTeam/internal/api/repotokens"
	"github.com/tidbcloud/AgentTeam/internal/api/roles"
	apiskills "github.com/tidbcloud/AgentTeam/internal/api/skills"
	"github.com/tidbcloud/AgentTeam/internal/automation/blob"
	"github.com/tidbcloud/AgentTeam/internal/config"
	"github.com/tidbcloud/AgentTeam/internal/contentpayload"
	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/database"
	"github.com/tidbcloud/AgentTeam/internal/identity/trusted"
	"github.com/tidbcloud/AgentTeam/internal/kbgovernance"
	"github.com/tidbcloud/AgentTeam/internal/observability/logging"
	"github.com/tidbcloud/AgentTeam/internal/router"
	serviceactionsrunner "github.com/tidbcloud/AgentTeam/internal/service/actionsrunner"
	automationservice "github.com/tidbcloud/AgentTeam/internal/service/automation"
	"github.com/tidbcloud/AgentTeam/internal/service/automation/runtime"
	"github.com/tidbcloud/AgentTeam/internal/service/content"
	kbservice "github.com/tidbcloud/AgentTeam/internal/service/kb"
	memoryservice "github.com/tidbcloud/AgentTeam/internal/service/memory"
	publicrepocacheservice "github.com/tidbcloud/AgentTeam/internal/service/repocache"
	"github.com/tidbcloud/AgentTeam/internal/service/repotokens"
	reposservice "github.com/tidbcloud/AgentTeam/internal/service/resource/repos"
	roleservice "github.com/tidbcloud/AgentTeam/internal/service/roles"
	skillservice "github.com/tidbcloud/AgentTeam/internal/service/skills"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
	"github.com/tidbcloud/AgentTeam/internal/store/automationcutover"
	"github.com/tidbcloud/AgentTeam/internal/store/kb"
	personalaccesstokensstore "github.com/tidbcloud/AgentTeam/internal/store/providertokens"
	publicrepocachestore "github.com/tidbcloud/AgentTeam/internal/store/repocache"
	repositoryaccesstokensstore "github.com/tidbcloud/AgentTeam/internal/store/repotokens"
	activitystore "github.com/tidbcloud/AgentTeam/internal/store/resource/activity"
	memorystore "github.com/tidbcloud/AgentTeam/internal/store/resource/memory"
	resourcerepostore "github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
	rolesstore "github.com/tidbcloud/AgentTeam/internal/store/resource/roles"
	skillstore "github.com/tidbcloud/AgentTeam/internal/store/resource/skills"
	"gorm.io/gorm"
)

type agsServer interface {
	Handler() http.Handler
	Shutdown(context.Context) error
}

type agsWorkerStarter interface {
	StartWorkers() error
}

type agsReadiness interface {
	Ready(context.Context) error
}

type agsRepositoryCollaboratorGrantor interface {
	GrantRepositoryCollaborator(context.Context, agsserver.RepositoryCollaboratorGrantRequest) (agsserver.RepositoryCollaboratorGrantResult, error)
}

type agsRepositoryCollaboratorRevoker interface {
	RevokeRepositoryCollaborator(context.Context, agsserver.RepositoryCollaboratorRevokeRequest) error
}

type agsRepositoryArchiver interface {
	ArchiveRepository(context.Context, agsserver.RepositoryArchiveRequest) error
}

type agsRepositoryMirrorPusher interface {
	PushRepositoryMirror(context.Context, agsserver.RepositoryMirrorPushRequest) error
}

type agsResourceRepositoryGrantor struct {
	grantor agsRepositoryCollaboratorGrantor
	revoker agsRepositoryCollaboratorRevoker
}

type agsAutomationRepositoryArchiver struct {
	archiver agsRepositoryArchiver
}

type agsNativeSourceMirrorPusher struct {
	pusher agsRepositoryMirrorPusher
}

func (p agsNativeSourceMirrorPusher) PushSourceMirror(ctx context.Context, request kbservice.NativeSourceMirrorPushRequest) error {
	fullName := strings.TrimSpace(request.Target.FullName)
	if fullName == "" && strings.TrimSpace(request.Target.Owner) != "" && strings.TrimSpace(request.Target.Name) != "" {
		fullName = strings.TrimSpace(request.Target.Owner) + "/" + strings.TrimSpace(request.Target.Name)
	}
	return p.pusher.PushRepositoryMirror(ctx, agsserver.RepositoryMirrorPushRequest{
		RepositoryFullName: fullName,
		SourceGitDir:       request.SourceGitDir,
		RefSpecs:           append([]string(nil), request.RefSpecs...),
	})
}

func nativeSourceMirrorPusher(agsSrv agsServer) kbservice.NativeSourceMirrorPusher {
	pusher, ok := agsSrv.(agsRepositoryMirrorPusher)
	if !ok {
		return nil
	}
	return agsNativeSourceMirrorPusher{pusher: pusher}
}

func (g agsResourceRepositoryGrantor) GrantRepositoryCollaborator(ctx context.Context, request reposservice.ResourceRepositoryCollaboratorGrant) (reposservice.ResourceRepositoryCollaboratorGrantResult, error) {
	if g.grantor == nil {
		return reposservice.ResourceRepositoryCollaboratorGrantResult{}, nil
	}
	permission := strings.TrimSpace(request.Permission)
	if permission == "" {
		permission = "write"
	}
	result, err := g.grantor.GrantRepositoryCollaborator(ctx, agsserver.RepositoryCollaboratorGrantRequest{
		Owner:                     request.Repository.Owner,
		Repo:                      request.Repository.Name,
		Permission:                permission,
		AllowExistingDirectUpdate: request.AllowExistingDirectUpdate,
		User: agsauth.Identity{
			Provider: request.Actor.Provider,
			Subject:  request.Actor.Subject,
			Login:    request.Actor.Login,
			Name:     request.Actor.Name,
			Email:    request.Actor.Email,
			Groups:   request.Actor.Groups,
		},
	})
	return reposservice.ResourceRepositoryCollaboratorGrantResult{Materialized: result.Materialized}, err
}

func (g agsResourceRepositoryGrantor) RevokeRepositoryCollaborator(ctx context.Context, request reposservice.ResourceRepositoryCollaboratorRevoke) error {
	if g.revoker == nil {
		return nil
	}
	return g.revoker.RevokeRepositoryCollaborator(ctx, agsserver.RepositoryCollaboratorRevokeRequest{
		Owner: request.Repository.Owner,
		Repo:  request.Repository.Name,
		User: agsauth.Identity{
			Provider: request.Actor.Provider,
			Subject:  request.Actor.Subject,
			Login:    request.Actor.Login,
			Name:     request.Actor.Name,
			Email:    request.Actor.Email,
			Groups:   request.Actor.Groups,
		},
	})
}

func (a agsAutomationRepositoryArchiver) ArchiveRepository(ctx context.Context, request automationservice.AutomationRepositoryArchiveRequest) error {
	if a.archiver == nil {
		return fmt.Errorf("AGS repository archiver is not configured")
	}
	return a.archiver.ArchiveRepository(ctx, agsserver.RepositoryArchiveRequest{
		RepositoryFullName: request.RepositoryFullName,
		CommitSHA:          request.CommitSHA,
		Writer:             request.Writer,
	})
}

type Options struct {
	NewAGS                        func(agsconfig.Config, ...agsserver.Option) (agsServer, error)
	AutomationAuthorizer          automationservice.AutomationAuthorizer
	AutomationBlobStore           blob.Store
	AutomationJobExecutor         runtime.JobExecutor
	AutomationJobExecutorResolver automationJobExecutorResolver
	KBWikiGenerator               kbservice.KBWikiGenerator
	DisableBackgroundWorkers      bool
	// DeferBackgroundWorkers leaves workers stopped until StartBackgroundWorkers
	// is called. Embedded runtimes use this so construction never starts work.
	DeferBackgroundWorkers bool
	// workerStartStageHook is a test-only failure injection seam. Production
	// callers cannot set it outside this package.
	workerStartStageHook func(string) error
}

// WorkspaceScope returns the currently routable workspaces owned by one
// workspace-profile process. A nil scope means the all-profile runtime owns
// every workspace; an empty result means it owns none.
type WorkspaceScope func(context.Context) ([]string, error)

type Server struct {
	cfg                      config.ResourceRuntimeConfig
	handlerMu                sync.RWMutex
	handler                  http.Handler
	baseHandler              http.Handler
	ags                      agsServer
	meshxDB                  *gorm.DB
	agentteamCoreDB               *gorm.DB
	contentPayload           contentpayload.TreePayloadBackend
	kbHandler                *apikb.KBHandler
	kbGovernance             kbgovernance.Authority
	automationsHandler       *apiautomations.AutomationsHandler
	automations              *automationservice.AutomationsService
	memoryHandler            *apimemory.MemoryHandler
	memoryService            *memoryservice.MemoryService
	rolesHandler             *roles.RolesHandler
	skillsHandler            *apiskills.SkillsHandler
	serverRunner             *runtime.ServerRunner
	runtimeCancel            context.CancelFunc
	runtimeDone              <-chan struct{}
	kbWorkerCancel           context.CancelFunc
	kbWorkerDone             <-chan struct{}
	contentOutboxCancel      context.CancelFunc
	contentOutboxDone        <-chan struct{}
	knowledgeQACleanupCancel context.CancelFunc
	knowledgeQACleanupDone   <-chan struct{}
	resourceActivityBridge   *agsResourceActivityEventBridge
	kbSourceRevisionBridge   *agsKBSourceRevisionEventBridge
	automationBranchBridge   *agsAutomationBranchUpdateBridge
	workersMu                sync.Mutex
	workersStarted           bool
	workersStopped           bool
	workersDrained           bool
	retiring                 bool
	startBackgroundWorkers   func() error
	unregisterGit            func()
	ready                    atomic.Bool
	schemaMu                 sync.Mutex
	schemaVerified           bool
	schemaFlight             *schemaVerificationFlight
	verifySchema             func(context.Context) error
	shutdownMu               sync.Mutex
	shutdownComplete         bool
	workspaceScope           WorkspaceScope
	globalMaintenance        bool
}

type schemaVerificationFlight struct {
	done chan struct{}
	err  error
}

func New(cfg config.ResourceRuntimeConfig, options ...Options) (*Server, error) {
	normalized, err := config.NormalizeResourceRuntime(cfg)
	if err != nil {
		return nil, err
	}
	kbLogger := slog.Default().With(slog.String("domain", "kb"))
	sourceMirrorLogger := kbLogger.With(slog.String("component", "source_mirror"))
	for name, path := range map[string]string{
		"automation blob root":       normalized.Automation.BlobFilesystemRoot,
		"KB source mirror work root": normalized.KB.SourceMirrorWorkDir,
	} {
		if err := os.MkdirAll(path, 0o750); err != nil {
			return nil, fmt.Errorf("create %s: %w", name, err)
		}
	}
	automationCredentialsKey, err := normalized.Automation.CredentialEncryptionKey()
	if err != nil {
		return nil, err
	}
	personalAccessTokenCredentialsKey, err := normalized.PersonalAccessTokens.CredentialEncryptionKey()
	if err != nil {
		return nil, err
	}
	agsFactory := func(cfg agsconfig.Config, opts ...agsserver.Option) (agsServer, error) {
		return agsserver.New(cfg, opts...)
	}
	if len(options) > 0 && options[0].NewAGS != nil {
		agsFactory = options[0].NewAGS
	}
	internalAGSAuthorizationHeader := agsbridge.AuthorizationHeader()
	automationBranchBridge := newAGSAutomationBranchUpdateBridge(nil, nil, internalAGSAuthorizationHeader)
	resourceActivityBridge := newAGSResourceActivityEventBridge(nil, nil, defaultAGSResourceActivityEventQueueSize)
	kbSourceRevisionBridge := &agsKBSourceRevisionEventBridge{}
	agentteamAuth := ags.AgentTeamAuthenticator{Options: authOptions(normalized)}
	var repositoryAccessTokenService *repotokens.Service
	repositoryAuthenticator := &ags.RepositoryTokenAuthenticator{AgentTeam: agentteamAuth}
	agsOptions := []agsserver.Option{
		agsserver.WithAuthenticator(repositoryAuthenticator),
		agsserver.WithRepositoryEventSink(repositoryEventSinks{automationBranchBridge, kbSourceRevisionBridge, resourceActivityBridge}),
	}
	if normalized.Database.DisableAutoMigrate {
		agsOptions = append(agsOptions, agsserver.WithDatabaseMigrationsDisabled())
	}
	if len(options) > 0 && options[0].DeferBackgroundWorkers {
		agsOptions = append(agsOptions, agsserver.WithWorkersDeferred())
	}
	agsSrv, err := agsFactory(toAGSConfig(normalized), agsOptions...)
	if err != nil {
		resourceActivityBridge.Close()
		return nil, fmt.Errorf("agent-git-service: %w", err)
	}
	openMeshXDatabase := database.Open
	if normalized.Database.DisableAutoMigrate {
		openMeshXDatabase = database.OpenWithoutMigrate
	}
	meshxDB, err := openMeshXDatabase(database.Config{DSN: normalized.Database.DSN})
	if err != nil {
		resourceActivityBridge.Close()
		_ = agsSrv.Shutdown(context.Background())
		return nil, fmt.Errorf("meshx database: %w", err)
	}
	repositoryAccessTokenService = repotokens.NewService(repositoryaccesstokensstore.New(meshxDB), nil)
	repositoryAuthenticator.Tokens = repositoryAccessTokenService
	blobStore, err := automationBlobStoreOption(normalized.Automation, options)
	if err != nil {
		resourceActivityBridge.Close()
		_ = database.Close(meshxDB)
		_ = agsSrv.Shutdown(context.Background())
		return nil, err
	}
	mutationAuthority := newWorkspaceAGSRepositoryAuthority(meshxDB)
	agsHandler := legacyRepositoryMutationCutoff(
		agsSrv.Handler(),
		mutationAuthority,
		agsGraphQLMutationResolver(agsSrv),
	)
	internalAGSBaseURL, unregisterGit, err := gittransport.Register(agsHandler)
	if err != nil {
		_ = database.Close(meshxDB)
		_ = agsSrv.Shutdown(context.Background())
		return nil, err
	}
	automationStore := automationstore.New(meshxDB)
	var resourceRepositoryGrantor reposservice.ResourceRepositoryGrantor
	if grantor, ok := agsSrv.(agsRepositoryCollaboratorGrantor); ok {
		resourceGrantor := agsResourceRepositoryGrantor{grantor: grantor}
		if revoker, ok := agsSrv.(agsRepositoryCollaboratorRevoker); ok {
			resourceGrantor.revoker = revoker
		}
		resourceRepositoryGrantor = resourceGrantor
	}
	resourceRepoStore := resourcerepostore.New(meshxDB)
	kbWikiStore := kbservice.AGSHTTPRepositoryStore{
		Handler:             agsHandler,
		BaseURL:             internalAGSBaseURL,
		AuthHeader:          normalized.Auth.Header,
		AuthorizationHeader: internalAGSAuthorizationHeader,
		RepositoryGrantor:   resourceRepositoryGrantor,
	}

	memoryRepositoryStore := memoryservice.AGSHTTPRepositoryStore{
		Handler:             agsHandler,
		BaseURL:             internalAGSBaseURL,
		AuthHeader:          normalized.Auth.Header,
		AuthorizationHeader: internalAGSAuthorizationHeader,
		RepositoryGrantor:   resourceRepositoryGrantor,
	}
	wikiPublisher := automationservice.AGSAutomationWikiPublisher{
		Store:        kbWikiStore,
		MaxPages:     normalized.Automation.WikiPublishMaxPages,
		MaxEntries:   normalized.Automation.WikiPublishMaxEntries,
		MaxPageBytes: normalized.Automation.WikiPublishMaxPageBytes,
	}
	treePublisher := automationservice.AGSAutomationTreePublisher{
		Store:               kbWikiStore,
		Handler:             agsHandler,
		AuthHeader:          normalized.Auth.Header,
		AuthorizationHeader: internalAGSAuthorizationHeader,
		MaxFiles:            normalized.Automation.TreePublishMaxFiles,
		MaxEntries:          normalized.Automation.TreePublishMaxEntries,
		MaxFileBytes:        normalized.Automation.TreePublishMaxFileBytes,
		MaxBytes:            normalized.Automation.TreePublishMaxBytes,
	}
	checkoutMaterializer := &automationCheckoutMaterializer{legacy: automationCheckoutMaterializerOption(agsSrv)}
	routedWikiPublisher := &automationservice.AuthorityRoutedAutomationWikiPublisher{Runs: automationStore, Legacy: wikiPublisher}
	routedTreePublisher := &automationservice.AuthorityRoutedAutomationTreePublisher{Runs: automationStore, Legacy: treePublisher}
	serverRunner, err := automationServerRunnerOption(normalized.Automation, automationStore, blobStore, checkoutMaterializer, routedWikiPublisher, routedTreePublisher, automationCredentialsKey, options)
	if err != nil {
		unregisterGit()
		resourceActivityBridge.Close()
		_ = database.Close(meshxDB)
		_ = agsSrv.Shutdown(context.Background())
		return nil, err
	}
	var runnerSignal func(string)
	if serverRunner != nil {
		runnerSignal = serverRunner.SignalWorkspace
	}
	wikiGenerator := kbservice.KBWikiGenerator(nil)
	if len(options) > 0 {
		wikiGenerator = options[0].KBWikiGenerator
	}
	if wikiGenerator == nil {
		wikiGenerator = kbservice.NewKBWikiRuntimeGenerator(kbservice.KBWikiRuntimeGeneratorConfig{
			Enabled: normalized.KB.WikiGenerationEnabled, Binary: normalized.KB.WikiGenerationBinary,
			WorkDir: normalized.KB.WikiGenerationWorkDir, Timeout: normalized.KB.WikiGenerationTimeout,
			CheckoutMaterializer: checkoutMaterializer, WikiPublisher: wikiPublisher,
			TreePublisher: treePublisher, Repositories: resourceRepoStore,
		})
	}
	repoStatusWorker := runtime.NewAutomationRepoStatusOutboxWorker(
		automationStore,
		automationservice.AGSHTTPAutomationRepoStatusWriter{
			Handler:             agsHandler,
			AuthHeader:          normalized.Auth.Header,
			AuthorizationHeader: internalAGSAuthorizationHeader,
		},
		runtime.AutomationRepoStatusOutboxWorkerConfig{
			LeaseOwner:    automationRepoStatusLeaseOwner(),
			LeaseDuration: normalized.Automation.ReconcilerInterval * 2,
			TargetBaseURL: automationRepoStatusTargetBaseURL(normalized),
		},
		slog.Default().With(
			slog.String("domain", "automation"),
			slog.String("component", "repo_status_worker"),
		),
	)
	automationSourceMirrorCredentialResolver := kbservice.AutomationSecretSourceMirrorCredentialResolver{
		Store:          automationStore,
		CredentialsKey: automationCredentialsKey,
	}
	personalAccessTokenStore := personalaccesstokensstore.New(meshxDB)
	personalSourceMirrorCredentialResolver := kbservice.PersonalAccessTokenSourceMirrorCredentialResolver{
		Store:          personalAccessTokenStore,
		CredentialsKey: personalAccessTokenCredentialsKey,
	}
	sourceMirrorCredentialResolver := kbservice.SourceMirrorCredentialResolverSet{
		Automation: automationSourceMirrorCredentialResolver,
		Personal:   personalSourceMirrorCredentialResolver,
	}
	activityStore := activitystore.New(meshxDB)
	resourceActivityBridge.repositories = resourceRepoStore
	resourceActivityBridge.activity = activityStore
	kbSourceRevisionBridge.repositories = resourceRepoStore
	kbSourceRevisionBridge.kb = kb.New(meshxDB)
	activityProjector := &activityservice.ResourceActivityProjector{
		Repositories: resourceRepoStore,
		Activity:     activityStore,
		Source: activityservice.AGSHTTPResourceActivitySource{
			Handler:             agsHandler,
			AuthHeader:          normalized.Auth.Header,
			AuthorizationHeader: internalAGSAuthorizationHeader,
		},
	}
	agentteamCoreDB, principalWorkspaceAccessVerifier, err := agentteamCoreWorkspaceAccessVerifierOption(normalized)
	if err != nil {
		unregisterGit()
		resourceActivityBridge.Close()
		_ = database.Close(meshxDB)
		_ = agsSrv.Shutdown(context.Background())
		return nil, err
	}
	repositoryAuthenticator.WorkspaceAccess = principalWorkspaceAccessVerifier
	publicRepoCacheService := publicrepocacheservice.New(
		publicrepocachestore.New(meshxDB),
		normalized.KB.PublicRepoCacheDir,
		normalized.KB.SourceMirrorOutboundProxy,
		normalized.KB.SourceMirrorTimeout,
	)
	sourceMirrorSyncer := kbservice.AGSHTTPSourceMirrorSyncer{
		BaseURL:                 internalAGSBaseURL,
		Handler:                 agsHandler,
		AuthHeader:              normalized.Auth.Header,
		AuthorizationHeader:     internalAGSAuthorizationHeader,
		CredentialResolver:      sourceMirrorCredentialResolver,
		WorkDir:                 normalized.KB.SourceMirrorWorkDir,
		UpstreamProxyURL:        normalized.KB.SourceMirrorOutboundProxy,
		Timeout:                 normalized.KB.SourceMirrorTimeout,
		Logger:                  sourceMirrorLogger,
		PublicRepoCache:         publicRepoCacheService,
		NativePusher:            nativeSourceMirrorPusher(agsSrv),
		ValidateInternalBaseURL: gittransport.ValidateRegisteredBaseURL,
	}
	contentStore, err := newContentStoreRuntime(normalized.ContentStore, meshxDB, principalWorkspaceAccessVerifier)
	if err != nil {
		unregisterGit()
		resourceActivityBridge.Close()
		_ = database.Close(meshxDB)
		_ = database.Close(agentteamCoreDB)
		_ = agsSrv.Shutdown(context.Background())
		return nil, err
	}
	kbStore := kb.New(meshxDB)
	var kbNativeWikiReader kbservice.KBWikiReader
	var kbContentAuthority kbservice.KBSourceAuthorityReader
	var kbNativeUADashboardRepository kbservice.UADashboardRepository
	if normalized.ContentStore.Enabled {
		kbNativeWikiReader = kbservice.NativeKBSourceContentReader{Content: contentStore.kb, Search: kbStore}
		kbContentAuthority = contentStore.store
		kbNativeUADashboardRepository = kbservice.NativeUADashboardRepository{Content: contentStore.kb}
		resourceActivityBridge.contentAuthority = contentStore.store
		kbSourceRevisionBridge.contentAuthority = contentStore.store
	}
	legacyAutomationSource := automationservice.AGSHTTPAutomationSourceResolver{
		Handler: agsHandler, AuthHeader: normalized.Auth.Header,
		MaxAutomationSourceBytes: int64(normalized.Automation.MaxAutomationSourceBytes),
	}
	legacyAutomationCredentialChecker := automationservice.AGSHTTPAutomationRepositoryCredentialChecker{
		Handler: agsHandler, AuthHeader: normalized.Auth.Header,
		AuthorizationHeader: internalAGSAuthorizationHeader,
	}
	resourceOptions := resourceServiceOptions{
		Logger:                                kbLogger,
		AutomationSourceResolver:              legacyAutomationSource,
		AutomationRepositoryCredentialChecker: legacyAutomationCredentialChecker,
		AutomationLimits:                      automationLimitsFromConfig(normalized.Automation),
		AutomationAuthorizer:                  automationAuthorizerOption(normalized, options),
		AutomationBlobStore:                   blobStore,
		AutomationCredentialsKey:              automationCredentialsKey,
		PersonalAccessTokenCredentialsKey:     personalAccessTokenCredentialsKey,
		AutomationRunnerSignal:                runnerSignal,
		KBWikiReader:                          kbservice.AGSHTTPKBWikiReader{Handler: agsHandler, AuthHeader: normalized.Auth.Header},
		KBNativeWikiReader:                    kbNativeWikiReader,
		KBContentAuthority:                    kbContentAuthority,
		KBNativeUADashboardRepository:         kbNativeUADashboardRepository,
		AGSRepositoryStore:                    kbWikiStore,
		KBAgentOwnershipSource:                memoryservice.IdentityAgentOwnershipSource{},
		MemoryRepositoryInitializer:           memoryRepositoryStore,
		MemoryRepositoryForker:                memoryRepositoryStore,
		MemoryRepositoryDeleter:               memoryRepositoryStore,
		MemoryRepositoryBackups:               memoryRepositoryStore,
		MemoryAgentOwnershipSource:            memoryservice.IdentityAgentOwnershipSource{},
		RepositorySyncTokenIssuer:             repositorySyncTokenIssuerOption(repositoryAccessTokenService),
		PrincipalWorkspaceAccessVerifier:      principalWorkspaceAccessVerifier,
		WorkspaceGovernanceAuthorizer:         principalWorkspaceAccessVerifier,
		KBWikiGenerator:                       wikiGenerator,
		ActivityProjector:                     activityProjector,
		ActivityRefreshAfter:                  normalized.ResourceActivity.RefreshAfter,
		SourceMirrorSyncer:                    sourceMirrorSyncer,
		SourceMirrorPreflighter: kbservice.AGSHTTPSourceMirrorSyncer{
			BaseURL:             internalAGSBaseURL,
			Handler:             agsHandler,
			AuthHeader:          normalized.Auth.Header,
			AuthorizationHeader: internalAGSAuthorizationHeader,
			CredentialResolver:  sourceMirrorCredentialResolver,
			WorkDir:             normalized.KB.SourceMirrorWorkDir,
			UpstreamProxyURL:    normalized.KB.SourceMirrorOutboundProxy,
			Timeout:             normalized.KB.SourceMirrorTimeout,
			Logger:              sourceMirrorLogger,
		},
	}
	if normalized.ContentStore.Enabled {
		resourceOptions.MemoryContentAuthority = contentStore.store
		resourceOptions.NativeMemoryContent = memoryservice.NativeMemoryContentStore{Content: contentStore.memory}
		resourceOptions.NativeMemoryContentEnabled = true
		resourceOptions.MemoryMigrationSource = memoryRepositoryStore
		resourceOptions.MemoryMigrationWorker = contentStore.memoryMigration
		resourceOptions.MemoryMigrationMappings = contentStore.migrationStore
		resourceOptions.MemoryMigrationContent = contentStore.memory
	}
	resourceServices := newResourceServices(
		resourceServiceStores{KB: kbStore, Automations: automationStore, Activity: activityStore, Memory: memorystore.New(meshxDB), PersonalAccessTokens: personalAccessTokenStore, Repos: resourceRepoStore},
		resourceOptions,
	)
	var contentOutboxWorker *content.OutboxWorker
	if normalized.ContentStore.Enabled {
		automationBranchBridge.authority = contentStore.store
		automationContentLimits := contentpayload.DefaultLimits
		automationContentLimits.MaxFiles = min(normalized.Automation.TreePublishMaxFiles, automationContentLimits.MaxFiles)
		automationContentLimits.MaxFileBytes = min(normalized.Automation.TreePublishMaxFileBytes, automationContentLimits.MaxFileBytes)
		automationContentLimits.MaxTotalBytes = min(normalized.Automation.TreePublishMaxBytes, automationContentLimits.MaxTotalBytes)
		nativeAutomationSource := automationservice.ContentStoreAutomationSource{
			Content: contentStore.automation, Authority: contentStore.store, Store: automationStore,
			Limits: automationContentLimits, Now: func() time.Time { return time.Now().UTC() },
		}
		resourceServices.Automations.SetSourceResolver(automationservice.AuthorityRoutedAutomationSource{
			NativeEnabled: true, Authority: contentStore.store,
			Legacy: legacyAutomationSource, Native: nativeAutomationSource,
		})
		resourceServices.Automations.SetRepositoryCredentialChecker(automationservice.AuthorityRoutedAutomationRepositoryCredentialChecker{
			NativeEnabled: true, Authority: contentStore.store, Legacy: legacyAutomationCredentialChecker,
		})
		resourceServices.Automations.ConfigureContentMigration(automationservice.AutomationContentMigrationOptions{
			Worker: contentStore.automationMigration,
			Exporter: automationservice.CheckoutAutomationContentMigrationExporter{
				Checkout: checkoutMaterializer.legacy,
				WorkRoot: normalized.Automation.ServerRunnerWorkDir,
			},
			Mappings:  contentStore.migrationStore,
			Content:   contentStore.automation,
			Authority: contentStore.store,
			Cutover:   automationcutover.New(meshxDB),
		})
		nativePublisher := automationservice.ContentStoreAutomationPublisher{
			Content: contentStore.automation, Authority: contentStore.store, Automations: resourceServices.Automations,
			Limits: automationContentLimits, Now: func() time.Time { return time.Now().UTC() },
		}
		routedWikiPublisher.Native = nativePublisher
		routedTreePublisher.Native = nativePublisher
		checkoutMaterializer.native = automationservice.ContentStoreAutomationCheckoutMaterializer{
			Content: contentStore.automation, MaxFileBytes: normalized.Automation.TreePublishMaxFileBytes,
		}

		cleanupContentStoreWiring := func() {
			_ = contentStore.payload.Close()
			unregisterGit()
			resourceActivityBridge.Close()
			_ = database.Close(meshxDB)
			_ = database.Close(agentteamCoreDB)
			_ = agsSrv.Shutdown(context.Background())
		}
		contentActivityConsumer, consumerErr := activityservice.NewContentVersionActivityConsumer(contentStore.automation, activityStore)
		if consumerErr != nil {
			cleanupContentStoreWiring()
			return nil, consumerErr
		}
		otherContentActivityConsumer, consumerErr := activityservice.NewContentVersionActivityConsumer(contentStore.core, activityStore)
		if consumerErr != nil {
			cleanupContentStoreWiring()
			return nil, consumerErr
		}
		contentAutomationConsumer, consumerErr := automationservice.NewContentVersionAutomationConsumer(contentStore.automation, resourceServices.Automations)
		if consumerErr != nil {
			cleanupContentStoreWiring()
			return nil, consumerErr
		}
		leaseID, idErr := identifiers.NewEntityID("cow")
		if idErr != nil {
			cleanupContentStoreWiring()
			return nil, fmt.Errorf("content outbox worker identity: %w", idErr)
		}
		consumerGroup := authorityGatedVersionCommittedConsumers{
			content: contentStore.core, authority: contentStore.store,
			automationConsumers: versionCommittedConsumers{contentAutomationConsumer, contentActivityConsumer},
			otherConsumers:      versionCommittedConsumers{otherContentActivityConsumer},
		}
		contentOutboxWorker, err = content.NewOutboxWorker(contentStore.store, consumerGroup, "automation-activity:"+leaseID, time.Minute)
		if err != nil {
			cleanupContentStoreWiring()
			return nil, err
		}
	}
	automationBranchBridge.repositories = resourceRepoStore
	automationBranchBridge.automations = resourceServices.Automations
	kbHandler := apikb.NewKBHandler(resourceServices.KB)
	automationsHandler := apiautomations.NewAutomationsHandler(resourceServices.Automations, normalized.Automation, normalized.Auth.Header)
	actionsRunnerService := serviceactionsrunner.NewService(automationStore, resourceServices.Automations, nil)
	actionsRunnerBaseURL := normalized.Automation.ActionsRunnerBaseURL
	if actionsRunnerBaseURL == "" {
		actionsRunnerBaseURL = normalized.AGS.BaseURL
	}
	repositoryAccessTokensHandler := apirepositoryaccesstokens.NewHandler(repositoryAccessTokenService)
	personalAccessTokensHandler := apipersonalaccesstokens.NewHandler(resourceServices.PersonalAccessTokens)
	memoryHandler := apimemory.NewMemoryHandler(resourceServices.Memory)
	skillRepositoryContent := skillservice.GoGitRepositoryContentManager{
		Handler:             agsHandler,
		BaseURL:             internalAGSBaseURL,
		AuthHeader:          normalized.Auth.Header,
		AuthorizationHeader: internalAGSAuthorizationHeader,
		WorkDir:             filepath.Join(normalized.KB.SourceMirrorWorkDir, "skills"),
	}
	skillOptions := skillservice.ServiceOptions{
		RepositoryContentManager:     skillRepositoryContent,
		RepositoryDeleter:            kbWikiStore,
		RepositoryBackups:            memoryRepositoryStore,
		AgentOwnershipSource:         skillAgentOwnershipSource(memoryservice.IdentityAgentOwnershipSource{}),
		AssetAccessOutbox:            memorystore.New(meshxDB),
		SyncTokenIssuer:              repositorySyncTokenIssuerOption(repositoryAccessTokenService),
		ActivityStore:                activityStore,
		ActivityProjector:            activityProjector,
		ActivityRefreshAfter:         normalized.ResourceActivity.RefreshAfter,
		EnableOfficialSkillLibraries: true,
	}
	if normalized.ContentStore.Enabled {
		skillOptions.ContentAuthority = contentStore.store
		skillOptions.NativeContent = skillservice.NativeSkillContentStore{Content: contentStore.skill}
		skillOptions.NativeContentEnabled = true
		skillOptions.MigrationWorker = contentStore.skillMigration
	}
	skillsService := skillservice.NewWithOptions(skillstore.New(meshxDB), resourceRepoStore, kbWikiStore, func() time.Time { return time.Now().UTC() }, skillOptions)
	skillsHandler := apiskills.NewSkillsHandler(skillsService)
	legacyRolePackages := roleservice.AGSHTTPRolePackageContentStore{
		Handler: agsHandler, BaseURL: internalAGSBaseURL, AuthHeader: normalized.Auth.Header,
		AuthorizationHeader: internalAGSAuthorizationHeader,
	}
	var rolePackages roleservice.RolePackageContentStore = legacyRolePackages
	if normalized.ContentStore.Enabled {
		rolePackages = roleservice.AuthorityRoutedRolePackageContentStore{
			NativeEnabled: true,
			Authority:     contentStore.store,
			Legacy:        legacyRolePackages,
			Native:        roleservice.NativeRolePackageContentStore{Content: contentStore.role},
		}
	}
	roleOptions := roleservice.Options{
		Now:          func() time.Time { return time.Now().UTC() },
		ContentStore: rolePackages,
		Skills:       skillsService,
		KB:           resourceServices.KB,
	}
	if normalized.ContentStore.Enabled {
		roleOptions.ContentAuthority = contentStore.store
		roleOptions.MigrationWorker = contentStore.roleMigration
		roleOptions.MigrationExporter = legacyRolePackages
	}
	roleService := roleservice.New(rolesstore.New(meshxDB), roleOptions)
	rolesHandler := roles.NewRolesHandler(roleService)
	var roleOperationsHandler http.Handler
	if normalized.Auth.TrustedJWTClaims &&
		normalized.Auth.TrustedJWTClaimsProtectedListener {
		roleOperationsHandler = roles.NewRoleOperationTransportHandler(roleService)
	}
	knowledgeQAHandler, knowledgeQAStateStore := newKnowledgeQARuntimeHandler(
		normalized.KnowledgeQA,
		agsSrv,
		contentStore,
		meshxDB,
		kbStore,
		resourceRepoStore,
		principalWorkspaceAccessVerifier,
	)
	handler := router.Register(
		normalized,
		agsHandler,
		kbHandler,
		automationsHandler,
		repositoryAccessTokensHandler,
		personalAccessTokensHandler,
		apiactionsrunner.NewHandler(actionsRunnerService, actionsRunnerBaseURL),
		memoryHandler,
		rolesHandler,
		skillsHandler,
		router.WithRepositoryNameAvailability(resourceRepoStore, resourceServices.KB),
		router.WithPublicRepoCache(apipublicrepocache.NewHandler(publicRepoCacheService, normalized.InternalAdmin.ServiceToken)),
		router.WithKnowledgeQA(knowledgeQAHandler),
		router.WithRoleOperations(roleOperationsHandler),
	)
	disableBackgroundWorkers := len(options) > 0 && options[0].DisableBackgroundWorkers
	deferBackgroundWorkers := len(options) > 0 && options[0].DeferBackgroundWorkers
	srv := &Server{
		cfg:                    normalized,
		handler:                handler,
		baseHandler:            handler,
		ags:                    agsSrv,
		meshxDB:                meshxDB,
		agentteamCoreDB:             agentteamCoreDB,
		contentPayload:         contentStore.payload,
		kbHandler:              kbHandler,
		kbGovernance:           resourceServices.KB,
		automationsHandler:     automationsHandler,
		automations:            resourceServices.Automations,
		memoryHandler:          memoryHandler,
		memoryService:          resourceServices.Memory,
		rolesHandler:           rolesHandler,
		skillsHandler:          skillsHandler,
		serverRunner:           serverRunner,
		resourceActivityBridge: resourceActivityBridge,
		kbSourceRevisionBridge: kbSourceRevisionBridge,
		automationBranchBridge: automationBranchBridge,
		unregisterGit:          unregisterGit,
		verifySchema:           func(ctx context.Context) error { return database.VerifySchema(meshxDB.WithContext(ctx)) },
	}
	srv.handler = srv.readinessGate(handler)
	if !disableBackgroundWorkers {
		srv.startBackgroundWorkers = func() error {
			stageHook := func(string) error { return nil }
			if len(options) > 0 && options[0].workerStartStageHook != nil {
				stageHook = options[0].workerStartStageHook
			}
			return startWorkerStages([]workerStartStage{
				{name: "ags", start: func() error {
					if !srv.globalMaintenance {
						return nil
					}
					if starter, ok := agsSrv.(agsWorkerStarter); ok {
						return starter.StartWorkers()
					}
					return nil
				}, rollback: agsSrv.Shutdown},
				{name: "resource-activity-bridge", start: func() error { resourceActivityBridge.Start(); return nil }, rollback: resourceActivityBridge.Quiesce},
				{name: "automation-bridge", start: func() error { automationBranchBridge.Start(); return nil }, rollback: automationBranchBridge.Quiesce},
				{name: "automation-runtime", start: func() error {
					runtimeRoles := startAutomationRuntimeRoles(normalized.Automation, automationStore, serverRunner, repoStatusWorker, srv.workspaceScope)
					srv.runtimeCancel, srv.runtimeDone = runtimeRoles.cancel, runtimeRoles.done
					if runtimeRoles.reconciler != nil {
						resourceServices.Automations.SetReconcilerStatus(runtimeRoles.reconciler.Status)
					}
					return nil
				}, rollback: func(ctx context.Context) error { return cancelAndWait(ctx, srv.runtimeCancel, srv.runtimeDone) }},
				{name: "kb-periodic-workers", start: func() error {
					srv.kbWorkerCancel, srv.kbWorkerDone = startKBCodeWikiWorker(normalized.KB, resourceServices.KB, sourceMirrorSyncer, publicRepoCacheService, srv.workspaceScope, kbLogger, srv.globalMaintenance)
					return nil
				}, rollback: func(ctx context.Context) error { return cancelAndWait(ctx, srv.kbWorkerCancel, srv.kbWorkerDone) }},
				{name: "content-outbox", start: func() error {
					if !srv.globalMaintenance || contentOutboxWorker == nil {
						return nil
					}
					srv.contentOutboxCancel, srv.contentOutboxDone = startContentOutboxWorker(contentOutboxWorker)
					return nil
				}, rollback: func(ctx context.Context) error {
					return cancelAndWait(ctx, srv.contentOutboxCancel, srv.contentOutboxDone)
				}},
				{name: "knowledge-qa-cleanup", start: func() error {
					if knowledgeQAStateStore != nil {
						srv.knowledgeQACleanupCancel, srv.knowledgeQACleanupDone = startKnowledgeQAStateCleanup(knowledgeQAStateStore, srv.workspaceScope, kbLogger)
					}
					return nil
				}, rollback: func(ctx context.Context) error {
					return cancelAndWait(ctx, srv.knowledgeQACleanupCancel, srv.knowledgeQACleanupDone)
				}},
			}, stageHook)
		}
	}
	if !disableBackgroundWorkers && !deferBackgroundWorkers {
		if err := srv.StartBackgroundWorkers(); err != nil {
			_ = srv.Shutdown(context.Background())
			return nil, err
		}
		if err := srv.Ready(context.Background()); err != nil {
			_ = srv.Shutdown(context.Background())
			return nil, err
		}
	}
	return srv, nil
}

// Handler returns the MeshX/AGS HTTP surface without opening a listener.
func (s *Server) Handler() http.Handler {
	if s == nil {
		return nil
	}
	s.handlerMu.RLock()
	defer s.handlerMu.RUnlock()
	return s.handler
}

func skillAgentOwnershipSource(source kbservice.AgentOwnershipSource) reposservice.AgentOwnershipSource {
	return reposservice.AgentOwnershipSourceFunc(func(ctx context.Context, workspaceID, subject string) ([]reposservice.AgentOwnership, error) {
		agents, err := source.ListOwnedAgents(ctx, workspaceID, subject)
		if err != nil {
			return nil, err
		}
		out := make([]reposservice.AgentOwnership, 0, len(agents))
		for _, agent := range agents {
			out = append(out, reposservice.AgentOwnership{ID: agent.ID, Name: agent.Name, DisplayName: agent.DisplayName})
		}
		return out, nil
	})
}

type agentteamCoreWorkspaceAuthorizer interface {
	kbservice.PrincipalWorkspaceAccessVerifier
	kbservice.WorkspaceGovernanceAuthorizer
}

func agentteamCoreWorkspaceAccessVerifierOption(cfg config.ResourceRuntimeConfig) (*gorm.DB, agentteamCoreWorkspaceAuthorizer, error) {
	dsn := strings.TrimSpace(cfg.AgentTeamCore.DatabaseDSN)
	if dsn == "" {
		return nil, nil, nil
	}
	db, err := database.OpenWithoutMigrate(database.Config{DSN: dsn})
	if err != nil {
		return nil, nil, fmt.Errorf("agentteam core database: %w", err)
	}
	return db, kbservice.AgentTeamCoreWorkspaceAccessVerifier{DB: db}, nil
}

type automationRuntimeRoles struct {
	cancel     context.CancelFunc
	done       <-chan struct{}
	reconciler *runtime.Reconciler
}

func startAutomationRuntimeRoles(cfg config.AutomationConfig, store *automationstore.Store, serverRunner *runtime.ServerRunner, repoStatusWorker *runtime.AutomationRepoStatusOutboxWorker, workspaceScope WorkspaceScope) automationRuntimeRoles {
	if !cfg.HasRole(config.AutomationRoleReconciler) && serverRunner == nil {
		return automationRuntimeRoles{}
	}
	ctx, cancel := context.WithCancel(context.Background())
	done := make(chan struct{})
	var wg sync.WaitGroup
	var reconciler *runtime.Reconciler
	automationLogger := slog.Default().With(slog.String("domain", "automation"))
	reconcilerLogger := automationLogger.With(slog.String("component", "reconciler"))
	repoStatusLogger := automationLogger.With(slog.String("component", "repo_status_worker"))
	serverRunnerLogger := automationLogger.With(slog.String("component", "server_runner"))
	if serverRunner != nil {
		serverRunner.SetWorkspaceScope(workspaceScope)
	}
	if repoStatusWorker != nil {
		repoStatusWorker.SetWorkspaceScope(workspaceScope)
	}
	if cfg.HasRole(config.AutomationRoleReconciler) {
		interval := cfg.ReconcilerInterval
		batchSize := cfg.ReconcilerBatchSize
		reconciler = runtime.NewReconciler(store, nil, reconcilerLogger)
		wg.Add(1)
		logging.Go(reconcilerLogger, func() {
			defer wg.Done()
			ticker := time.NewTicker(interval)
			defer ticker.Stop()
			for {
				select {
				case <-ctx.Done():
					return
				case <-ticker.C:
					workspaceIDs, err := loadWorkspaceScope(context.Background(), workspaceScope)
					if err != nil {
						reconcilerLogger.Error("automation runtime workspace scope failed", "error", err)
						continue
					}
					if _, err := reconciler.Reconcile(context.Background(), runtime.ReconcileOptions{Limit: batchSize, WorkspaceIDs: workspaceIDs}); err != nil {
						reconcilerLogger.Error("automation runtime reconciler tick failed", "error", err)
					}
				}
			}
		})
		if repoStatusWorker != nil {
			wg.Add(1)
			logging.Go(repoStatusLogger, func() {
				defer wg.Done()
				ticker := time.NewTicker(interval)
				defer ticker.Stop()
				for {
					select {
					case <-ctx.Done():
						return
					case <-ticker.C:
						if _, err := repoStatusWorker.RunOnce(context.Background(), batchSize); err != nil {
							repoStatusLogger.Error("automation repo status outbox worker tick failed", "error", err)
						}
					}
				}
			})
		}
	}
	if serverRunner != nil {
		wg.Add(1)
		logging.Go(serverRunnerLogger, func() {
			defer wg.Done()
			serverRunner.Run(ctx)
		})
	}
	logging.Go(automationLogger, func() {
		defer close(done)
		wg.Wait()
	})
	return automationRuntimeRoles{cancel: cancel, done: done, reconciler: reconciler}
}

func automationRepoStatusTargetBaseURL(cfg config.ResourceRuntimeConfig) string {
	if strings.TrimSpace(cfg.PublicBaseURL) != "" {
		return cfg.PublicBaseURL
	}
	if strings.TrimSpace(cfg.AGS.BaseURL) != "" {
		return cfg.AGS.BaseURL
	}
	return cfg.Automation.ActionsRunnerBaseURL
}

func automationRepoStatusLeaseOwner() string {
	var random [8]byte
	if _, err := rand.Read(random[:]); err == nil {
		return "meshx-automation-repo-status-" + hex.EncodeToString(random[:])
	}
	return fmt.Sprintf("meshx-automation-repo-status-%d", time.Now().UnixNano())
}

func authOptions(cfg config.ResourceRuntimeConfig) trusted.Options {
	return trusted.Options{Enabled: cfg.Auth.Enabled, Header: cfg.Auth.Header, AllowMock: cfg.Auth.AllowMock}
}

func toAGSConfig(cfg config.ResourceRuntimeConfig) agsconfig.Config {
	environment := "production"
	if cfg.AGS.AllowAnyToken {
		environment = "development"
	}
	return agsconfig.Config{
		Port:                       config.HTTPPort(cfg.HTTPAddress),
		BaseURL:                    cfg.AGS.BaseURL,
		OAuthDeviceVerificationURL: strings.TrimRight(cfg.AGS.BaseURL, "/") + "/login/device",
		DBdsn:                      cfg.AGS.DatabaseDSN,
		GitRepoDir:                 cfg.AGS.GitRepoDir,
		ListenMode:                 "production",
		Environment:                environment,
		AllowAnyToken:              cfg.AGS.AllowAnyToken,
		AdminLogin:                 cfg.AGS.AdminLogin,
		AdminToken:                 cfg.AGS.AdminToken,
		EmbeddingAPIKey:            cfg.AGS.EmbeddingAPIKey,
		EmbeddingBaseURL:           cfg.AGS.EmbeddingBaseURL,
		EmbeddingModel:             cfg.AGS.EmbeddingModel,
		EmbeddingDimensions:        cfg.AGS.EmbeddingDimensions,
	}
}
