package kb

import (
	"context"
	"encoding/json"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/identity/trusted"
	reposservice "github.com/tidbcloud/AgentTeam/internal/service/resource/repos"
	"github.com/tidbcloud/AgentTeam/internal/store/kb"
	reposstore "github.com/tidbcloud/AgentTeam/internal/store/resource/repos"
)

type repositoryAccessReconcileOptions struct {
	AllowExistingDirectUpdate bool
}

type repositoryAccessRepositoryOptions struct {
	AllowExistingDirectUpdateGrantID string
}

type repositoryAccessDesiredRow struct {
	Repo          reposstore.ResourceRepository
	Permission    string
	IntentProfile string
}

func (s *KBService) reconcileKnowledgeBaseGrantRepositoryAccess(ctx context.Context, base kb.KnowledgeBase, grant kb.KnowledgeBaseGrant, actor string) error {
	repos, err := s.listKnowledgeBaseSourceRepositoryMappings(ctx, base.WorkspaceID, base.ID)
	if err != nil {
		return err
	}
	return s.reconcileKnowledgeBaseGrantRepositoryAccessForRepositories(ctx, grant, repos, actor)
}

func (s *KBService) reconcileKnowledgeBaseRepositoryAccessForRepositories(ctx context.Context, base kb.KnowledgeBase, repos []reposstore.ResourceRepository, actor string, options ...repositoryAccessRepositoryOptions) error {
	if s == nil || s.store == nil {
		return nil
	}
	grants, err := s.store.ListKnowledgeBaseGrants(ctx, base.WorkspaceID, base.ID)
	if err != nil {
		return err
	}
	allowExistingDirectUpdateGrantID := ""
	if len(options) > 0 {
		allowExistingDirectUpdateGrantID = strings.TrimSpace(options[0].AllowExistingDirectUpdateGrantID)
	}
	for _, grant := range grants {
		grantOptions := repositoryAccessReconcileOptions{AllowExistingDirectUpdate: strings.TrimSpace(grant.ID) != "" && strings.TrimSpace(grant.ID) == allowExistingDirectUpdateGrantID}
		if err := s.reconcileKnowledgeBaseGrantRepositoryAccessForRepositories(ctx, grant, repos, actor, grantOptions); err != nil {
			return err
		}
	}
	return nil
}

func (s *KBService) reconcileKnowledgeBaseGrantRepositoryAccessForRepositories(ctx context.Context, grant kb.KnowledgeBaseGrant, repos []reposstore.ResourceRepository, actor string, options ...repositoryAccessReconcileOptions) error {
	if s == nil || s.repositoryStore == nil {
		return nil
	}
	reconcileOptions := repositoryAccessReconcileOptions{}
	if len(options) > 0 {
		reconcileOptions = options[0]
	}
	desiredRows, target, err := s.desiredRepositoryAccessRowsForGrant(ctx, grant, repos)
	if err != nil {
		return err
	}
	return s.reconcileDesiredRepositoryAccessRowsByProfile(ctx, grant, desiredRows, actor, target, reconcileOptions)
}

func (s *KBService) desiredRepositoryAccessRowsForGrant(ctx context.Context, grant kb.KnowledgeBaseGrant, repos []reposstore.ResourceRepository) ([]repositoryAccessDesiredRow, reposservice.ResourceRepositoryActor, error) {
	switch grant.SubjectType {
	case "user":
		target := reposservice.ResourceRepositoryActorFromAgentTeam(trusted.Identity{Subject: strings.TrimSpace(grant.SubjectID)})
		desiredRows, err := s.desiredHumanRepositoryAccessRows(ctx, grant, repos)
		if err != nil {
			return nil, reposservice.ResourceRepositoryActor{}, err
		}
		return desiredRows, target, nil
	case "agent":
		profile := kb.NormalizeKnowledgeBaseRepositoryAccessProfile(grant.RepositoryAccessProfile)
		if profile == kb.KnowledgeBaseRepositoryAccessProfileNone {
			profile = kb.KnowledgeBaseRepositoryAccessProfileRead
		}
		permission := repositoryPermissionForAccessProfile(profile)
		if permission == "" {
			permission = reposstore.AccessPermissionRead
			profile = kb.KnowledgeBaseRepositoryAccessProfileRead
		}
		eligibleRepos := eligibleAgentRepositoryAccessRepos(repos)
		desiredRows := make([]repositoryAccessDesiredRow, 0, len(eligibleRepos))
		for _, repo := range eligibleRepos {
			desiredRows = append(desiredRows, repositoryAccessDesiredRow{Repo: repo, Permission: permission, IntentProfile: profile})
		}
		return desiredRows, resourceRepositoryActorForAgent(grant.SubjectID), nil
	default:
		return nil, reposservice.ResourceRepositoryActor{}, nil
	}
}

