import mongoose, { Types, Model } from 'mongoose';
import { createModels, createMethods, RoleBits } from '@librechat/data-schemas';
import { MongoMemoryServer } from 'mongodb-memory-server';
import { ResourceType, AccessRoleIds, PrincipalType } from 'librechat-data-provider';
import { AccessControlService } from './accessControlService';

// Mock the logger
jest.mock('@librechat/data-schemas', () => ({
  ...jest.requireActual('@librechat/data-schemas'),
  logger: {
    error: jest.fn(),
    warn: jest.fn(),
    debug: jest.fn(),
    info: jest.fn(),
  },
}));

let mongoServer: MongoMemoryServer;
let AclEntry: Model<unknown>;
let service: AccessControlService;
let dbMethods: ReturnType<typeof createMethods>;

// Mock getUserPrincipals to control test scenarios
const mockGetUserPrincipals = jest.fn();

beforeAll(async () => {
  mongoServer = await MongoMemoryServer.create();
  const mongoUri = mongoServer.getUri();
  await mongoose.connect(mongoUri);

  // Initialize all models
  createModels(mongoose);

  AclEntry = mongoose.models.AclEntry;

  // Create methods and seed default roles
  dbMethods = createMethods(mongoose);
  await dbMethods.seedDefaultRoles();

  // Create service instance
  service = new AccessControlService(mongoose);

  // Mock getUserPrincipals in the dbMethods
  const originalMethods = service['_dbMethods'];
  service['_dbMethods'] = {
    ...originalMethods,
    getUserPrincipals: mockGetUserPrincipals,
  };
});

afterAll(async () => {
  await mongoose.disconnect();
  await mongoServer.stop();
});

beforeEach(async () => {
  // Clear test data but keep seeded roles
  await AclEntry.deleteMany({});
  mockGetUserPrincipals.mockReset();
});

