package skills

import (
	"archive/zip"
	"context"
	"errors"
	"fmt"
	"io"
	"math"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/service/memory"
	"github.com/tidbcloud/AgentTeam/internal/service/resource/activity"
	"github.com/tidbcloud/AgentTeam/presets"

	"github.com/stretchr/testify/require"
	reposservice "github.com/tidbcloud/AgentTeam/internal/service/resource/repos"

	"github.com/tidbcloud/AgentTeam/internal/store/content"
	activitystore "github.com/tidbcloud/AgentTeam/internal/store/resource/activity"
	memorystore "github.com/tidbcloud/AgentTeam/internal/store/resource/memory"
	reposstore "github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
	store "github.com/tidbcloud/AgentTeam/internal/store/resource/skills"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

type fakeRepositoryEnsurer struct {
	mu                sync.Mutex
	requests          []reposservice.AGSRepositoryEnsureRequest
	visibilityUpdates []fakeRepositoryVisibilityUpdate
	deletes           []reposservice.RepositoryIdentity
	result            reposservice.RepositoryIdentity
	err               error
	visibilityErr     error
	deleteErr         error
}

type countingSkillAuthority struct {
	listCalls    atomic.Int64
	getCalls     atomic.Int64
	nativeSkills map[string]bool
	store        *content.Store
}

func (authority *countingSkillAuthority) GetAuthority(ctx context.Context, workspaceID, domain, bindingKey string) (content.Authority, bool, error) {
	authority.getCalls.Add(1)
	if authority.store != nil {
		return authority.store.GetAuthority(ctx, workspaceID, domain, bindingKey)
	}
	if !authority.nativeSkills[bindingKey] {
		return content.Authority{}, false, nil
	}
	return content.Authority{WorkspaceID: workspaceID, Domain: domain, BindingKey: bindingKey, State: content.AuthorityStateNativeActive}, true, nil
}

func (authority *countingSkillAuthority) ListAuthoritiesForBindings(ctx context.Context, workspaceID, domain string, bindingKeys []string) (map[string]content.Authority, error) {
	authority.listCalls.Add(1)
	if authority.store != nil {
		return authority.store.ListAuthoritiesForBindings(ctx, workspaceID, domain, bindingKeys)
	}
	out := make(map[string]content.Authority)
	for _, bindingKey := range bindingKeys {
		if authority.nativeSkills[bindingKey] {
			out[bindingKey] = content.Authority{WorkspaceID: workspaceID, Domain: domain, BindingKey: bindingKey, State: content.AuthorityStateNativeActive}
		}
	}
	return out, nil
}

type fakeRepositoryVisibilityUpdate struct {
	repository reposservice.RepositoryIdentity
	visibility string
}

func (f *fakeRepositoryEnsurer) EnsureRepository(_ context.Context, request reposservice.AGSRepositoryEnsureRequest) (reposservice.RepositoryIdentity, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.requests = append(f.requests, request)
	if f.err != nil {
		return reposservice.RepositoryIdentity{}, f.err
	}
	if f.result.Owner != "" || f.result.Name != "" || f.result.FullName != "" || f.result.ID != "" {
		return f.result, nil
	}
	return reposservice.RepositoryIdentity{Owner: request.Owner, Name: request.Name, FullName: request.Owner + "/" + request.Name, ID: "repo-1", DefaultBranch: request.DefaultBranch}, nil
}

func (f *fakeRepositoryEnsurer) DeleteRepository(_ context.Context, repository reposservice.RepositoryIdentity) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.deletes = append(f.deletes, repository)
	return f.deleteErr
}

func (f *fakeRepositoryEnsurer) UpdateRepositoryVisibility(_ context.Context, repository reposservice.RepositoryIdentity, visibility string) (reposservice.RepositoryIdentity, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.visibilityUpdates = append(f.visibilityUpdates, fakeRepositoryVisibilityUpdate{repository: repository, visibility: visibility})
	if f.visibilityErr != nil {
		return reposservice.RepositoryIdentity{}, f.visibilityErr
	}
	return reposservice.RepositoryIdentity{Owner: repository.Owner, Name: repository.Name, FullName: repository.FullName, ID: firstNonEmpty(repository.ID, "repo-1"), DefaultBranch: firstNonEmpty(repository.DefaultBranch, "main")}, nil
}

type raceAwareRepositoryEnsurer struct {
	mu            sync.Mutex
	requests      []reposservice.AGSRepositoryEnsureRequest
	repos         map[string]reposservice.RepositoryIdentity
	err           error
	visibilityErr error
}

func newRaceAwareRepositoryEnsurer() *raceAwareRepositoryEnsurer {
	return &raceAwareRepositoryEnsurer{
		repos: make(map[string]reposservice.RepositoryIdentity),
	}
}

func (f *raceAwareRepositoryEnsurer) EnsureRepository(_ context.Context, request reposservice.AGSRepositoryEnsureRequest) (reposservice.RepositoryIdentity, error) {
	fullName := strings.TrimSpace(request.Owner) + "/" + strings.TrimSpace(request.Name)
	f.mu.Lock()
	f.requests = append(f.requests, request)
	if f.err != nil {
		err := f.err
		f.mu.Unlock()
		return reposservice.RepositoryIdentity{}, err
	}
	defer f.mu.Unlock()
	if existing, ok := f.repos[fullName]; ok {
		if request.RejectExisting {
			return reposservice.RepositoryIdentity{}, reposservice.RepositoryAlreadyExistsConflict(
				existing.FullName,
				request.ResourceType,
				request.ResourceID,
				request.RepoRole,
				firstNonEmpty(request.NameConflictField, reposservice.RepositoryNameFieldRepoNameOverride),
				request.SuggestedRepoName,
			)
		}
		return existing, nil
	}
	repo := reposservice.RepositoryIdentity{
		Owner:         strings.TrimSpace(request.Owner),
		Name:          strings.TrimSpace(request.Name),
		FullName:      fullName,
		ID:            fmt.Sprintf("repo-%d", len(f.repos)+1),
		DefaultBranch: firstNonEmpty(request.DefaultBranch, "main"),
	}
	f.repos[fullName] = repo
	return repo, nil
}

func (f *raceAwareRepositoryEnsurer) UpdateRepositoryVisibility(_ context.Context, repository reposservice.RepositoryIdentity, visibility string) (reposservice.RepositoryIdentity, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	if f.visibilityErr != nil {
		return reposservice.RepositoryIdentity{}, f.visibilityErr
	}
	fullName := firstNonEmpty(repository.FullName, repository.Owner+"/"+repository.Name)
	existing, ok := f.repos[fullName]
	if !ok {
		existing = repository
	}
	return reposservice.RepositoryIdentity{
		Owner:         firstNonEmpty(existing.Owner, repository.Owner),
		Name:          firstNonEmpty(existing.Name, repository.Name),
		FullName:      firstNonEmpty(existing.FullName, fullName),
		ID:            firstNonEmpty(existing.ID, repository.ID),
		DefaultBranch: firstNonEmpty(existing.DefaultBranch, repository.DefaultBranch, "main"),
	}, nil
}

func (f *raceAwareRepositoryEnsurer) seedRepository(owner, name string) reposservice.RepositoryIdentity {
	f.mu.Lock()
	defer f.mu.Unlock()
	fullName := strings.TrimSpace(owner) + "/" + strings.TrimSpace(name)
	repo := reposservice.RepositoryIdentity{
		Owner:         strings.TrimSpace(owner),
		Name:          strings.TrimSpace(name),
		FullName:      fullName,
		ID:            fmt.Sprintf("repo-%d", len(f.repos)+1),
		DefaultBranch: "main",
	}
	f.repos[fullName] = repo
	return repo
}

func (f *raceAwareRepositoryEnsurer) requestSnapshot() []reposservice.AGSRepositoryEnsureRequest {
	f.mu.Lock()
	defer f.mu.Unlock()
	return append([]reposservice.AGSRepositoryEnsureRequest(nil), f.requests...)
}

func (f *raceAwareRepositoryEnsurer) repositoryCount() int {
	f.mu.Lock()
	defer f.mu.Unlock()
	return len(f.repos)
}

type fakeRepositoryContentManager struct {
	mu                 sync.Mutex
	initializeRequests []SkillRepositoryInitializeRequest
	importRequests     []SkillRepositoryImportRequest
	listRequests       []reposservice.RepositoryIdentity
	readRepositories   []reposservice.RepositoryIdentity
	readRequests       []SkillRepositoryContentsRequest
	listErrors         []error
	importErrors       []error
	scan               SkillRepositoryScanData
	importResult       SkillRepositoryImportResult
	importResults      map[string]SkillRepositoryImportResult
	err                error
	importStarted      chan struct{}
	importRelease      <-chan struct{}
	activeImports      atomic.Int32
	maxActiveImports   atomic.Int32
}

type fakeOfficialNativeSkillContent struct {
	NativeSkillContent
	mu       sync.Mutex
	imports  []NativeSkillContentRequest
	versions int
}

func (f *fakeOfficialNativeSkillContent) Import(_ context.Context, request NativeSkillContentRequest, _ SkillContentImportMutation) (NativeSkillVersionResult, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.imports = append(f.imports, request)
	f.versions++
	return NativeSkillVersionResult{
		ContentID:  request.ContentID,
		VersionID:  fmt.Sprintf("cv_official_%d", f.versions),
		RootDigest: fmt.Sprintf("sha256:official-%d", f.versions),
		FileCount:  1,
		SizeBytes:  128,
		SkillCount: 1,
		ImportedSkills: []SkillRepositoryDetectedSkill{{
			Name: "workflow-plan-author", Path: "skills/workflow-plan-author/SKILL.md",
			Status: SkillRepositorySkillStatusValid, IssueCode: SkillRepositoryIssueOK,
		}},
	}, nil
}

type blockingFirstImportContentManager struct {
	inner         *fakeRepositoryContentManager
	sourceName    string
	entered       chan struct{}
	release       chan struct{}
	secondStarted chan struct{}
	mu            sync.Mutex
	started       int
}

func newBlockingFirstImportContentManager(inner *fakeRepositoryContentManager, sourceName string) *blockingFirstImportContentManager {
	return &blockingFirstImportContentManager{
		inner:         inner,
		sourceName:    strings.TrimSpace(sourceName),
		entered:       make(chan struct{}),
		release:       make(chan struct{}),
		secondStarted: make(chan struct{}),
	}
}

type fakeSkillRepositoryBackupStore struct {
	listRequest    memory.MemoryRepositoryBackupListRequest
	createRequest  memory.MemoryRepositoryBackupCreateRequest
	restoreRequest memory.MemoryRepositoryBackupRestoreRequest
}

func (f *fakeSkillRepositoryBackupStore) ListMemoryRepositoryBackups(_ context.Context, request memory.MemoryRepositoryBackupListRequest) (memory.AgentCloudMemoryBackupPage, error) {
	f.listRequest = request
	return memory.AgentCloudMemoryBackupPage{
		Items: []memory.AgentCloudMemoryBackup{{
			TagName:   "memory-20260708-010203",
			Name:      "Before import",
			CommitSHA: "abc123",
			CreatedAt: time.Date(2026, 7, 8, 1, 2, 3, 0, time.UTC),
		}},
		Page:    request.Page,
		PerPage: request.PerPage,
		HasMore: true,
	}, nil
}

func (f *fakeSkillRepositoryBackupStore) CreateMemoryRepositoryBackup(_ context.Context, request memory.MemoryRepositoryBackupCreateRequest) (memory.AgentCloudMemoryBackup, error) {
	f.createRequest = request
	return memory.AgentCloudMemoryBackup{TagName: "memory-20260708-010203", Name: request.Name, CommitSHA: "abc123", CreatedAt: request.Now}, nil
}

func (f *fakeSkillRepositoryBackupStore) RestoreMemoryRepositoryBackup(_ context.Context, request memory.MemoryRepositoryBackupRestoreRequest) (memory.MemoryRepositoryBackupRestoreResult, error) {
	f.restoreRequest = request
	return memory.MemoryRepositoryBackupRestoreResult{
		Restored: memory.AgentCloudMemoryBackup{TagName: request.TagName, Name: "Before import", CommitSHA: "abc123", CreatedAt: request.Now},
	}, nil
}

func (f *fakeRepositoryContentManager) InitializeSkillRepository(_ context.Context, request SkillRepositoryInitializeRequest) error {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.initializeRequests = append(f.initializeRequests, request)
	return f.err
}

func (f *fakeRepositoryContentManager) ListSkillRepositorySkills(_ context.Context, repository reposservice.RepositoryIdentity) (SkillRepositoryScanData, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.listRequests = append(f.listRequests, repository)
	if len(f.listErrors) > 0 {
		err := f.listErrors[0]
		f.listErrors = f.listErrors[1:]
		if err != nil {
			return SkillRepositoryScanData{}, err
		}
	}
	if f.err != nil {
		return SkillRepositoryScanData{}, f.err
	}
	if len(f.scan.DetectedSkills) > 0 {
		return f.scan, nil
	}
	return SkillRepositoryScanData{CommitSHA: "abc123", Limit: SkillRepositoryScanLimit, DetectedSkills: []SkillRepositoryDetectedSkill{{Name: "backend-sk", Description: "Backend skill", Path: "skills/backend/SKILL.md", Layout: SkillRepositoryLayoutTemplate, Status: SkillRepositorySkillStatusValid, IssueCode: SkillRepositoryIssueOK}}}, nil
}

func (f *fakeRepositoryContentManager) ReadSkillRepositorySkills(_ context.Context, repository reposservice.RepositoryIdentity, request SkillRepositoryContentsRequest) (SkillRepositoryContents, error) {
	f.mu.Lock()
	defer f.mu.Unlock()
	f.readRepositories = append(f.readRepositories, repository)
	f.readRequests = append(f.readRequests, request)
	items := make([]SkillRepositoryContent, 0, len(request.Paths))
	for _, path := range request.Paths {
		items = append(items, SkillRepositoryContent{Name: "backend-sk", Path: path, Content: "skill content"})
	}
	return SkillRepositoryContents{CommitSHA: request.ExpectedCommitSHA, Items: items}, f.err
}

func (f *fakeRepositoryContentManager) ImportSkillRepository(_ context.Context, request SkillRepositoryImportRequest) (SkillRepositoryImportResult, error) {
	active := f.activeImports.Add(1)
	defer f.activeImports.Add(-1)
	for {
		max := f.maxActiveImports.Load()
		if active <= max || f.maxActiveImports.CompareAndSwap(max, active) {
			break
		}
	}
	if f.importStarted != nil {
		select {
		case f.importStarted <- struct{}{}:
		default:
		}
	}
	if f.importRelease != nil {
		<-f.importRelease
	}
	f.mu.Lock()
	f.importRequests = append(f.importRequests, request)
	if len(f.importErrors) > 0 {
		err := f.importErrors[0]
		f.importErrors = f.importErrors[1:]
		if err != nil {
			f.mu.Unlock()
			return SkillRepositoryImportResult{}, err
		}
	}
	if f.err != nil {
		f.mu.Unlock()
		return SkillRepositoryImportResult{}, f.err
	}
	if result, ok := f.importResults[request.SourceName]; ok {
		f.mu.Unlock()
		return result, nil
	}
	if f.importResult.CommitSHA != "" || f.importResult.SkillCount != 0 || len(f.importResult.ImportedSkills) > 0 {
		f.mu.Unlock()
		return f.importResult, nil
	}
	f.mu.Unlock()
	return SkillRepositoryImportResult{CommitSHA: "def456", SkillCount: 2, ImportedSkills: []SkillRepositoryDetectedSkill{{Name: "backend-sk", Description: "Backend skill", Path: "skills/backend/SKILL.md", Layout: SkillRepositoryLayoutTemplate, Status: SkillRepositorySkillStatusValid, IssueCode: SkillRepositoryIssueOK}, {Name: "review-sk", Description: "Review skill", Path: "review/SKILL.md", Layout: SkillRepositoryLayoutCollection, Status: SkillRepositorySkillStatusValid, IssueCode: SkillRepositoryIssueOK}}}, nil
}