func (s *KBService) desiredHumanRepositoryAccessRows(ctx context.Context, grant kb.KnowledgeBaseGrant, repos []reposstore.ResourceRepository) ([]repositoryAccessDesiredRow, error) {
	profile := kb.NormalizeKnowledgeBaseRepositoryAccessProfile(grant.RepositoryAccessProfile)
	overrides, err := s.store.ListKnowledgeBaseSourceRepositoryAccessOverrides(ctx, grant.WorkspaceID, grant.KnowledgeBaseID, []string{grant.ID})
	if err != nil {
		return nil, err
	}
	overrideBySourceRepo := make(map[string]string, len(overrides))
	for _, row := range overrides {
		overrideBySourceRepo[repositoryOverrideKey(row.SourceID, row.RepoRole)] = kb.NormalizeKnowledgeBaseRepositoryAccessProfile(row.RepositoryAccessProfile)
	}
	eligibleRepos := eligibleHumanRepositoryAccessRepos(repos)
	desiredRows := make([]repositoryAccessDesiredRow, 0, len(eligibleRepos))
	for _, repo := range eligibleRepos {
		effectiveProfile := profile
		if overrideProfile, ok := overrideBySourceRepo[repositoryOverrideKey(repo.ResourceID, repo.RepoRole)]; ok {
			effectiveProfile = overrideProfile
		}
		if effectiveProfile == kb.KnowledgeBaseRepositoryAccessProfileNone {
			continue
		}
		permission := repositoryPermissionForAccessProfile(effectiveProfile)
		if permission == "" {
			continue
		}
		desiredRows = append(desiredRows, repositoryAccessDesiredRow{Repo: repo, Permission: permission, IntentProfile: effectiveProfile})
	}
	return desiredRows, nil
}

func (s *KBService) reconcileDesiredRepositoryAccessRows(ctx context.Context, grant kb.KnowledgeBaseGrant, desiredRepos []reposstore.ResourceRepository, permission, intentProfile, actor string, target reposservice.ResourceRepositoryActor, options ...repositoryAccessReconcileOptions) error {
	desiredRows := make([]repositoryAccessDesiredRow, 0, len(desiredRepos))
	for _, repo := range desiredRepos {
		desiredRows = append(desiredRows, repositoryAccessDesiredRow{Repo: repo, Permission: permission, IntentProfile: intentProfile})
	}
	return s.reconcileDesiredRepositoryAccessRowsByProfile(ctx, grant, desiredRows, actor, target, options...)
}

func (s *KBService) repairKnowledgeBaseRepositoryAccessForRepositories(ctx context.Context, base kb.KnowledgeBase, repos []reposstore.ResourceRepository, actor string, options ...repositoryAccessRepositoryOptions) error {
	if s == nil || s.store == nil {
		return nil
	}
	grants, err := s.store.ListKnowledgeBaseGrants(ctx, base.WorkspaceID, base.ID)
	if err != nil {
		return err
	}
	allowExistingDirectUpdateGrantID := ""
	if len(options) > 0 {
		allowExistingDirectUpdateGrantID = strings.TrimSpace(options[0].AllowExistingDirectUpdateGrantID)
	}
	for _, grant := range grants {
		grantOptions := repositoryAccessReconcileOptions{AllowExistingDirectUpdate: strings.TrimSpace(grant.ID) != "" && strings.TrimSpace(grant.ID) == allowExistingDirectUpdateGrantID}
		if err := s.repairKnowledgeBaseGrantRepositoryAccessForRepositories(ctx, grant, repos, actor, grantOptions); err != nil {
			return err
		}
	}
	return nil
}

func (s *KBService) repairKnowledgeBaseGrantRepositoryAccessForRepositories(ctx context.Context, grant kb.KnowledgeBaseGrant, repos []reposstore.ResourceRepository, actor string, options ...repositoryAccessReconcileOptions) error {
	if s == nil || s.repositoryStore == nil || len(repos) == 0 {
		return nil
	}
	repairOptions := repositoryAccessReconcileOptions{}
	if len(options) > 0 {
		repairOptions = options[0]
	}
	desiredRows, target, err := s.desiredRepositoryAccessRowsForGrant(ctx, grant, repos)
	if err != nil {
		return err
	}
	repairRows, err := s.repositoryAccessRowsNeedingRepair(ctx, grant, desiredRows)
	if err != nil {
		return err
	}
	return s.repairDesiredRepositoryAccessRowsByProfile(ctx, grant, repairRows, actor, target, repairOptions)
}

