package permissions

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/nexuscrm/backend/internal/domain/ports"
	"github.com/nexuscrm/backend/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// ---------------------------------------------------------------------------
// Mock repositories
// ---------------------------------------------------------------------------

type mockPermRepo struct {
	// loadEffectiveObjectPerm is called by loadEffectiveObjectPermission
	loadEffectiveObjectPerm func(ctx context.Context, user *models.UserSession, objectAPIName string) (*models.ObjectPermissions, error)
	// loadEffectiveFieldPerm is called by loadEffectiveFieldPermission
	loadEffectiveFieldPerm func(ctx context.Context, user *models.UserSession, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error)
	// getAllRoles is called by refreshRoleHierarchy
	getAllRoles func(ctx context.Context) ([]*models.UserRole, error)
	// getRole is called by CreateRole / UpdateRole
	getRole func(ctx context.Context, id string) (*models.UserRole, error)
	// createRole is called by CreateRole
	createRole func(ctx context.Context, name, description string, parentRoleID *string) (string, error)
	// updateRole is called by UpdateRole
	updateRole func(ctx context.Context, id, name, description string, parentRoleID *string) error
	// deleteRole is called by DeleteRole
	deleteRole func(ctx context.Context, id string) error
	// isUserInGroup is called by CheckRecordAccess
	isUserInGroup func(ctx context.Context, groupID, userID string) (bool, error)
	// getManualShareAccessLevels is called by checkManualShareAccess
	getManualShareAccessLevels func(ctx context.Context, objectAPIName, recordID, userID string) ([]string, error)
	// getTeamMemberAccessLevel is called by checkTeamMemberAccess
	getTeamMemberAccessLevel func(ctx context.Context, objectAPIName, recordID, userID string) (*string, error)
	// getPendingApprovalLockInfo is called by approval lock enforcement
	getPendingApprovalLockInfo func(ctx context.Context, targetObjectID string) (string, string, error)
	// hasPendingApprovalWorkitemForUser is called by approval lock enforcement
	hasPendingApprovalWorkitemForUser func(ctx context.Context, processInstanceID, userID string) (bool, error)
	// getAllProfiles is called by GrantInitialPermissions
	getAllProfiles func(ctx context.Context) ([]*models.Profile, error)
	// batchUpsertObjectPermissions is called by GrantInitialPermissions
	batchUpsertObjectPerms func(ctx context.Context, perms []models.ObjectPermissions) error
	// batchUpsertFieldPermissions is called by field-permission seeders
	batchUpsertFieldPerms func(ctx context.Context, perms []models.FieldPermissions) error
	// listPermissionSetObjectPerms for GetPermissionSetObjectPermissions
	listPermSetObjPerms func(ctx context.Context, permissionSetID string) ([]models.ObjectPermissions, error)
	// upsertObjectPermission for UpdatePermissionSetObjectPermission
	upsertObjPerm func(ctx context.Context, perm models.ObjectPermissions) error
	// upsertFieldPermission for UpdatePermissionSetFieldPermission
	upsertFieldPerm func(ctx context.Context, perm models.FieldPermissions) error
	// getUserProfileID for GetEffectiveObjectPermissions
	getUserProfileID func(ctx context.Context, userID string) (string, error)
	// listEffectiveObjPermsForUser for GetEffectiveObjectPermissions
	listEffObjPermsForUser func(ctx context.Context, userID, profileID string) ([]models.ObjectPermissions, error)
	// listEffFieldPermsForUser for GetEffectiveFieldPermissions
	listEffFieldPermsForUser func(ctx context.Context, userID, profileID string) ([]models.FieldPermissions, error)
	// getEffectiveSystemPerms for GetEffectiveSystemPermissions
	getEffSysPerms func(ctx context.Context, userID, profileID string) (map[string]bool, error)
	// getEffectiveCustomPerms for GetEffectiveCustomPermissions
	getEffCustomPerms func(ctx context.Context, userID, profileID string) (map[string]bool, error)
	// listSystemPermissions for ValidatePermissionPrerequisites + ApplyPermissionChanges catalog reads
	listSystemPermissions func(ctx context.Context) ([]models.SystemPermission, error)
}