func (f *blockingFirstImportContentManager) InitializeSkillRepository(ctx context.Context, request SkillRepositoryInitializeRequest) error {
	return f.inner.InitializeSkillRepository(ctx, request)
}

func (f *blockingFirstImportContentManager) ListSkillRepositorySkills(ctx context.Context, repository reposservice.RepositoryIdentity) (SkillRepositoryScanData, error) {
	return f.inner.ListSkillRepositorySkills(ctx, repository)
}

func (f *blockingFirstImportContentManager) ReadSkillRepositorySkills(ctx context.Context, repository reposservice.RepositoryIdentity, request SkillRepositoryContentsRequest) (SkillRepositoryContents, error) {
	return f.inner.ReadSkillRepositorySkills(ctx, repository, request)
}

func (f *blockingFirstImportContentManager) ImportSkillRepository(ctx context.Context, request SkillRepositoryImportRequest) (SkillRepositoryImportResult, error) {
	block := false
	f.mu.Lock()
	if strings.TrimSpace(request.SourceName) == f.sourceName {
		f.started++
		switch f.started {
		case 1:
			close(f.entered)
			block = true
		case 2:
			close(f.secondStarted)
		}
	}
	f.mu.Unlock()
	if block {
		select {
		case <-ctx.Done():
			return SkillRepositoryImportResult{}, ctx.Err()
		case <-f.release:
		}
	}
	return f.inner.ImportSkillRepository(ctx, request)
}

type fakeSkillAgentOwnershipSource struct {
	agents []reposservice.AgentOwnership
}

func (s fakeSkillAgentOwnershipSource) ListOwnedAgents(context.Context, string, string) ([]reposservice.AgentOwnership, error) {
	return append([]reposservice.AgentOwnership(nil), s.agents...), nil
}

type fakeSkillRepositorySyncTokenIssuer struct {
	requests []reposservice.RepositorySyncTokenRequest
	lease    reposservice.RepositorySyncTokenLease
	err      error
}

func (i *fakeSkillRepositorySyncTokenIssuer) CreateRepositorySyncToken(_ context.Context, req reposservice.RepositorySyncTokenRequest) (reposservice.RepositorySyncTokenLease, error) {
	i.requests = append(i.requests, req)
	if i.err != nil {
		return reposservice.RepositorySyncTokenLease{}, i.err
	}
	return i.lease, nil
}

func newSkillServiceForTest(t *testing.T) (*Service, *fakeRepositoryEnsurer) {
	t.Helper()
	db := newSkillServiceDBForTest(t)
	fake := &fakeRepositoryEnsurer{}
	fixed := time.Date(2026, 6, 18, 1, 0, 0, 0, time.UTC)
	return NewWithOptions(store.New(db), reposstore.New(db), fake, func() time.Time { return fixed }, ServiceOptions{
		RepositoryContentManager: &fakeRepositoryContentManager{},
		AgentOwnershipSource:     fakeSkillAgentOwnershipSource{agents: []reposservice.AgentOwnership{{ID: "agt_1", Name: "one"}, {ID: "agt_2", Name: "two"}}},
		AssetAccessOutbox:        memorystore.New(db),
		ActivityStore:            activitystore.New(db),
	}), fake
}

func newSkillServiceDBForTest(t *testing.T) *gorm.DB {
	t.Helper()
	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.AutoMigrate(&store.Skill{}, &store.SkillVersion{}, &store.SkillRepositoryBranch{}, &store.SkillAgentBinding{}, &store.SkillGrant{}, &memorystore.AgentAssetAccessChangeOutbox{}, &reposstore.ResourceRepository{}, &reposstore.ResourceRepositoryAccessGrant{}, &reposstore.ResourceRepositoryLifecycleEvent{}, &activitystore.ResourceActivityEvent{}, &activitystore.ResourceActivityLatestCommit{}))
	return db
}

func newSkillServiceWithDBForTest(t *testing.T, db *gorm.DB, repositories reposservice.RepositoryEnsurer, content RepositoryContentManager) *Service {
	t.Helper()
	fixed := time.Date(2026, 6, 18, 1, 0, 0, 0, time.UTC)
	return NewWithOptions(store.New(db), reposstore.New(db), repositories, func() time.Time { return fixed }, ServiceOptions{
		RepositoryContentManager:     content,
		AgentOwnershipSource:         fakeSkillAgentOwnershipSource{agents: []reposservice.AgentOwnership{{ID: "agt_1", Name: "one"}, {ID: "agt_2", Name: "two"}}},
		AssetAccessOutbox:            memorystore.New(db),
		ActivityStore:                activitystore.New(db),
		EnableOfficialSkillLibraries: true,
	})
}

type fakeSkillActivityProjector struct {
	repos   []reposstore.ResourceRepository
	opts    []activity.ResourceActivityProjectorRefreshOptions
	refresh func(context.Context, []reposstore.ResourceRepository)
}

func (p *fakeSkillActivityProjector) RefreshRepositories(ctx context.Context, repos []reposstore.ResourceRepository, opts activity.ResourceActivityProjectorRefreshOptions) (activity.ResourceActivityProjectorRunResult, error) {
	p.repos = append(p.repos, repos...)
	p.opts = append(p.opts, opts)
	if p.refresh != nil {
		p.refresh(ctx, repos)
	}
	return activity.ResourceActivityProjectorRunResult{Repositories: len(repos)}, nil
}

func TestCreateSkillUsesPersonalOwnerWithoutResourceSuffix(t *testing.T) {
	ctx := context.Background()
	svc, fake := newSkillServiceForTest(t)

	skill, err := svc.CreateSkill(ctx, "WS_1", "usr_1", SkillCreateMutation{Name: "Reviewer", Actor: reposservice.ResourceRepositoryActor{Provider: "agentteam", Subject: "usr_1", Login: "lloyd"}})
	require.NoError(t, err)
	require.Equal(t, "reviewer", skill.Slug)
	require.Equal(t, 1, skill.SkillCount)
	require.Len(t, fake.requests, 1)
	request := fake.requests[0]
	require.Equal(t, reposstore.ResourceTypeSkillRepository, request.ResourceType)
	require.Equal(t, reposstore.RepoRolePrimary, request.RepoRole)
	require.Equal(t, "usr_1", request.Owner)
	require.NotContains(t, request.Owner, ".sk")
	require.Equal(t, "reviewer.sk", request.Name)
	require.Equal(t, "main", request.DefaultBranch)
	require.Equal(t, "private", request.Visibility)
	require.False(t, request.UserVisible)
	require.False(t, request.AutoInit)
	require.True(t, request.RejectExisting)
	require.Equal(t, reposservice.RepositoryNameFieldName, request.NameConflictField)
	require.Equal(t, reposservice.ResourceRepositoryActor{Provider: "agentteam", Subject: "usr_1", Login: "lloyd"}, request.Actor)
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	require.Len(t, content.initializeRequests, 1)
	require.Equal(t, "reviewer", content.initializeRequests[0].Slug)
	require.Equal(t, "Skill repository for Reviewer", content.initializeRequests[0].Description)
	require.Equal(t, "lloyd", content.initializeRequests[0].AuthorName)
	require.Equal(t, "meshx-skills@agentteam.local", content.initializeRequests[0].AuthorEmail)

	repos, err := svc.GetSkillRepositories(ctx, "WS_1", "usr_1", skill.ID)
	require.NoError(t, err)
	require.NotNil(t, repos.Primary)
	require.Equal(t, "usr_1", repos.Primary.RepoOwner)
	require.NotContains(t, repos.Primary.RepoOwner, ".sk")
	require.Equal(t, "reviewer.sk", repos.Primary.RepoName)
	require.Equal(t, "skill_repository", repos.Primary.ResourceType)
	require.Equal(t, "private", repos.Primary.Visibility)
	require.False(t, repos.Primary.UserVisible)
}