func (s *KBService) repositoryAccessRowsNeedingRepair(ctx context.Context, grant kb.KnowledgeBaseGrant, desiredRows []repositoryAccessDesiredRow) ([]repositoryAccessDesiredRow, error) {
	if s == nil || s.repositoryStore == nil || len(desiredRows) == 0 {
		return nil, nil
	}
	existing, err := s.repositoryStore.ListAccessGrantsForIntent(ctx, grant.WorkspaceID, reposstore.AccessIntentTypeKBGrant, grant.ID)
	if err != nil {
		return nil, err
	}
	existingByID := make(map[string]reposstore.ResourceRepositoryAccessGrant, len(existing))
	for _, row := range existing {
		existingByID[row.ID] = row
	}
	out := make([]repositoryAccessDesiredRow, 0, len(desiredRows))
	for _, desired := range desiredRows {
		repo := desired.Repo
		rowID := reposstore.ResourceRepositoryAccessGrantID(repo.WorkspaceID, repo.ResourceType, repo.ResourceID, repo.RepoRole, grant.SubjectType, grant.SubjectID, reposstore.AccessIntentTypeKBGrant, grant.ID)
		row, ok := existingByID[rowID]
		if !ok || !row.Materialized || strings.TrimSpace(row.Status) != reposstore.AccessStatusCurrent || repositoryAccessPermissionRank(row.Permission) < repositoryAccessPermissionRank(desired.Permission) {
			out = append(out, desired)
		}
	}
	return out, nil
}

func (s *KBService) repairDesiredRepositoryAccessRowsByProfile(ctx context.Context, grant kb.KnowledgeBaseGrant, desiredRows []repositoryAccessDesiredRow, actor string, target reposservice.ResourceRepositoryActor, options ...repositoryAccessReconcileOptions) error {
	repairOptions := repositoryAccessReconcileOptions{}
	if len(options) > 0 {
		repairOptions = options[0]
	}
	native, err := s.nativeRepositoryAccessSourceIDs(ctx, grant.WorkspaceID, desiredRows, nil)
	if err != nil {
		return err
	}
	for _, desired := range desiredRows {
		if _, isNative := native[desired.Repo.ResourceID]; isNative {
			if err := s.applyNativeRepositoryAccessGrant(ctx, grant, desired, actor); err != nil {
				return err
			}
			continue
		}
		if err := s.applyRepositoryAccessGrant(ctx, grant, desired.Repo, desired.Permission, desired.IntentProfile, actor, target, repairOptions); err != nil {
			return err
		}
	}
	return nil
}

func (s *KBService) reconcileDesiredRepositoryAccessRowsByProfile(ctx context.Context, grant kb.KnowledgeBaseGrant, desiredRows []repositoryAccessDesiredRow, actor string, target reposservice.ResourceRepositoryActor, options ...repositoryAccessReconcileOptions) error {
	reconcileOptions := repositoryAccessReconcileOptions{}
	if len(options) > 0 {
		reconcileOptions = options[0]
	}
	existing, err := s.repositoryStore.ListAccessGrantsForIntent(ctx, grant.WorkspaceID, reposstore.AccessIntentTypeKBGrant, grant.ID)
	if err != nil {
		return err
	}
	native, err := s.nativeRepositoryAccessSourceIDs(ctx, grant.WorkspaceID, desiredRows, existing)
	if err != nil {
		return err
	}
	desiredIDs := make(map[string]struct{}, len(desiredRows))
	for _, desired := range desiredRows {
		repo := desired.Repo
		rowID := reposstore.ResourceRepositoryAccessGrantID(repo.WorkspaceID, repo.ResourceType, repo.ResourceID, repo.RepoRole, grant.SubjectType, grant.SubjectID, reposstore.AccessIntentTypeKBGrant, grant.ID)
		desiredIDs[rowID] = struct{}{}
		if _, isNative := native[repo.ResourceID]; isNative {
			if err := s.applyNativeRepositoryAccessGrant(ctx, grant, desired, actor); err != nil {
				return err
			}
			continue
		}
		if err := s.applyRepositoryAccessGrant(ctx, grant, repo, desired.Permission, desired.IntentProfile, actor, target, reconcileOptions); err != nil {
			return err
		}
	}
	for _, row := range existing {
		if _, keep := desiredIDs[row.ID]; keep || row.Status == reposstore.AccessStatusRevoked {
			continue
		}
		if _, isNative := native[row.ResourceID]; isNative {
			if err := s.revokeNativeRepositoryAccessRow(ctx, grant, row, actor); err != nil {
				return err
			}
			continue
		}
		if err := s.revokeRepositoryAccessRow(ctx, grant, row, actor); err != nil {
			return err
		}
	}
	return nil
}