// Implement PermissionRepository interface (only methods used by the service)
func (m *mockPermRepo) LoadObjectPermission(ctx context.Context, profileID, objectAPIName string) (*models.ObjectPermissions, error) {
	return nil, nil
}
func (m *mockPermRepo) LoadFieldPermission(ctx context.Context, profileID, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error) {
	return nil, nil
}
func (m *mockPermRepo) LoadEffectiveObjectPermission(ctx context.Context, user *models.UserSession, objectAPIName string) (*models.ObjectPermissions, error) {
	if m.loadEffectiveObjectPerm != nil {
		return m.loadEffectiveObjectPerm(ctx, user, objectAPIName)
	}
	return nil, nil
}
func (m *mockPermRepo) LoadEffectiveFieldPermission(ctx context.Context, user *models.UserSession, objectAPIName, fieldAPIName string) (*models.FieldPermissions, error) {
	if m.loadEffectiveFieldPerm != nil {
		return m.loadEffectiveFieldPerm(ctx, user, objectAPIName, fieldAPIName)
	}
	return nil, nil
}
func (m *mockPermRepo) ListObjectPermissions(ctx context.Context, profileID string) ([]models.ObjectPermissions, error) {
	return nil, nil
}
func (m *mockPermRepo) ListFieldPermissions(ctx context.Context, profileID string) ([]models.FieldPermissions, error) {
	return nil, nil
}
func (m *mockPermRepo) UpsertObjectPermission(ctx context.Context, perm models.ObjectPermissions) error {
	if m.upsertObjPerm != nil {
		return m.upsertObjPerm(ctx, perm)
	}
	return nil
}
func (m *mockPermRepo) BatchUpsertObjectPermissions(ctx context.Context, perms []models.ObjectPermissions) error {
	if m.batchUpsertObjectPerms != nil {
		return m.batchUpsertObjectPerms(ctx, perms)
	}
	return nil
}
func (m *mockPermRepo) BatchUpsertFieldPermissions(ctx context.Context, perms []models.FieldPermissions) error {
	if m.batchUpsertFieldPerms != nil {
		return m.batchUpsertFieldPerms(ctx, perms)
	}
	return nil
}
func (m *mockPermRepo) DeleteFieldPermissionsForFields(ctx context.Context, objectAPIName string, fieldAPINames []string) error {
	return nil
}
func (m *mockPermRepo) CountObjectPermissionsByProfile(ctx context.Context, profileID string) (int, error) {
	return 0, nil
}
func (m *mockPermRepo) UpsertFieldPermission(ctx context.Context, perm models.FieldPermissions) error {
	if m.upsertFieldPerm != nil {
		return m.upsertFieldPerm(ctx, perm)
	}
	return nil
}
func (m *mockPermRepo) ListPermissionSetObjectPermissions(ctx context.Context, permissionSetID string) ([]models.ObjectPermissions, error) {
	if m.listPermSetObjPerms != nil {
		return m.listPermSetObjPerms(ctx, permissionSetID)
	}
	return nil, nil
}
func (m *mockPermRepo) ListPermissionSetFieldPermissions(ctx context.Context, permissionSetID string) ([]models.FieldPermissions, error) {
	return nil, nil
}
func (m *mockPermRepo) ListEffectiveObjectPermissionsForUser(ctx context.Context, userID, profileID string) ([]models.ObjectPermissions, error) {
	if m.listEffObjPermsForUser != nil {
		return m.listEffObjPermsForUser(ctx, userID, profileID)
	}
	return nil, nil
}
func (m *mockPermRepo) ListEffectiveFieldPermissionsForUser(ctx context.Context, userID, profileID string) ([]models.FieldPermissions, error) {
	if m.listEffFieldPermsForUser != nil {
		return m.listEffFieldPermsForUser(ctx, userID, profileID)
	}
	return nil, nil
}
func (m *mockPermRepo) CreatePermissionSet(ctx context.Context, name, label, description string) (string, error) {
	return "perm-set-1", nil
}
func (m *mockPermRepo) UpdatePermissionSet(ctx context.Context, id, name, label, description string) error {
	return nil
}
func (m *mockPermRepo) DeletePermissionSet(ctx context.Context, id string) error {
	return nil
}
func (m *mockPermRepo) GetUserProfileID(ctx context.Context, userID string) (string, error) {
	if m.getUserProfileID != nil {
		return m.getUserProfileID(ctx, userID)
	}
	return "", nil
}
func (m *mockPermRepo) GetAllRoles(ctx context.Context) ([]*models.UserRole, error) {
	if m.getAllRoles != nil {
		return m.getAllRoles(ctx)
	}
	return nil, nil
}
func (m *mockPermRepo) CreateRole(ctx context.Context, name, description string, parentRoleID *string) (string, error) {
	if m.createRole != nil {
		return m.createRole(ctx, name, description, parentRoleID)
	}
	return "new-role-id", nil
}
func (m *mockPermRepo) GetRole(ctx context.Context, id string) (*models.UserRole, error) {
	if m.getRole != nil {
		return m.getRole(ctx, id)
	}
	return nil, nil
}
func (m *mockPermRepo) UpdateRole(ctx context.Context, id, name, description string, parentRoleID *string) error {
	if m.updateRole != nil {
		return m.updateRole(ctx, id, name, description, parentRoleID)
	}
	return nil
}
func (m *mockPermRepo) DeleteRole(ctx context.Context, id string) error {
	if m.deleteRole != nil {
		return m.deleteRole(ctx, id)
	}
	return nil
}
func (m *mockPermRepo) IsUserInGroup(ctx context.Context, groupID, userID string) (bool, error) {
	if m.isUserInGroup != nil {
		return m.isUserInGroup(ctx, groupID, userID)
	}
	return false, nil
}
func (m *mockPermRepo) GetManualShareAccessLevels(ctx context.Context, objectAPIName, recordID, userID string) ([]string, error) {
	if m.getManualShareAccessLevels != nil {
		return m.getManualShareAccessLevels(ctx, objectAPIName, recordID, userID)
	}
	return nil, nil
}
func (m *mockPermRepo) GetTeamMemberAccessLevel(ctx context.Context, objectAPIName, recordID, userID string) (*string, error) {
	if m.getTeamMemberAccessLevel != nil {
		return m.getTeamMemberAccessLevel(ctx, objectAPIName, recordID, userID)
	}
	return nil, nil
}
func (m *mockPermRepo) GetPendingApprovalLockInfo(ctx context.Context, targetObjectID string) (string, string, error) {
	if m.getPendingApprovalLockInfo != nil {
		return m.getPendingApprovalLockInfo(ctx, targetObjectID)
	}
	return "", "", nil
}
func (m *mockPermRepo) HasPendingApprovalWorkitemForUser(ctx context.Context, processInstanceID, userID string) (bool, error) {
	if m.hasPendingApprovalWorkitemForUser != nil {
		return m.hasPendingApprovalWorkitemForUser(ctx, processInstanceID, userID)
	}
	return false, nil
}
func (m *mockPermRepo) GetRecordOwnerID(ctx context.Context, tableName, recordID string) (string, error) {
	return "", nil
}
func (m *mockPermRepo) GetRecordByID(ctx context.Context, tableName, recordID string) (models.SObject, error) {
	return nil, nil
}
func (m *mockPermRepo) GetUserGroupIDs(ctx context.Context, userID string) ([]string, error) {
	return nil, nil
}
func (m *mockPermRepo) GetManualShareRecordIDs(ctx context.Context, objectAPIName, userID string) ([]string, error) {
	return nil, nil
}
func (m *mockPermRepo) GetTeamMemberRecordIDs(ctx context.Context, objectAPIName, userID string) ([]string, error) {
	return nil, nil
}
func (m *mockPermRepo) GetAllProfiles(ctx context.Context) ([]*models.Profile, error) {
	if m.getAllProfiles != nil {
		return m.getAllProfiles(ctx)
	}
	return nil, nil
}
func (m *mockPermRepo) ListSystemPermissions(ctx context.Context) ([]models.SystemPermission, error) {
	if m.listSystemPermissions != nil {
		return m.listSystemPermissions(ctx)
	}
	return nil, nil
}
func (m *mockPermRepo) GetEffectiveSystemPermissions(ctx context.Context, userID, profileID string) (map[string]bool, error) {
	if m.getEffSysPerms != nil {
		return m.getEffSysPerms(ctx, userID, profileID)
	}
	return nil, nil
}
func (m *mockPermRepo) GetEffectiveCustomPermissions(ctx context.Context, userID, profileID string) (map[string]bool, error) {
	if m.getEffCustomPerms != nil {
		return m.getEffCustomPerms(ctx, userID, profileID)
	}
	return nil, nil
}
func (m *mockPermRepo) ListCustomPermissions(ctx context.Context) ([]models.SystemCustomPermission, error) {
	return nil, nil
}
func (m *mockPermRepo) GetCustomPermission(ctx context.Context, id string) (*models.SystemCustomPermission, error) {
	return nil, nil
}
func (m *mockPermRepo) CreateCustomPermission(ctx context.Context, perm *models.SystemCustomPermission) error {
	return nil
}
func (m *mockPermRepo) UpdateCustomPermission(ctx context.Context, perm *models.SystemCustomPermission) error {
	return nil
}
func (m *mockPermRepo) DeleteCustomPermission(ctx context.Context, id string) error {
	return nil
}
func (m *mockPermRepo) ListPermissionSetGroups(ctx context.Context) ([]models.PermissionSetGroup, error) {
	return nil, nil
}
func (m *mockPermRepo) GetPermissionSetGroupByID(ctx context.Context, id string) (*models.PermissionSetGroup, error) {
	return nil, nil
}
func (m *mockPermRepo) GetPermissionSetGroupComponents(ctx context.Context, groupID string) ([]ports.PermissionSetRef, error) {
	return nil, nil
}
func (m *mockPermRepo) CreatePermissionSetGroup(ctx context.Context, group *models.PermissionSetGroup, permSetIDs []string) error {
	return nil
}
func (m *mockPermRepo) UpdatePermissionSetGroup(ctx context.Context, group *models.PermissionSetGroup, permSetIDs []string) error {
	return nil
}
func (m *mockPermRepo) DeletePermissionSetGroup(ctx context.Context, id string) error {
	return nil
}
func (m *mockPermRepo) CleanupDirectProfilePermissions(ctx context.Context, profileIDs []string) error {
	return nil
}
func (m *mockPermRepo) BulkEnsureProfilePermissionSets(_ context.Context, _ []struct{ ID, Name string }) (map[string]string, error) {
	return nil, nil
}
func (m *mockPermRepo) BulkCountObjectPermissionsByProfiles(_ context.Context, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *mockPermRepo) BulkCountFieldPermissionsByProfiles(_ context.Context, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *mockPermRepo) BulkCountObjectPermissionsByProfilesAndTypes(_ context.Context, _ []string, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *mockPermRepo) BulkCountFieldPermissionsByProfilesAndTypes(_ context.Context, _ []string, _ []string) (map[string]int, error) {
	return nil, nil
}
func (m *mockPermRepo) EnsureProfilePermissionSet(ctx context.Context, profileID, profileName string) (string, error) {
	return "ps_" + profileID, nil
}
func (m *mockPermRepo) CleanupProfilePermissionSet(ctx context.Context, profileID string) error {
	return nil
}
func (m *mockPermRepo) LockPermissionSet(_ context.Context, _ string) (bool, error) {
	return true, nil
}
func (m *mockPermRepo) GetPermissionSetGrants(ctx context.Context, permSetID string) (map[string]bool, error) {
	return nil, nil
}
func (m *mockPermRepo) GetPermissionSetGrantsMany(ctx context.Context, permSetIDs []string) (map[string]map[string]bool, error) {
	return nil, nil
}
func (m *mockPermRepo) SetSystemPermission(ctx context.Context, permSetID, apiName string, enabled bool) error {
	return nil
}

// mockUserRepo satisfies ports.UserRepository
type mockUserRepo struct {
	getUserRoleID       func(ctx context.Context, userID string) (*string, error)
	getUserByID         func(ctx context.Context, userID string) (*models.User, error)
	getUserIDsByRoleIDs func(ctx context.Context, roleIDs []string) ([]string, error)
}

func (m *mockUserRepo) CheckUserExistsByEmail(ctx context.Context, email string) (bool, error) {
	return false, nil
}
func (m *mockUserRepo) CheckUserExistsByID(ctx context.Context, id string) (bool, error) {
	return false, nil
}
func (m *mockUserRepo) CheckEmailConflict(ctx context.Context, email, excludeID string) (bool, error) {
	return false, nil
}
func (m *mockUserRepo) UpdateUser(ctx context.Context, userID string, updates map[string]interface{}) error {
	return nil
}
func (m *mockUserRepo) DeleteUser(ctx context.Context, userID string) error { return nil }
func (m *mockUserRepo) FindAll(ctx context.Context) ([]*models.User, error) {
	return nil, nil
}
func (m *mockUserRepo) GetUserRoleID(ctx context.Context, userID string) (*string, error) {
	if m.getUserRoleID != nil {
		return m.getUserRoleID(ctx, userID)
	}
	return nil, nil
}
func (m *mockUserRepo) FindUserByEmailWithPassword(ctx context.Context, email string) (*ports.UserWithPassword, error) {
	return nil, nil
}
func (m *mockUserRepo) FindUserByIDWithPassword(ctx context.Context, userID string) (*ports.UserWithPassword, error) {
	return nil, nil
}
func (m *mockUserRepo) UpdatePassword(ctx context.Context, userID, passwordHash string) error {
	return nil
}
func (m *mockUserRepo) GetUserByID(ctx context.Context, userID string) (*models.User, error) {
	if m.getUserByID != nil {
		return m.getUserByID(ctx, userID)
	}
	return nil, nil
}
func (m *mockUserRepo) CreateUser(ctx context.Context, user *models.User) error { return nil }
func (m *mockUserRepo) GetFirstUserIDByProfile(ctx context.Context, profileID string) (string, error) {
	return "", nil
}
func (m *mockUserRepo) GetRoleNameByID(ctx context.Context, roleID string) (string, error) {
	return "", nil
}
func (m *mockUserRepo) GetProfileNameByID(ctx context.Context, profileID string) (string, error) {
	return "", nil
}
func (m *mockUserRepo) GetUserIDsByRoleIDs(ctx context.Context, roleIDs []string) ([]string, error) {
	if m.getUserIDsByRoleIDs != nil {
		return m.getUserIDsByRoleIDs(ctx, roleIDs)
	}
	return []string{}, nil
}
func (m *mockUserRepo) ResetFailedLoginAttempts(ctx context.Context, userID string) error {
	return nil
}
func (m *mockUserRepo) IncrementFailedAndLockIfNeeded(ctx context.Context, userID string, maxAttempts int, lockDuration time.Duration) (bool, error) {
	return false, nil
}