func TestListSkillsRefreshesVisibleSkillActivity(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	now := svc.now()
	refreshedAt := now.Add(2 * time.Hour)
	projector := &fakeSkillActivityProjector{refresh: func(ctx context.Context, repos []reposstore.ResourceRepository) {
		for _, repo := range repos {
			if repo.ResourceID != "skill_refreshed" {
				continue
			}
			_, err := svc.activityStore.UpsertEvent(ctx, activitystore.ResourceActivityEvent{
				ID: "act_refreshed", WorkspaceID: repo.WorkspaceID, AssetType: activitystore.AssetTypeSkill,
				AssetID: repo.ResourceID, EventType: activitystore.EventTypeCommit, OccurredAt: refreshedAt,
			})
			require.NoError(t, err)
		}
	}}
	svc.activityProjector = projector
	svc.activityRefreshAfter = 30 * time.Minute

	visible, err := svc.store.CreateSkill(ctx, store.Skill{ID: "skill_visible", WorkspaceID: "ws_1", Slug: "visible", Name: "Visible", IsActive: true, CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now.Add(time.Hour)})
	require.NoError(t, err)
	refreshed, err := svc.store.CreateSkill(ctx, store.Skill{ID: "skill_refreshed", WorkspaceID: "ws_1", Slug: "refreshed", Name: "Refreshed", IsActive: true, CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	hidden, err := svc.store.CreateSkill(ctx, store.Skill{ID: "skill_hidden", WorkspaceID: "ws_1", Slug: "hidden", Name: "Hidden", IsActive: true, CreatedBy: "usr_2", UpdatedBy: "usr_2", CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	_, err = svc.repositoryStore.Upsert(ctx, reposstore.ResourceRepository{ID: "rr_visible", WorkspaceID: "ws_1", ResourceType: reposstore.ResourceTypeSkillRepository, ResourceID: visible.ID, RepoRole: reposstore.RepoRolePrimary, OwnerKind: "personal", RepoOwner: "usr_1", RepoName: "visible.sk", RepoFullName: "usr_1/visible.sk", CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	_, err = svc.repositoryStore.Upsert(ctx, reposstore.ResourceRepository{ID: "rr_refreshed", WorkspaceID: "ws_1", ResourceType: reposstore.ResourceTypeSkillRepository, ResourceID: refreshed.ID, RepoRole: reposstore.RepoRolePrimary, OwnerKind: "personal", RepoOwner: "usr_1", RepoName: "refreshed.sk", RepoFullName: "usr_1/refreshed.sk", CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	_, err = svc.repositoryStore.Upsert(ctx, reposstore.ResourceRepository{ID: "rr_hidden", WorkspaceID: "ws_1", ResourceType: reposstore.ResourceTypeSkillRepository, ResourceID: hidden.ID, RepoRole: reposstore.RepoRolePrimary, OwnerKind: "personal", RepoOwner: "usr_2", RepoName: "hidden.sk", RepoFullName: "usr_2/hidden.sk", CreatedBy: "usr_2", UpdatedBy: "usr_2", CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 1})
	require.NoError(t, err)
	require.Len(t, page.Items, 1)
	require.Equal(t, refreshed.ID, page.Items[0].ID, "same-request refresh must affect pagination order")
	require.Equal(t, refreshedAt, page.Items[0].UpdatedAt, "same-request refresh must decorate UpdatedAt")
	require.Len(t, projector.repos, 2)
	require.ElementsMatch(t, []string{visible.ID, refreshed.ID}, []string{projector.repos[0].ResourceID, projector.repos[1].ResourceID})
	require.Equal(t, reposstore.ResourceTypeSkillRepository, projector.repos[0].ResourceType)
	require.Equal(t, 30*time.Minute, projector.opts[0].RefreshAfter)
}

func TestListSkillsNativeActivityRefreshIsBoundedAndLegacyOnlyBeforePagination(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	now := svc.now()
	refreshedAt := now.Add(2 * time.Hour)

	native, err := svc.store.CreateSkill(ctx, store.Skill{ID: "skill_native", WorkspaceID: "ws_1", Slug: "native", Name: "Native", IsActive: true, CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now.Add(time.Hour)})
	require.NoError(t, err)
	legacy, err := svc.store.CreateSkill(ctx, store.Skill{ID: "skill_legacy", WorkspaceID: "ws_1", Slug: "legacy", Name: "Legacy", IsActive: true, CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now})
	require.NoError(t, err)
	for _, repo := range []reposstore.ResourceRepository{
		{ID: "rr_native", WorkspaceID: "ws_1", ResourceType: reposstore.ResourceTypeSkillRepository, ResourceID: native.ID, RepoRole: reposstore.RepoRolePrimary, OwnerKind: "personal", RepoOwner: "usr_1", RepoName: "native.sk", RepoFullName: "usr_1/native.sk", CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now},
		{ID: "rr_legacy", WorkspaceID: "ws_1", ResourceType: reposstore.ResourceTypeSkillRepository, ResourceID: legacy.ID, RepoRole: reposstore.RepoRolePrimary, OwnerKind: "personal", RepoOwner: "usr_1", RepoName: "legacy.sk", RepoFullName: "usr_1/legacy.sk", CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now},
	} {
		_, err = svc.repositoryStore.Upsert(ctx, repo)
		require.NoError(t, err)
	}
	authority := &countingSkillAuthority{nativeSkills: map[string]bool{native.ID: true}}
	svc.contentAuthority = authority
	svc.nativeContentEnabled = true
	svc.nativeContent = NativeSkillContentStore{}
	projector := &fakeSkillActivityProjector{refresh: func(ctx context.Context, repos []reposstore.ResourceRepository) {
		require.Len(t, repos, 1)
		require.Equal(t, legacy.ID, repos[0].ResourceID, "native Skills must not reach the repository projector")
		_, err := svc.activityStore.UpsertEvent(ctx, activitystore.ResourceActivityEvent{
			ID: "act_legacy_refreshed", WorkspaceID: "ws_1", AssetType: activitystore.AssetTypeSkill,
			AssetID: legacy.ID, EventType: activitystore.EventTypeCommit, OccurredAt: refreshedAt,
		})
		require.NoError(t, err)
	}}
	svc.activityProjector = projector

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 1})
	require.NoError(t, err)
	require.Len(t, page.Items, 1)
	require.Equal(t, legacy.ID, page.Items[0].ID, "legacy refresh must affect the same request's page boundary")
	require.Equal(t, refreshedAt, page.Items[0].UpdatedAt)
	require.Equal(t, int64(1), authority.listCalls.Load(), "visible candidates must use one authority batch")
	require.Len(t, projector.repos, 1)
	require.Equal(t, legacy.ID, projector.repos[0].ResourceID)
}

func TestListSkillsNativeActivityRejectsOversizedVisibleSetBeforeAuthorityOrProjector(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	authority := &countingSkillAuthority{}
	projector := &fakeSkillActivityProjector{}
	svc.contentAuthority = authority
	svc.nativeContentEnabled = true
	svc.nativeContent = NativeSkillContentStore{}
	svc.activityProjector = projector
	now := svc.now()
	rows := make([]store.Skill, 0, store.MaxAgentAccessibleSkillRows+1)
	for i := 0; i <= store.MaxAgentAccessibleSkillRows; i++ {
		id := fmt.Sprintf("skill_visible_%04d", i)
		rows = append(rows, store.Skill{
			ID: id, WorkspaceID: "ws_bound", Slug: id, Name: id, IsActive: true,
			CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now,
		})
	}
	require.NoError(t, svc.store.DB().CreateInBatches(rows, 256).Error)

	_, err := svc.ListSkills(ctx, "ws_bound", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.ErrorContains(t, err, "visible skill list exceeds 2000 rows")
	require.Zero(t, authority.listCalls.Load(), "oversized visible SQL results must fail before authority lookup")
	require.Empty(t, projector.repos, "oversized visible SQL results must fail before repository projection")
}

func TestNativeSkillPaginationRejectsIntegerOverflowBeforeIO(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	authority := &countingSkillAuthority{}
	svc.contentAuthority = authority
	var queries atomic.Int64
	require.NoError(t, svc.store.DB().Callback().Query().Before("gorm:query").Register("test:skill_pagination_overflow", func(*gorm.DB) {
		queries.Add(1)
	}))

	_, err := svc.ListSkillVersions(ctx, "ws_1", "usr_1", "skill_1", math.MaxInt, 50)
	require.ErrorIs(t, err, ErrInvalidArgument)
	_, err = svc.ListSkillRepositoryBackups(ctx, "ws_1", "usr_1", "skill_1", "", math.MaxInt, 50)
	require.ErrorIs(t, err, ErrInvalidArgument)
	require.Zero(t, queries.Load(), "overflowing pagination must fail before Skill store I/O")
	require.Zero(t, authority.getCalls.Load(), "overflowing pagination must fail before authority I/O")
	require.Zero(t, authority.listCalls.Load(), "overflowing pagination must fail before authority batch I/O")
}

func TestCreateSkillSupportsWorkspaceStorageScope(t *testing.T) {
	ctx := context.Background()
	svc, fake := newSkillServiceForTest(t)

	skill, err := svc.CreateSkill(ctx, "WS_1", "usr_1", SkillCreateMutation{Name: "Reviewer", StorageScope: reposservice.RepositoryStorageScopeWorkspace})
	require.NoError(t, err)
	require.Equal(t, reposservice.RepositoryStorageScopeWorkspace, skill.StorageScope)
	require.Equal(t, "WS_1", skill.StorageOwnerLogin)
	require.Len(t, fake.requests, 1)
	require.Equal(t, "WS_1", fake.requests[0].Owner)
	require.Equal(t, "reviewer.sk", fake.requests[0].Name)

	repos, err := svc.GetSkillRepositories(ctx, "WS_1", "usr_1", skill.ID)
	require.NoError(t, err)
	require.NotNil(t, repos.Primary)
	require.Equal(t, "WS_1/reviewer.sk", repos.Primary.RepoFullName)
	require.Equal(t, reposservice.RepositoryStorageScopeWorkspace, repos.Primary.OwnerKind)
}

func TestCreateSkillWorkspaceVisibilityCreatesWorkspaceGrant(t *testing.T) {
	ctx := context.Background()
	svc, fake := newSkillServiceForTest(t)

	skill, err := svc.CreateSkill(ctx, "WS_1", "usr_1", SkillCreateMutation{Name: "Reviewer", VisibilityScope: SkillVisibilityScopeWorkspace})
	require.NoError(t, err)
	require.Equal(t, SkillVisibilityScopeWorkspace, skill.VisibilityScope)
	require.Equal(t, "WS_1", skill.VisibilityOwnerID)
	require.Equal(t, reposservice.RepositoryStorageScopePersonal, skill.StorageScope)
	require.Equal(t, "usr_1", skill.StorageOwnerLogin)
	require.Len(t, fake.requests, 1)
	require.Equal(t, "usr_1", fake.requests[0].Owner)

	workspaceGrant, err := svc.store.GetSkillGrant(ctx, "WS_1", skill.ID, "workspace", "WS_1")
	require.NoError(t, err)
	require.Equal(t, "view", workspaceGrant.Role)
	require.Equal(t, "view", svc.skillRole(ctx, skill, "usr_other"))

	ownerGrant, err := svc.store.GetSkillGrant(ctx, "WS_1", skill.ID, "user", "usr_1")
	require.NoError(t, err)
	require.Equal(t, "owner", ownerGrant.Role)
}

func TestCreateSkillWorkspaceVisibilityHonorsExplicitWorkspaceStorage(t *testing.T) {
	ctx := context.Background()
	svc, fake := newSkillServiceForTest(t)

	skill, err := svc.CreateSkill(ctx, "WS_1", "usr_1", SkillCreateMutation{
		Name:            "Reviewer",
		StorageScope:    reposservice.RepositoryStorageScopeWorkspace,
		VisibilityScope: SkillVisibilityScopeWorkspace,
	})
	require.NoError(t, err)
	require.Equal(t, SkillVisibilityScopeWorkspace, skill.VisibilityScope)
	require.Equal(t, "WS_1", skill.VisibilityOwnerID)
	require.Equal(t, reposservice.RepositoryStorageScopeWorkspace, skill.StorageScope)
	require.Equal(t, "WS_1", skill.StorageOwnerLogin)
	require.Len(t, fake.requests, 1)
	require.Equal(t, "WS_1", fake.requests[0].Owner)

	workspaceGrant, err := svc.store.GetSkillGrant(ctx, "WS_1", skill.ID, "workspace", "WS_1")
	require.NoError(t, err)
	require.Equal(t, "view", workspaceGrant.Role)
}

func TestUpdateSkillWorkspaceVisibilityCreatesWorkspaceGrant(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "WS_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)
	next := SkillVisibilityScopeWorkspace

	updated, err := svc.UpdateSkill(ctx, "WS_1", "usr_1", skill.ID, SkillUpdateMutation{VisibilityScope: &next})

	require.NoError(t, err)
	require.Equal(t, SkillVisibilityScopeWorkspace, updated.VisibilityScope)
	require.Equal(t, "WS_1", updated.VisibilityOwnerID)
	workspaceGrant, err := svc.store.GetSkillGrant(ctx, "WS_1", skill.ID, "workspace", "WS_1")
	require.NoError(t, err)
	require.Equal(t, "view", workspaceGrant.Role)
	require.Equal(t, "view", svc.skillRole(ctx, updated, "usr_other"))
}

func TestUpdateSkillWorkspaceVisibilityReportsSlugConflict(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	_, err := svc.CreateSkill(ctx, "WS_1", "usr_1", SkillCreateMutation{Name: "Reviewer", VisibilityScope: SkillVisibilityScopeWorkspace})
	require.NoError(t, err)
	privateSkill, err := svc.CreateSkill(ctx, "WS_1", "usr_2", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)
	next := SkillVisibilityScopeWorkspace

	_, err = svc.UpdateSkill(ctx, "WS_1", "usr_2", privateSkill.ID, SkillUpdateMutation{VisibilityScope: &next})

	require.ErrorIs(t, err, ErrConflict)
}

func TestUpdateSkillPrivateVisibilityRemovesWorkspaceGrantOnly(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "WS_1", "usr_1", SkillCreateMutation{Name: "Reviewer", VisibilityScope: SkillVisibilityScopeWorkspace})
	require.NoError(t, err)
	_, err = svc.store.UpsertSkillGrant(ctx, store.SkillGrant{
		ID:          newSkillGrantID(),
		WorkspaceID: "WS_1",
		SkillID:     skill.ID,
		SubjectType: "user",
		SubjectID:   "usr_viewer",
		Role:        "view",
		CreatedBy:   "usr_1",
		UpdatedBy:   "usr_1",
		CreatedAt:   skill.CreatedAt,
		UpdatedAt:   skill.UpdatedAt,
	})
	require.NoError(t, err)
	next := SkillVisibilityScopePrivate

	updated, err := svc.UpdateSkill(ctx, "WS_1", "usr_1", skill.ID, SkillUpdateMutation{VisibilityScope: &next})

	require.NoError(t, err)
	require.Equal(t, SkillVisibilityScopePrivate, updated.VisibilityScope)
	require.Equal(t, "usr_1", updated.VisibilityOwnerID)
	_, err = svc.store.GetSkillGrant(ctx, "WS_1", skill.ID, "workspace", "WS_1")
	require.ErrorIs(t, err, gorm.ErrRecordNotFound)
	ownerGrant, err := svc.store.GetSkillGrant(ctx, "WS_1", skill.ID, "user", "usr_1")
	require.NoError(t, err)
	require.Equal(t, "owner", ownerGrant.Role)
	viewerGrant, err := svc.store.GetSkillGrant(ctx, "WS_1", skill.ID, "user", "usr_viewer")
	require.NoError(t, err)
	require.Equal(t, "view", viewerGrant.Role)
	require.Empty(t, svc.skillRole(ctx, updated, "usr_other"))
	require.Equal(t, "view", svc.skillRole(ctx, updated, "usr_viewer"))
}

func TestSkillRepositoryBackupsUsePrimaryRepositoryAndRefreshSkillCount(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	backups := &fakeSkillRepositoryBackupStore{}
	svc.repositoryBackups = backups
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	content.scan = SkillRepositoryScanData{
		CommitSHA: "restored123",
		Limit:     SkillRepositoryScanLimit,
		DetectedSkills: []SkillRepositoryDetectedSkill{
			{Name: "backend-sk", Description: "Backend skill", Path: "skills/backend/SKILL.md", Status: SkillRepositorySkillStatusValid},
			{Name: "review-sk", Description: "Review skill", Path: "skills/review/SKILL.md", Status: SkillRepositorySkillStatusValid},
		},
	}

	skill, err := svc.CreateSkill(ctx, "WS_1", "usr_1", SkillCreateMutation{Name: "Reviewer", Actor: reposservice.ResourceRepositoryActor{Subject: "usr_1", Login: "lloyd"}})
	require.NoError(t, err)

	page, err := svc.ListSkillRepositoryBackups(ctx, "WS_1", "usr_1", skill.ID, "before", 2, 7)
	require.NoError(t, err)
	require.Len(t, page.Items, 1)
	require.True(t, page.HasMore)
	require.Equal(t, "usr_1", backups.listRequest.Repository.Owner)
	require.Equal(t, "reviewer.sk", backups.listRequest.Repository.Name)
	require.Equal(t, "main", backups.listRequest.Repository.DefaultBranch)
	require.Equal(t, "before", backups.listRequest.Query)
	require.Equal(t, 2, backups.listRequest.Page)
	require.Equal(t, 7, backups.listRequest.PerPage)

	created, err := svc.CreateSkillRepositoryBackup(ctx, "WS_1", "usr_1", skill.ID, SkillRepositoryBackupCreateMutation{Name: "Before import", Actor: reposservice.ResourceRepositoryActor{Subject: "usr_1", Login: "lloyd", Email: "lloyd@example.test"}})
	require.NoError(t, err)
	require.Equal(t, "Before import", created.Name)
	require.Equal(t, "Before import", backups.createRequest.Name)
	require.Equal(t, "main", backups.createRequest.Branch)
	require.Equal(t, "lloyd", backups.createRequest.AuthorName)
	require.Equal(t, "lloyd@example.test", backups.createRequest.AuthorEmail)

	restored, err := svc.RestoreSkillRepositoryBackup(ctx, "WS_1", "usr_1", skill.ID, "memory-20260708-010203")
	require.NoError(t, err)
	require.Equal(t, "memory-20260708-010203", restored.TagName)
	require.Equal(t, "memory-20260708-010203", backups.restoreRequest.TagName)
	require.Equal(t, "main", backups.restoreRequest.Branch)
	require.Len(t, content.listRequests, 1)
	require.Equal(t, "usr_1/reviewer.sk", content.listRequests[0].FullName)

	updated, err := svc.GetSkill(ctx, "WS_1", "usr_1", skill.ID)
	require.NoError(t, err)
	require.Equal(t, 2, updated.SkillCount)
}

func TestCreateSkillRejectsRepoFullNameConflicts(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	now := svc.now()
	_, err := svc.repositoryStore.Upsert(ctx, reposstore.ResourceRepository{
		ID:           "rr_existing",
		WorkspaceID:  "ws_1",
		ResourceType: reposstore.ResourceTypeMemory,
		ResourceID:   "mem_existing",
		RepoRole:     reposstore.RepoRolePrimary,
		OwnerKind:    reposservice.RepositoryStorageScopePersonal,
		RepoOwner:    "usr_1",
		RepoName:     "reviewer.sk",
		RepoFullName: "usr_1/reviewer.sk",
		CreatedBy:    "usr_1",
		UpdatedBy:    "usr_1",
		CreatedAt:    now,
		UpdatedAt:    now,
	})
	require.NoError(t, err)

	_, err = svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})

	require.Error(t, err)
	require.ErrorIs(t, err, reposservice.ErrConflict)
	require.Equal(t, reposservice.RepositoryNameConflictCode, reposservice.Code(err))
}

func TestCreateSkillStillRejectsExistingAGSRepository(t *testing.T) {
	ctx := context.Background()
	db := newSkillServiceDBForTest(t)
	repositories := newRaceAwareRepositoryEnsurer()
	repositories.seedRepository("usr_1", "reviewer.sk")
	content := &fakeRepositoryContentManager{}
	svc := newSkillServiceWithDBForTest(t, db, repositories, content)
	svc.officialSkillLibrariesEnabled = false

	_, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})

	require.Error(t, err)
	require.ErrorIs(t, err, reposservice.ErrConflict)
	require.Equal(t, reposservice.RepositoryAlreadyExistsCode, reposservice.Code(err))
	requests := repositories.requestSnapshot()
	require.Len(t, requests, 1)
	require.True(t, requests[0].RejectExisting)
	require.Empty(t, content.initializeRequests)
	_, err = svc.store.GetSkillBySlugAndCreator(ctx, "ws_1", "reviewer", "usr_1")
	require.ErrorIs(t, err, gorm.ErrRecordNotFound)
}

func TestCreateSkillRecordsRepositoryLifecycleFailureWhenInitializationFails(t *testing.T) {
	ctx := context.Background()
	svc, fake := newSkillServiceForTest(t)
	svc.repositoryContent = &fakeRepositoryContentManager{err: errors.New("template push failed")}

	_, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})

	require.Error(t, err)
	require.Contains(t, err.Error(), "template push failed")
	require.Len(t, fake.requests, 1)
	lifecycleEvents, err := svc.repositoryStore.ListLifecycleEventsForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, fake.requests[0].ResourceID)
	require.NoError(t, err)
	require.Len(t, lifecycleEvents, 1)
	require.Equal(t, reposstore.ResourceRepositoryLifecycleOperationCreateBind, lifecycleEvents[0].Operation)
	require.Equal(t, reposstore.ResourceRepositoryLifecycleStatusFailed, lifecycleEvents[0].Status)
	require.Contains(t, lifecycleEvents[0].LastError, "template push failed")
}

func TestUpdateSkillPartialActivePreservesDescription(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer", Description: "Original description"})
	require.NoError(t, err)
	inactive := false

	updated, err := svc.UpdateSkill(ctx, "ws_1", "usr_1", skill.ID, SkillUpdateMutation{IsActive: &inactive})

	require.NoError(t, err)
	require.False(t, updated.IsActive)
	require.Equal(t, "Original description", updated.Description)
}

func TestUpdateSkillCanClearDescriptionWhenProvided(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer", Description: "Original description"})
	require.NoError(t, err)
	empty := "   "

	updated, err := svc.UpdateSkill(ctx, "ws_1", "usr_1", skill.ID, SkillUpdateMutation{Description: &empty})

	require.NoError(t, err)
	require.Empty(t, updated.Description)
}