// Native source access keeps the existing product projection rows current so
// retrieval and source-specific overrides retain their semantics, but it does
// not materialize or mutate an AGS collaborator after authority cutover.
func (s *KBService) applyNativeRepositoryAccessGrant(ctx context.Context, grant kb.KnowledgeBaseGrant, desired repositoryAccessDesiredRow, actor string) error {
	now := s.now()
	repo := desired.Repo
	actor = firstNonEmptyString(actor, grant.UpdatedBy, grant.CreatedBy)
	row, err := s.repositoryStore.UpsertAccessGrant(ctx, reposstore.ResourceRepositoryAccessGrant{
		WorkspaceID: repo.WorkspaceID, ResourceType: repo.ResourceType, ResourceID: repo.ResourceID,
		RepoRole: repo.RepoRole, RepoFullName: repo.RepoFullName, AGSRepoID: repo.AGSRepoID,
		SubjectType: grant.SubjectType, SubjectID: grant.SubjectID, Permission: desired.Permission,
		IntentType: reposstore.AccessIntentTypeKBGrant, IntentID: grant.ID, IntentProfile: desired.IntentProfile,
		Materialized: true, Status: reposstore.AccessStatusCurrent,
		CreatedBy: actor, UpdatedBy: actor, ReconciledBy: actor,
		CreatedAt: now, UpdatedAt: now, LastReconciledAt: &now,
	})
	if err != nil {
		return err
	}
	return s.auditRepositoryAccessReconciliation(ctx, grant, row, actor)
}

func (s *KBService) revokeNativeRepositoryAccessRow(ctx context.Context, grant kb.KnowledgeBaseGrant, row reposstore.ResourceRepositoryAccessGrant, actor string) error {
	now := s.now()
	actor = firstNonEmptyString(actor, row.UpdatedBy, row.CreatedBy)
	row.Materialized = false
	row.Status = reposstore.AccessStatusRevoked
	row.LastError = ""
	row.UpdatedBy = actor
	row.ReconciledBy = actor
	row.RevokedBy = actor
	row.UpdatedAt = now
	row.LastReconciledAt = &now
	row.RevokedAt = &now
	stored, err := s.repositoryStore.UpsertAccessGrant(ctx, row)
	if err != nil {
		return err
	}
	return s.auditRepositoryAccessReconciliation(ctx, grant, stored, actor)
}

func (s *KBService) nativeRepositoryAccessSourceIDs(ctx context.Context, workspaceID string, desiredRows []repositoryAccessDesiredRow, existing []reposstore.ResourceRepositoryAccessGrant) (map[string]struct{}, error) {
	sourceIDs := make([]string, 0, len(desiredRows)+len(existing))
	for _, desired := range desiredRows {
		if desired.Repo.ResourceType == reposstore.ResourceTypeKBSource {
			sourceIDs = append(sourceIDs, desired.Repo.ResourceID)
		}
	}
	for _, row := range existing {
		if row.ResourceType == reposstore.ResourceTypeKBSource {
			sourceIDs = append(sourceIDs, row.ResourceID)
		}
	}
	return s.kbSourceNativeBindings(ctx, workspaceID, sourceIDs)
}
func (s *KBService) reconcileRepositoryAccessAfterSourceCreate(ctx context.Context, base kb.KnowledgeBase, source kb.Source, sourceRepositories []reposstore.ResourceRepository, actor string) error {
	creatorGrant, previousCreatorProfile, creatorProfileGranted, err := s.ensureSourceCreatorRepositoryAccessIntent(ctx, base, actor)
	if err != nil {
		return err
	}
	creatorGrantID := ""
	if kb.NormalizeKnowledgeBaseRepositoryAccessProfile(creatorGrant.RepositoryAccessProfile) != kb.KnowledgeBaseRepositoryAccessProfileNone {
		creatorGrantID = strings.TrimSpace(creatorGrant.ID)
	}
	repositoryOptions := repositoryAccessRepositoryOptions{AllowExistingDirectUpdateGrantID: creatorGrantID}
	if err := s.repairKnowledgeBaseRepositoryAccessForRepositories(ctx, base, sourceRepositories, actor, repositoryOptions); err != nil {
		return err
	}
	if creatorGrantID != "" {
		allRepositories, err := s.listKnowledgeBaseSourceRepositoryMappings(ctx, base.WorkspaceID, base.ID)
		if err != nil {
			return err
		}
		creatorRepairRepositories := sourceRepositoryMappingsExceptSource(allRepositories, source.ID)
		if err := s.repairKnowledgeBaseGrantRepositoryAccessForRepositories(ctx, creatorGrant, creatorRepairRepositories, actor, repositoryAccessReconcileOptions{AllowExistingDirectUpdate: true}); err != nil {
			return err
		}
	}
	if creatorProfileGranted {
		access, err := s.knowledgeBaseRepositoryAccessForGrant(ctx, strings.TrimSpace(source.WorkspaceID), creatorGrant.ID)
		if err != nil {
			return err
		}
		converted := serviceGrantFromStoreWithRepositoryAccess(creatorGrant, access)
		if err := s.auditRepositoryAccessProfileIntent(ctx, creatorGrant, actor, previousCreatorProfile, creatorGrant.RepositoryAccessProfile, converted.RepositoryAccessStatus, converted.RepositoryAccessLastError); err != nil {
			return err
		}
	}
	return nil
}