// ---------------------------------------------------------------------------
// Helper to build a PermissionService with mock repos and pre-loaded cache
// ---------------------------------------------------------------------------

func newTestService(permRepo *mockPermRepo, userRepo *mockUserRepo, roleCache map[string]*string) *PermissionService {
	return newTestServiceWithMeta(permRepo, userRepo, roleCache, nil)
}

func newTestServiceWithMeta(permRepo *mockPermRepo, userRepo *mockUserRepo, roleCache map[string]*string, meta metadataProvider) *PermissionService {
	ps := &PermissionService{
		repo:               permRepo,
		userRepo:           userRepo,
		roleHierarchyCache: make(map[string]*string),
		sharingRuleCache:   make(map[string]*cachedSharingRules),
		metadata:           meta,
	}
	if roleCache != nil {
		ps.roleHierarchyCache = roleCache
	}
	return ps
}

func strPtr(s string) *string { return &s }

// ---------------------------------------------------------------------------
// Tests: CheckObjectPermissionWithUser — DB-backed paths
// ---------------------------------------------------------------------------

func TestCheckObjectPermission_WithMock(t *testing.T) {
	tests := []struct {
		name      string
		user      *models.UserSession
		object    string
		operation string
		perm      *models.ObjectPermissions
		permErr   error
		want      bool
	}{
		{
			name:      "nil user returns false",
			user:      nil,
			object:    "Account",
			operation: constants.PermRead,
			want:      false,
		},
		{
			name:      "system admin bypasses all checks",
			user:      &models.UserSession{ID: "u1", ProfileID: constants.ProfileSystemAdmin},
			object:    "Account",
			operation: constants.PermDelete,
			want:      true,
		},
		{
			name:      "IsSystemAdmin flag bypasses all checks",
			user:      &models.UserSession{ID: "u1", ProfileID: "regular", IsSystemAdmin: true},
			object:    "Account",
			operation: constants.PermDelete,
			want:      true,
		},
		{
			name:      "repo error returns false",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			object:    "Account",
			operation: constants.PermRead,
			permErr:   fmt.Errorf("db down"),
			want:      false,
		},
		{
			name:      "nil perm record returns false",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			object:    "Account",
			operation: constants.PermRead,
			perm:      nil,
			want:      false,
		},
		{
			name:      "Read granted",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			object:    "Account",
			operation: constants.PermRead,
			perm:      &models.ObjectPermissions{PermissionsRead: true},
			want:      true,
		},
		{
			name:      "Read denied",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			object:    "Account",
			operation: constants.PermRead,
			perm:      &models.ObjectPermissions{PermissionsRead: false},
			want:      false,
		},
		{
			name:      "Create granted",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			object:    "Account",
			operation: constants.PermCreate,
			perm:      &models.ObjectPermissions{PermissionsCreate: true},
			want:      true,
		},
		{
			name:      "Edit granted",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			object:    "Account",
			operation: constants.PermEdit,
			perm:      &models.ObjectPermissions{PermissionsEdit: true},
			want:      true,
		},
		{
			name:      "Delete granted",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			object:    "Account",
			operation: constants.PermDelete,
			perm:      &models.ObjectPermissions{PermissionsDelete: true},
			want:      true,
		},
		{
			name:      "unknown operation returns false",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			object:    "Account",
			operation: "Merge",
			perm:      &models.ObjectPermissions{PermissionsRead: true, PermissionsCreate: true, PermissionsEdit: true, PermissionsDelete: true},
			want:      false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &mockPermRepo{
				loadEffectiveObjectPerm: func(_ context.Context, _ *models.UserSession, _ string) (*models.ObjectPermissions, error) {
					return tt.perm, tt.permErr
				},
			}
			ps := newTestService(repo, &mockUserRepo{}, nil)
			got := ps.CheckObjectPermissionWithUser(context.Background(), tt.object, tt.operation, tt.user)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCheckObjectPermission_ProtectedSetupObjectsRequireSystemPermission(t *testing.T) {
	tests := []struct {
		name       string
		object     string
		operation  string
		systemPerm map[string]bool
		objectPerm *models.ObjectPermissions
		want       bool
	}{
		{
			name:       "user object requires ManageUsers",
			object:     constants.TableUser,
			operation:  constants.PermRead,
			systemPerm: map[string]bool{"ManageUsers": true},
			objectPerm: &models.ObjectPermissions{PermissionsRead: true},
			want:       true,
		},
		{
			name:       "user object Read is exempt from ManageUsers (SF Read-exempt model)",
			object:     constants.TableUser,
			operation:  constants.PermRead,
			systemPerm: map[string]bool{"ManageUsers": false},
			objectPerm: &models.ObjectPermissions{PermissionsRead: true},
			want:       true,
		},
		{
			name:       "profile read allowed for ManageUsers",
			object:     constants.TableProfile,
			operation:  constants.PermRead,
			systemPerm: map[string]bool{"ManageUsers": true},
			objectPerm: &models.ObjectPermissions{PermissionsRead: true},
			want:       true,
		},
		{
			name:       "profile edit allowed when metadata service unavailable (no setup gate without metadata)",
			object:     constants.TableProfile,
			operation:  constants.PermEdit,
			systemPerm: map[string]bool{"ManageUsers": true, "ManageProfilesPermSets": false},
			objectPerm: &models.ObjectPermissions{PermissionsEdit: true},
			want:       true, // Without metadata, setup gate is skipped — object perm alone decides
		},
		{
			name:       "permission set requires ManageProfilesPermSets",
			object:     constants.TablePermissionSet,
			operation:  constants.PermRead,
			systemPerm: map[string]bool{"ManageProfilesPermSets": true},
			objectPerm: &models.ObjectPermissions{PermissionsRead: true},
			want:       true,
		},
		{
			name:       "role object Read is exempt from ManageRoles (SF Read-exempt model)",
			object:     constants.TableUserRole,
			operation:  constants.PermRead,
			systemPerm: map[string]bool{"ManageRoles": false},
			objectPerm: &models.ObjectPermissions{PermissionsRead: true},
			want:       true,
		},
		{
			name:       "group member requires ManageSharing",
			object:     constants.TableGroupMember,
			operation:  constants.PermRead,
			systemPerm: map[string]bool{"ManageSharing": true},
			objectPerm: &models.ObjectPermissions{PermissionsRead: true},
			want:       true,
		},
		{
			name:       "non-setup object still uses object permissions only",
			object:     "Account",
			operation:  constants.PermRead,
			systemPerm: map[string]bool{},
			objectPerm: &models.ObjectPermissions{PermissionsRead: true},
			want:       true,
		},
		{
			name:       "setup object still needs object permission after system permission passes",
			object:     constants.TableUser,
			operation:  constants.PermEdit,
			systemPerm: map[string]bool{"ManageUsers": true},
			objectPerm: &models.ObjectPermissions{PermissionsEdit: false},
			want:       false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &mockPermRepo{
				loadEffectiveObjectPerm: func(_ context.Context, _ *models.UserSession, _ string) (*models.ObjectPermissions, error) {
					return tt.objectPerm, nil
				},
				getEffSysPerms: func(_ context.Context, _, _ string) (map[string]bool, error) {
					return tt.systemPerm, nil
				},
			}

			ps := newTestService(repo, &mockUserRepo{}, nil)
			user := &models.UserSession{ID: "u1", ProfileID: "standard"}

			got := ps.CheckObjectPermissionWithUser(context.Background(), tt.object, tt.operation, user)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: CheckSystemPermission
// ---------------------------------------------------------------------------

func TestCheckSystemPermission(t *testing.T) {
	tests := []struct {
		name       string
		user       *models.UserSession
		permission string
		want       bool
	}{
		{
			name: "nil user returns false",
			user: nil,
			want: false,
		},
		{
			name: "system admin has all system permissions",
			user: &models.UserSession{ID: "u1", ProfileID: constants.ProfileSystemAdmin},
			want: true,
		},
		{
			name: "IsSystemAdmin flag grants all system permissions",
			user: &models.UserSession{ID: "u1", ProfileID: "standard", IsSystemAdmin: true},
			want: true,
		},
		{
			name: "regular user has no system permissions",
			user: &models.UserSession{ID: "u1", ProfileID: "standard"},
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
			got := ps.CheckSystemPermission(context.Background(), tt.user, "ManageUsers")
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: CheckPermissionOrErrorWithUser
// ---------------------------------------------------------------------------

func TestCheckPermissionOrError_WithMock(t *testing.T) {
	tests := []struct {
		name      string
		user      *models.UserSession
		operation string
		perm      *models.ObjectPermissions
		wantErr   bool
		errType   string // "permission" or ""
	}{
		{
			name:      "nil user returns PermissionError",
			user:      nil,
			operation: constants.PermRead,
			wantErr:   true,
			errType:   "permission",
		},
		{
			name:      "super user returns nil error",
			user:      &models.UserSession{ID: "u1", ProfileID: constants.ProfileSystemAdmin},
			operation: constants.PermRead,
			wantErr:   false,
		},
		{
			name:      "permission granted returns nil error",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			operation: constants.PermRead,
			perm:      &models.ObjectPermissions{PermissionsRead: true},
			wantErr:   false,
		},
		{
			name:      "permission denied returns PermissionError",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			operation: constants.PermEdit,
			perm:      &models.ObjectPermissions{PermissionsEdit: false},
			wantErr:   true,
			errType:   "permission",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &mockPermRepo{
				loadEffectiveObjectPerm: func(_ context.Context, _ *models.UserSession, _ string) (*models.ObjectPermissions, error) {
					return tt.perm, nil
				},
			}
			ps := newTestService(repo, &mockUserRepo{}, nil)
			err := ps.CheckPermissionOrErrorWithUser(context.Background(), "Account", tt.operation, tt.user)
			if tt.wantErr && err == nil {
				t.Error("expected error, got nil")
			}
			if !tt.wantErr && err != nil {
				t.Errorf("expected no error, got: %v", err)
			}
			if tt.errType == "permission" && err != nil && !errors.IsPermission(err) {
				t.Errorf("expected PermissionError, got: %T", err)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: CheckFieldVisibilityWithUser — DB-backed paths
// ---------------------------------------------------------------------------

func TestCheckFieldVisibility_WithMock(t *testing.T) {
	tests := []struct {
		name      string
		user      *models.UserSession
		fieldPerm *models.FieldPermissions
		fieldErr  error
		objPerm   *models.ObjectPermissions // fallback
		want      bool
	}{
		{
			name: "nil user returns false",
			user: nil,
			want: false,
		},
		{
			name: "super user sees all fields",
			user: &models.UserSession{ID: "u1", ProfileID: constants.ProfileSystemAdmin},
			want: true,
		},
		{
			name:      "explicit field perm read=true",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			fieldPerm: &models.FieldPermissions{PermissionsRead: true},
			want:      true,
		},
		{
			name:      "explicit field perm read=false",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			fieldPerm: &models.FieldPermissions{PermissionsRead: false},
			want:      false,
		},
		{
			name:    "no field perm, fallback to object read=true",
			user:    &models.UserSession{ID: "u1", ProfileID: "standard"},
			objPerm: &models.ObjectPermissions{PermissionsRead: true},
			want:    true,
		},
		{
			name:    "no field perm, fallback to object read=false",
			user:    &models.UserSession{ID: "u1", ProfileID: "standard"},
			objPerm: &models.ObjectPermissions{PermissionsRead: false},
			want:    false,
		},
		{
			name:     "field perm error, fallback to object read",
			user:     &models.UserSession{ID: "u1", ProfileID: "standard"},
			fieldErr: fmt.Errorf("db error"),
			objPerm:  &models.ObjectPermissions{PermissionsRead: true},
			// When fieldErr != nil, loadEffectiveFieldPermission returns error -> false
			// Then it does NOT fall through to object perm. It returns false.
			want: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &mockPermRepo{
				loadEffectiveFieldPerm: func(_ context.Context, _ *models.UserSession, _, _ string) (*models.FieldPermissions, error) {
					return tt.fieldPerm, tt.fieldErr
				},
				loadEffectiveObjectPerm: func(_ context.Context, _ *models.UserSession, _ string) (*models.ObjectPermissions, error) {
					return tt.objPerm, nil
				},
			}
			ps := newTestService(repo, &mockUserRepo{}, nil)
			got := ps.CheckFieldVisibilityWithUser(context.Background(), "Account", "Industry", tt.user)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: CheckFieldEditabilityWithUser — DB-backed paths
// ---------------------------------------------------------------------------

func TestCheckFieldEditability_WithMock(t *testing.T) {
	tests := []struct {
		name      string
		user      *models.UserSession
		field     string
		fieldPerm *models.FieldPermissions
		objPerm   *models.ObjectPermissions // fallback
		want      bool
	}{
		{
			name:  "system field Id never editable even for admin",
			user:  &models.UserSession{ID: "u1", ProfileID: constants.ProfileSystemAdmin},
			field: constants.FieldID,
			want:  false,
		},
		{
			name:  "system field CreatedDate never editable",
			user:  &models.UserSession{ID: "u1", ProfileID: constants.ProfileSystemAdmin},
			field: constants.FieldCreatedDate,
			want:  false,
		},
		{
			name:  "nil user returns false for non-system field",
			user:  nil,
			field: "Industry",
			want:  false,
		},
		{
			name:  "super user can edit non-system field",
			user:  &models.UserSession{ID: "u1", ProfileID: constants.ProfileSystemAdmin},
			field: "Industry",
			want:  true,
		},
		{
			name:      "explicit field perm edit=true",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			field:     "Industry",
			fieldPerm: &models.FieldPermissions{PermissionsEdit: true},
			want:      true,
		},
		{
			name:      "explicit field perm edit=false",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard"},
			field:     "Industry",
			fieldPerm: &models.FieldPermissions{PermissionsEdit: false},
			want:      false,
		},
		{
			name:    "no field perm, fallback to object edit=true",
			user:    &models.UserSession{ID: "u1", ProfileID: "standard"},
			field:   "Industry",
			objPerm: &models.ObjectPermissions{PermissionsEdit: true},
			want:    true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &mockPermRepo{
				loadEffectiveFieldPerm: func(_ context.Context, _ *models.UserSession, _, _ string) (*models.FieldPermissions, error) {
					return tt.fieldPerm, nil
				},
				loadEffectiveObjectPerm: func(_ context.Context, _ *models.UserSession, _ string) (*models.ObjectPermissions, error) {
					return tt.objPerm, nil
				},
			}
			ps := newTestService(repo, &mockUserRepo{}, nil)
			got := ps.CheckFieldEditabilityWithUser(context.Background(), "Account", tt.field, tt.user)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: isFieldSystemReadOnlyByName
// ---------------------------------------------------------------------------

func TestIsFieldSystemReadOnlyByName(t *testing.T) {
	systemFields := []string{
		constants.FieldID,
		constants.FieldCreatedDate,
		constants.FieldCreatedByID,
		constants.FieldLastModifiedDate,
		constants.FieldLastModifiedByID,
		constants.FieldIsDeleted,
	}
	for _, f := range systemFields {
		if !isFieldSystemReadOnlyByName(f) {
			t.Errorf("expected %s to be system read-only", f)
		}
	}

	nonSystemFields := []string{"Name", "Industry", "OwnerId", "CustomField__c"}
	for _, f := range nonSystemFields {
		if isFieldSystemReadOnlyByName(f) {
			t.Errorf("expected %s to NOT be system read-only", f)
		}
	}
}

// ---------------------------------------------------------------------------
// Tests: accessLevelAllowsOperation
// ---------------------------------------------------------------------------

func TestAccessLevelAllowsOperation(t *testing.T) {
	ps := &PermissionService{}
	tests := []struct {
		level string
		op    string
		want  bool
	}{
		{constants.PermEdit, constants.PermRead, true},
		{constants.PermEdit, constants.PermEdit, true},
		{constants.PermEdit, constants.PermDelete, false},
		{constants.PermRead, constants.PermRead, true},
		{constants.PermRead, constants.PermEdit, false},
		{constants.PermRead, constants.PermDelete, false},
		{"", constants.PermRead, false},
		{"Full", constants.PermRead, true},
	}

	for _, tt := range tests {
		name := fmt.Sprintf("level=%s,op=%s", tt.level, tt.op)
		t.Run(name, func(t *testing.T) {
			got := ps.accessLevelAllowsOperation(tt.level, tt.op)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: Role Hierarchy (GetRoleAncestors, isUserAboveInHierarchy)
// ---------------------------------------------------------------------------

func TestGetRoleAncestors(t *testing.T) {
	// Hierarchy: CEO -> VP -> Manager -> Rep
	ceo := "ceo"
	vp := "vp"
	mgr := "mgr"
	cache := map[string]*string{
		"rep": &mgr,
		"mgr": &vp,
		"vp":  &ceo,
		"ceo": nil,
	}
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, cache)

	tests := []struct {
		roleID string
		want   []string
	}{
		{"rep", []string{"mgr", "vp", "ceo"}},
		{"mgr", []string{"vp", "ceo"}},
		{"vp", []string{"ceo"}},
		{"ceo", []string{}},
		{"nonexistent", []string{}},
	}

	for _, tt := range tests {
		t.Run(tt.roleID, func(t *testing.T) {
			got := ps.GetRoleAncestors(tt.roleID)
			if len(got) != len(tt.want) {
				t.Errorf("got %v, want %v", got, tt.want)
				return
			}
			for i, id := range got {
				if id != tt.want[i] {
					t.Errorf("ancestor[%d] = %s, want %s", i, id, tt.want[i])
				}
			}
		})
	}
}

func TestGetRoleAncestors_CircularDetection(t *testing.T) {
	// Circular hierarchy: A -> B -> C -> A
	a := "a"
	b := "b"
	c := "c"
	cache := map[string]*string{
		"a": &c,
		"b": &a,
		"c": &b,
	}
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, cache)
	ancestors := ps.GetRoleAncestors("a")
	// Should terminate without infinite loop; exact output depends on visit order
	if len(ancestors) > 3 {
		t.Errorf("expected circular detection to limit ancestors, got %d", len(ancestors))
	}
}

func TestIsUserAboveInHierarchy(t *testing.T) {
	ceo := "ceo"
	vp := "vp"
	mgr := "mgr"
	cache := map[string]*string{
		"rep": &mgr,
		"mgr": &vp,
		"vp":  &ceo,
		"ceo": nil,
	}
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, cache)

	tests := []struct {
		name       string
		userRole   *string
		targetRole *string
		want       bool
	}{
		{"manager above rep", strPtr("mgr"), strPtr("rep"), true},
		{"vp above rep (two levels)", strPtr("vp"), strPtr("rep"), true},
		{"ceo above rep (three levels)", strPtr("ceo"), strPtr("rep"), true},
		{"rep NOT above manager", strPtr("rep"), strPtr("mgr"), false},
		{"same role is NOT above", strPtr("mgr"), strPtr("mgr"), false},
		{"nil user role", nil, strPtr("rep"), false},
		{"nil target role", strPtr("mgr"), nil, false},
		{"both nil", nil, nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ps.isUserAboveInHierarchy(tt.userRole, tt.targetRole)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: isUserInRoleOrAbove (sharing rules helper)
// ---------------------------------------------------------------------------

func TestIsUserInRoleOrAbove(t *testing.T) {
	ceo := "ceo"
	vp := "vp"
	mgr := "mgr"
	cache := map[string]*string{
		"rep": &mgr,
		"mgr": &vp,
		"vp":  &ceo,
		"ceo": nil,
	}
	ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, cache)

	tests := []struct {
		name       string
		userRole   *string
		targetRole *string
		want       bool
	}{
		{"exact match", strPtr("mgr"), strPtr("mgr"), true},
		{"user above target", strPtr("vp"), strPtr("rep"), true},
		{"user below target", strPtr("rep"), strPtr("mgr"), false},
		{"nil user role", nil, strPtr("rep"), false},
		{"nil target role", strPtr("mgr"), nil, false},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := ps.isUserInRoleOrAbove(tt.userRole, tt.targetRole)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: CheckRecordAccess — with mocked repos
// ---------------------------------------------------------------------------

func TestCheckRecordAccess_WithMock(t *testing.T) {
	mgr := "mgr"
	cache := map[string]*string{
		"rep": &mgr,
		"mgr": nil,
	}

	tests := []struct {
		name      string
		user      *models.UserSession
		record    models.SObject
		schema    *models.ObjectMetadata
		operation string
		setupRepo func(r *mockPermRepo)
		setupUser func(r *mockUserRepo)
		want      bool
	}{
		{
			name: "nil user returns false",
			user: nil,
			record: models.SObject{
				constants.FieldID:      "rec1",
				constants.FieldOwnerID: "user1",
			},
			operation: constants.PermRead,
			want:      false,
		},
		{
			name: "super user bypasses all",
			user: &models.UserSession{ID: "u1", ProfileID: constants.ProfileSystemAdmin},
			record: models.SObject{
				constants.FieldID:      "rec1",
				constants.FieldOwnerID: "other",
			},
			operation: constants.PermEdit,
			want:      true,
		},
		{
			name: "owner has full access",
			user: &models.UserSession{ID: "user1", ProfileID: "standard"},
			record: models.SObject{
				constants.FieldID:      "rec1",
				constants.FieldOwnerID: "user1",
			},
			operation: constants.PermEdit,
			want:      true,
		},
		{
			name: "group membership grants access",
			user: &models.UserSession{ID: "user1", ProfileID: "standard"},
			record: models.SObject{
				constants.FieldID:      "rec1",
				constants.FieldOwnerID: "queue-group-1",
			},
			operation: constants.PermRead,
			setupRepo: func(r *mockPermRepo) {
				r.isUserInGroup = func(_ context.Context, groupID, userID string) (bool, error) {
					return groupID == "queue-group-1" && userID == "user1", nil
				}
			},
			want: true,
		},
		{
			name: "role hierarchy denied when schema is nil (fail-closed)",
			user: &models.UserSession{ID: "mgr-user", ProfileID: "standard", RoleID: strPtr("mgr")},
			record: models.SObject{
				constants.FieldID:      "rec1",
				constants.FieldOwnerID: "rep-user",
			},
			operation: constants.PermRead,
			setupUser: func(r *mockUserRepo) {
				r.getUserRoleID = func(_ context.Context, userID string) (*string, error) {
					if userID == "rep-user" {
						return strPtr("rep"), nil
					}
					return nil, nil
				}
			},
			want: false,
		},
		{
			name: "role hierarchy denied for edit when schema is nil (fail-closed)",
			user: &models.UserSession{ID: "mgr-user", ProfileID: "standard", RoleID: strPtr("mgr")},
			record: models.SObject{
				constants.FieldID:      "rec1",
				constants.FieldOwnerID: "rep-user",
			},
			operation: constants.PermEdit,
			setupUser: func(r *mockUserRepo) {
				r.getUserRoleID = func(_ context.Context, userID string) (*string, error) {
					if userID == "rep-user" {
						return strPtr("rep"), nil
					}
					return nil, nil
				}
			},
			want: false,
		},
		{
			name: "no access when nothing matches",
			user: &models.UserSession{ID: "user1", ProfileID: "standard"},
			record: models.SObject{
				constants.FieldID:      "rec1",
				constants.FieldOwnerID: "other-user",
			},
			operation: constants.PermRead,
			want:      false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &mockPermRepo{}
			userRepo := &mockUserRepo{}
			if tt.setupRepo != nil {
				tt.setupRepo(repo)
			}
			if tt.setupUser != nil {
				tt.setupUser(userRepo)
			}
			ps := newTestService(repo, userRepo, cache)
			got := ps.CheckRecordAccess(context.Background(), tt.schema, tt.record, tt.operation, tt.user)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: checkManualShareAccess (direct method test)
// ---------------------------------------------------------------------------

func TestCheckManualShareAccess(t *testing.T) {
	tests := []struct {
		name      string
		levels    []string
		levelsErr error
		operation string
		want      bool
	}{
		{
			name:      "Edit level grants read",
			levels:    []string{constants.PermEdit},
			operation: constants.PermRead,
			want:      true,
		},
		{
			name:      "Edit level grants edit",
			levels:    []string{constants.PermEdit},
			operation: constants.PermEdit,
			want:      true,
		},
		{
			name:      "Read level grants read",
			levels:    []string{constants.PermRead},
			operation: constants.PermRead,
			want:      true,
		},
		{
			name:      "Read level denies edit",
			levels:    []string{constants.PermRead},
			operation: constants.PermEdit,
			want:      false,
		},
		{
			name:      "empty levels denies everything",
			levels:    []string{},
			operation: constants.PermRead,
			want:      false,
		},
		{
			name:      "repo error denies access",
			levelsErr: fmt.Errorf("db error"),
			operation: constants.PermRead,
			want:      false,
		},
		{
			name:      "multiple levels - first match wins",
			levels:    []string{constants.PermRead, constants.PermEdit},
			operation: constants.PermEdit,
			want:      true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &mockPermRepo{
				getManualShareAccessLevels: func(_ context.Context, _, _, _ string) ([]string, error) {
					return tt.levels, tt.levelsErr
				},
			}
			ps := newTestService(repo, &mockUserRepo{}, nil)
			user := &models.UserSession{ID: "u1", ProfileID: "standard"}
			got := ps.checkManualShareAccess(context.Background(), "Account", "rec1", user, tt.operation)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

func TestCheckRecordAccess_PendingApprovalLock(t *testing.T) {
	schema := &models.ObjectMetadata{
		APIName:      "Account",
		SharingModel: constants.SharingModelPublicReadWrite,
	}
	record := models.SObject{
		constants.FieldID:      "acc-1",
		constants.FieldOwnerID: "user-1",
	}
	user := &models.UserSession{ID: "user-1", ProfileID: "standard"}

	t.Run("admin lock denies owner edit", func(t *testing.T) {
		repo := &mockPermRepo{
			getPendingApprovalLockInfo: func(_ context.Context, targetObjectID string) (string, string, error) {
				if targetObjectID == "acc-1" {
					return "pi-1", "Admin", nil
				}
				return "", "", nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, map[string]*string{})

		if ps.CheckRecordAccess(context.Background(), schema, record, constants.PermEdit, user) {
			t.Fatal("expected pending admin lock to block edit access")
		}
	})

	t.Run("current approver can edit when process allows it", func(t *testing.T) {
		repo := &mockPermRepo{
			getPendingApprovalLockInfo: func(_ context.Context, targetObjectID string) (string, string, error) {
				if targetObjectID == "acc-1" {
					return "pi-1", "WorkItem", nil
				}
				return "", "", nil
			},
			hasPendingApprovalWorkitemForUser: func(_ context.Context, processInstanceID, userID string) (bool, error) {
				return processInstanceID == "pi-1" && userID == "user-1", nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, map[string]*string{})

		if !ps.CheckRecordAccess(context.Background(), schema, record, constants.PermEdit, user) {
			t.Fatal("expected current approver to retain edit access")
		}
	})

	t.Run("service context bypasses approval lock for runtime actions", func(t *testing.T) {
		repo := &mockPermRepo{
			getPendingApprovalLockInfo: func(_ context.Context, targetObjectID string) (string, string, error) {
				if targetObjectID == "acc-1" {
					return "pi-1", "Admin", nil
				}
				return "", "", nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, map[string]*string{})
		ctx := constants.WithSecurityContext(context.Background(), constants.ServiceContext, "test: approval runtime")

		if !ps.CheckRecordAccess(ctx, schema, record, constants.PermEdit, user) {
			t.Fatal("expected service context to bypass approval lock")
		}
	})
}

// ---------------------------------------------------------------------------
// Tests: checkTeamMemberAccess (direct method test)
// ---------------------------------------------------------------------------

func TestCheckTeamMemberAccess(t *testing.T) {
	tests := []struct {
		name      string
		level     *string
		levelErr  error
		operation string
		want      bool
	}{
		{
			name:      "Edit level grants read",
			level:     strPtr(constants.PermEdit),
			operation: constants.PermRead,
			want:      true,
		},
		{
			name:      "Edit level grants edit",
			level:     strPtr(constants.PermEdit),
			operation: constants.PermEdit,
			want:      true,
		},
		{
			name:      "Read level grants read",
			level:     strPtr(constants.PermRead),
			operation: constants.PermRead,
			want:      true,
		},
		{
			name:      "Read level denies edit",
			level:     strPtr(constants.PermRead),
			operation: constants.PermEdit,
			want:      false,
		},
		{
			name:      "nil level denies access",
			level:     nil,
			operation: constants.PermRead,
			want:      false,
		},
		{
			name:      "repo error denies access",
			levelErr:  fmt.Errorf("db error"),
			operation: constants.PermRead,
			want:      false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &mockPermRepo{
				getTeamMemberAccessLevel: func(_ context.Context, _, _, _ string) (*string, error) {
					return tt.level, tt.levelErr
				},
			}
			ps := newTestService(repo, &mockUserRepo{}, nil)
			user := &models.UserSession{ID: "u1", ProfileID: "standard"}
			got := ps.checkTeamMemberAccess(context.Background(), "Account", "rec1", user, tt.operation)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: GetEffectiveSchema — with mocked repos
// ---------------------------------------------------------------------------

func TestGetEffectiveSchema_WithMock(t *testing.T) {
	t.Run("nil schema returns nil", func(t *testing.T) {
		ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
		if ps.GetEffectiveSchema(context.Background(), nil, nil) != nil {
			t.Error("expected nil")
		}
	})

	t.Run("super user sees all fields unfiltered", func(t *testing.T) {
		ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
		schema := &models.ObjectMetadata{
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: "Name"},
				{APIName: "Secret"},
				{APIName: "Revenue"},
			},
		}
		user := &models.UserSession{ID: "admin", ProfileID: constants.ProfileSystemAdmin}
		result := ps.GetEffectiveSchema(context.Background(), schema, user)
		if len(result.Fields) != 3 {
			t.Errorf("expected 3 fields, got %d", len(result.Fields))
		}
	})

	t.Run("nil user sees no fields", func(t *testing.T) {
		ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
		schema := &models.ObjectMetadata{
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: "Name"},
				{APIName: "Secret"},
			},
		}
		result := ps.GetEffectiveSchema(context.Background(), schema, nil)
		if len(result.Fields) != 0 {
			t.Errorf("expected 0 fields, got %d", len(result.Fields))
		}
	})

	t.Run("regular user sees only permitted fields", func(t *testing.T) {
		repo := &mockPermRepo{
			loadEffectiveFieldPerm: func(_ context.Context, _ *models.UserSession, _, fieldAPIName string) (*models.FieldPermissions, error) {
				switch fieldAPIName {
				case "Name":
					return &models.FieldPermissions{PermissionsRead: true}, nil
				case "Secret":
					return &models.FieldPermissions{PermissionsRead: false}, nil
				case "Revenue":
					return &models.FieldPermissions{PermissionsRead: true}, nil
				default:
					return nil, nil
				}
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		schema := &models.ObjectMetadata{
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: "Name"},
				{APIName: "Secret"},
				{APIName: "Revenue"},
			},
		}
		user := &models.UserSession{ID: "u1", ProfileID: "standard"}
		result := ps.GetEffectiveSchema(context.Background(), schema, user)
		if len(result.Fields) != 2 {
			t.Errorf("expected 2 visible fields, got %d", len(result.Fields))
		}
		// Verify correct fields
		names := make(map[string]bool)
		for _, f := range result.Fields {
			names[f.APIName] = true
		}
		if !names["Name"] || !names["Revenue"] {
			t.Errorf("expected Name and Revenue to be visible, got %v", names)
		}
	})

	t.Run("does not mutate original schema", func(t *testing.T) {
		repo := &mockPermRepo{
			loadEffectiveFieldPerm: func(_ context.Context, _ *models.UserSession, _, _ string) (*models.FieldPermissions, error) {
				return &models.FieldPermissions{PermissionsRead: false}, nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		original := &models.ObjectMetadata{
			APIName: "Account",
			Fields: []models.FieldMetadata{
				{APIName: "Name"},
				{APIName: "Secret"},
			},
		}
		user := &models.UserSession{ID: "u1", ProfileID: "standard"}
		result := ps.GetEffectiveSchema(context.Background(), original, user)
		if len(result.Fields) != 0 {
			t.Errorf("expected 0 fields for user, got %d", len(result.Fields))
		}
		// Original should still have 2 fields
		if len(original.Fields) != 2 {
			t.Errorf("original schema was mutated: expected 2 fields, got %d", len(original.Fields))
		}
	})
}

// ---------------------------------------------------------------------------
// Tests: GrantInitialPermissions
// ---------------------------------------------------------------------------

func TestGrantInitialPermissions(t *testing.T) {
	t.Run("grants CRUD to all profiles and full access to system admin", func(t *testing.T) {
		var capturedPerms []models.ObjectPermissions
		repo := &mockPermRepo{
			getAllProfiles: func(_ context.Context) ([]*models.Profile, error) {
				return []*models.Profile{
					{ID: constants.ProfileSystemAdmin, Name: "System Admin"},
					{ID: constants.ProfileStandardUser, Name: "Standard User"},
				}, nil
			},
			batchUpsertObjectPerms: func(_ context.Context, perms []models.ObjectPermissions) error {
				capturedPerms = perms
				return nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		err := ps.GrantInitialPermissions(context.Background(), "CustomObject__c")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(capturedPerms) != 2 {
			t.Fatalf("expected 2 permission records, got %d", len(capturedPerms))
		}

		for _, perm := range capturedPerms {
			if perm.SobjectType != "CustomObject__c" {
				t.Errorf("expected SobjectType=CustomObject__c, got %s", perm.SobjectType)
			}
			if !perm.PermissionsRead || !perm.PermissionsCreate || !perm.PermissionsEdit || !perm.PermissionsDelete {
				t.Errorf("expected all CRUD=true for %s", perm.ParentID)
			}
			if perm.ParentID == "ps_"+constants.ProfileSystemAdmin {
				if !perm.PermissionsViewAllRecords || !perm.PermissionsModifyAllRecords {
					t.Error("system admin should have ViewAll + ModifyAll")
				}
			} else {
				if perm.PermissionsViewAllRecords || perm.PermissionsModifyAllRecords {
					t.Error("standard user should NOT have ViewAll or ModifyAll")
				}
			}
		}
	})

	t.Run("returns error when GetAllProfiles fails", func(t *testing.T) {
		repo := &mockPermRepo{
			getAllProfiles: func(_ context.Context) ([]*models.Profile, error) {
				return nil, fmt.Errorf("db error")
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		err := ps.GrantInitialPermissions(context.Background(), "Account")
		if err == nil {
			t.Error("expected error, got nil")
		}
	})
}

// ---------------------------------------------------------------------------
// Tests: UpdatePermissionSetObjectPermission validation
// ---------------------------------------------------------------------------

func TestUpdatePermissionSetObjectPermission_Validation(t *testing.T) {
	tests := []struct {
		name    string
		perm    models.ObjectPermissions
		wantErr bool
	}{
		{
			name:    "empty ParentID returns validation error",
			perm:    models.ObjectPermissions{ParentID: "", SobjectType: "Account"},
			wantErr: true,
		},
		{
			name:    "valid ParentID succeeds",
			perm:    models.ObjectPermissions{ParentID: "ps-1", SobjectType: "Account"},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
			err := ps.UpdatePermissionSetObjectPermission(context.Background(), tt.perm)
			if tt.wantErr && err == nil {
				t.Error("expected error, got nil")
			}
			if !tt.wantErr && err != nil {
				t.Errorf("expected no error, got: %v", err)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: UpdatePermissionSetFieldPermission validation
// ---------------------------------------------------------------------------

func TestUpdatePermissionSetFieldPermission_Validation(t *testing.T) {
	tests := []struct {
		name    string
		perm    models.FieldPermissions
		wantErr bool
	}{
		{
			name:    "empty ParentID returns validation error",
			perm:    models.FieldPermissions{ParentID: ""},
			wantErr: true,
		},
		{
			name:    "valid ParentID succeeds",
			perm:    models.FieldPermissions{ParentID: "ps-1"},
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
			err := ps.UpdatePermissionSetFieldPermission(context.Background(), tt.perm)
			if tt.wantErr && err == nil {
				t.Error("expected error, got nil")
			}
			if !tt.wantErr && err != nil {
				t.Errorf("expected no error, got: %v", err)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: CreateRole validation
// ---------------------------------------------------------------------------

func TestCreateRole(t *testing.T) {
	t.Run("valid parent role succeeds", func(t *testing.T) {
		repo := &mockPermRepo{
			getRole: func(_ context.Context, id string) (*models.UserRole, error) {
				if id == "parent-1" {
					return &models.UserRole{ID: "parent-1", Name: "Parent"}, nil
				}
				return nil, nil
			},
			createRole: func(_ context.Context, name, desc string, parentID *string) (string, error) {
				return "new-role", nil
			},
			getAllRoles: func(_ context.Context) ([]*models.UserRole, error) {
				return nil, nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		role, err := ps.CreateRole(context.Background(), "Sales Rep", "Sales representative", strPtr("parent-1"))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if role.ID != "new-role" {
			t.Errorf("expected ID=new-role, got %s", role.ID)
		}
		if role.Name != "Sales Rep" {
			t.Errorf("expected Name=Sales Rep, got %s", role.Name)
		}
	})

	t.Run("nonexistent parent role fails", func(t *testing.T) {
		repo := &mockPermRepo{
			getRole: func(_ context.Context, id string) (*models.UserRole, error) {
				return nil, nil // not found
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		_, err := ps.CreateRole(context.Background(), "Sales Rep", "desc", strPtr("nonexistent"))
		if err == nil {
			t.Error("expected error for nonexistent parent")
		}
	})

	t.Run("nil parent role succeeds (top level)", func(t *testing.T) {
		repo := &mockPermRepo{
			createRole: func(_ context.Context, name, desc string, parentID *string) (string, error) {
				return "new-role", nil
			},
			getAllRoles: func(_ context.Context) ([]*models.UserRole, error) {
				return nil, nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		role, err := ps.CreateRole(context.Background(), "CEO", "CEO", nil)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if role.ParentRoleID != nil {
			t.Error("expected nil parent for top-level role")
		}
	})
}

// ---------------------------------------------------------------------------
// Tests: UpdateRole validation
// ---------------------------------------------------------------------------

func TestUpdateRole(t *testing.T) {
	t.Run("self-referencing parent is rejected", func(t *testing.T) {
		ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
		_, err := ps.UpdateRole(context.Background(), "role-1", "Name", "Desc", strPtr("role-1"))
		if err == nil {
			t.Error("expected error for self-referencing parent")
		}
	})

	t.Run("nonexistent parent role is rejected", func(t *testing.T) {
		repo := &mockPermRepo{
			getRole: func(_ context.Context, id string) (*models.UserRole, error) {
				return nil, nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		_, err := ps.UpdateRole(context.Background(), "role-1", "Name", "Desc", strPtr("nonexistent"))
		if err == nil {
			t.Error("expected error for nonexistent parent")
		}
	})

	t.Run("valid update succeeds", func(t *testing.T) {
		repo := &mockPermRepo{
			getRole: func(_ context.Context, id string) (*models.UserRole, error) {
				return &models.UserRole{ID: id, Name: "Role " + id}, nil
			},
			updateRole: func(_ context.Context, id, name, desc string, parent *string) error {
				return nil
			},
			getAllRoles: func(_ context.Context) ([]*models.UserRole, error) {
				return nil, nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		role, err := ps.UpdateRole(context.Background(), "role-1", "Updated", "New desc", strPtr("parent-1"))
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if role == nil {
			t.Error("expected non-nil role")
		}
	})
}

// ---------------------------------------------------------------------------
// Tests: BulkGrantPermissions / BulkRevokePermissions
// ---------------------------------------------------------------------------

// ---------------------------------------------------------------------------
// Tests: GetEffectivePermissionsForUser
// ---------------------------------------------------------------------------

func TestGetEffectivePermissionsForUser(t *testing.T) {
	t.Run("returns combined system and custom permissions", func(t *testing.T) {
		permRepo := &mockPermRepo{
			getEffSysPerms: func(_ context.Context, _, _ string) (map[string]bool, error) {
				return map[string]bool{"ManageUsers": true, "ViewSetup": true}, nil
			},
			getEffCustomPerms: func(_ context.Context, _, _ string) (map[string]bool, error) {
				return map[string]bool{"CustomPerm1": true}, nil
			},
		}
		userRepo := &mockUserRepo{
			getUserByID: func(_ context.Context, userID string) (*models.User, error) {
				return &models.User{ID: userID, ProfileID: "standard"}, nil
			},
		}
		ps := newTestService(permRepo, userRepo, nil)
		result, err := ps.GetEffectivePermissionsForUser(context.Background(), "user1")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if result.UserID != "user1" {
			t.Errorf("expected UserID=user1, got %s", result.UserID)
		}
		if result.ProfileID != "standard" {
			t.Errorf("expected ProfileID=standard, got %s", result.ProfileID)
		}
		if len(result.SystemPermissions) != 2 {
			t.Errorf("expected 2 system permissions, got %d", len(result.SystemPermissions))
		}
		if len(result.CustomPermissions) != 1 {
			t.Errorf("expected 1 custom permission, got %d", len(result.CustomPermissions))
		}
	})

	t.Run("returns error for unknown user", func(t *testing.T) {
		userRepo := &mockUserRepo{
			getUserByID: func(_ context.Context, _ string) (*models.User, error) {
				return nil, nil // not found
			},
		}
		ps := newTestService(&mockPermRepo{}, userRepo, nil)
		_, err := ps.GetEffectivePermissionsForUser(context.Background(), "unknown")
		if err == nil {
			t.Error("expected error for unknown user")
		}
	})
}

// ---------------------------------------------------------------------------
// Tests: GetEffectiveObjectPermissions / GetEffectiveFieldPermissions
// ---------------------------------------------------------------------------

func TestGetEffectiveObjectPermissions(t *testing.T) {
	t.Run("delegates to repo with resolved profile", func(t *testing.T) {
		repo := &mockPermRepo{
			getUserProfileID: func(_ context.Context, userID string) (string, error) {
				return "profile-1", nil
			},
			listEffObjPermsForUser: func(_ context.Context, userID, profileID string) ([]models.ObjectPermissions, error) {
				return []models.ObjectPermissions{
					{SobjectType: "Account", PermissionsRead: true},
				}, nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		result, err := ps.GetEffectiveObjectPermissions(context.Background(), "user1")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(result) != 1 {
			t.Errorf("expected 1 perm, got %d", len(result))
		}
	})

	t.Run("returns error when profile lookup fails", func(t *testing.T) {
		repo := &mockPermRepo{
			getUserProfileID: func(_ context.Context, _ string) (string, error) {
				return "", fmt.Errorf("db error")
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		_, err := ps.GetEffectiveObjectPermissions(context.Background(), "user1")
		if err == nil {
			t.Error("expected error")
		}
	})
}

func TestGetEffectiveFieldPermissions(t *testing.T) {
	t.Run("delegates to repo with resolved profile", func(t *testing.T) {
		repo := &mockPermRepo{
			getUserProfileID: func(_ context.Context, _ string) (string, error) {
				return "profile-1", nil
			},
			listEffFieldPermsForUser: func(_ context.Context, _, _ string) ([]models.FieldPermissions, error) {
				return []models.FieldPermissions{
					{Field: "Account.Name", PermissionsRead: true},
				}, nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		result, err := ps.GetEffectiveFieldPermissions(context.Background(), "user1")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(result) != 1 {
			t.Errorf("expected 1 perm, got %d", len(result))
		}
	})
}

// ---------------------------------------------------------------------------
// Tests: refreshRoleHierarchy
// ---------------------------------------------------------------------------

func TestRefreshRoleHierarchy(t *testing.T) {
	t.Run("loads roles into cache", func(t *testing.T) {
		mgr := "mgr"
		repo := &mockPermRepo{
			getAllRoles: func(_ context.Context) ([]*models.UserRole, error) {
				return []*models.UserRole{
					{ID: "rep", ParentRoleID: &mgr},
					{ID: "mgr", ParentRoleID: nil},
				}, nil
			},
		}
		ps := newTestService(repo, &mockUserRepo{}, nil)
		ps.refreshRoleHierarchy(context.Background())

		// Verify cache was populated
		ancestors := ps.GetRoleAncestors("rep")
		if len(ancestors) != 1 || ancestors[0] != "mgr" {
			t.Errorf("expected [mgr], got %v", ancestors)
		}
	})

	t.Run("handles repo error gracefully", func(t *testing.T) {
		repo := &mockPermRepo{
			getAllRoles: func(_ context.Context) ([]*models.UserRole, error) {
				return nil, fmt.Errorf("db error")
			},
		}
		// Pre-populate cache
		oldCache := map[string]*string{"old": strPtr("parent")}
		ps := newTestService(repo, &mockUserRepo{}, oldCache)
		ps.refreshRoleHierarchy(context.Background())

		// Cache should remain unchanged on error
		ancestors := ps.GetRoleAncestors("old")
		if len(ancestors) != 1 || ancestors[0] != "parent" {
			t.Errorf("cache should be unchanged on error, got ancestors: %v", ancestors)
		}
	})
}

// ---------------------------------------------------------------------------
// Tests: evaluateSharingCriteria
// ---------------------------------------------------------------------------

func TestEvaluateSharingCriteria(t *testing.T) {
	tests := []struct {
		name     string
		record   models.SObject
		criteria string
		want     bool
	}{
		{
			name:     "empty criteria matches all",
			record:   models.SObject{"Name": "Test"},
			criteria: "",
			want:     true,
		},
		{
			name:     "empty JSON array matches all",
			record:   models.SObject{"Name": "Test"},
			criteria: "[]",
			want:     true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ps := newTestService(&mockPermRepo{}, &mockUserRepo{}, nil)
			// evaluateSharingCriteria requires formula engine; test empty cases
			got := ps.evaluateSharingCriteria(tt.record, tt.criteria)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}

// ---------------------------------------------------------------------------
// Tests: checkSharingRuleAccess
// ---------------------------------------------------------------------------

func TestCheckSharingRuleAccess(t *testing.T) {
	ceo := "ceo"
	mgr := "mgr"
	cache := map[string]*string{
		"rep": &mgr,
		"mgr": &ceo,
		"ceo": nil,
	}

	// userRepo that returns "mgr" role for any owner — used by recordOwnerMatchesSharingRuleSource
	ownerRoleMgrUserRepo := &mockUserRepo{
		getUserRoleID: func(_ context.Context, _ string) (*string, error) {
			return strPtr("mgr"), nil
		},
	}

	tests := []struct {
		name      string
		user      *models.UserSession
		rule      *models.SharingRule
		record    models.SObject
		operation string
		setupRepo func(r *mockPermRepo)
		userRepo  *mockUserRepo
		want      bool
	}{
		{
			name:      "role match with read access allows read",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard", RoleID: strPtr("mgr")},
			rule:      &models.SharingRule{ShareWithRoleID: strPtr("mgr"), AccessLevel: constants.PermRead, SharedFromRoleID: strPtr("mgr")},
			record:    models.SObject{"Name": "Test", constants.FieldOwnerID: "owner-1"},
			operation: constants.PermRead,
			userRepo:  ownerRoleMgrUserRepo,
			want:      true,
		},
		{
			name:      "role match with read access denies edit",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard", RoleID: strPtr("mgr")},
			rule:      &models.SharingRule{ShareWithRoleID: strPtr("mgr"), AccessLevel: constants.PermRead, SharedFromRoleID: strPtr("mgr")},
			record:    models.SObject{"Name": "Test", constants.FieldOwnerID: "owner-1"},
			operation: constants.PermEdit,
			userRepo:  ownerRoleMgrUserRepo,
			want:      false,
		},
		{
			name:      "role match with edit access allows read",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard", RoleID: strPtr("mgr")},
			rule:      &models.SharingRule{ShareWithRoleID: strPtr("mgr"), AccessLevel: constants.PermEdit, SharedFromRoleID: strPtr("mgr")},
			record:    models.SObject{"Name": "Test", constants.FieldOwnerID: "owner-1"},
			operation: constants.PermRead,
			userRepo:  ownerRoleMgrUserRepo,
			want:      true,
		},
		{
			name:      "role match with edit access allows edit",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard", RoleID: strPtr("mgr")},
			rule:      &models.SharingRule{ShareWithRoleID: strPtr("mgr"), AccessLevel: constants.PermEdit, SharedFromRoleID: strPtr("mgr")},
			record:    models.SObject{"Name": "Test", constants.FieldOwnerID: "owner-1"},
			operation: constants.PermEdit,
			userRepo:  ownerRoleMgrUserRepo,
			want:      true,
		},
		{
			name:      "higher role inherits sharing (user above target)",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard", RoleID: strPtr("ceo")},
			rule:      &models.SharingRule{ShareWithRoleID: strPtr("mgr"), AccessLevel: constants.PermRead, SharedFromRoleID: strPtr("mgr")},
			record:    models.SObject{"Name": "Test", constants.FieldOwnerID: "owner-1"},
			operation: constants.PermRead,
			userRepo:  ownerRoleMgrUserRepo,
			want:      true,
		},
		{
			name:      "lower role does NOT inherit sharing",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard", RoleID: strPtr("rep")},
			rule:      &models.SharingRule{ShareWithRoleID: strPtr("mgr"), AccessLevel: constants.PermRead},
			record:    models.SObject{"Name": "Test"},
			operation: constants.PermRead,
			want:      false,
		},
		{
			name:      "group sharing match allows read",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard", RoleID: nil},
			rule:      &models.SharingRule{ShareWithGroupID: strPtr("group-1"), AccessLevel: constants.PermRead, SharedFromGroupID: strPtr("src-group")},
			record:    models.SObject{"Name": "Test", constants.FieldOwnerID: "owner-1"},
			operation: constants.PermRead,
			setupRepo: func(r *mockPermRepo) {
				r.isUserInGroup = func(_ context.Context, groupID, userID string) (bool, error) {
					// user u1 is in share-with group-1, owner-1 is in source src-group
					if groupID == "group-1" && userID == "u1" {
						return true, nil
					}
					if groupID == "src-group" && userID == "owner-1" {
						return true, nil
					}
					return false, nil
				}
			},
			want: true,
		},
		{
			name:      "group non-member denied",
			user:      &models.UserSession{ID: "u2", ProfileID: "standard", RoleID: nil},
			rule:      &models.SharingRule{ShareWithGroupID: strPtr("group-1"), AccessLevel: constants.PermRead},
			record:    models.SObject{"Name": "Test"},
			operation: constants.PermRead,
			setupRepo: func(r *mockPermRepo) {
				r.isUserInGroup = func(_ context.Context, groupID, userID string) (bool, error) {
					return false, nil
				}
			},
			want: false,
		},
		{
			name:      "delete operation denied even with edit access",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard", RoleID: strPtr("mgr")},
			rule:      &models.SharingRule{ShareWithRoleID: strPtr("mgr"), AccessLevel: constants.PermEdit},
			record:    models.SObject{"Name": "Test"},
			operation: constants.PermDelete,
			want:      false,
		},
		{
			name:      "unknown access level denies everything",
			user:      &models.UserSession{ID: "u1", ProfileID: "standard", RoleID: strPtr("mgr")},
			rule:      &models.SharingRule{ShareWithRoleID: strPtr("mgr"), AccessLevel: "Full"},
			record:    models.SObject{"Name": "Test"},
			operation: constants.PermRead,
			want:      false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			repo := &mockPermRepo{}
			if tt.setupRepo != nil {
				tt.setupRepo(repo)
			}
			ur := &mockUserRepo{}
			if tt.userRepo != nil {
				ur = tt.userRepo
			}
			ps := newTestService(repo, ur, cache)
			got := ps.checkSharingRuleAccess(context.Background(), tt.record, tt.rule, tt.user, tt.operation)
			if got != tt.want {
				t.Errorf("got %v, want %v", got, tt.want)
			}
		})
	}
}