func TestDeleteSkillDeletesManagedRepositoryAndMappings(t *testing.T) {
	ctx := context.Background()
	svc, fake := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)

	require.NoError(t, svc.DeleteSkill(ctx, "ws_1", "usr_1", skill.ID))

	require.Len(t, fake.deletes, 1)
	require.Equal(t, "usr_1/reviewer.sk", fake.deletes[0].FullName)
	_, err = svc.GetSkill(ctx, "ws_1", "usr_1", skill.ID)
	require.ErrorIs(t, err, ErrNotFound)
	repos, err := svc.repositoryStore.ListForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, skill.ID)
	require.NoError(t, err)
	require.Empty(t, repos)
	lifecycleEvents, err := svc.repositoryStore.ListLifecycleEventsForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, skill.ID)
	require.NoError(t, err)
	require.Len(t, lifecycleEvents, 1)
	require.Equal(t, reposstore.ResourceRepositoryLifecycleOperationDelete, lifecycleEvents[0].Operation)
	require.Equal(t, reposstore.ResourceRepositoryLifecycleStatusSucceeded, lifecycleEvents[0].Status)
}

func TestDeleteSkillKeepsResourceWhenManagedRepositoryDeleteFails(t *testing.T) {
	ctx := context.Background()
	svc, fake := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)
	fake.deleteErr = errors.New("ags delete failed")

	err = svc.DeleteSkill(ctx, "ws_1", "usr_1", skill.ID)

	require.Error(t, err)
	require.Contains(t, err.Error(), "ags delete failed")
	_, err = svc.GetSkill(ctx, "ws_1", "usr_1", skill.ID)
	require.NoError(t, err)
	repos, err := svc.repositoryStore.ListForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, skill.ID)
	require.NoError(t, err)
	require.Len(t, repos, 1)
	lifecycleEvents, err := svc.repositoryStore.ListLifecycleEventsForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, skill.ID)
	require.NoError(t, err)
	require.Len(t, lifecycleEvents, 1)
	require.Equal(t, reposstore.ResourceRepositoryLifecycleOperationDelete, lifecycleEvents[0].Operation)
	require.Equal(t, reposstore.ResourceRepositoryLifecycleStatusFailed, lifecycleEvents[0].Status)
	require.Contains(t, lifecycleEvents[0].LastError, "ags delete failed")
}

func TestBindAgentSkillRejectsSameRepoDifferentBranch(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)

	first, err := svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)
	require.Equal(t, "main", first.Branch)

	again, err := svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)
	require.Equal(t, "main", again.Branch)

	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_2", skill.ID, AgentSkillBindingMutation{Branch: "dev"})
	require.ErrorIs(t, err, ErrConflict)
	require.Equal(t, 409, HTTPStatus(err))
}

func TestBindAgentSkillAllowsFirstBranchChoice(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)

	first, err := svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID, AgentSkillBindingMutation{Branch: "release"})
	require.NoError(t, err)
	require.Equal(t, "release", first.Branch)

	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_2", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.ErrorIs(t, err, ErrConflict)

	_, err = svc.UnbindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID)
	require.NoError(t, err)
	next, err := svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_2", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)
	require.Equal(t, "main", next.Branch)
}

func TestBindAgentSkillUsesRoleBranchHintOnlyForLegacyOwner(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer", Actor: reposservice.ResourceRepositoryActor{Subject: "usr_1", Login: "usr_1"}})
	require.NoError(t, err)

	bound, err := svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID, AgentSkillBindingMutation{LegacyBranchHint: "release"})
	require.NoError(t, err)
	require.Equal(t, "release", bound.Branch)
	stored, err := svc.store.GetSkillAgentBinding(ctx, "ws_1", "agt_1", skill.ID)
	require.NoError(t, err)
	require.Equal(t, "release", stored.Branch)
}

func TestBindAgentSkillIdempotentOnSameBranch(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)

	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)
	countAfterFirst := countSkillAccessOutboxEvents(t, svc)
	require.Equal(t, int64(1), countAfterFirst, "first bind enqueues one access-change event")

	// Rebinding the same agent+skill+branch is a no-op: no new client notification.
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)
	require.Equal(t, countAfterFirst, countSkillAccessOutboxEvents(t, svc), "idempotent rebind must not enqueue another event")
}

func countSkillAccessOutboxEvents(t *testing.T, svc *Service) int64 {
	t.Helper()
	db := svc.assetAccessOutbox.DB()
	require.NotNil(t, db)
	var n int64
	require.NoError(t, db.Model(&memorystore.AgentAssetAccessChangeOutbox{}).Where("workspace_id = ?", "ws_1").Count(&n).Error)
	return n
}

func TestUnbindAgentSkillKeepsBranchPinWhileSameRepoSkillStillBound(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	reviewer, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)
	planner, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Planner"})
	require.NoError(t, err)
	reviewerRepo, err := svc.repositoryStore.Get(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, reviewer.ID, reposstore.RepoRolePrimary)
	require.NoError(t, err)
	plannerRepo, err := svc.repositoryStore.Get(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, planner.ID, reposstore.RepoRolePrimary)
	require.NoError(t, err)
	plannerRepo.RepoOwner = reviewerRepo.RepoOwner
	plannerRepo.RepoName = reviewerRepo.RepoName
	plannerRepo.RepoFullName = reviewerRepo.RepoFullName
	plannerRepo.AGSRepoID = reviewerRepo.AGSRepoID
	_, err = svc.repositoryStore.Upsert(ctx, plannerRepo)
	require.NoError(t, err)

	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", reviewer.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_2", planner.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)

	_, err = svc.UnbindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", reviewer.ID)
	require.NoError(t, err)
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", reviewer.ID, AgentSkillBindingMutation{Branch: "dev"})
	require.ErrorIs(t, err, ErrConflict)

	_, err = svc.UnbindAgentSkill(ctx, "ws_1", "usr_1", "agt_2", planner.ID)
	require.NoError(t, err)
	next, err := svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", reviewer.ID, AgentSkillBindingMutation{Branch: "dev"})
	require.NoError(t, err)
	require.Equal(t, "dev", next.Branch)
}

func TestBindAgentSkillAllowsMultipleSkillsForSameAgent(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	first, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)
	second, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Planner"})
	require.NoError(t, err)
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", first.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", second.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)
}

func TestBindAndUnbindAgentSkillEnqueueAgentAssetAccessEvents(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)

	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)

	now := svc.now()
	leased, err := svc.assetAccessOutbox.LeaseDueAgentAssetAccessChanges(ctx, now, "daemon-1", time.Time{}, 10, nil)
	require.NoError(t, err)
	require.Len(t, leased, 1)
	require.Equal(t, "ws_1", leased[0].WorkspaceID)
	require.Equal(t, "agt_1", leased[0].AgentID)
	require.Equal(t, memorystore.AssetTypeSkill, leased[0].AssetType)
	require.Equal(t, memorystore.AgentAssetAccessSourceOutbox, leased[0].Source)
	bindVersion := leased[0].Version
	updated, err := svc.assetAccessOutbox.MarkAgentAssetAccessChangeDelivered(ctx, leased[0].ID, "daemon-1", now)
	require.NoError(t, err)
	require.True(t, updated)

	_, err = svc.UnbindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID)
	require.NoError(t, err)
	leased, err = svc.assetAccessOutbox.LeaseDueAgentAssetAccessChanges(ctx, now, "daemon-2", time.Time{}, 10, nil)
	require.NoError(t, err)
	require.Len(t, leased, 1)
	require.Equal(t, "agt_1", leased[0].AgentID)
	require.Equal(t, memorystore.AssetTypeSkill, leased[0].AssetType)
	require.Greater(t, leased[0].Version, bindVersion)
}

func TestBindAndUnbindAgentSkillRequireOwnedAgent(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)

	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_not_owned", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.ErrorIs(t, err, ErrForbidden)

	_, err = svc.UnbindAgentSkill(ctx, "ws_1", "usr_1", "agt_not_owned", skill.ID)
	require.ErrorIs(t, err, ErrForbidden)
}

func TestListAgentSkillBindingsFiltersStrictlyToOwnedAgentsAndSetsSkillPermissions(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	reviewer, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)
	planner, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Planner"})
	require.NoError(t, err)
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", reviewer.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_2", planner.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)

	svc.agentOwnershipSource = fakeSkillAgentOwnershipSource{agents: []reposservice.AgentOwnership{{ID: "agt_1", DisplayName: "Agent One"}}}
	summary, err := svc.ListAgentSkillBindings(ctx, "ws_1", "usr_1", AgentSkillBindingsOptions{})
	require.NoError(t, err)
	require.Len(t, summary.OwnedAgents, 1)
	require.Len(t, summary.Bindings, 1)
	require.Equal(t, "agt_1", summary.Bindings[0].AgentID)
	require.Len(t, summary.Skills, 2)
	for _, skill := range summary.Skills {
		require.Equal(t, "owner", skill.Role)
		require.True(t, skill.CanManage)
		require.True(t, skill.CanBind)
	}

	agentBindings, err := svc.ListAgentSkillBindingsForAgent(ctx, "ws_1", "usr_1", "agt_1")
	require.NoError(t, err)
	require.Len(t, agentBindings, 1)
	require.Equal(t, reviewer.ID, agentBindings[0].SkillID)
	_, err = svc.ListAgentSkillBindingsForAgent(ctx, "ws_1", "usr_1", "agt_2")
	require.ErrorIs(t, err, ErrForbidden)

	svc.agentOwnershipSource = fakeSkillAgentOwnershipSource{}
	summary, err = svc.ListAgentSkillBindings(ctx, "ws_1", "usr_1", AgentSkillBindingsOptions{})
	require.NoError(t, err)
	require.Empty(t, summary.OwnedAgents)
	require.Empty(t, summary.Bindings)
}

func TestListAgentSkillBindingsRejectsOversizedStoreResultsBeforeAuthorityLookup(t *testing.T) {
	ctx := context.Background()
	for _, test := range []struct {
		name string
		seed func(*testing.T, *Service)
		want string
	}{
		{
			name: "assignable skills",
			seed: func(t *testing.T, service *Service) {
				t.Helper()
				now := service.now()
				rows := make([]store.Skill, 0, store.MaxAgentAccessibleSkillRows+1)
				for i := 0; i <= store.MaxAgentAccessibleSkillRows; i++ {
					id := fmt.Sprintf("skill_%04d", i)
					rows = append(rows, store.Skill{ID: id, WorkspaceID: "ws_bound", Slug: id, Name: id, IsActive: true, CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now})
				}
				require.NoError(t, service.store.DB().CreateInBatches(rows, 256).Error)
			},
			want: "assignable skill list exceeds 2000 rows",
		},
		{
			name: "workspace bindings",
			seed: func(t *testing.T, service *Service) {
				t.Helper()
				now := service.now()
				rows := make([]store.SkillAgentBinding, 0, store.MaxAgentAccessibleSkillRows+1)
				for i := 0; i <= store.MaxAgentAccessibleSkillRows; i++ {
					rows = append(rows, store.SkillAgentBinding{ID: fmt.Sprintf("binding_%04d", i), WorkspaceID: "ws_bound", AgentID: fmt.Sprintf("agent_%04d", i), SkillID: "skill_1", Branch: "main", CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now})
				}
				require.NoError(t, service.store.DB().CreateInBatches(rows, 256).Error)
			},
			want: "workspace skill agent binding list exceeds 2000 rows",
		},
	} {
		t.Run(test.name, func(t *testing.T) {
			service, _ := newSkillServiceForTest(t)
			authority := &countingSkillAuthority{}
			service.contentAuthority = authority
			service.agentOwnershipSource = fakeSkillAgentOwnershipSource{}
			test.seed(t, service)

			_, err := service.ListAgentSkillBindings(ctx, "ws_bound", "usr_1", AgentSkillBindingsOptions{})
			require.ErrorContains(t, err, test.want)
			require.Zero(t, authority.listCalls.Load(), "oversized SQL results must fail before the authority batch")
		})
	}
}

func TestListAgentSkillBindingsForAgentRejectsOversizedStoreResultsBeforeAuthorityLookup(t *testing.T) {
	ctx := context.Background()
	service, _ := newSkillServiceForTest(t)
	authority := &countingSkillAuthority{}
	service.contentAuthority = authority
	service.agentOwnershipSource = fakeSkillAgentOwnershipSource{agents: []reposservice.AgentOwnership{{ID: "agt_bound"}}}
	now := service.now()
	skills := make([]store.Skill, 0, store.MaxAgentAccessibleSkillRows+1)
	bindings := make([]store.SkillAgentBinding, 0, store.MaxAgentAccessibleSkillRows+1)
	for i := 0; i <= store.MaxAgentAccessibleSkillRows; i++ {
		id := fmt.Sprintf("skill_bound_%04d", i)
		skills = append(skills, store.Skill{ID: id, WorkspaceID: "ws_bound", Slug: id, Name: id, IsActive: true, CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now})
		bindings = append(bindings, store.SkillAgentBinding{ID: fmt.Sprintf("binding_bound_%04d", i), WorkspaceID: "ws_bound", AgentID: "agt_bound", SkillID: id, Branch: "main", CreatedBy: "usr_1", UpdatedBy: "usr_1", CreatedAt: now, UpdatedAt: now})
	}
	require.NoError(t, service.store.DB().CreateInBatches(skills, 256).Error)
	require.NoError(t, service.store.DB().CreateInBatches(bindings, 256).Error)

	_, err := service.ListAgentSkillBindingsForAgent(ctx, "ws_bound", "usr_1", "agt_bound")
	require.ErrorContains(t, err, "agent accessible skill batch exceeds 2000 rows")
	require.Zero(t, authority.listCalls.Load(), "oversized per-agent SQL results must fail before the authority batch")
}

func TestListSkillsDecoratesOwnerPermissions(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	_, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)
	require.Len(t, page.Items, 1)
	require.Equal(t, "owner", page.Items[0].Role)
	require.True(t, page.Items[0].CanManage)
}

func TestListSkillsEnsuresOfficialSkillLibraries(t *testing.T) {
	ctx := context.Background()
	svc, fake := newSkillServiceForTest(t)
	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)
	require.Len(t, page.Items, officialLibraryCountForTest())

	workflowInfo, err := presets.OfficialSkillLibrary(workflowPlanAuthorLibrarySlugForTest, presets.LocaleEnglish)
	require.NoError(t, err)
	workflowSkill := requireSkillBySlug(t, page.Items, workflowInfo.Slug)
	require.Equal(t, workflowInfo.Name, workflowSkill.Name)
	require.Equal(t, workflowInfo.Description, workflowSkill.Description)
	require.Equal(t, workflowInfo.SystemSubject, workflowSkill.CreatedBy)
	require.Equal(t, reposservice.RepositoryStorageScopeWorkspace, workflowSkill.StorageScope)
	require.Equal(t, 1, workflowSkill.SkillCount)
	require.Equal(t, "view", workflowSkill.Role)
	require.False(t, workflowSkill.CanManage)

	require.Len(t, fake.requests, officialLibraryCountForTest())
	repositoryNames := make([]string, 0, len(fake.requests))
	for _, request := range fake.requests {
		require.Equal(t, "ws_1", request.Owner)
		require.Equal(t, "public", request.Visibility)
		require.True(t, request.UserVisible)
		repositoryNames = append(repositoryNames, request.Name)
	}
	require.ElementsMatch(t, []string{
		workflowInfo.Slug + ".sk",
		superpowersLibrarySlugForTest + ".sk",
		addyAgentSkillsLibrarySlugForTest + ".sk",
	}, repositoryNames)

	require.Len(t, content.importRequests, officialLibraryCountForTest())
	sourceNames := make([]string, 0, len(content.importRequests))
	for _, request := range content.importRequests {
		require.NotEmpty(t, request.SourceFilePath)
		require.True(t, request.ReplaceSkills)
		sourceNames = append(sourceNames, request.SourceName)
	}
	require.ElementsMatch(t, []string{
		workflowInfo.ArchiveName,
		superpowersArchiveNameForTest,
		addyAgentSkillsArchiveNameForTest,
	}, sourceNames)
}