func (s *KBService) applyRepositoryAccessGrant(ctx context.Context, grant kb.KnowledgeBaseGrant, repo reposstore.ResourceRepository, permission, intentProfile, actor string, target reposservice.ResourceRepositoryActor, options repositoryAccessReconcileOptions) error {
	now := s.now()
	actor = firstNonEmptyString(actor, grant.UpdatedBy, grant.CreatedBy)
	status := reposstore.AccessStatusCurrent
	lastError := ""
	rowID := reposstore.ResourceRepositoryAccessGrantID(repo.WorkspaceID, repo.ResourceType, repo.ResourceID, repo.RepoRole, grant.SubjectType, grant.SubjectID, reposstore.AccessIntentTypeKBGrant, grant.ID)
	current, strongestOtherMaterializedPermission, err := s.repositoryAccessGrantMaterializationState(ctx, rowID, repo.WorkspaceID, repo.RepoFullName, grant.SubjectType, grant.SubjectID)
	if err != nil {
		return err
	}
	currentMaterializedPermission := ""
	if current != nil && current.Materialized {
		currentMaterializedPermission = current.Permission
	}
	strongestMaterializedPermission := strongerRepositoryAccessPermission(currentMaterializedPermission, strongestOtherMaterializedPermission)
	desiredDirectPermission := strongerRepositoryAccessPermission(permission, strongestOtherMaterializedPermission)
	materialized := repositoryAccessPermissionRank(strongestMaterializedPermission) >= repositoryAccessPermissionRank(permission)
	storedPermission := permission
	repository := repositoryIdentityFromResourceRepository(repo)
	if strings.TrimSpace(repository.Owner) == "" || strings.TrimSpace(repository.Name) == "" {
		status = reposstore.AccessStatusBlocked
		lastError = "source repository mapping is incomplete"
	} else if grantor := s.resourceRepositoryGrantor(); grantor != nil {
		if result, err := grantor.GrantRepositoryCollaborator(ctx, reposservice.ResourceRepositoryCollaboratorGrant{
			Repository:                repository,
			Actor:                     target,
			Permission:                desiredDirectPermission,
			AllowExistingDirectUpdate: options.AllowExistingDirectUpdate || strongestMaterializedPermission != "",
		}); err != nil {
			status = reposstore.AccessStatusBlocked
			lastError = strings.TrimSpace(err.Error())
			if current != nil && current.Materialized {
				storedPermission = current.Permission
				materialized = true
			} else {
				materialized = repositoryAccessPermissionRank(strongestOtherMaterializedPermission) >= repositoryAccessPermissionRank(permission)
			}
		} else if result.Materialized {
			materialized = true
		}
	}
	row, err := s.repositoryStore.UpsertAccessGrant(ctx, reposstore.ResourceRepositoryAccessGrant{
		WorkspaceID:      repo.WorkspaceID,
		ResourceType:     repo.ResourceType,
		ResourceID:       repo.ResourceID,
		RepoRole:         repo.RepoRole,
		RepoFullName:     repo.RepoFullName,
		AGSRepoID:        repo.AGSRepoID,
		SubjectType:      grant.SubjectType,
		SubjectID:        grant.SubjectID,
		Permission:       storedPermission,
		IntentType:       reposstore.AccessIntentTypeKBGrant,
		IntentID:         grant.ID,
		IntentProfile:    intentProfile,
		Materialized:     materialized,
		Status:           status,
		LastError:        lastError,
		CreatedBy:        actor,
		UpdatedBy:        actor,
		ReconciledBy:     actor,
		CreatedAt:        now,
		UpdatedAt:        now,
		LastReconciledAt: &now,
	})
	if err != nil {
		return err
	}
	return s.auditRepositoryAccessReconciliation(ctx, grant, row, actor)
}

