package permissions

import (
	"context"
	"errors"
	"strings"
	"sync"
	"testing"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// saveTestRepo embeds ports.PermissionRepository and overrides only the
// methods the Save* validation gates exercise; any other call is trapped by
// the embedded interface with a nil panic.
type saveTestRepo struct {
	ports.PermissionRepository

	recordTypes                        []ports.RecordTypeCatalogEntry
	customPerms                        []models.SystemCustomPermission
	systemPerms                        []models.SystemPermission
	lockPermissionSetMissing           bool
	setSystemPermissionCalls           int
	setRecordTypeGrantCalls            int
	setCustomPermGrantCalls            int
	deleteCustomPermGrantCalls         int
	insertMissingObjectPermissionCalls int
	getRecordByIDFn                    func(_ context.Context, tableName, id string) (models.SObject, error)
}

func (m *saveTestRepo) ListRecordTypeCatalog(context.Context) ([]ports.RecordTypeCatalogEntry, error) {
	return m.recordTypes, nil
}
func (m *saveTestRepo) ListCustomPermissions(context.Context) ([]models.SystemCustomPermission, error) {
	return m.customPerms, nil
}
func (m *saveTestRepo) SetPermissionSetRecordTypeGrant(context.Context, string, string, bool) error {
	m.setRecordTypeGrantCalls++
	return nil
}
func (m *saveTestRepo) SetPermissionSetCustomPermGrant(context.Context, string, string) error {
	m.setCustomPermGrantCalls++
	return nil
}
func (m *saveTestRepo) DeletePermissionSetCustomPermGrant(context.Context, string, string) error {
	m.deleteCustomPermGrantCalls++
	return nil
}

func (m *saveTestRepo) InsertMissingObjectPermissions(context.Context, []models.ObjectPermissions) error {
	m.insertMissingObjectPermissionCalls++
	return nil
}
func (m *saveTestRepo) GetRecordByID(ctx context.Context, tableName, id string) (models.SObject, error) {
	if m.getRecordByIDFn != nil {
		return m.getRecordByIDFn(ctx, tableName, id)
	}
	return nil, nil
}
func (m *saveTestRepo) ListSystemPermissions(context.Context) ([]models.SystemPermission, error) {
	return m.systemPerms, nil
}
func (m *saveTestRepo) SetSystemPermission(context.Context, string, string, bool) error {
	m.setSystemPermissionCalls++
	return nil
}
func (m *saveTestRepo) LockPermissionSet(context.Context, string) (bool, error) {
	if m.lockPermissionSetMissing {
		return false, nil
	}
	return true, nil
}

// Zero-value overrides for methods exercised by the package-wide test paths
// (role-hierarchy init, record-access sharing probes); everything else stays
// trapped by the embedded interface.
func (m *saveTestRepo) GetAllRoles(context.Context) ([]*models.UserRole, error) {
	return nil, nil
}
func (m *saveTestRepo) GetPendingApprovalLockInfo(context.Context, string) (string, string, error) {
	return "", "", nil
}
func (m *saveTestRepo) GetEffectiveSystemPermissions(context.Context, string, string) (map[string]bool, error) {
	return nil, nil
}
func (m *saveTestRepo) GetEffectiveCustomPermissions(context.Context, string, string) (map[string]bool, error) {
	return nil, nil
}
func (m *saveTestRepo) GetEffectiveRecordTypeVisibilities(context.Context, string, string) (map[string]models.PermissionSetRecordTypeVisibility, error) {
	return nil, nil
}
func (m *saveTestRepo) GetEffectiveAppVisibilities(context.Context, string, string) (map[string]models.ProfileAppVisibility, error) {
	return nil, nil
}
func (m *saveTestRepo) GetEffectiveTabSettings(context.Context, string, string) (map[string]models.ProfileTabSetting, error) {
	return nil, nil
}
func (m *saveTestRepo) LoadEffectiveObjectPermission(context.Context, *models.UserSession, string) (*models.ObjectPermissions, error) {
	return nil, nil
}
func (m *saveTestRepo) LoadEffectiveFieldPermission(context.Context, *models.UserSession, string, string) (*models.FieldPermissions, error) {
	return nil, nil
}
func (m *saveTestRepo) GetUserProfileID(context.Context, string) (string, error) {
	return "", nil
}
func (m *saveTestRepo) GetAllProfiles(context.Context) ([]*models.Profile, error) {
	return nil, nil
}
func (m *saveTestRepo) IsUserInGroup(context.Context, string, string) (bool, error) {
	return false, nil
}
func (m *saveTestRepo) GetUserGroupIDs(context.Context, string) ([]string, error) {
	return nil, nil
}
func (m *saveTestRepo) GetGroupMemberIDs(context.Context, []string) ([]string, error) {
	return nil, nil
}
func (m *saveTestRepo) GetRecordOwnerID(context.Context, string, string) (string, error) {
	return "", nil
}
func (m *saveTestRepo) GetManualShareAccessLevels(context.Context, string, string, string) ([]string, error) {
	return nil, nil
}
func (m *saveTestRepo) GetManualShareRecordIDs(context.Context, string, string) ([]string, error) {
	return nil, nil
}
func (m *saveTestRepo) GetTeamMemberAccessLevel(context.Context, string, string, string) (*string, error) {
	return nil, nil
}
func (m *saveTestRepo) GetTeamMemberRecordIDs(context.Context, string, string) ([]string, error) {
	return nil, nil
}
func (m *saveTestRepo) HasPendingApprovalWorkitemForUser(context.Context, string, string) (bool, error) {
	return false, nil
}
func (m *saveTestRepo) GetPermissionSetGrants(context.Context, string) (map[string]bool, error) {
	return nil, nil
}
func (m *saveTestRepo) GetPermissionSetGrantsMany(context.Context, []string) (map[string]map[string]bool, error) {
	return nil, nil
}
func (m *saveTestRepo) UpsertObjectPermission(context.Context, models.ObjectPermissions) error {
	return nil
}
func (m *saveTestRepo) UpsertFieldPermission(context.Context, models.FieldPermissions) error {
	return nil
}
func (m *saveTestRepo) InsertMissingFieldPermissions(context.Context, []models.FieldPermissions) error {
	return nil
}
func (m *saveTestRepo) DeleteFieldPermissionsForFields(context.Context, string, []string) error {
	return nil
}
func (m *saveTestRepo) CountObjectPermissionsByProfile(context.Context, string) (int, error) {
	return 0, nil
}
func (m *saveTestRepo) CreatePermissionSet(context.Context, string, string, string) (string, error) {
	return "", nil
}
func (m *saveTestRepo) EnsureProfilePermissionSet(context.Context, string, string) (string, error) {
	return "", nil
}
func (m *saveTestRepo) CleanupProfilePermissionSet(context.Context, string) error {
	return nil
}
func (m *saveTestRepo) UpdatePermissionSet(context.Context, string, string, string, string) error {
	return nil
}
func (m *saveTestRepo) DeletePermissionSet(context.Context, string) error {
	return nil
}

type invalidateCountingPermCache struct {
	count int
}

func (c *invalidateCountingPermCache) InvalidateUser(string) {}
func (c *invalidateCountingPermCache) Invalidate()           { c.count++ }

type recordingPermissionAdminTx struct {
	calls            int
	plainCalls       int
	pessimisticCalls int
}

func (m *recordingPermissionAdminTx) WithTransaction(ctx context.Context, fn func(context.Context) error) error {
	m.calls++
	m.plainCalls++
	return fn(ctx)
}

func (m *recordingPermissionAdminTx) WithPessimisticTransaction(ctx context.Context, fn func(context.Context) error) error {
	m.calls++
	m.pessimisticCalls++
	return fn(ctx)
}

func (m *recordingPermissionAdminTx) WithRetry(ctx context.Context, fn func(context.Context) error, _ int) error {
	return m.WithTransaction(ctx, fn)
}

func (m *recordingPermissionAdminTx) WithPessimisticRetry(ctx context.Context, fn func(context.Context) error, _ int) error {
	return m.WithTransaction(ctx, fn)
}

func (m *recordingPermissionAdminTx) IsTransactionActive(context.Context) bool { return false }
func (m *recordingPermissionAdminTx) IsPessimisticTx(context.Context) bool     { return false }
func (m *recordingPermissionAdminTx) ExtractTx(context.Context) interface{}    { return nil }
func (m *recordingPermissionAdminTx) DetachTx(ctx context.Context) context.Context {
	return ctx
}

func TestAdminServiceSaveSystemGrantsOwnsTransactionAndCacheInvalidation(t *testing.T) {
	repo := &saveTestRepo{
		systemPerms: []models.SystemPermission{{APIName: "ApiEnabled", IsActive: true}},
	}
	cache := &invalidateCountingPermCache{}
	svc := NewPermissionService(repo, nil, nil)
	svc.SetCacheInvalidator(cache)
	tx := &recordingPermissionAdminTx{}
	admin := NewAdminService(svc, tx, AdminServiceOptions{})

	err := admin.SaveSystemGrants(context.Background(), "ps-1", map[string]bool{"ApiEnabled": true}, &models.UserSession{ID: "user-1"})
	if err != nil {
		t.Fatalf("SaveSystemGrants() error = %v, want nil", err)
	}
	if tx.calls != 1 {
		t.Fatalf("transactions = %d, want 1", tx.calls)
	}
	if tx.pessimisticCalls != 1 || tx.plainCalls != 0 {
		t.Fatalf("transaction boundaries = pessimistic %d plain %d, want 1/0", tx.pessimisticCalls, tx.plainCalls)
	}
	if repo.setSystemPermissionCalls != 1 {
		t.Fatalf("SetSystemPermission calls = %d, want 1", repo.setSystemPermissionCalls)
	}
	if cache.count != 1 {
		t.Fatalf("cache invalidations = %d, want 1", cache.count)
	}
}

func TestAdminServiceSaveRecordTypeGrantsInvalidatesPermissionAndMetadataProjectionAfterCommit(t *testing.T) {
	repo := &saveTestRepo{
		recordTypes: []ports.RecordTypeCatalogEntry{{RecordTypeID: "rt-1", ObjectAPIName: "Account", Name: "Business", Label: "Business", IsActive: true}},
	}
	cache := &invalidateCountingPermCache{}
	svc := NewPermissionService(repo, nil, nil)
	svc.SetCacheInvalidator(cache)
	tx := &recordingPermissionAdminTx{}
	projectionInvalidations := 0
	admin := NewAdminService(svc, tx, AdminServiceOptions{InvalidateMetadataProjectionCache: func() { projectionInvalidations++ }})

	err := admin.SaveRecordTypeGrants(context.Background(), "ps-1", map[string]RecordTypeGrantChange{"rt-1": {IsVisible: true}})
	if err != nil {
		t.Fatalf("SaveRecordTypeGrants() error = %v, want nil", err)
	}
	if tx.calls != 1 {
		t.Fatalf("transactions = %d, want 1", tx.calls)
	}
	if repo.setRecordTypeGrantCalls != 1 {
		t.Fatalf("SetPermissionSetRecordTypeGrant calls = %d, want 1", repo.setRecordTypeGrantCalls)
	}
	if cache.count != 1 {
		t.Fatalf("permission cache invalidations = %d, want 1", cache.count)
	}
	if projectionInvalidations != 1 {
		t.Fatalf("metadata projection invalidations = %d, want 1", projectionInvalidations)
	}
}

func TestAdminServiceMissingPermissionSetFailsClosedBeforeInvalidation(t *testing.T) {
	repo := &saveTestRepo{
		systemPerms:              []models.SystemPermission{{APIName: "ApiEnabled", IsActive: true}},
		lockPermissionSetMissing: true,
	}
	cache := &invalidateCountingPermCache{}
	svc := NewPermissionService(repo, nil, nil)
	svc.SetCacheInvalidator(cache)
	admin := NewAdminService(svc, &recordingPermissionAdminTx{}, AdminServiceOptions{})

	err := admin.SaveSystemGrants(context.Background(), "missing", map[string]bool{"ApiEnabled": true}, &models.UserSession{ID: "user-1"})
	if !IsPermissionSetNotFound(err) {
		t.Fatalf("SaveSystemGrants() error = %v, want PermissionSetNotFoundError", err)
	}
	if repo.setSystemPermissionCalls != 0 {
		t.Fatalf("SetSystemPermission calls = %d, want 0", repo.setSystemPermissionCalls)
	}
	if cache.count != 0 {
		t.Fatalf("cache invalidations = %d, want 0", cache.count)
	}
}

func TestSavePermissionSetRecordTypeGrants_UnknownRecordType(t *testing.T) {
	repo := &saveTestRepo{
		recordTypes: []ports.RecordTypeCatalogEntry{{RecordTypeID: "rt-1", ObjectAPIName: "Account", Name: "Business", Label: "Business", IsActive: true}},
	}
	svc := &PermissionService{repo: repo}

	err := svc.SavePermissionSetRecordTypeGrants(context.Background(), "ps-1", map[string]RecordTypeGrantChange{"rt-missing": {IsVisible: true}})
	if err == nil {
		t.Fatal("expected validation error for unknown record type, got nil")
	}
}

func TestUpdatePermissionSetObjectPermissionInvalidatesCache(t *testing.T) {
	ctx := context.Background()
	repo := &saveTestRepo{}
	cache := &invalidateCountingPermCache{}
	svc := NewPermissionService(repo, nil, nil)
	svc.SetCacheInvalidator(cache)

	err := svc.UpdatePermissionSetObjectPermission(ctx, models.ObjectPermissions{
		ParentID:        "ps-1",
		SobjectType:     "Account",
		PermissionsRead: true,
	})
	if err != nil {
		t.Fatalf("UpdatePermissionSetObjectPermission() error = %v, want nil", err)
	}
	if cache.count != 1 {
		t.Fatalf("cache invalidations = %d, want 1", cache.count)
	}
}

func TestUpdatePermissionSetFieldPermissionInvalidatesCache(t *testing.T) {
	ctx := context.Background()
	repo := &saveTestRepo{}
	cache := &invalidateCountingPermCache{}
	svc := NewPermissionService(repo, nil, nil)
	svc.SetCacheInvalidator(cache)

	err := svc.UpdatePermissionSetFieldPermission(ctx, models.FieldPermissions{
		ParentID:        "ps-1",
		SobjectType:     "Account",
		Field:           "Account.Secret__c",
		PermissionsRead: true,
	})
	if err != nil {
		t.Fatalf("UpdatePermissionSetFieldPermission() error = %v, want nil", err)
	}
	if cache.count != 1 {
		t.Fatalf("cache invalidations = %d, want 1", cache.count)
	}
}

func TestSavePermissionSetRecordTypeGrants_Valid(t *testing.T) {
	repo := &saveTestRepo{
		recordTypes: []ports.RecordTypeCatalogEntry{{RecordTypeID: "rt-1", ObjectAPIName: "Account", Name: "Business", Label: "Business", IsActive: true}},
	}
	svc := &PermissionService{repo: repo}

	err := svc.SavePermissionSetRecordTypeGrants(context.Background(), "ps-1", map[string]RecordTypeGrantChange{"rt-1": {IsVisible: true}})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

// Test SavePermissionSetCustomPermGrants rejects unknown custom permission.
func TestSavePermissionSetCustomPermGrants_UnknownCustomPerm(t *testing.T) {
	repo := &saveTestRepo{
		customPerms: []models.SystemCustomPermission{{ID: "cp-1", APIName: "CanExport"}},
	}
	svc := &PermissionService{repo: repo}

	err := svc.SavePermissionSetCustomPermGrants(context.Background(), "ps-1", map[string]bool{"UnknownPerm": true})
	if err == nil {
		t.Fatal("expected validation error for unknown custom permission, got nil")
	}
}

// Test SavePermissionSetCustomPermGrants succeeds for valid custom permission.
func TestSavePermissionSetCustomPermGrants_Valid(t *testing.T) {
	repo := &saveTestRepo{
		customPerms: []models.SystemCustomPermission{{ID: "cp-1", APIName: "CanExport"}},
	}
	svc := &PermissionService{repo: repo}

	// The frontend sends the database ID (p.id from catalog), not the APIName.
	err := svc.SavePermissionSetCustomPermGrants(context.Background(), "ps-1", map[string]bool{"cp-1": true})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

// --- Auditor Recording tests ---

// mockAuditor captures calls to Record for verification.
type mockAuditor struct {
	mu    sync.Mutex
	calls []struct {
		action, section, display, userID string
	}
}

func (m *mockAuditor) Record(_ context.Context, action, section, display, userID string) {
	m.mu.Lock()
	defer m.mu.Unlock()
	m.calls = append(m.calls, struct {
		action, section, display, userID string
	}{action, section, display, userID})
}

func (m *mockAuditor) callCount() int {
	m.mu.Lock()
	defer m.mu.Unlock()
	return len(m.calls)
}

// repoWithAuditor is like saveTestRepo but accepts an auditor.
type saveTestRepoWithAuditor struct {
	saveTestRepo
}

func TestSavePermissionSetCustomPermGrants_AuditorRecording(t *testing.T) {
	repo := &saveTestRepoWithAuditor{
		saveTestRepo: saveTestRepo{
			customPerms: []models.SystemCustomPermission{{ID: "cp-1", APIName: "CanExport"}},
		},
	}
	auditor := &mockAuditor{}
	svc := &PermissionService{repo: repo}
	svc.SetSetupAuditor(auditor)

	err := svc.SavePermissionSetCustomPermGrants(context.Background(), "ps-1", map[string]bool{"cp-1": true})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if auditor.callCount() != 1 {
		t.Fatalf("expected 1 auditor call, got %d", auditor.callCount())
	}
	// P3: verify message content, not just call count.
	c := auditor.calls[0]
	if c.action != "Updated custom permissions" {
		t.Errorf("action = %q; want %q", c.action, "Updated custom permissions")
	}
	if c.section != "Permission Sets" {
		t.Errorf("section = %q; want %q", c.section, "Permission Sets")
	}
}

// Test SavePermissionSetCustomPermGrants with granted=false exercises the
// DeletePermissionSetCustomPermGrant path (P2: previously untested branch).
func TestSavePermissionSetCustomPermGrants_DeleteBranch(t *testing.T) {
	repo := &saveTestRepo{
		customPerms: []models.SystemCustomPermission{{ID: "cp-1", APIName: "CanExport"}},
	}
	svc := &PermissionService{repo: repo}

	err := svc.SavePermissionSetCustomPermGrants(context.Background(), "ps-1", map[string]bool{"cp-1": false})
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestValidateObjectPermissionDependencies_CreateRequiresRead(t *testing.T) {
	err := ValidateObjectPermissionDependencies(models.ObjectPermissions{
		SobjectType:       "Account",
		PermissionsCreate: true,
	})
	if err == nil {
		t.Fatal("ValidateObjectPermissionDependencies() error = nil, want validation error")
	}
	if !strings.Contains(err.Error(), "Create Account") {
		t.Fatalf("error = %q, want Create Account context", err.Error())
	}
	if !strings.Contains(err.Error(), "Read Account") {
		t.Fatalf("error = %q, want Read Account prerequisite", err.Error())
	}
}

func TestValidateObjectPermissionDependencies_CoherentCreatePasses(t *testing.T) {
	err := ValidateObjectPermissionDependencies(models.ObjectPermissions{
		SobjectType:       "Account",
		PermissionsRead:   true,
		PermissionsCreate: true,
	})
	if err != nil {
		t.Fatalf("ValidateObjectPermissionDependencies() error = %v, want nil", err)
	}
}

func TestInsertMissingObjectPermissions_RejectsIncoherentBatch(t *testing.T) {
	repo := &saveTestRepo{}
	svc := &PermissionService{repo: repo}

	err := svc.InsertMissingObjectPermissions(context.Background(), []models.ObjectPermissions{{
		ParentID:          "0PS000000000001",
		SobjectType:       "Account",
		PermissionsCreate: true,
		PermissionsRead:   false,
	}})
	if err == nil {
		t.Fatal("InsertMissingObjectPermissions() error = nil, want validation error")
	}
	if repo.insertMissingObjectPermissionCalls != 0 {
		t.Fatalf("insertMissingObjectPermissionCalls = %d, want 0 on validation failure", repo.insertMissingObjectPermissionCalls)
	}
}

// A PermissionSet owned by a system profile (IsOwnedByProfile=true and the
// owning profile has IsSystem=true) is system-managed. Grant mutations on it
// through the permission-admin surface must be rejected before any write,
// mirroring the sobject-surface immutability guard.
func TestAdminServiceRejectsSystemProfileOwnedPermissionSetGrantMutations(t *testing.T) {
	systemProfileOwnedLookup := func(_ context.Context, tableName, id string) (models.SObject, error) {
		if tableName == constants.TablePermissionSet && id == "ps-system" {
			return models.SObject{
				constants.FieldID: "ps-system",
				constants.FieldPermissionSet_IsOwnedByProfile: true,
				constants.FieldPermissionSet_ProfileID:        "00E-sys-profile",
			}, nil
		}
		if tableName == constants.TableProfile && id == "00E-sys-profile" {
			return models.SObject{
				constants.FieldID:               "00E-sys-profile",
				constants.FieldProfile_IsSystem: true,
			}, nil
		}
		return nil, nil
	}

	cases := []struct {
		name    string
		invoke  func(*AdminService) error
		writeCt func(*saveTestRepo) int
	}{
		{
			name: "system grants",
			invoke: func(a *AdminService) error {
				return a.SaveSystemGrants(context.Background(), "ps-system", map[string]bool{"ApiEnabled": true}, &models.UserSession{ID: "user-1"})
			},
			writeCt: func(r *saveTestRepo) int { return r.setSystemPermissionCalls },
		},
		{
			name: "record type grants",
			invoke: func(a *AdminService) error {
				return a.SaveRecordTypeGrants(context.Background(), "ps-system", map[string]RecordTypeGrantChange{"rt-1": {IsVisible: true}})
			},
			writeCt: func(r *saveTestRepo) int { return r.setRecordTypeGrantCalls },
		},
		{
			name: "custom permission grants",
			invoke: func(a *AdminService) error {
				return a.SaveCustomPermissionGrants(context.Background(), "ps-system", map[string]bool{"cp-1": true})
			},
			writeCt: func(r *saveTestRepo) int { return r.setCustomPermGrantCalls },
		},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			repo := &saveTestRepo{
				systemPerms:     []models.SystemPermission{{APIName: "ApiEnabled", IsActive: true}},
				recordTypes:     []ports.RecordTypeCatalogEntry{{RecordTypeID: "rt-1", ObjectAPIName: "Account", Name: "Business", Label: "Business", IsActive: true}},
				customPerms:     []models.SystemCustomPermission{{ID: "cp-1"}},
				getRecordByIDFn: systemProfileOwnedLookup,
			}
			svc := NewPermissionService(repo, nil, nil)
			admin := NewAdminService(svc, &recordingPermissionAdminTx{}, AdminServiceOptions{})

			err := tc.invoke(admin)
			if err == nil {
				t.Fatalf("expected rejection of system-profile-owned grant mutation, got nil")
			}
			var forbidden *appErrors.ForbiddenError
			if !errors.As(err, &forbidden) {
				t.Fatalf("error = %v, want a *ForbiddenError", err)
			}
			if !strings.Contains(err.Error(), "immutable") {
				t.Fatalf("error = %v, want an immutability message", err)
			}
			if writes := tc.writeCt(repo); writes != 0 {
				t.Fatalf("repo writes = %d, want 0 (mutation must be rejected before any write)", writes)
			}
		})
	}
}

// A regular (non-system-profile-owned) permission set is still editable through
// the admin surface — the immutability check only rejects system-managed sets.
func TestAdminServiceAllowsNonSystemProfileOwnedPermissionSetGrants(t *testing.T) {
	regularLookup := func(_ context.Context, tableName, id string) (models.SObject, error) {
		if tableName == constants.TablePermissionSet && id == "ps-regular" {
			// Not owned by a profile, or owned by a non-system profile.
			return models.SObject{
				constants.FieldID: "ps-regular",
				constants.FieldPermissionSet_IsOwnedByProfile: false,
			}, nil
		}
		return nil, nil
	}
	repo := &saveTestRepo{
		systemPerms:     []models.SystemPermission{{APIName: "ApiEnabled", IsActive: true}},
		getRecordByIDFn: regularLookup,
	}
	svc := NewPermissionService(repo, nil, nil)
	admin := NewAdminService(svc, &recordingPermissionAdminTx{}, AdminServiceOptions{})

	err := admin.SaveSystemGrants(context.Background(), "ps-regular", map[string]bool{"ApiEnabled": true}, &models.UserSession{ID: "user-1"})
	if err != nil {
		t.Fatalf("SaveSystemGrants() error = %v, want nil for non-system-owned set", err)
	}
	if repo.setSystemPermissionCalls != 1 {
		t.Fatalf("SetSystemPermission calls = %d, want 1", repo.setSystemPermissionCalls)
	}
}