func TestEnsureOfficialSkillLibraryCoalescesConcurrentImports(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)
	started := make(chan struct{}, 1)
	release := make(chan struct{})
	content.importStarted = started
	content.importRelease = release

	const callers = 5
	type result struct {
		skill store.Skill
		repo  *reposstore.ResourceRepository
		err   error
	}
	results := make(chan result, callers)
	definition := officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest]
	for range callers {
		go func() {
			skill, repo, err := svc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, definition, nil)
			results <- result{skill: skill, repo: repo, err: err}
		}()
	}

	select {
	case <-started:
	case <-time.After(5 * time.Second):
		t.Fatal("official Skill import did not start")
	}
	require.Never(t, func() bool {
		return content.activeImports.Load() > 1
	}, 50*time.Millisecond, time.Millisecond)
	require.EqualValues(t, 1, content.activeImports.Load())
	require.EqualValues(t, 1, content.maxActiveImports.Load())
	close(release)

	var skillID string
	var repoFullName string
	for range callers {
		res := <-results
		require.NoError(t, res.err)
		require.NotNil(t, res.repo)
		if skillID == "" {
			skillID = res.skill.ID
			repoFullName = res.repo.RepoFullName
		}
		require.Equal(t, skillID, res.skill.ID)
		require.Equal(t, repoFullName, res.repo.RepoFullName)
	}
	content.mu.Lock()
	importRequests := append([]SkillRepositoryImportRequest(nil), content.importRequests...)
	content.mu.Unlock()
	require.Len(t, importRequests, 1)
	require.Equal(t, workflowPlanAuthorArchiveNameForTest, importRequests[0].SourceName)
}

func TestOfficialRefreshRetiresTeamLibraryAndPreservesUserSkillWithSameSlug(t *testing.T) {
	ctx := context.Background()
	svc, repositories := newSkillServiceForTest(t)
	now := svc.now()

	retired, err := svc.store.CreateSkill(ctx, store.Skill{
		ID:                "skill_retired_team",
		WorkspaceID:       "ws_1",
		Slug:              retiredTeamCollaborationSkillLibrarySlug,
		Name:              "Retired AgentTeam Team library",
		StorageScope:      reposservice.RepositoryStorageScopeWorkspace,
		VisibilityScope:   SkillVisibilityScopeWorkspace,
		VisibilityOwnerID: "ws_1",
		IsActive:          true,
		CreatedBy:         officialSkillSystemSubject,
		UpdatedBy:         officialSkillSystemSubject,
		CreatedAt:         now,
		UpdatedAt:         now,
	})
	require.NoError(t, err)
	userSkill, err := svc.store.CreateSkill(ctx, store.Skill{
		ID:                "skill_user_same_slug",
		WorkspaceID:       "ws_1",
		Slug:              retiredTeamCollaborationSkillLibrarySlug,
		Name:              "My Team helpers",
		VisibilityScope:   SkillVisibilityScopePrivate,
		VisibilityOwnerID: "usr_1",
		IsActive:          true,
		CreatedBy:         "usr_1",
		UpdatedBy:         "usr_1",
		CreatedAt:         now,
		UpdatedAt:         now,
	})
	require.NoError(t, err)
	_, err = svc.store.UpsertSkillGrant(ctx, store.SkillGrant{
		ID:          "skg_retired_workspace",
		WorkspaceID: retired.WorkspaceID,
		SkillID:     retired.ID,
		SubjectType: "workspace",
		SubjectID:   retired.WorkspaceID,
		Role:        "view",
		CreatedBy:   officialSkillSystemSubject,
		UpdatedBy:   officialSkillSystemSubject,
		CreatedAt:   now,
		UpdatedAt:   now,
	})
	require.NoError(t, err)
	_, err = svc.store.UpsertSkillAgentBinding(ctx,
		store.SkillAgentBinding{
			ID:          "sab_retired",
			WorkspaceID: retired.WorkspaceID,
			AgentID:     "agt_1",
			SkillID:     retired.ID,
			Branch:      "main",
			CreatedBy:   officialSkillSystemSubject,
			UpdatedBy:   officialSkillSystemSubject,
			CreatedAt:   now,
			UpdatedAt:   now,
		},
		store.SkillRepositoryBranch{
			ID:          "srb_retired",
			WorkspaceID: retired.WorkspaceID,
			RepoOwner:   retired.WorkspaceID,
			RepoName:    retiredTeamCollaborationSkillLibrarySlug + ".sk",
			Branch:      "main",
			CreatedBy:   officialSkillSystemSubject,
			UpdatedBy:   officialSkillSystemSubject,
			CreatedAt:   now,
			UpdatedAt:   now,
		},
	)
	require.NoError(t, err)
	_, err = svc.repositoryStore.Upsert(ctx, reposstore.ResourceRepository{
		ID:           "rr_retired_team",
		WorkspaceID:  retired.WorkspaceID,
		ResourceType: reposstore.ResourceTypeSkillRepository,
		ResourceID:   retired.ID,
		RepoRole:     reposstore.RepoRolePrimary,
		OwnerKind:    reposservice.RepositoryStorageScopeWorkspace,
		RepoOwner:    retired.WorkspaceID,
		RepoName:     retiredTeamCollaborationSkillLibrarySlug + ".sk",
		RepoFullName: retired.WorkspaceID + "/" + retiredTeamCollaborationSkillLibrarySlug + ".sk",
		AGSRepoID:    "repo_retired_team",
		DefaultRef:   "main",
		Visibility:   "public",
		UserVisible:  true,
		CreatedBy:    officialSkillSystemSubject,
		UpdatedBy:    officialSkillSystemSubject,
		CreatedAt:    now,
		UpdatedAt:    now,
	})
	require.NoError(t, err)

	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)
	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)

	_, err = svc.store.GetSkillBySlugAndCreator(ctx, "ws_1", retiredTeamCollaborationSkillLibrarySlug, officialSkillSystemSubject)
	require.ErrorIs(t, err, gorm.ErrRecordNotFound)
	preserved, err := svc.store.GetSkill(ctx, "ws_1", userSkill.ID)
	require.NoError(t, err)
	require.Equal(t, userSkill.ID, preserved.ID)
	require.Contains(t, skillIDsForTest(page.Items), userSkill.ID)
	require.Len(t, repositories.deletes, 1)
	require.Equal(t, "ws_1/"+retiredTeamCollaborationSkillLibrarySlug+".sk", repositories.deletes[0].FullName)
	bindings, err := svc.store.ListWorkspaceSkillAgentBindings(ctx, "ws_1", 0)
	require.NoError(t, err)
	for _, binding := range bindings {
		require.NotEqual(t, retired.ID, binding.SkillID)
	}
	var branchPinCount int64
	require.NoError(t, svc.store.DB().
		Model(&store.SkillRepositoryBranch{}).
		Where("workspace_id = ? AND repo_owner = ? AND repo_name = ?", retired.WorkspaceID, retired.WorkspaceID, retiredTeamCollaborationSkillLibrarySlug+".sk").
		Count(&branchPinCount).Error)
	require.Zero(t, branchPinCount)
	grants, err := svc.store.ListSkillGrants(ctx, "ws_1", retired.ID)
	require.NoError(t, err)
	require.Empty(t, grants)
	repoRows, err := svc.repositoryStore.ListForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, retired.ID)
	require.NoError(t, err)
	require.Empty(t, repoRows)
	require.Equal(t, int64(1), countSkillAccessOutboxEvents(t, svc))
	require.NoError(t, svc.retireRemovedOfficialSkillLibraries(ctx, "ws_1"))
	require.Len(t, repositories.deletes, 1, "retirement must be idempotent")
	require.Equal(t, int64(1), countSkillAccessOutboxEvents(t, svc))
}

func TestNativeOfficialSkillLifecycleNeverCallsAGS(t *testing.T) {
	ctx := context.Background()
	svc, repositories := newSkillServiceForTest(t)
	svc.officialSkillLibrariesEnabled = true
	legacyContent := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, legacyContent)

	initial, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)
	workflow := requireSkillBySlug(t, initial.Items, workflowPlanAuthorLibrarySlugForTest)
	db := svc.store.DB()
	require.NoError(t, db.AutoMigrate(&content.Authority{}))
	require.NoError(t, db.Model(&store.Skill{}).
		Where("workspace_id = ? AND id = ?", workflow.WorkspaceID, workflow.ID).
		Updates(map[string]any{
			"content_id":                  "ctn_official_workflow",
			"active_content_version_id":   "cv_official_0",
			"official_source_fingerprint": "stale-fingerprint",
		}).Error)
	require.NoError(t, db.Model(&reposstore.ResourceRepository{}).
		Where("workspace_id = ? AND resource_id = ?", workflow.WorkspaceID, workflow.ID).
		Updates(map[string]any{"visibility": "private", "user_visible": false}).Error)
	now := svc.now()
	require.NoError(t, db.Create(&content.Authority{
		ID: "cta_official_workflow", WorkspaceID: workflow.WorkspaceID, Domain: skillContentDomain, BindingKey: workflow.ID,
		State: content.AuthorityStateNativeActive, Generation: 1,
		UpdatedBy: "operator", CreatedAt: now, UpdatedAt: now,
	}).Error)
	authorityStore := content.New(db)
	authority := &countingSkillAuthority{store: authorityStore}
	nativeContent := &fakeOfficialNativeSkillContent{}
	svc.contentAuthority = authority
	svc.nativeContent = nativeContent
	svc.nativeContentEnabled = true

	repositories.mu.Lock()
	repositories.requests = nil
	repositories.visibilityUpdates = nil
	repositories.deletes = nil
	repositories.mu.Unlock()
	legacyContent.mu.Lock()
	legacyContent.importRequests = nil
	legacyContent.mu.Unlock()

	materialized, err := svc.ensureOfficialSkillLibraries(ctx, "ws_1", presets.LocaleEnglish)
	require.NoError(t, err)
	require.EqualValues(t, 1, authority.listCalls.Load(), "fixed official inventory must use one authority batch")
	var nativeOfficial *officialSkillLibraryMaterialization
	for i := range materialized {
		if materialized[i].Skill.ID == workflow.ID {
			nativeOfficial = &materialized[i]
			break
		}
	}
	require.NotNil(t, nativeOfficial)
	require.Nil(t, nativeOfficial.Repository)
	nativeContent.mu.Lock()
	require.Len(t, nativeContent.imports, 1)
	nativeContent.mu.Unlock()
	repositories.mu.Lock()
	require.Empty(t, repositories.requests, "native official refresh must not ensure an AGS repository")
	require.Empty(t, repositories.visibilityUpdates, "native official refresh must not update AGS visibility")
	require.Empty(t, repositories.deletes)
	repositories.mu.Unlock()
	legacyContent.mu.Lock()
	require.Empty(t, legacyContent.importRequests, "native official refresh must not import repository content")
	legacyContent.mu.Unlock()

	var authorityRows int64
	require.NoError(t, db.Model(&content.Authority{}).Count(&authorityRows).Error)
	require.EqualValues(t, 1, authorityRows)
	_, err = svc.ensureOfficialSkillLibraries(ctx, "ws_1", presets.LocaleEnglish)
	require.NoError(t, err)
	require.EqualValues(t, 2, authority.listCalls.Load())
	require.NoError(t, db.Model(&content.Authority{}).Count(&authorityRows).Error)
	require.EqualValues(t, 1, authorityRows)

	retired := store.Skill{
		ID: "skill_retired_native", WorkspaceID: "ws_1", Slug: retiredTeamCollaborationSkillLibrarySlug,
		Name: "Retired native official", StorageScope: reposservice.RepositoryStorageScopeWorkspace,
		VisibilityScope: SkillVisibilityScopeWorkspace, VisibilityOwnerID: "ws_1", IsActive: true,
		ContentID: "ctn_retired", ActiveContentVersionID: "cv_retired",
		CreatedBy: officialSkillSystemSubject, UpdatedBy: officialSkillSystemSubject, CreatedAt: now, UpdatedAt: now,
	}
	_, err = svc.store.CreateSkill(ctx, retired)
	require.NoError(t, err)
	_, err = svc.repositoryStore.Upsert(ctx, reposstore.ResourceRepository{
		ID: "rr_retired_native", WorkspaceID: retired.WorkspaceID, ResourceType: reposstore.ResourceTypeSkillRepository,
		ResourceID: retired.ID, RepoRole: reposstore.RepoRolePrimary, OwnerKind: reposservice.RepositoryStorageScopeWorkspace,
		RepoOwner: retired.WorkspaceID, RepoName: retired.Slug + ".sk", RepoFullName: retired.WorkspaceID + "/" + retired.Slug + ".sk",
		AGSRepoID: "ags_retired_native", DefaultRef: "main", Visibility: "public", UserVisible: true,
		CreatedBy: officialSkillSystemSubject, UpdatedBy: officialSkillSystemSubject, CreatedAt: now, UpdatedAt: now,
	})
	require.NoError(t, err)
	require.NoError(t, db.Create(&content.Authority{
		ID: "cta_retired_native", WorkspaceID: retired.WorkspaceID, Domain: skillContentDomain, BindingKey: retired.ID,
		State: content.AuthorityStateNativeActive, Generation: 1,
		UpdatedBy: "operator", CreatedAt: now, UpdatedAt: now,
	}).Error)
	_, err = svc.CountWorkspaceSkills(ctx, "ws_1", "usr_1")
	require.NoError(t, err)
	repositories.mu.Lock()
	require.Empty(t, repositories.deletes, "native official retirement must not delete an AGS repository")
	repositories.mu.Unlock()
	_, err = svc.store.GetSkill(ctx, retired.WorkspaceID, retired.ID)
	require.ErrorIs(t, err, gorm.ErrRecordNotFound)

	_, err = svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)
	_, err = svc.ListAgentSkillBindings(ctx, "ws_1", "usr_1", AgentSkillBindingsOptions{})
	require.NoError(t, err)
	_, err = svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_1"}})
	require.NoError(t, err)
	repositories.mu.Lock()
	require.Empty(t, repositories.requests)
	require.Empty(t, repositories.visibilityUpdates)
	require.Empty(t, repositories.deletes)
	repositories.mu.Unlock()
	legacyContent.mu.Lock()
	require.Empty(t, legacyContent.importRequests)
	legacyContent.mu.Unlock()
	require.NoError(t, db.Model(&content.Authority{}).Count(&authorityRows).Error)
	require.EqualValues(t, 2, authorityRows, "official reads must not create authority rows for implicit legacy Skills")
}