func (s *KBService) revokeRepositoryAccessRow(ctx context.Context, grant kb.KnowledgeBaseGrant, row reposstore.ResourceRepositoryAccessGrant, actor string) error {
	now := s.now()
	actor = firstNonEmptyString(actor, row.UpdatedBy, row.CreatedBy)
	status := reposstore.AccessStatusRevoked
	lastError := ""
	revokedBy := actor
	revokedAt := &now
	remainingPermission, err := s.remainingMaterializedRepositoryAccessPermission(ctx, row)
	if err != nil {
		return err
	}
	if row.Materialized {
		switch {
		case remainingPermission == "":
			if revoker := s.resourceRepositoryRevoker(); revoker != nil {
				if err := revoker.RevokeRepositoryCollaborator(ctx, reposservice.ResourceRepositoryCollaboratorRevoke{
					Repository: repositoryIdentityFromAccessGrant(row),
					Actor:      resourceRepositoryActorForAccessRow(row),
				}); err != nil {
					status = reposstore.AccessStatusBlocked
					lastError = strings.TrimSpace(err.Error())
					revokedBy = ""
					revokedAt = nil
				}
			}
		case repositoryAccessPermissionRank(remainingPermission) < repositoryAccessPermissionRank(row.Permission):
			if grantor := s.resourceRepositoryGrantor(); grantor != nil {
				if _, err := grantor.GrantRepositoryCollaborator(ctx, reposservice.ResourceRepositoryCollaboratorGrant{
					Repository:                repositoryIdentityFromAccessGrant(row),
					Actor:                     resourceRepositoryActorForAccessRow(row),
					Permission:                remainingPermission,
					AllowExistingDirectUpdate: true,
				}); err != nil {
					status = reposstore.AccessStatusBlocked
					lastError = strings.TrimSpace(err.Error())
					revokedBy = ""
					revokedAt = nil
				}
			}
		}
	}
	row.Status = status
	row.LastError = lastError
	row.UpdatedBy = actor
	row.ReconciledBy = actor
	row.RevokedBy = revokedBy
	row.UpdatedAt = now
	row.LastReconciledAt = &now
	row.RevokedAt = revokedAt
	_, err = s.repositoryStore.UpsertAccessGrant(ctx, row)
	if err != nil {
		return err
	}
	return s.auditRepositoryAccessReconciliation(ctx, grant, row, actor)
}

func (s *KBService) remainingMaterializedRepositoryAccessPermission(ctx context.Context, row reposstore.ResourceRepositoryAccessGrant) (string, error) {
	active, err := s.repositoryStore.ListActiveAccessGrantsForRepositorySubject(ctx, row.WorkspaceID, row.RepoFullName, row.SubjectType, row.SubjectID)
	if err != nil {
		return "", err
	}
	permission := ""
	for _, other := range active {
		if other.ID == row.ID || !other.Materialized {
			continue
		}
		permission = strongerRepositoryAccessPermission(permission, other.Permission)
	}
	return permission, nil
}

func strongerRepositoryAccessPermission(current, next string) string {
	if repositoryAccessPermissionRank(next) > repositoryAccessPermissionRank(current) {
		return strings.TrimSpace(next)
	}
	return strings.TrimSpace(current)
}

func repositoryAccessPermissionRank(permission string) int {
	switch strings.TrimSpace(permission) {
	case reposstore.AccessPermissionAdmin:
		return 3
	case reposstore.AccessPermissionWrite:
		return 2
	case reposstore.AccessPermissionRead:
		return 1
	default:
		return 0
	}
}

func (s *KBService) repositoryAccessGrantMaterializationState(ctx context.Context, rowID, workspaceID, repoFullName, subjectType, subjectID string) (*reposstore.ResourceRepositoryAccessGrant, string, error) {
	active, err := s.repositoryStore.ListActiveAccessGrantsForRepositorySubject(ctx, workspaceID, repoFullName, subjectType, subjectID)
	if err != nil {
		return nil, "", err
	}
	var current *reposstore.ResourceRepositoryAccessGrant
	strongestOtherMaterializedPermission := ""
	for _, other := range active {
		if other.ID == rowID {
			row := other
			current = &row
			continue
		}
		if other.Materialized {
			strongestOtherMaterializedPermission = strongerRepositoryAccessPermission(strongestOtherMaterializedPermission, other.Permission)
		}
	}
	return current, strongestOtherMaterializedPermission, nil
}

func (s *KBService) listKnowledgeBaseSourceRepositoryMappings(ctx context.Context, workspaceID, kbID string) ([]reposstore.ResourceRepository, error) {
	if s == nil || s.store == nil || s.repositoryStore == nil {
		return nil, nil
	}
	sources, err := s.store.ListSources(ctx, workspaceID, kbID, 1, 10000)
	if err != nil {
		return nil, err
	}
	sourceIDs := make([]string, 0, len(sources))
	for _, source := range sources {
		sourceIDs = append(sourceIDs, source.ID)
	}
	return s.repositoryStore.ListForResourceBatch(ctx, workspaceID, reposstore.ResourceTypeKBSource, sourceIDs)
}

func eligibleHumanRepositoryAccessRepos(repos []reposstore.ResourceRepository) []reposstore.ResourceRepository {
	out := make([]reposstore.ResourceRepository, 0, len(repos))
	for _, repo := range repos {
		if repo.ResourceType != reposstore.ResourceTypeKBSource {
			continue
		}
		switch repo.RepoRole {
		case reposstore.RepoRolePrimary, reposstore.RepoRoleMirror:
			if repo.UserVisible {
				out = append(out, repo)
			}
		case reposstore.RepoRoleInsights:
			out = append(out, repo)
		}
	}
	return out
}