describe('AccessControlService', () => {
  // Common test data
  const userId = new Types.ObjectId();
  const groupId = new Types.ObjectId();
  const resourceId = new Types.ObjectId();
  const grantedById = new Types.ObjectId();

  describe('grantPermission', () => {
    describe('validation', () => {
      test('should throw error for invalid principal type', async () => {
        await expect(
          service.grantPermission({
            principalType: 'invalid' as PrincipalType,
            principalId: userId,
            resourceType: ResourceType.AGENT,
            resourceId,
            accessRoleId: AccessRoleIds.AGENT_VIEWER,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Invalid principal type: invalid');
      });

      test('should throw error for missing principalId with user type', async () => {
        await expect(
          service.grantPermission({
            principalType: PrincipalType.USER,
            principalId: null,
            resourceType: ResourceType.AGENT,
            resourceId,
            accessRoleId: AccessRoleIds.AGENT_VIEWER,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Principal ID is required for user, group, and role principals');
      });

      test('should throw error for missing principalId with group type', async () => {
        await expect(
          service.grantPermission({
            principalType: PrincipalType.GROUP,
            principalId: null,
            resourceType: ResourceType.AGENT,
            resourceId,
            accessRoleId: AccessRoleIds.AGENT_VIEWER,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Principal ID is required for user, group, and role principals');
      });

      test('should throw error for missing principalId with role type', async () => {
        await expect(
          service.grantPermission({
            principalType: PrincipalType.ROLE,
            principalId: null,
            resourceType: ResourceType.AGENT,
            resourceId,
            accessRoleId: AccessRoleIds.AGENT_VIEWER,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Principal ID is required for user, group, and role principals');
      });

      test('should throw error for invalid role ID (empty string)', async () => {
        // Empty string is falsy, so it triggers the "principalId required" check first
        await expect(
          service.grantPermission({
            principalType: PrincipalType.ROLE,
            principalId: '',
            resourceType: ResourceType.AGENT,
            resourceId,
            accessRoleId: AccessRoleIds.AGENT_VIEWER,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Principal ID is required for user, group, and role principals');
      });

      test('should throw error for invalid role ID (whitespace only)', async () => {
        await expect(
          service.grantPermission({
            principalType: PrincipalType.ROLE,
            principalId: '   ',
            resourceType: ResourceType.AGENT,
            resourceId,
            accessRoleId: AccessRoleIds.AGENT_VIEWER,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Invalid role ID:');
      });

      test('should throw error for invalid user principal ID (non-ObjectId)', async () => {
        await expect(
          service.grantPermission({
            principalType: PrincipalType.USER,
            principalId: 'invalid-id',
            resourceType: ResourceType.AGENT,
            resourceId,
            accessRoleId: AccessRoleIds.AGENT_VIEWER,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Invalid principal ID: invalid-id');
      });

      test('should throw error for invalid resource ID', async () => {
        await expect(
          service.grantPermission({
            principalType: PrincipalType.USER,
            principalId: userId,
            resourceType: ResourceType.AGENT,
            resourceId: 'invalid-id',
            accessRoleId: AccessRoleIds.AGENT_VIEWER,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Invalid resource ID: invalid-id');
      });

      test('should throw error for invalid resource type', async () => {
        await expect(
          service.grantPermission({
            principalType: PrincipalType.USER,
            principalId: userId,
            resourceType: 'invalidType' as ResourceType,
            resourceId,
            accessRoleId: AccessRoleIds.AGENT_VIEWER,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Invalid resourceType: invalidType');
      });
    });

    describe('role lookup', () => {
      test('should throw error for non-existent role', async () => {
        await expect(
          service.grantPermission({
            principalType: PrincipalType.USER,
            principalId: userId,
            resourceType: ResourceType.AGENT,
            resourceId,
            accessRoleId: 'non_existent_role' as AccessRoleIds,
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Role non_existent_role not found');
      });

      test('should throw error for role-resource type mismatch', async () => {
        await expect(
          service.grantPermission({
            principalType: PrincipalType.USER,
            principalId: userId,
            resourceType: ResourceType.AGENT,
            resourceId,
            accessRoleId: AccessRoleIds.PROMPTGROUP_VIEWER, // PromptGroup role for agent resource
            grantedBy: grantedById,
          }),
        ).rejects.toThrow('Role promptGroup_viewer is for promptGroup resources, not agent');
      });
    });

    describe('successful grant', () => {
      test('should grant permission to a user with a role', async () => {
        const entry = await service.grantPermission({
          principalType: PrincipalType.USER,
          principalId: userId,
          resourceType: ResourceType.AGENT,
          resourceId,
          accessRoleId: AccessRoleIds.AGENT_VIEWER,
          grantedBy: grantedById,
        });

        expect(entry).toBeDefined();
        expect(entry!.principalType).toBe(PrincipalType.USER);
        expect(entry!.principalId!.toString()).toBe(userId.toString());
        expect(entry!.resourceType).toBe(ResourceType.AGENT);
        expect(entry!.resourceId.toString()).toBe(resourceId.toString());
        expect(entry!.permBits).toBe(RoleBits.VIEWER);
      });

      test('should grant permission to a group with a role', async () => {
        const entry = await service.grantPermission({
          principalType: PrincipalType.GROUP,
          principalId: groupId,
          resourceType: ResourceType.AGENT,
          resourceId,
          accessRoleId: AccessRoleIds.AGENT_EDITOR,
          grantedBy: grantedById,
        });

        expect(entry).toBeDefined();
        expect(entry!.principalType).toBe(PrincipalType.GROUP);
        expect(entry!.principalId!.toString()).toBe(groupId.toString());
        expect(entry!.permBits).toBe(RoleBits.EDITOR);
      });

      test('should grant public permission with a role', async () => {
        const entry = await service.grantPermission({
          principalType: PrincipalType.PUBLIC,
          principalId: null,
          resourceType: ResourceType.AGENT,
          resourceId,
          accessRoleId: AccessRoleIds.AGENT_VIEWER,
          grantedBy: grantedById,
        });

        expect(entry).toBeDefined();
        expect(entry!.principalType).toBe(PrincipalType.PUBLIC);
        expect(entry!.principalId).toBeUndefined();
        expect(entry!.permBits).toBe(RoleBits.VIEWER);
      });

      test('should grant permission to a role principal', async () => {
        const entry = await service.grantPermission({
          principalType: PrincipalType.ROLE,
          principalId: 'admin',
          resourceType: ResourceType.AGENT,
          resourceId,
          accessRoleId: AccessRoleIds.AGENT_EDITOR,
          grantedBy: grantedById,
        });

        expect(entry).toBeDefined();
        expect(entry!.principalType).toBe(PrincipalType.ROLE);
        expect(entry!.principalId).toBe('admin');
        expect(entry!.permBits).toBe(RoleBits.EDITOR);
      });

      test('should update existing permission when granting to same principal and resource', async () => {
        // First grant with viewer role
        await service.grantPermission({
          principalType: PrincipalType.USER,
          principalId: userId,
          resourceType: ResourceType.AGENT,
          resourceId,
          accessRoleId: AccessRoleIds.AGENT_VIEWER,
          grantedBy: grantedById,
        });

        // Then update to editor role
        const updated = await service.grantPermission({
          principalType: PrincipalType.USER,
          principalId: userId,
          resourceType: ResourceType.AGENT,
          resourceId,
          accessRoleId: AccessRoleIds.AGENT_EDITOR,
          grantedBy: grantedById,
        });

        expect(updated!.permBits).toBe(RoleBits.EDITOR);

        // Verify there's only one entry
        const entries = await AclEntry.find({
          principalType: PrincipalType.USER,
          principalId: userId,
          resourceType: ResourceType.AGENT,
          resourceId,
        });
        expect(entries).toHaveLength(1);
      });
    });
  });

  describe('findAccessibleResources', () => {
    const resource1 = new Types.ObjectId();
    const resource2 = new Types.ObjectId();
    const resource3 = new Types.ObjectId();

    beforeEach(async () => {
      // User can view resource 1
      await service.grantPermission({
        principalType: PrincipalType.USER,
        principalId: userId,
        resourceType: ResourceType.AGENT,
        resourceId: resource1,
        accessRoleId: AccessRoleIds.AGENT_VIEWER,
        grantedBy: grantedById,
      });

      // User can edit resource 2
      await service.grantPermission({
        principalType: PrincipalType.USER,
        principalId: userId,
        resourceType: ResourceType.AGENT,
        resourceId: resource2,
        accessRoleId: AccessRoleIds.AGENT_EDITOR,
        grantedBy: grantedById,
      });

      // Group can view resource 3
      await service.grantPermission({
        principalType: PrincipalType.GROUP,
        principalId: groupId,
        resourceType: ResourceType.AGENT,
        resourceId: resource3,
        accessRoleId: AccessRoleIds.AGENT_VIEWER,
        grantedBy: grantedById,
      });
    });

    describe('validation errors', () => {
      test('should throw error when requiredPermissions is not a positive number', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
        ]);

        await expect(
          service.findAccessibleResources({
            userId,
            resourceType: ResourceType.AGENT,
            requiredPermissions: 0,
          }),
        ).rejects.toThrow('requiredPermissions must be a positive number');
      });

      test('should throw error when requiredPermissions is negative', async () => {
        await expect(
          service.findAccessibleResources({
            userId,
            resourceType: ResourceType.AGENT,
            requiredPermissions: -1,
          }),
        ).rejects.toThrow('requiredPermissions must be a positive number');
      });

      test('should return empty array for invalid resource type (error is caught)', async () => {
        // The service catches invalid resourceType errors and returns empty array
        const result = await service.findAccessibleResources({
          userId,
          resourceType: 'invalid' as ResourceType,
          requiredPermissions: 1,
        });

        expect(result).toEqual([]);
      });
    });

    describe('empty principals', () => {
      test('should return empty array when no principals found', async () => {
        mockGetUserPrincipals.mockResolvedValue([]);

        const result = await service.findAccessibleResources({
          userId,
          resourceType: ResourceType.AGENT,
          requiredPermissions: 1,
        });

        expect(result).toEqual([]);
      });
    });

    describe('successful queries', () => {
      test('should find resources user can view', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
        ]);

        const viewableResources = await service.findAccessibleResources({
          userId,
          resourceType: ResourceType.AGENT,
          requiredPermissions: 1, // VIEW
        });

        expect(viewableResources).toHaveLength(2);
        const resourceIds = viewableResources.map((id) => id.toString());
        expect(resourceIds).toContain(resource1.toString());
        expect(resourceIds).toContain(resource2.toString());
      });

      test('should find resources user can edit', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
        ]);

        const editableResources = await service.findAccessibleResources({
          userId,
          resourceType: ResourceType.AGENT,
          requiredPermissions: 3, // EDIT
        });

        expect(editableResources).toHaveLength(1);
        expect(editableResources[0].toString()).toBe(resource2.toString());
      });

      test('should find resources accessible via group membership', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
          { principalType: PrincipalType.GROUP, principalId: groupId },
        ]);

        const viewableResources = await service.findAccessibleResources({
          userId,
          resourceType: ResourceType.AGENT,
          requiredPermissions: 1, // VIEW
        });

        expect(viewableResources).toHaveLength(3);
      });

      test('should pass role when provided', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
          { principalType: PrincipalType.ROLE, principalId: 'admin' },
        ]);

        await service.findAccessibleResources({
          userId,
          role: 'admin',
          resourceType: ResourceType.AGENT,
          requiredPermissions: 1,
        });

        expect(mockGetUserPrincipals).toHaveBeenCalledWith({
          userId,
          role: 'admin',
        });
      });
    });
  });

  describe('findPubliclyAccessibleResources', () => {
    const publicResource1 = new Types.ObjectId();
    const publicResource2 = new Types.ObjectId();
    const privateResource = new Types.ObjectId();

    beforeEach(async () => {
      // Public can view resource 1
      await service.grantPermission({
        principalType: PrincipalType.PUBLIC,
        principalId: null,
        resourceType: ResourceType.AGENT,
        resourceId: publicResource1,
        accessRoleId: AccessRoleIds.AGENT_VIEWER,
        grantedBy: grantedById,
      });

      // Public can edit resource 2
      await service.grantPermission({
        principalType: PrincipalType.PUBLIC,
        principalId: null,
        resourceType: ResourceType.AGENT,
        resourceId: publicResource2,
        accessRoleId: AccessRoleIds.AGENT_EDITOR,
        grantedBy: grantedById,
      });

      // Private resource - only user access
      await service.grantPermission({
        principalType: PrincipalType.USER,
        principalId: userId,
        resourceType: ResourceType.AGENT,
        resourceId: privateResource,
        accessRoleId: AccessRoleIds.AGENT_OWNER,
        grantedBy: grantedById,
      });
    });

    describe('validation', () => {
      test('should throw error when requiredPermissions is not a positive number', async () => {
        await expect(
          service.findPubliclyAccessibleResources({
            resourceType: ResourceType.AGENT,
            requiredPermissions: 0,
          }),
        ).rejects.toThrow('requiredPermissions must be a positive number');
      });

      test('should return empty array for invalid resource type (error is caught)', async () => {
        // The service catches invalid resourceType errors and returns empty array
        const result = await service.findPubliclyAccessibleResources({
          resourceType: 'invalid' as ResourceType,
          requiredPermissions: 1,
        });

        expect(result).toEqual([]);
      });
    });

    describe('finding public resources', () => {
      test('should find publicly viewable resources', async () => {
        const publicResources = await service.findPubliclyAccessibleResources({
          resourceType: ResourceType.AGENT,
          requiredPermissions: 1, // VIEW
        });

        expect(publicResources).toHaveLength(2);
        const resourceIds = publicResources.map((id) => id.toString());
        expect(resourceIds).toContain(publicResource1.toString());
        expect(resourceIds).toContain(publicResource2.toString());
        expect(resourceIds).not.toContain(privateResource.toString());
      });

      test('should find publicly editable resources', async () => {
        const editableResources = await service.findPubliclyAccessibleResources({
          resourceType: ResourceType.AGENT,
          requiredPermissions: 3, // EDIT
        });

        expect(editableResources).toHaveLength(1);
        expect(editableResources[0].toString()).toBe(publicResource2.toString());
      });

      test('should return empty array when no public permissions exist', async () => {
        const noPublicResources = await service.findPubliclyAccessibleResources({
          resourceType: ResourceType.PROMPTGROUP,
          requiredPermissions: 1,
        });

        expect(noPublicResources).toEqual([]);
      });
    });
  });

  describe('getResourcePermissionsMap', () => {
    const resource1 = new Types.ObjectId();
    const resource2 = new Types.ObjectId();
    const resource3 = new Types.ObjectId();

    beforeEach(async () => {
      // User has VIEW on resource1
      await service.grantPermission({
        principalType: PrincipalType.USER,
        principalId: userId,
        resourceType: ResourceType.AGENT,
        resourceId: resource1,
        accessRoleId: AccessRoleIds.AGENT_VIEWER,
        grantedBy: grantedById,
      });

      // User has EDIT on resource2
      await service.grantPermission({
        principalType: PrincipalType.USER,
        principalId: userId,
        resourceType: ResourceType.AGENT,
        resourceId: resource2,
        accessRoleId: AccessRoleIds.AGENT_EDITOR,
        grantedBy: grantedById,
      });

      // Group has EDIT on resource1 (higher permission)
      await service.grantPermission({
        principalType: PrincipalType.GROUP,
        principalId: groupId,
        resourceType: ResourceType.AGENT,
        resourceId: resource1,
        accessRoleId: AccessRoleIds.AGENT_EDITOR,
        grantedBy: grantedById,
      });
      // resource3 has no permissions
    });

    describe('empty arrays', () => {
      test('should return empty map for empty resourceIds array', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
        ]);

        const permissionsMap = await service.getResourcePermissionsMap({
          userId,
          role: 'user',
          resourceType: ResourceType.AGENT,
          resourceIds: [],
        });

        expect(permissionsMap).toBeInstanceOf(Map);
        expect(permissionsMap.size).toBe(0);
      });

      test('should throw on invalid resource type', async () => {
        await expect(
          service.getResourcePermissionsMap({
            userId,
            role: 'user',
            resourceType: 'invalid' as ResourceType,
            resourceIds: [resource1],
          }),
        ).rejects.toThrow('Invalid resourceType: invalid');
      });
    });

    describe('batch queries', () => {
      test('should get permissions for multiple resources in single query', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
          { principalType: PrincipalType.PUBLIC },
        ]);

        const permissionsMap = await service.getResourcePermissionsMap({
          userId,
          role: 'user',
          resourceType: ResourceType.AGENT,
          resourceIds: [resource1, resource2, resource3],
        });

        expect(permissionsMap).toBeInstanceOf(Map);
        expect(permissionsMap.size).toBe(2); // resource1 and resource2
        expect(permissionsMap.get(resource1.toString())).toBe(RoleBits.VIEWER);
        expect(permissionsMap.get(resource2.toString())).toBe(RoleBits.EDITOR);
        expect(permissionsMap.get(resource3.toString())).toBeUndefined();
      });

      test('should combine permissions from multiple principals', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
          { principalType: PrincipalType.GROUP, principalId: groupId },
        ]);

        const permissionsMap = await service.getResourcePermissionsMap({
          userId,
          role: 'user',
          resourceType: ResourceType.AGENT,
          resourceIds: [resource1, resource2],
        });

        expect(permissionsMap.size).toBe(2);
        // Resource1 should have VIEW (1) | EDIT (3) = 3 from combined user+group
        expect(permissionsMap.get(resource1.toString())).toBe(RoleBits.EDITOR);
        expect(permissionsMap.get(resource2.toString())).toBe(RoleBits.EDITOR);
      });

      test('should use role optimization when provided', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
          { principalType: PrincipalType.ROLE, principalId: 'admin' },
        ]);

        await service.getResourcePermissionsMap({
          userId,
          role: 'admin',
          resourceType: ResourceType.AGENT,
          resourceIds: [resource1],
        });

        expect(mockGetUserPrincipals).toHaveBeenCalledWith({ userId, role: 'admin' });
      });
    });
  });

  describe('removeAllPermissions', () => {
    const resourceToDelete = new Types.ObjectId();

    beforeEach(async () => {
      // Grant multiple permissions to the resource
      await service.grantPermission({
        principalType: PrincipalType.USER,
        principalId: userId,
        resourceType: ResourceType.AGENT,
        resourceId: resourceToDelete,
        accessRoleId: AccessRoleIds.AGENT_VIEWER,
        grantedBy: grantedById,
      });

      await service.grantPermission({
        principalType: PrincipalType.GROUP,
        principalId: groupId,
        resourceType: ResourceType.AGENT,
        resourceId: resourceToDelete,
        accessRoleId: AccessRoleIds.AGENT_EDITOR,
        grantedBy: grantedById,
      });

      await service.grantPermission({
        principalType: PrincipalType.PUBLIC,
        principalId: null,
        resourceType: ResourceType.AGENT,
        resourceId: resourceToDelete,
        accessRoleId: AccessRoleIds.AGENT_VIEWER,
        grantedBy: grantedById,
      });
    });

    describe('validation', () => {
      test('should throw error for invalid resource type', async () => {
        await expect(
          service.removeAllPermissions({
            resourceType: 'invalid' as ResourceType,
            resourceId: resourceToDelete,
          }),
        ).rejects.toThrow('Invalid resourceType: invalid');
      });

      test('should throw error for invalid resource ID', async () => {
        await expect(
          service.removeAllPermissions({
            resourceType: ResourceType.AGENT,
            resourceId: 'invalid-id',
          }),
        ).rejects.toThrow('Invalid resource ID: invalid-id');
      });
    });

    describe('cleanup', () => {
      test('should delete all permissions for a resource', async () => {
        // Verify permissions exist
        const beforeCount = await AclEntry.countDocuments({
          resourceType: ResourceType.AGENT,
          resourceId: resourceToDelete,
        });
        expect(beforeCount).toBe(3);

        const result = await service.removeAllPermissions({
          resourceType: ResourceType.AGENT,
          resourceId: resourceToDelete,
        });

        expect(result.acknowledged).toBe(true);
        expect(result.deletedCount).toBe(3);

        // Verify permissions are deleted
        const afterCount = await AclEntry.countDocuments({
          resourceType: ResourceType.AGENT,
          resourceId: resourceToDelete,
        });
        expect(afterCount).toBe(0);
      });

      test('should return result even when no permissions existed', async () => {
        const newResourceId = new Types.ObjectId();

        const result = await service.removeAllPermissions({
          resourceType: ResourceType.AGENT,
          resourceId: newResourceId,
        });

        expect(result.acknowledged).toBe(true);
        expect(result.deletedCount).toBe(0);
      });

      test('should not affect other resources permissions', async () => {
        const otherResource = new Types.ObjectId();

        // Grant permission to another resource
        await service.grantPermission({
          principalType: PrincipalType.USER,
          principalId: userId,
          resourceType: ResourceType.AGENT,
          resourceId: otherResource,
          accessRoleId: AccessRoleIds.AGENT_VIEWER,
          grantedBy: grantedById,
        });

        await service.removeAllPermissions({
          resourceType: ResourceType.AGENT,
          resourceId: resourceToDelete,
        });

        // Verify other resource still has permissions
        const otherResourcePerms = await AclEntry.countDocuments({
          resourceType: ResourceType.AGENT,
          resourceId: otherResource,
        });
        expect(otherResourcePerms).toBe(1);
      });
    });
  });

  describe('checkPermission', () => {
    const testResource = new Types.ObjectId();
    const groupResource = new Types.ObjectId();

    beforeEach(async () => {
      // User has VIEW on testResource
      await service.grantPermission({
        principalType: PrincipalType.USER,
        principalId: userId,
        resourceType: ResourceType.AGENT,
        resourceId: testResource,
        accessRoleId: AccessRoleIds.AGENT_VIEWER,
        grantedBy: grantedById,
      });

      // Group has EDIT on groupResource
      await service.grantPermission({
        principalType: PrincipalType.GROUP,
        principalId: groupId,
        resourceType: ResourceType.AGENT,
        resourceId: groupResource,
        accessRoleId: AccessRoleIds.AGENT_EDITOR,
        grantedBy: grantedById,
      });
    });

    describe('validation', () => {
      test('should throw error when requiredPermission is not a positive number', async () => {
        await expect(
          service.checkPermission({
            userId: userId.toString(),
            resourceType: ResourceType.AGENT,
            resourceId: testResource,
            requiredPermission: 0,
          }),
        ).rejects.toThrow('requiredPermission must be a positive number');
      });

      test('should throw error when requiredPermission is negative', async () => {
        await expect(
          service.checkPermission({
            userId: userId.toString(),
            resourceType: ResourceType.AGENT,
            resourceId: testResource,
            requiredPermission: -1,
          }),
        ).rejects.toThrow('requiredPermission must be a positive number');
      });

      test('should return false for invalid resource type (error is caught)', async () => {
        // The service catches invalid resourceType errors and returns false
        const hasPermission = await service.checkPermission({
          userId: userId.toString(),
          resourceType: 'invalid' as ResourceType,
          resourceId: testResource,
          requiredPermission: 1,
        });

        expect(hasPermission).toBe(false);
      });
    });

    describe('permission scenarios', () => {
      test('should return true when user has required permission', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
        ]);

        const hasPermission = await service.checkPermission({
          userId: userId.toString(),
          resourceType: ResourceType.AGENT,
          resourceId: testResource,
          requiredPermission: 1, // VIEW
        });

        expect(hasPermission).toBe(true);
      });

      test('should return false when user lacks required permission', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
        ]);

        const hasPermission = await service.checkPermission({
          userId: userId.toString(),
          resourceType: ResourceType.AGENT,
          resourceId: testResource,
          requiredPermission: 3, // EDIT
        });

        expect(hasPermission).toBe(false);
      });

      test('should return false when no principals found', async () => {
        mockGetUserPrincipals.mockResolvedValue([]);

        const hasPermission = await service.checkPermission({
          userId: userId.toString(),
          resourceType: ResourceType.AGENT,
          resourceId: testResource,
          requiredPermission: 1,
        });

        expect(hasPermission).toBe(false);
      });

      test('should check permission via group membership', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
          { principalType: PrincipalType.GROUP, principalId: groupId },
        ]);

        const hasPermission = await service.checkPermission({
          userId: userId.toString(),
          resourceType: ResourceType.AGENT,
          resourceId: groupResource,
          requiredPermission: 1, // VIEW (editor includes view)
        });

        expect(hasPermission).toBe(true);
      });

      test('should return false for non-existent resource', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
        ]);

        const hasPermission = await service.checkPermission({
          userId: userId.toString(),
          resourceType: ResourceType.AGENT,
          resourceId: new Types.ObjectId(),
          requiredPermission: 1,
        });

        expect(hasPermission).toBe(false);
      });

      test('should pass role when provided for optimization', async () => {
        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
          { principalType: PrincipalType.ROLE, principalId: 'admin' },
        ]);

        await service.checkPermission({
          userId: userId.toString(),
          role: 'admin',
          resourceType: ResourceType.AGENT,
          resourceId: testResource,
          requiredPermission: 1,
        });

        expect(mockGetUserPrincipals).toHaveBeenCalledWith({
          userId: userId.toString(),
          role: 'admin',
        });
      });
    });

    describe('public access', () => {
      test('should check permission for public access', async () => {
        const publicResource = new Types.ObjectId();

        await service.grantPermission({
          principalType: PrincipalType.PUBLIC,
          principalId: null,
          resourceType: ResourceType.AGENT,
          resourceId: publicResource,
          accessRoleId: AccessRoleIds.AGENT_VIEWER,
          grantedBy: grantedById,
        });

        mockGetUserPrincipals.mockResolvedValue([
          { principalType: PrincipalType.USER, principalId: userId },
          { principalType: PrincipalType.PUBLIC },
        ]);

        const hasPermission = await service.checkPermission({
          userId: userId.toString(),
          resourceType: ResourceType.AGENT,
          resourceId: publicResource,
          requiredPermission: 1,
        });

        expect(hasPermission).toBe(true);
      });
    });
  });

  describe('validateResourceType (via public methods)', () => {
    test('should accept AGENT resource type', async () => {
      mockGetUserPrincipals.mockResolvedValue([]);

      const result = await service.findAccessibleResources({
        userId,
        resourceType: ResourceType.AGENT,
        requiredPermissions: 1,
      });

      expect(result).toEqual([]);
    });

    test('should accept PROMPTGROUP resource type', async () => {
      mockGetUserPrincipals.mockResolvedValue([]);

      const result = await service.findAccessibleResources({
        userId,
        resourceType: ResourceType.PROMPTGROUP,
        requiredPermissions: 1,
      });

      expect(result).toEqual([]);
    });

    test('should accept MCPSERVER resource type', async () => {
      mockGetUserPrincipals.mockResolvedValue([]);

      const result = await service.findAccessibleResources({
        userId,
        resourceType: ResourceType.MCPSERVER,
        requiredPermissions: 1,
      });

      expect(result).toEqual([]);
    });

    test('should return empty array for unknown resource type (error caught)', async () => {
      // findAccessibleResources catches invalid resource type and returns empty array
      const result = await service.findAccessibleResources({
        userId,
        resourceType: 'unknown_type' as ResourceType,
        requiredPermissions: 1,
      });

      expect(result).toEqual([]);
    });

    test('should return empty array for empty string resource type (error caught)', async () => {
      // findAccessibleResources catches invalid resource type and returns empty array
      const result = await service.findAccessibleResources({
        userId,
        resourceType: '' as ResourceType,
        requiredPermissions: 1,
      });

      expect(result).toEqual([]);
    });

    test('should throw for invalid resource type in grantPermission', async () => {
      // grantPermission throws directly for invalid resource type
      await expect(
        service.grantPermission({
          principalType: PrincipalType.USER,
          principalId: userId,
          resourceType: 'unknown_type' as ResourceType,
          resourceId,
          accessRoleId: AccessRoleIds.AGENT_VIEWER,
          grantedBy: grantedById,
        }),
      ).rejects.toThrow('Invalid resourceType: unknown_type');
    });

    test('should throw for empty string resource type in removeAllPermissions', async () => {
      // removeAllPermissions throws directly for invalid resource type
      await expect(
        service.removeAllPermissions({
          resourceType: '' as ResourceType,
          resourceId,
        }),
      ).rejects.toThrow('Invalid resourceType:');
    });
  });
});