func TestOfficialSkillLegacyImportDoesNotBlockOneWayActivation(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	svc.officialSkillLibrariesEnabled = true
	initialContent := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, initialContent)
	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)
	workflow := requireSkillBySlug(t, page.Items, workflowPlanAuthorLibrarySlugForTest)
	info, err := presets.OfficialSkillLibrary(workflowPlanAuthorLibrarySlugForTest, presets.LocaleEnglish)
	require.NoError(t, err)

	db := svc.store.DB()
	require.NoError(t, db.AutoMigrate(&content.Authority{}))
	now := svc.now()
	require.NoError(t, db.Create(&content.Authority{
		ID: "cta_official_legacy_barrier", WorkspaceID: workflow.WorkspaceID, Domain: skillContentDomain, BindingKey: workflow.ID,
		State: content.AuthorityStateLegacyActive, Generation: 1,
		UpdatedBy: "operator", CreatedAt: now, UpdatedAt: now,
	}).Error)
	authorityStore := content.New(db)
	authority := &countingSkillAuthority{store: authorityStore}
	svc.contentAuthority = authority

	inner := &fakeRepositoryContentManager{}
	configureOfficialImportResultsForTest(t, inner)
	blocking := newBlockingFirstImportContentManager(inner, workflowPlanAuthorArchiveNameForTest)
	svc.repositoryContent = blocking
	result := make(chan error, 1)
	go func() {
		_, mutateErr := svc.mutateOfficialSkillLibrary(ctx, workflow, info, true)
		result <- mutateErr
	}()
	select {
	case <-blocking.entered:
	case <-time.After(time.Second):
		t.Fatal("official legacy import did not reach the repository barrier")
	}
	exact, found, err := authorityStore.GetAuthority(ctx, workflow.WorkspaceID, skillContentDomain, workflow.ID)
	require.NoError(t, err)
	require.True(t, found)
	native, err := authorityStore.ActivateNativeAuthority(ctx, content.ActivateNativeAuthorityParams{
		WorkspaceID: exact.WorkspaceID, Domain: exact.Domain, BindingKey: exact.BindingKey,
		ExpectedGeneration: exact.Generation, Checkpoint: "official-import", Actor: "operator", Now: now,
	})
	require.NoError(t, err, "the best-effort cutover contract does not drain legacy writers")
	require.Equal(t, content.AuthorityStateNativeActive, native.State)
	close(blocking.release)
	require.NoError(t, <-result)
	exact, found, err = authorityStore.GetAuthority(ctx, workflow.WorkspaceID, skillContentDomain, workflow.ID)
	require.NoError(t, err)
	require.True(t, found)
	require.Equal(t, content.AuthorityStateNativeActive, exact.State)
}

func TestOfficialRefreshRevokesRetiredTeamLibraryWhenAGSDeleteFails(t *testing.T) {
	ctx := context.Background()
	svc, repositories := newSkillServiceForTest(t)
	now := svc.now()
	retired, err := svc.store.CreateSkill(ctx, store.Skill{
		ID:                "skill_retired_team_delete_failure",
		WorkspaceID:       "ws_1",
		Slug:              retiredTeamCollaborationSkillLibrarySlug,
		Name:              "Retired AgentTeam Team library",
		VisibilityScope:   SkillVisibilityScopeWorkspace,
		VisibilityOwnerID: "ws_1",
		IsActive:          true,
		CreatedBy:         officialSkillSystemSubject,
		UpdatedBy:         officialSkillSystemSubject,
		CreatedAt:         now,
		UpdatedAt:         now,
	})
	require.NoError(t, err)
	_, err = svc.repositoryStore.Upsert(ctx, reposstore.ResourceRepository{
		ID:           "rr_retired_team_delete_failure",
		WorkspaceID:  retired.WorkspaceID,
		ResourceType: reposstore.ResourceTypeSkillRepository,
		ResourceID:   retired.ID,
		RepoRole:     reposstore.RepoRolePrimary,
		OwnerKind:    reposservice.RepositoryStorageScopeWorkspace,
		RepoOwner:    retired.WorkspaceID,
		RepoName:     retiredTeamCollaborationSkillLibrarySlug + ".sk",
		RepoFullName: retired.WorkspaceID + "/" + retiredTeamCollaborationSkillLibrarySlug + ".sk",
		AGSRepoID:    "repo_retired_team_delete_failure",
		DefaultRef:   "main",
		CreatedBy:    officialSkillSystemSubject,
		UpdatedBy:    officialSkillSystemSubject,
		CreatedAt:    now,
		UpdatedAt:    now,
	})
	require.NoError(t, err)
	repositories.deleteErr = errors.New("ags unavailable")
	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)

	_, err = svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_1"}})
	require.NoError(t, err)
	_, err = svc.store.GetSkillBySlugAndCreator(ctx, "ws_1", retiredTeamCollaborationSkillLibrarySlug, officialSkillSystemSubject)
	require.ErrorIs(t, err, gorm.ErrRecordNotFound)
	repoRows, err := svc.repositoryStore.ListForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, retired.ID)
	require.NoError(t, err)
	require.Empty(t, repoRows)
	events, err := svc.repositoryStore.ListLifecycleEventsForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, retired.ID)
	require.NoError(t, err)
	require.Len(t, events, 1)
	require.Equal(t, reposstore.ResourceRepositoryLifecycleStatusFailed, events[0].Status)
	require.Contains(t, events[0].LastError, "ags unavailable")
}

func TestCountWorkspaceSkillsRetiresTeamLibrary(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	now := svc.now()
	_, err := svc.store.CreateSkill(ctx, store.Skill{
		ID:                "skill_retired_team_count",
		WorkspaceID:       "ws_1",
		Slug:              retiredTeamCollaborationSkillLibrarySlug,
		Name:              "Retired AgentTeam Team library",
		VisibilityScope:   SkillVisibilityScopeWorkspace,
		VisibilityOwnerID: "ws_1",
		IsActive:          true,
		CreatedBy:         officialSkillSystemSubject,
		UpdatedBy:         officialSkillSystemSubject,
		CreatedAt:         now,
		UpdatedAt:         now,
	})
	require.NoError(t, err)
	svc.officialSkillLibrariesEnabled = true

	total, err := svc.CountWorkspaceSkills(ctx, "ws_1", "usr_1")
	require.NoError(t, err)
	require.Zero(t, total)
	_, err = svc.store.GetSkillBySlugAndCreatorIncludingDeleted(
		ctx,
		"ws_1",
		retiredTeamCollaborationSkillLibrarySlug,
		officialSkillSystemSubject,
	)
	require.ErrorIs(t, err, gorm.ErrRecordNotFound)
}

func TestOfficialLibraryCoexistsWithPrivateSkillUsingSameSlug(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	privateSkill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{
		Name:             "My Workflow Plan Author",
		RepoNameOverride: workflowPlanAuthorLibrarySlugForTest,
	})
	require.NoError(t, err)
	require.Equal(t, SkillVisibilityScopePrivate, privateSkill.VisibilityScope)
	require.Equal(t, "usr_1", privateSkill.VisibilityOwnerID)

	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)
	require.Len(t, page.Items, officialLibraryCountForTest()+1)

	var matching []store.Skill
	for _, skill := range page.Items {
		if skill.Slug == workflowPlanAuthorLibrarySlugForTest {
			matching = append(matching, skill)
		}
	}
	require.Len(t, matching, 2)
	require.ElementsMatch(t, []string{"usr_1", "agentteam-system"}, []string{
		matching[0].CreatedBy,
		matching[1].CreatedBy,
	})

	official, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{
		Page:    1,
		PerPage: 20,
		View:    "official",
	})
	require.NoError(t, err)
	require.Len(t, official.Items, officialLibraryCountForTest())
	require.NotContains(t, skillIDsForTest(official.Items), privateSkill.ID)
}

func TestOfficialLibraryRejectsWorkspaceSlugCollisionBeforeRepositorySideEffects(t *testing.T) {
	ctx := context.Background()
	svc, repositories := newSkillServiceForTest(t)
	_, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{
		Name:             "Workspace Workflow Plan Author",
		RepoNameOverride: workflowPlanAuthorLibrarySlugForTest,
		StorageScope:     reposservice.RepositoryStorageScopeWorkspace,
		VisibilityScope:  SkillVisibilityScopeWorkspace,
	})
	require.NoError(t, err)

	repositories.requests = nil
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	content.initializeRequests = nil
	content.importRequests = nil
	configureOfficialImportResultsForTest(t, content)
	svc.officialSkillLibrariesEnabled = true

	_, _, err = svc.ensureOfficialSkillLibrary(
		ctx,
		"ws_1",
		presets.LocaleEnglish,
		officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest],
		nil,
	)
	require.ErrorIs(t, err, ErrConflict)
	require.Empty(t, repositories.requests)
	require.Empty(t, content.initializeRequests)
	require.Empty(t, content.importRequests)
}

func TestListSkillsEnsuresLocalizedOfficialSkillLibraries(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{
		Page:           1,
		PerPage:        20,
		TemplateLocale: "zh-Hans-CN",
	})

	require.NoError(t, err)
	require.Len(t, page.Items, officialLibraryCountForTest())
	workflowInfo, err := presets.OfficialSkillLibrary(workflowPlanAuthorLibrarySlugForTest, presets.LocaleSimplifiedChinese)
	require.NoError(t, err)
	workflowSkill := requireSkillBySlug(t, page.Items, workflowInfo.Slug)
	require.Equal(t, workflowInfo.Name, workflowSkill.Name)
	require.Equal(t, workflowInfo.Description, workflowSkill.Description)
	require.Equal(t, 1, workflowSkill.SkillCount)
	require.Empty(t, content.initializeRequests)
	require.Len(t, content.importRequests, officialLibraryCountForTest())
	for _, request := range content.importRequests {
		require.NotEmpty(t, request.SourceFilePath)
	}

	official, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{
		Page:           1,
		PerPage:        20,
		TemplateLocale: "zh-CN",
		View:           "official",
	})
	require.NoError(t, err)
	require.Len(t, official.Items, officialLibraryCountForTest())
	require.Contains(t, skillIDsForTest(official.Items), workflowSkill.ID)
	for _, skill := range official.Items {
		require.Equal(t, "view", skill.Role)
		require.False(t, skill.CanManage)
	}
}

func TestExistingOfficialSkillLibrariesKeepOriginalLocale(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)
	svc.officialSkillLibrariesEnabled = true

	created, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{
		Page:           1,
		PerPage:        20,
		TemplateLocale: "zh-CN",
	})
	require.NoError(t, err)
	require.Len(t, created.Items, officialLibraryCountForTest())
	zhWorkflowInfo, err := presets.OfficialSkillLibrary(workflowPlanAuthorLibrarySlugForTest, presets.LocaleSimplifiedChinese)
	require.NoError(t, err)
	createdWorkflow := requireSkillBySlug(t, created.Items, workflowPlanAuthorLibrarySlugForTest)
	require.Equal(t, zhWorkflowInfo.Name, createdWorkflow.Name)

	again, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{
		Page:           1,
		PerPage:        20,
		TemplateLocale: "ja",
	})

	require.NoError(t, err)
	require.Len(t, again.Items, officialLibraryCountForTest())
	againWorkflow := requireSkillBySlug(t, again.Items, workflowPlanAuthorLibrarySlugForTest)
	require.Equal(t, zhWorkflowInfo.Name, againWorkflow.Name)
	require.Equal(t, zhWorkflowInfo.Description, againWorkflow.Description)
	require.Len(t, content.importRequests, officialLibraryCountForTest())
}

func TestOfficialSkillLibrarySelfHealsWhenFingerprintChanges(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)
	svc.officialSkillLibrariesEnabled = true

	initial, _, err := svc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest], nil)
	require.NoError(t, err)
	require.Equal(t, 1, initial.SkillCount)
	require.NotEmpty(t, initial.OfficialSourceFingerprint)
	require.Len(t, content.importRequests, 1)

	stale, err := svc.store.UpdateOfficialSourceFingerprint(ctx, "ws_1", initial.ID, "sha256:stale", "agentteam-system", svc.now())
	require.NoError(t, err)
	require.Equal(t, initial.SkillCount, stale.SkillCount)
	content.importRequests = nil

	healed, _, err := svc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest], nil)
	require.NoError(t, err)
	require.Equal(t, initial.SkillCount, healed.SkillCount)
	require.Equal(t, initial.OfficialSourceFingerprint, healed.OfficialSourceFingerprint)
	require.Len(t, content.importRequests, 1)
	require.Equal(t, workflowPlanAuthorArchiveNameForTest, content.importRequests[0].SourceName)
}

func TestExistingOfficialLibraryIsPreservedWhenOtherLibrariesAreAdded(t *testing.T) {
	ctx := context.Background()
	svc, repositories := newSkillServiceForTest(t)
	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)

	definition := officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest]
	existingWorkflow, existingRepo, err := svc.ensureOfficialSkillLibrary(
		ctx,
		"ws_1",
		presets.LocaleSimplifiedChinese,
		definition,
		nil,
	)
	require.NoError(t, err)
	require.NotNil(t, existingRepo)
	require.Len(t, content.importRequests, 1)

	content.importRequests = nil
	content.initializeRequests = nil
	repositories.requests = nil

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{
		Page:           1,
		PerPage:        20,
		TemplateLocale: presets.LocaleJapanese,
	})
	require.NoError(t, err)
	require.Len(t, page.Items, officialLibraryCountForTest())

	workflow := requireSkillBySlug(t, page.Items, workflowPlanAuthorLibrarySlugForTest)
	require.Equal(t, existingWorkflow.ID, workflow.ID)
	require.Equal(t, existingWorkflow.Name, workflow.Name)
	require.Equal(t, existingWorkflow.Description, workflow.Description)
	require.Equal(t, existingRepo.RepoFullName, "ws_1/"+workflowPlanAuthorLibrarySlugForTest+".sk")

	superpowersInfo, err := presets.OfficialSkillLibrary(superpowersLibrarySlugForTest, presets.LocaleJapanese)
	require.NoError(t, err)
	superpowers := requireSkillBySlug(t, page.Items, superpowersInfo.Slug)
	require.Equal(t, superpowersInfo.Name, superpowers.Name)
	require.Equal(t, superpowersInfo.Description, superpowers.Description)

	require.Len(t, content.importRequests, officialLibraryCountForTest()-1)
	require.Contains(t, sourceNamesForImportRequests(content.importRequests), superpowersInfo.ArchiveName)
	require.Empty(t, content.initializeRequests)
	require.Len(t, repositories.requests, officialLibraryCountForTest()-1)
	require.Contains(t, repositoryNamesForEnsureRequests(repositories.requests), superpowersInfo.Slug+".sk")
}

func TestEnsureOfficialSkillLibraryCoalescesConcurrentMaterializationOnOneService(t *testing.T) {
	ctx := context.Background()
	db := newSkillServiceDBForTest(t)

	repositories := newRaceAwareRepositoryEnsurer()
	content := &fakeRepositoryContentManager{}
	configureOfficialImportResultsForTest(t, content)
	svc := newSkillServiceWithDBForTest(t, db, repositories, content)
	definition := officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest]

	const workers = 8
	start := make(chan struct{})
	var wg sync.WaitGroup
	skills := make([]store.Skill, workers)
	errs := make([]error, workers)
	for i := range workers {
		wg.Add(1)
		go func(index int) {
			defer wg.Done()
			<-start
			skill, _, err := svc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, definition, nil)
			skills[index] = skill
			errs[index] = err
		}(i)
	}
	close(start)
	wg.Wait()

	for _, err := range errs {
		require.NoError(t, err)
	}
	for _, skill := range skills {
		require.Equal(t, skills[0].ID, skill.ID)
		require.Equal(t, workflowPlanAuthorLibrarySlugForTest, skill.Slug)
		require.Equal(t, len(definition.SkillNames), skill.SkillCount)
	}
	stored, err := svc.store.GetSkillBySlugAndCreator(ctx, "ws_1", workflowPlanAuthorLibrarySlugForTest, definition.DefaultInfo.SystemSubject)
	require.NoError(t, err)
	require.Equal(t, skills[0].ID, stored.ID)
	repos, err := svc.repositoryStore.ListForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, stored.ID)
	require.NoError(t, err)
	require.Len(t, repos, 1)
	require.Equal(t, reposstore.RepoRolePrimary, repos[0].RepoRole)
	require.Equal(t, 1, repositories.repositoryCount())
	require.Len(t, repositories.requestSnapshot(), 1)
	require.Len(t, content.importRequests, 1)
}