func eligibleAgentRepositoryAccessRepos(repos []reposstore.ResourceRepository) []reposstore.ResourceRepository {
	out := make([]reposstore.ResourceRepository, 0, len(repos))
	for _, repo := range repos {
		if repo.ResourceType == reposstore.ResourceTypeKBSource && strings.TrimSpace(repo.RepoFullName) != "" {
			out = append(out, repo)
		}
	}
	return out
}

func repositoryPermissionForAccessProfile(profile string) string {
	switch kb.NormalizeKnowledgeBaseRepositoryAccessProfile(profile) {
	case kb.KnowledgeBaseRepositoryAccessProfileManage:
		return reposstore.AccessPermissionAdmin
	case kb.KnowledgeBaseRepositoryAccessProfileWrite:
		return reposstore.AccessPermissionWrite
	case kb.KnowledgeBaseRepositoryAccessProfileRead:
		return reposstore.AccessPermissionRead
	default:
		return ""
	}
}

func repositoryOverrideKey(sourceID, repoRole string) string {
	return strings.TrimSpace(sourceID) + "\x00" + strings.TrimSpace(repoRole)
}

func (s *KBService) resourceRepositoryGrantor() reposservice.ResourceRepositoryGrantor {
	if s == nil || s.wikiStore == nil {
		return nil
	}
	grantor, _ := s.wikiStore.(reposservice.ResourceRepositoryGrantor)
	return grantor
}

func (s *KBService) resourceRepositoryRevoker() reposservice.ResourceRepositoryRevoker {
	if s == nil || s.wikiStore == nil {
		return nil
	}
	revoker, _ := s.wikiStore.(reposservice.ResourceRepositoryRevoker)
	return revoker
}

func (s *KBService) resourceRepositoryDeleter() reposservice.RepositoryDeleter {
	if s == nil || s.wikiStore == nil {
		return nil
	}
	deleter, _ := s.wikiStore.(reposservice.RepositoryDeleter)
	return deleter
}

func repositoryIdentityFromResourceRepository(repo reposstore.ResourceRepository) reposservice.RepositoryIdentity {
	return sourceRepositoryIdentity(reposservice.RepositoryIdentity{
		Owner:         repo.RepoOwner,
		Name:          repo.RepoName,
		FullName:      repo.RepoFullName,
		ID:            repo.AGSRepoID,
		DefaultBranch: repo.DefaultRef,
	})
}

func repositoryIdentityFromAccessGrant(row reposstore.ResourceRepositoryAccessGrant) reposservice.RepositoryIdentity {
	return sourceRepositoryIdentity(reposservice.RepositoryIdentity{
		FullName: row.RepoFullName,
		ID:       row.AGSRepoID,
	})
}

func resourceRepositoryActorForAccessRow(row reposstore.ResourceRepositoryAccessGrant) reposservice.ResourceRepositoryActor {
	if row.SubjectType == "agent" {
		return resourceRepositoryActorForAgent(row.SubjectID)
	}
	return reposservice.ResourceRepositoryActorFromAgentTeam(trusted.Identity{Subject: strings.TrimSpace(row.SubjectID)})
}

func (s *KBService) auditRepositoryAccessOverrideIntent(ctx context.Context, grant kb.KnowledgeBaseGrant, actor, sourceID, repoRole, previousProfile, nextProfile string, cleared bool) error {
	if s == nil || s.store == nil || grant.SubjectType != "user" {
		return nil
	}
	sourceID = strings.TrimSpace(sourceID)
	repoRole = strings.TrimSpace(repoRole)
	if sourceID == "" || repoRole == "" {
		return nil
	}
	hadPrevious := strings.TrimSpace(previousProfile) != ""
	previousValue := "inherit"
	if hadPrevious {
		previousValue = kb.NormalizeKnowledgeBaseRepositoryAccessProfile(previousProfile)
	}
	nextValue := "inherit"
	action := "kb.repository_access.override.delete"
	summary := "Deleted KB source repository access override"
	if !cleared {
		nextValue = kb.NormalizeKnowledgeBaseRepositoryAccessProfile(nextProfile)
		if hadPrevious && previousValue == nextValue {
			return nil
		}
		action = "kb.repository_access.override.grant"
		summary = "Granted KB source repository access override"
		if hadPrevious {
			action = "kb.repository_access.override.update"
			summary = "Updated KB source repository access override"
		}
	} else if !hadPrevious {
		return nil
	}
	overrideID := kb.KnowledgeBaseSourceRepositoryAccessOverrideID(grant.WorkspaceID, grant.KnowledgeBaseID, grant.ID, sourceID, repoRole)
	details, err := repositoryAccessAuditDetails(map[string]any{
		"grant_id":         grant.ID,
		"override_id":      overrideID,
		"subject_type":     grant.SubjectType,
		"subject_id":       grant.SubjectID,
		"source_id":        sourceID,
		"repo_role":        repoRole,
		"previous_profile": previousValue,
		"profile":          nextValue,
	})
	if err != nil {
		return err
	}
	return s.createRepositoryAccessAuditEvent(ctx, grant.WorkspaceID, grant.KnowledgeBaseID, actor, action, "source_repository_access_override", overrideID, summary, details)
}