func TestEnsureOfficialSkillLibraryConvergesAcrossServicesWhileWinnerImports(t *testing.T) {
	ctx := context.Background()
	db := newSkillServiceDBForTest(t)
	sqlDB, err := db.DB()
	require.NoError(t, err)
	sqlDB.SetMaxOpenConns(1)

	repositories := newRaceAwareRepositoryEnsurer()
	firstContent := &fakeRepositoryContentManager{}
	configureOfficialImportResultsForTest(t, firstContent)
	blockingContent := newBlockingFirstImportContentManager(firstContent, workflowPlanAuthorArchiveNameForTest)
	secondContent := &fakeRepositoryContentManager{}
	configureOfficialImportResultsForTest(t, secondContent)
	firstSvc := newSkillServiceWithDBForTest(t, db, repositories, blockingContent)
	secondSvc := newSkillServiceWithDBForTest(t, db, repositories, secondContent)
	definition := officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest]

	firstResult := make(chan struct {
		skill store.Skill
		err   error
	}, 1)
	go func() {
		skill, _, err := firstSvc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, definition, nil)
		firstResult <- struct {
			skill store.Skill
			err   error
		}{skill: skill, err: err}
	}()
	select {
	case <-blockingContent.entered:
	case <-time.After(time.Second):
		t.Fatal("first service did not reach the official import barrier")
	}

	secondResult := make(chan struct {
		skill store.Skill
		err   error
	}, 1)
	go func() {
		skill, _, err := secondSvc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, definition, nil)
		secondResult <- struct {
			skill store.Skill
			err   error
		}{skill: skill, err: err}
	}()
	select {
	case <-blockingContent.secondStarted:
		t.Fatal("second service imported instead of waiting for the winner to finish")
	case <-time.After(officialSkillLibraryReadyPollBackoff * 2):
	}
	close(blockingContent.release)

	var first struct {
		skill store.Skill
		err   error
	}
	select {
	case first = <-firstResult:
	case <-time.After(time.Second):
		t.Fatal("first service did not finish")
	}
	require.NoError(t, first.err)
	var second struct {
		skill store.Skill
		err   error
	}
	select {
	case second = <-secondResult:
	case <-time.After(time.Second):
		t.Fatal("second service did not converge")
	}
	require.NoError(t, second.err)
	require.Equal(t, first.skill.ID, second.skill.ID)
	require.Equal(t, len(definition.SkillNames), second.skill.SkillCount)
	require.NotEmpty(t, second.skill.OfficialSourceFingerprint)
	stored, err := firstSvc.store.GetSkillBySlugAndCreator(ctx, "ws_1", workflowPlanAuthorLibrarySlugForTest, definition.DefaultInfo.SystemSubject)
	require.NoError(t, err)
	repos, err := firstSvc.repositoryStore.ListForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, stored.ID)
	require.NoError(t, err)
	require.Len(t, repos, 1)
	require.Equal(t, 1, repositories.repositoryCount())
	require.Len(t, firstContent.importRequests, 1)
	require.Empty(t, secondContent.importRequests)
}

func TestEnsureOfficialSkillLibraryAdoptsExistingAGSRepositoryWhenDatabaseIsMissing(t *testing.T) {
	ctx := context.Background()
	db := newSkillServiceDBForTest(t)

	repositories := newRaceAwareRepositoryEnsurer()
	repositories.seedRepository("ws_1", workflowPlanAuthorLibrarySlugForTest+".sk")
	content := &fakeRepositoryContentManager{}
	configureOfficialImportResultsForTest(t, content)
	svc := newSkillServiceWithDBForTest(t, db, repositories, content)
	definition := officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest]

	skill, repo, err := svc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, definition, nil)

	require.NoError(t, err)
	require.Equal(t, workflowPlanAuthorLibrarySlugForTest, skill.Slug)
	require.Equal(t, len(definition.SkillNames), skill.SkillCount)
	require.NotNil(t, repo)
	require.Equal(t, "ws_1/"+workflowPlanAuthorLibrarySlugForTest+".sk", repo.RepoFullName)
	requests := repositories.requestSnapshot()
	require.Len(t, requests, 1)
	require.False(t, requests[0].RejectExisting)
	require.Empty(t, content.initializeRequests)
	require.Len(t, content.importRequests, 1)
	storedRepos, err := svc.repositoryStore.ListForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, skill.ID)
	require.NoError(t, err)
	require.Len(t, storedRepos, 1)
}

func TestEnsureOfficialSkillLibraryRetriesAfterInitialImportFailure(t *testing.T) {
	ctx := context.Background()
	db := newSkillServiceDBForTest(t)

	repositories := newRaceAwareRepositoryEnsurer()
	content := &fakeRepositoryContentManager{importErrors: []error{errors.New("official import unavailable")}}
	configureOfficialImportResultsForTest(t, content)
	svc := newSkillServiceWithDBForTest(t, db, repositories, content)
	definition := officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest]

	_, _, err := svc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, definition, nil)
	require.Error(t, err)
	require.Contains(t, err.Error(), "official import unavailable")
	incomplete, err := svc.store.GetSkillBySlugAndCreator(ctx, "ws_1", workflowPlanAuthorLibrarySlugForTest, definition.DefaultInfo.SystemSubject)
	require.NoError(t, err)
	require.Equal(t, 1, incomplete.SkillCount)
	require.Empty(t, incomplete.OfficialSourceFingerprint)
	repos, err := svc.repositoryStore.ListForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, incomplete.ID)
	require.NoError(t, err)
	require.Len(t, repos, 1)

	healed, _, err := svc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, definition, nil)

	require.NoError(t, err)
	require.Equal(t, incomplete.ID, healed.ID)
	require.Equal(t, len(definition.SkillNames), healed.SkillCount)
	require.NotEmpty(t, healed.OfficialSourceFingerprint)
	require.Len(t, content.importRequests, 2)
	require.Equal(t, 1, repositories.repositoryCount())
}

func TestEnsureOfficialSkillLibraryReturnsNonCompetitionRepositoryErrors(t *testing.T) {
	ctx := context.Background()
	db := newSkillServiceDBForTest(t)

	repositories := newRaceAwareRepositoryEnsurer()
	repositories.err = errors.New("ags unavailable")
	content := &fakeRepositoryContentManager{}
	configureOfficialImportResultsForTest(t, content)
	svc := newSkillServiceWithDBForTest(t, db, repositories, content)
	definition := officialSkillLibraryDefinitionsBySlug[workflowPlanAuthorLibrarySlugForTest]

	_, _, err := svc.ensureOfficialSkillLibrary(ctx, "ws_1", presets.LocaleEnglish, definition, nil)

	require.Error(t, err)
	require.Contains(t, err.Error(), "ags unavailable")
	_, loadErr := svc.store.GetSkillBySlugAndCreator(ctx, "ws_1", workflowPlanAuthorLibrarySlugForTest, definition.DefaultInfo.SystemSubject)
	require.ErrorIs(t, loadErr, gorm.ErrRecordNotFound)
	require.Empty(t, content.importRequests)
}

func TestListSkillRepositorySkillsRebuildsMissingWorkflowPlanAuthorRepository(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)
	content.scan = SkillRepositoryScanData{
		CommitSHA: "workflow-scan",
		Limit:     SkillRepositoryScanLimit,
		DetectedSkills: []SkillRepositoryDetectedSkill{{
			Name:        workflowPlanAuthorSkillNameForTest,
			Description: "Create or update a AgentTeam Workflow Plan",
			Path:        "skills/" + workflowPlanAuthorSkillNameForTest + "/SKILL.md",
			Layout:      SkillRepositoryLayoutTemplate,
			Status:      SkillRepositorySkillStatusValid,
			IssueCode:   SkillRepositoryIssueOK,
		}},
	}
	svc.officialSkillLibrariesEnabled = true

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{
		Page:           1,
		PerPage:        20,
		TemplateLocale: "zh-CN",
	})
	require.NoError(t, err)
	require.Len(t, page.Items, officialLibraryCountForTest())
	workflowSkill := requireSkillBySlug(t, page.Items, workflowPlanAuthorLibrarySlugForTest)

	_, err = svc.repositoryStore.DeleteForResource(ctx, "ws_1", reposstore.ResourceTypeSkillRepository, workflowSkill.ID)
	require.NoError(t, err)
	content.importRequests = nil
	content.listRequests = nil

	preview, err := svc.ListSkillRepositorySkills(ctx, "ws_1", "usr_1", workflowSkill.ID)
	require.NoError(t, err)
	require.Len(t, preview.DetectedSkills, 1)
	require.Equal(t, workflowPlanAuthorSkillNameForTest, preview.DetectedSkills[0].Name)
	require.Len(t, content.importRequests, 1)
	require.Equal(t, workflowPlanAuthorArchiveNameForTest, content.importRequests[0].SourceName)
	require.Len(t, content.listRequests, 1)
	require.Equal(t, "ws_1/"+workflowPlanAuthorLibrarySlugForTest+".sk", content.listRequests[0].FullName)
}

func TestListSkillRepositorySkillsRebuildsUnreachableWorkflowPlanAuthorRepository(t *testing.T) {
	testListSkillRepositorySkillsRebuildsWorkflowPlanAuthorRepository(
		t,
		codedInvalidArgument(SkillImportErrorRepositoryUnavailable, "repository is unreachable"),
	)
}

func TestListSkillRepositorySkillsRebuildsEmptyWorkflowPlanAuthorRepository(t *testing.T) {
	testListSkillRepositorySkillsRebuildsWorkflowPlanAuthorRepository(
		t,
		codedInvalidArgument(SkillImportErrorNoSkillsFound, "no valid skills found in import source"),
	)
}

func testListSkillRepositorySkillsRebuildsWorkflowPlanAuthorRepository(t *testing.T, firstListError error) {
	t.Helper()
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)
	content.scan = SkillRepositoryScanData{
		CommitSHA: "workflow-scan",
		Limit:     SkillRepositoryScanLimit,
		DetectedSkills: []SkillRepositoryDetectedSkill{{
			Name:        workflowPlanAuthorSkillNameForTest,
			Description: "Create or update a AgentTeam Workflow Plan",
			Path:        "skills/" + workflowPlanAuthorSkillNameForTest + "/SKILL.md",
			Layout:      SkillRepositoryLayoutTemplate,
			Status:      SkillRepositorySkillStatusValid,
			IssueCode:   SkillRepositoryIssueOK,
		}},
	}
	content.listErrors = []error{firstListError, nil}
	svc.officialSkillLibrariesEnabled = true
	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)
	workflowSkill := requireSkillBySlug(t, page.Items, workflowPlanAuthorLibrarySlugForTest)
	content.importRequests = nil

	preview, err := svc.ListSkillRepositorySkills(ctx, "ws_1", "usr_1", workflowSkill.ID)
	require.NoError(t, err)
	require.Len(t, preview.DetectedSkills, 1)
	require.Equal(t, workflowPlanAuthorSkillNameForTest, preview.DetectedSkills[0].Name)
	require.Len(t, content.importRequests, 1)
	require.Equal(t, workflowPlanAuthorArchiveNameForTest, content.importRequests[0].SourceName)
	require.Len(t, content.listRequests, 2)
	for _, request := range content.listRequests {
		require.Equal(t, "ws_1/"+workflowPlanAuthorLibrarySlugForTest+".sk", request.FullName)
	}
}

func TestBatchListAgentAccessibleSkillsIncludesOfficialLibraries(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)
	issuer := &fakeSkillRepositorySyncTokenIssuer{
		lease: reposservice.RepositorySyncTokenLease{
			Token:     "ags_rat_official_skills",
			ExpiresAt: time.Date(2026, 6, 18, 2, 0, 0, 0, time.UTC),
		},
	}
	svc.syncTokenIssuer = issuer

	result, err := svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_1"}})
	require.NoError(t, err)
	require.Equal(t, "ags_rat_official_skills", result.SyncToken)
	require.Len(t, result.Agents["agt_1"], 1)
	repositories := make([]string, 0, len(result.Agents["agt_1"]))
	for _, skill := range result.Agents["agt_1"] {
		require.Equal(t, "main", skill.Branch)
		require.NotNil(t, skill.Repository)
		repositories = append(repositories, skill.Repository.RepoFullName)
	}
	require.Equal(t, []string{"ws_1/" + workflowPlanAuthorLibrarySlugForTest + ".sk"}, repositories)
	workflowInfo, err := presets.OfficialSkillLibrary(workflowPlanAuthorLibrarySlugForTest, presets.LocaleEnglish)
	require.NoError(t, err)
	workflowSkill := requireAgentAccessibleSkillByRepository(t, result.Agents["agt_1"], "ws_1/"+workflowInfo.Slug+".sk")
	require.Equal(t, workflowInfo.Name, workflowSkill.Name)
	require.Equal(t, workflowInfo.Description, workflowSkill.Description)
	require.Len(t, issuer.requests, 1)
	require.ElementsMatch(t, repositories, issuer.requests[0].Repositories)
	require.ElementsMatch(t, []string{reposservice.RepositorySyncCapabilityRead, reposservice.RepositorySyncCapabilityClone}, issuer.requests[0].Capabilities)
}

func TestBatchListAgentAccessibleSkillsRequiresBindingForNonAutoOfficialLibraries(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)
	issuer := &fakeSkillRepositorySyncTokenIssuer{
		lease: reposservice.RepositorySyncTokenLease{
			Token:     "ags_rat_official",
			ExpiresAt: time.Date(2026, 6, 18, 2, 0, 0, 0, time.UTC),
		},
	}
	svc.syncTokenIssuer = issuer

	initial, err := svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_1"}})
	require.NoError(t, err)
	require.Len(t, initial.Agents["agt_1"], 1)
	require.NotContains(t, repositoriesForAgentAccessibleSkills(initial.Agents["agt_1"]), "ws_1/"+addyAgentSkillsLibrarySlugForTest+".sk")
	require.NotContains(t, sourceNamesForImportRequests(content.importRequests), addyAgentSkillsArchiveNameForTest)
	require.NotContains(t, sourceNamesForImportRequests(content.importRequests), superpowersArchiveNameForTest)

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)
	addySkill := requireSkillBySlug(t, page.Items, addyAgentSkillsLibrarySlugForTest)
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", addySkill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)

	bound, err := svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_1"}})
	require.NoError(t, err)
	repositories := repositoriesForAgentAccessibleSkills(bound.Agents["agt_1"])
	require.Len(t, bound.Agents["agt_1"], 2)
	require.Contains(t, repositories, "ws_1/"+workflowPlanAuthorLibrarySlugForTest+".sk")
	require.Contains(t, repositories, "ws_1/"+addyAgentSkillsLibrarySlugForTest+".sk")

	_, err = svc.UnbindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", addySkill.ID)
	require.NoError(t, err)
	unbound, err := svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_1"}})
	require.NoError(t, err)
	require.NotContains(t, repositoriesForAgentAccessibleSkills(unbound.Agents["agt_1"]), "ws_1/"+addyAgentSkillsLibrarySlugForTest+".sk")
}

func TestEmbeddedWorkflowPlanAuthorArchiveScansSkillAndReference(t *testing.T) {
	info, err := presets.OfficialSkillLibrary(workflowPlanAuthorLibrarySlugForTest, presets.LocaleEnglish)
	require.NoError(t, err)
	archivePath, cleanup, err := writeOfficialSkillsArchive(info)
	require.NoError(t, err)
	defer cleanup()

	extractDir := t.TempDir()
	warnings, err := extractSkillSource(archivePath, info.ArchiveName, extractDir)
	require.NoError(t, err)
	require.Empty(t, warnings)
	scan, err := ScanSkillRepository(extractDir, 20)
	require.NoError(t, err)
	require.False(t, scan.Truncated)
	require.Len(t, scan.DetectedSkills, 1)
	detected := scan.DetectedSkills[0]
	require.Equal(t, workflowPlanAuthorSkillNameForTest, detected.Name)
	require.Equal(t, SkillRepositorySkillStatusValid, detected.Status)
	require.Equal(t, "skills/"+workflowPlanAuthorSkillNameForTest+"/SKILL.md", detected.Path)
	require.ElementsMatch(t, []string{"description", "name"}, detected.Frontmatter)

	skillBody := readZipFileForTest(t, archivePath, "skills/"+workflowPlanAuthorSkillNameForTest+"/SKILL.md")
	require.Contains(t, skillBody, "references/complete-plan.yaml")
	reference := readZipFileForTest(t, archivePath, "skills/"+workflowPlanAuthorSkillNameForTest+"/references/complete-plan.yaml")
	require.Contains(t, reference, "apiVersion: loop.plan/v1")
	require.Contains(t, reference, "kind: PlanVersion")
}

func TestEmbeddedExternalOfficialSkillArchivesScanExpectedSkills(t *testing.T) {
	for _, librarySlug := range []string{
		superpowersLibrarySlugForTest,
		addyAgentSkillsLibrarySlugForTest,
	} {
		t.Run(librarySlug, func(t *testing.T) {
			info, err := presets.OfficialSkillLibrary(librarySlug, presets.LocaleEnglish)
			require.NoError(t, err)
			wantSlugs, err := presets.OfficialSkillSlugs(librarySlug)
			require.NoError(t, err)

			archivePath, cleanup, err := writeOfficialSkillsArchive(info)
			require.NoError(t, err)
			defer cleanup()

			extractDir := t.TempDir()
			warnings, err := extractSkillSource(archivePath, info.ArchiveName, extractDir)
			require.NoError(t, err)
			require.Empty(t, warnings)
			scan, err := ScanSkillRepository(extractDir, len(wantSlugs)+1)
			require.NoError(t, err)
			require.False(t, scan.Truncated)
			require.Len(t, scan.DetectedSkills, len(wantSlugs))

			paths := make([]string, 0, len(scan.DetectedSkills))
			for _, detected := range scan.DetectedSkills {
				require.Equal(t, SkillRepositorySkillStatusValid, detected.Status, detected.Path)
				require.Contains(t, detected.Frontmatter, "description", detected.Path)
				require.Contains(t, detected.Frontmatter, "name", detected.Path)
				paths = append(paths, detected.Path)
			}
			wantPaths := make([]string, 0, len(wantSlugs))
			for _, slug := range wantSlugs {
				wantPaths = append(wantPaths, "skills/"+slug+"/SKILL.md")
			}
			require.ElementsMatch(t, wantPaths, paths)
		})
	}
}

func readZipFileForTest(t *testing.T, archivePath, name string) string {
	t.Helper()
	reader, err := zip.OpenReader(archivePath)
	require.NoError(t, err)
	defer reader.Close()
	for _, file := range reader.File {
		if file.Name != name {
			continue
		}
		rc, err := file.Open()
		require.NoError(t, err)
		defer rc.Close()
		body, err := io.ReadAll(rc)
		require.NoError(t, err)
		return string(body)
	}
	t.Fatalf("zip entry %q not found", name)
	return ""
}

const (
	workflowPlanAuthorLibrarySlugForTest = "agentteam-workflow-plan-author-skills"
	workflowPlanAuthorArchiveNameForTest = "agentteam-workflow-plan-author-skills.zip"
	workflowPlanAuthorSkillNameForTest   = "workflow-plan-author"
	superpowersLibrarySlugForTest        = "superpowers"
	superpowersArchiveNameForTest        = "superpowers.zip"
	addyAgentSkillsLibrarySlugForTest    = "addy-agent-skills"
	addyAgentSkillsArchiveNameForTest    = "addy-agent-skills.zip"
)

func workflowPlanAuthorImportResultForTest() SkillRepositoryImportResult {
	return SkillRepositoryImportResult{
		CommitSHA:  "workflow123",
		SkillCount: 1,
		ImportedSkills: []SkillRepositoryDetectedSkill{{
			Name:        workflowPlanAuthorSkillNameForTest,
			Description: "Create or update a AgentTeam Workflow Plan",
			Path:        workflowPlanAuthorSkillNameForTest + "/SKILL.md",
			Layout:      SkillRepositoryLayoutCollection,
			Status:      SkillRepositorySkillStatusValid,
			IssueCode:   SkillRepositoryIssueOK,
		}},
	}
}

func officialLibraryImportResultForTest(t *testing.T, librarySlug, archiveName string) SkillRepositoryImportResult {
	t.Helper()
	slugs, err := presets.OfficialSkillSlugs(librarySlug)
	require.NoError(t, err)
	imported := make([]SkillRepositoryDetectedSkill, 0, len(slugs))
	for _, slug := range slugs {
		imported = append(imported, SkillRepositoryDetectedSkill{
			Name:        slug,
			Description: "Official skill",
			Path:        "skills/" + slug + "/SKILL.md",
			Layout:      SkillRepositoryLayoutTemplate,
			Status:      SkillRepositorySkillStatusValid,
			IssueCode:   SkillRepositoryIssueOK,
		})
	}
	return SkillRepositoryImportResult{CommitSHA: archiveName + "-commit", SkillCount: len(slugs), ImportedSkills: imported}
}

func configureOfficialImportResultsForTest(t *testing.T, content *fakeRepositoryContentManager) {
	t.Helper()
	content.importResults = map[string]SkillRepositoryImportResult{
		workflowPlanAuthorArchiveNameForTest: workflowPlanAuthorImportResultForTest(),
		superpowersArchiveNameForTest: officialLibraryImportResultForTest(
			t,
			superpowersLibrarySlugForTest,
			superpowersArchiveNameForTest,
		),
		addyAgentSkillsArchiveNameForTest: officialLibraryImportResultForTest(
			t,
			addyAgentSkillsLibrarySlugForTest,
			addyAgentSkillsArchiveNameForTest,
		),
	}
}

func officialLibraryCountForTest() int {
	return len(officialSkillLibraryDefinitions)
}

func sourceNamesForImportRequests(requests []SkillRepositoryImportRequest) []string {
	names := make([]string, 0, len(requests))
	for _, request := range requests {
		names = append(names, request.SourceName)
	}
	return names
}

func repositoryNamesForEnsureRequests(requests []reposservice.AGSRepositoryEnsureRequest) []string {
	names := make([]string, 0, len(requests))
	for _, request := range requests {
		names = append(names, request.Name)
	}
	return names
}

func requireSkillBySlug(t *testing.T, skills []store.Skill, slug string) store.Skill {
	t.Helper()
	for _, skill := range skills {
		if skill.Slug == slug {
			return skill
		}
	}
	t.Fatalf("skill with slug %q not found", slug)
	return store.Skill{}
}

func skillIDsForTest(skills []store.Skill) []string {
	ids := make([]string, 0, len(skills))
	for _, skill := range skills {
		ids = append(ids, skill.ID)
	}
	return ids
}

func requireAgentAccessibleSkillByRepository(t *testing.T, skills []AgentAccessibleSkill, fullName string) AgentAccessibleSkill {
	t.Helper()
	for _, skill := range skills {
		if skill.Repository != nil && skill.Repository.RepoFullName == fullName {
			return skill
		}
	}
	t.Fatalf("agent-accessible skill repository %q not found", fullName)
	return AgentAccessibleSkill{}
}

func repositoriesForAgentAccessibleSkills(skills []AgentAccessibleSkill) []string {
	repositories := make([]string, 0, len(skills))
	for _, skill := range skills {
		if skill.Repository != nil {
			repositories = append(repositories, skill.Repository.RepoFullName)
		}
	}
	return repositories
}

func TestSkillVisibilityDefaultsToNone(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	owned, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Owned"})
	require.NoError(t, err)
	hidden, err := svc.CreateSkill(ctx, "ws_1", "usr_2", SkillCreateMutation{Name: "Hidden"})
	require.NoError(t, err)
	granted, err := svc.CreateSkill(ctx, "ws_1", "usr_2", SkillCreateMutation{Name: "Granted"})
	require.NoError(t, err)
	_, err = svc.UpsertSkillPermission(ctx, "ws_1", "usr_2", granted.ID, SkillPermissionGrantMutation{SubjectType: "user", SubjectID: "usr_1", Role: "view"})
	require.NoError(t, err)

	page, err := svc.ListSkills(ctx, "ws_1", "usr_1", SkillListOptions{Page: 1, PerPage: 20})
	require.NoError(t, err)
	require.Len(t, page.Items, 2)
	require.ElementsMatch(t, []string{owned.ID, granted.ID}, []string{page.Items[0].ID, page.Items[1].ID})
	require.Equal(t, int64(2), page.Total)

	visible, err := svc.GetSkill(ctx, "ws_1", "usr_1", granted.ID)
	require.NoError(t, err)
	require.Equal(t, "view", visible.Role)
	require.False(t, visible.CanManage)

	_, err = svc.GetSkill(ctx, "ws_1", "usr_1", hidden.ID)
	require.ErrorIs(t, err, ErrNotFound)
}

func TestListSkillRepositorySkillsUsesPrimaryRepository(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)

	preview, err := svc.ListSkillRepositorySkills(ctx, "ws_1", "usr_1", skill.ID)
	require.NoError(t, err)
	require.Len(t, preview.DetectedSkills, 1)
	require.Equal(t, "skills/backend/SKILL.md", preview.DetectedSkills[0].Path)

	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	require.Len(t, content.listRequests, 1)
	require.Equal(t, "usr_1/reviewer.sk", content.listRequests[0].FullName)
	require.Equal(t, "main", content.listRequests[0].DefaultBranch)
}

func TestReadSkillRepositorySkillsUsesViewGrantAndPrimaryRepository(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_owner", SkillCreateMutation{Name: "Reviewer", VisibilityScope: SkillVisibilityScopeWorkspace})
	require.NoError(t, err)

	content, err := svc.ReadSkillRepositorySkills(ctx, "ws_1", "usr_viewer", skill.ID, SkillRepositoryContentsRequest{
		ExpectedCommitSHA: "abc123",
		Paths:             []string{"skills/backend/SKILL.md"},
	})
	require.NoError(t, err)
	require.Equal(t, "skill content", content.Items[0].Content)

	repositoryContent := svc.repositoryContent.(*fakeRepositoryContentManager)
	require.Equal(t, []string{"skills/backend/SKILL.md"}, repositoryContent.readRequests[0].Paths)
	require.Equal(t, "usr_owner/reviewer.sk", repositoryContent.readRepositories[0].FullName)
}

func TestReadSkillRepositorySkillsRejectsOutsiderBeforeRepositoryRead(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_owner", SkillCreateMutation{Name: "Private"})
	require.NoError(t, err)

	_, err = svc.ReadSkillRepositorySkills(ctx, "ws_1", "usr_outsider", skill.ID, SkillRepositoryContentsRequest{
		ExpectedCommitSHA: "abc123",
		Paths:             []string{"skills/backend/SKILL.md"},
	})
	require.ErrorIs(t, err, ErrNotFound)
	require.Empty(t, svc.repositoryContent.(*fakeRepositoryContentManager).readRequests)
}

func TestImportSkillContentUsesPrimaryRepositoryAndUpdatesSkillCount(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)

	result, err := svc.ImportSkillContent(ctx, "ws_1", "usr_1", skill.ID, SkillContentImportMutation{SourceFilePath: "/tmp/skills.zip", SourceName: "skills.zip"})
	require.NoError(t, err)
	require.Equal(t, "def456", result.CommitSHA)
	require.Equal(t, 2, result.SkillCount)
	require.Equal(t, 2, result.Skill.SkillCount)
	require.Len(t, result.ImportedSkills, 2)

	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	require.Len(t, content.importRequests, 1)
	require.Equal(t, "usr_1/reviewer.sk", content.importRequests[0].TargetRepository.FullName)
	require.Equal(t, "/tmp/skills.zip", content.importRequests[0].SourceFilePath)
	require.Equal(t, "skills.zip", content.importRequests[0].SourceName)
	require.Equal(t, "Import skills from skills.zip", content.importRequests[0].Message)
}

func TestBatchListAgentAccessibleSkillsIssuesReadCloneSyncLease(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	issuer := &fakeSkillRepositorySyncTokenIssuer{
		lease: reposservice.RepositorySyncTokenLease{
			Token:     "ags_rat_skill",
			ExpiresAt: time.Date(2026, 6, 18, 2, 0, 0, 0, time.UTC),
		},
	}
	svc.syncTokenIssuer = issuer
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)

	result, err := svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_1"}})
	require.NoError(t, err)
	require.Equal(t, "ags_rat_skill", result.SyncToken)
	require.Equal(t, issuer.lease.ExpiresAt, result.SyncTokenExpires)
	require.Len(t, result.Agents["agt_1"], 1)
	require.Len(t, issuer.requests, 1)
	require.ElementsMatch(t, []string{"usr_1/reviewer.sk"}, issuer.requests[0].Repositories)
	require.ElementsMatch(t, []string{reposservice.RepositorySyncCapabilityRead, reposservice.RepositorySyncCapabilityClone}, issuer.requests[0].Capabilities)
	require.NotContains(t, issuer.requests[0].Capabilities, reposservice.RepositorySyncCapabilityWrite)
}

// TestBatchListAgentAccessibleSkillsRejectsNonOwnedAgent locks in the D-2
// fix on the skill side: if any of the requested agent IDs is not owned by
// the caller, the whole batch request is rejected with ErrForbidden.
func TestBatchListAgentAccessibleSkillsRejectsNonOwnedAgent(t *testing.T) {
	ctx := context.Background()
	svc, _ := newSkillServiceForTest(t)
	skill, err := svc.CreateSkill(ctx, "ws_1", "usr_1", SkillCreateMutation{Name: "Reviewer"})
	require.NoError(t, err)
	_, err = svc.BindAgentSkill(ctx, "ws_1", "usr_1", "agt_1", skill.ID, AgentSkillBindingMutation{Branch: "main"})
	require.NoError(t, err)

	// Caller owns agt_1 only — agt_2 must be rejected even though it has no
	// binding in this workspace.
	svc.agentOwnershipSource = fakeSkillAgentOwnershipSource{agents: []reposservice.AgentOwnership{{ID: "agt_1", Name: "one"}}}
	_, err = svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_1"}})
	require.NoError(t, err)
	_, err = svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_1", "agt_2"}})
	require.ErrorIs(t, err, ErrForbidden)
	_, err = svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{AgentIDs: []string{"agt_2"}})
	require.ErrorIs(t, err, ErrForbidden)
}

func TestBatchListAgentAccessibleSkillsRejectsBeforeOfficialLibraryMaterialization(t *testing.T) {
	ctx := context.Background()
	svc, repositories := newSkillServiceForTest(t)
	svc.officialSkillLibrariesEnabled = true
	content := svc.repositoryContent.(*fakeRepositoryContentManager)
	configureOfficialImportResultsForTest(t, content)
	svc.agentOwnershipSource = fakeSkillAgentOwnershipSource{
		agents: []reposservice.AgentOwnership{{ID: "agt_owned", Name: "owned"}},
	}

	_, err := svc.BatchListAgentAccessibleSkills(ctx, "ws_1", "usr_1", AgentSkillAccessBatchOptions{
		AgentIDs: []string{"agt_not_owned"},
	})
	require.ErrorIs(t, err, ErrForbidden)
	require.Empty(t, repositories.requests)
	require.Empty(t, content.initializeRequests)
	require.Empty(t, content.importRequests)

	page, err := svc.store.ListSkills(ctx, store.SkillListOptions{
		WorkspaceID: "ws_1",
		SubjectID:   "agentteam-system",
		Page:        1,
		PerPage:     20,
	})
	require.NoError(t, err)
	require.Empty(t, page.Items)
}