func (s *KBService) auditRepositoryAccessProfileIntent(ctx context.Context, grant kb.KnowledgeBaseGrant, actor, previousProfile, nextProfile, status, lastError string) error {
	if s == nil || s.store == nil || grant.SubjectType != "user" {
		return nil
	}
	previousProfile = kb.NormalizeKnowledgeBaseRepositoryAccessProfile(previousProfile)
	nextProfile = kb.NormalizeKnowledgeBaseRepositoryAccessProfile(nextProfile)
	if previousProfile == nextProfile {
		return nil
	}
	action := "kb.repository_access.update"
	summary := "Updated KB repository access profile"
	switch {
	case nextProfile == kb.KnowledgeBaseRepositoryAccessProfileNone:
		action = "kb.repository_access.revoke"
		summary = "Revoked KB repository access profile"
	case previousProfile == kb.KnowledgeBaseRepositoryAccessProfileNone:
		action = "kb.repository_access.grant"
		summary = "Granted KB repository access profile"
	}
	details, err := repositoryAccessAuditDetails(map[string]any{
		"grant_id":         grant.ID,
		"subject_type":     grant.SubjectType,
		"subject_id":       grant.SubjectID,
		"previous_profile": previousProfile,
		"profile":          nextProfile,
		"status":           status,
		"last_error":       lastError,
	})
	if err != nil {
		return err
	}
	return s.createRepositoryAccessAuditEvent(ctx, grant.WorkspaceID, grant.KnowledgeBaseID, actor, action, "permission_grant", grant.ID, summary, details)
}

func (s *KBService) auditRepositoryAccessRepair(ctx context.Context, grant kb.KnowledgeBaseGrant, actor, status, lastError string) error {
	if s == nil || s.store == nil {
		return nil
	}
	profile := kb.NormalizeKnowledgeBaseRepositoryAccessProfile(grant.RepositoryAccessProfile)
	if grant.SubjectType == "agent" {
		profile = kb.KnowledgeBaseRepositoryAccessProfileRead
	}
	details, err := repositoryAccessAuditDetails(map[string]any{
		"grant_id":     grant.ID,
		"subject_type": grant.SubjectType,
		"subject_id":   grant.SubjectID,
		"profile":      profile,
		"status":       status,
		"last_error":   lastError,
	})
	if err != nil {
		return err
	}
	return s.createRepositoryAccessAuditEvent(ctx, grant.WorkspaceID, grant.KnowledgeBaseID, actor, "kb.repository_access.repair", "permission_grant", grant.ID, "Retried KB repository access repair", details)
}

func (s *KBService) auditRepositoryAccessReconciliation(ctx context.Context, grant kb.KnowledgeBaseGrant, row reposstore.ResourceRepositoryAccessGrant, actor string) error {
	if s == nil || s.store == nil {
		return nil
	}
	status := strings.TrimSpace(row.Status)
	summary := "Reconciled KB repository access"
	switch status {
	case reposstore.AccessStatusBlocked:
		summary = "Blocked KB repository access reconciliation"
	case reposstore.AccessStatusRevoked:
		summary = "Revoked KB repository access materialization"
	}
	details, err := repositoryAccessAuditDetails(map[string]any{
		"grant_id":      grant.ID,
		"access_row_id": row.ID,
		"subject_type":  row.SubjectType,
		"subject_id":    row.SubjectID,
		"source_id":     row.ResourceID,
		"repo_role":     row.RepoRole,
		"permission":    row.Permission,
		"profile":       row.IntentProfile,
		"status":        status,
		"materialized":  row.Materialized,
		"last_error":    row.LastError,
	})
	if err != nil {
		return err
	}
	return s.createRepositoryAccessAuditEvent(ctx, row.WorkspaceID, grant.KnowledgeBaseID, actor, "kb.repository_access.reconcile", "repository_access", row.ID, summary, details)
}

func (s *KBService) createRepositoryAccessAuditEvent(ctx context.Context, workspaceID, kbID, actor, action, resourceType, resourceID, summary, details string) error {
	if s == nil || s.store == nil {
		return nil
	}
	audit := s.auditEvent(workspaceID, kbID, actor, action, resourceType, resourceID, summary, details)
	if _, err := s.store.CreateAuditEvent(ctx, audit); err != nil {
		return err
	}
	return s.writeKnowledgeBaseAuditActivity(ctx, audit)
}

func repositoryAccessAuditDetails(fields map[string]any) (string, error) {
	for key, value := range fields {
		if text, ok := value.(string); ok {
			fields[key] = strings.TrimSpace(text)
		}
	}
	payload, err := json.Marshal(fields)
	if err != nil {
		return "", err
	}
	return string(payload), nil
}
