import fc from 'fast-check';
import { RBACService, Role, Permission } from '../services/rbac.service';

// Feature: website-cloning-platform, Property 38: Role-Based Permission Enforcement
describe('Role-Based Access Control Properties', () => {
  
  beforeEach(() => {
    // Clear all data and reinitialize for each test
    RBACService.clearAll();
    RBACService.initialize();
  });
  
  test('Property 38: Permission assignment should be consistent', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.record({
          userId: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          roleId: fc.constantFrom('admin', 'user', 'viewer', 'project-owner', 'project-collaborator'),
          projectId: fc.option(fc.string({ minLength: 1, maxLength: 20 }))
        }),
        async ({ userId, roleId, projectId }) => {
          // Assign role to user
          const assigned = RBACService.assignRole(userId, roleId, projectId);
          
          // Property: Assignment should succeed for valid roles
          if (!assigned) {
            return false;
          }
          
          // Property: User should have the role after assignment
          const userRoles = RBACService.getUserRoles(userId, projectId);
          const hasRole = userRoles.some(ur => ur.roleId === roleId && ur.projectId === projectId);
          
          if (!hasRole) {
            return false;
          }
          
          // Property: User should have permissions from the assigned role
          const role = RBACService.getRole(roleId);
          if (!role) {
            return false;
          }
          
          for (const permissionId of role.permissions) {
            if (!RBACService.hasPermission(userId, permissionId, projectId)) {
              return false;
            }
          }
          
          return true;
        }
      ),
      { 
        numRuns: 100,
        timeout: 5000
      }
    );
  });

  test('Property 38: Permission inheritance should work correctly', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.record({
          userId: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          globalRole: fc.constantFrom('admin', 'user', 'viewer'),
          projectRole: fc.constantFrom('project-owner', 'project-collaborator'),
          projectId: fc.string({ minLength: 1, maxLength: 20 })
        }),
        async ({ userId, globalRole, projectRole, projectId }) => {
          // Assign both global and project-specific roles
          const globalAssigned = RBACService.assignRole(userId, globalRole);
          const projectAssigned = RBACService.assignRole(userId, projectRole, projectId);
          
          if (!globalAssigned || !projectAssigned) {
            return false;
          }
          
          // Property: User should have permissions from both roles
          const globalRoleObj = RBACService.getRole(globalRole);
          const projectRoleObj = RBACService.getRole(projectRole);
          
          if (!globalRoleObj || !projectRoleObj) {
            return false;
          }
          
          // Check global permissions
          for (const permissionId of globalRoleObj.permissions) {
            if (!RBACService.hasPermission(userId, permissionId)) {
              return false;
            }
          }
          
          // Check project-specific permissions
          for (const permissionId of projectRoleObj.permissions) {
            if (!RBACService.hasPermission(userId, permissionId, projectId)) {
              return false;
            }
          }
          
          return true;
        }
      ),
      { 
        numRuns: 50,
        timeout: 5000
      }
    );
  });

  test('Property 38: Role removal should revoke permissions', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.record({
          userId: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          roleId: fc.constantFrom('user', 'viewer', 'project-owner'),
          projectId: fc.option(fc.string({ minLength: 1, maxLength: 20 }))
        }),
        async ({ userId, roleId, projectId }) => {
          // Assign role
          const assigned = RBACService.assignRole(userId, roleId, projectId);
          if (!assigned) {
            return true; // Skip if assignment failed
          }
          
          // Get role permissions before removal
          const role = RBACService.getRole(roleId);
          if (!role) {
            return false;
          }
          
          // Verify user has permissions
          const hadPermissionsBefore = role.permissions.every(permissionId =>
            RBACService.hasPermission(userId, permissionId, projectId)
          );
          
          if (!hadPermissionsBefore) {
            return false;
          }
          
          // Remove role
          const removed = RBACService.removeRole(userId, roleId, projectId);
          if (!removed) {
            return false;
          }
          
          // Property: User should no longer have role-specific permissions
          // (unless they have them from another role)
          const userRoles = RBACService.getUserRoles(userId, projectId);
          const otherRolePermissions = new Set<string>();
          
          for (const userRole of userRoles) {
            const otherRole = RBACService.getRole(userRole.roleId);
            if (otherRole) {
              otherRole.permissions.forEach(p => otherRolePermissions.add(p));
            }
          }
          
          // Check that permissions not in other roles are revoked
          for (const permissionId of role.permissions) {
            if (!otherRolePermissions.has(permissionId)) {
              if (RBACService.hasPermission(userId, permissionId, projectId)) {
                return false; // Permission should be revoked
              }
            }
          }
          
          return true;
        }
      ),
      { 
        numRuns: 50,
        timeout: 5000
      }
    );
  });

  test('Property 38: Admin role should have all permissions', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.record({
          userId: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          permissionId: fc.constantFrom(
            'project:create', 'project:read', 'project:update', 'project:delete',
            'crawl:start', 'crawl:stop', 'code:generate', 'admin:users', 'admin:system'
          )
        }),
        async ({ userId, permissionId }) => {
          // Assign admin role
          const assigned = RBACService.assignRole(userId, 'admin');
          if (!assigned) {
            return false;
          }
          
          // Property: Admin should have all permissions
          const hasPermission = RBACService.hasPermission(userId, permissionId);
          const isAdmin = RBACService.isAdmin(userId);
          
          return hasPermission && isAdmin;
        }
      ),
      { 
        numRuns: 50,
        timeout: 5000
      }
    );
  });

  test('Property 38: Project access control should work correctly', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.record({
          ownerId: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          collaboratorId: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          viewerId: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          projectId: fc.string({ minLength: 1, maxLength: 20 }),
          action: fc.constantFrom('read', 'update', 'delete', 'share')
        }),
        async ({ ownerId, collaboratorId, viewerId, projectId, action }) => {
          // Ensure users are different
          if (ownerId === collaboratorId || ownerId === viewerId || collaboratorId === viewerId) {
            return true; // Skip this test case
          }
          
          // Assign roles
          RBACService.assignRole(ownerId, 'project-owner', projectId);
          RBACService.assignRole(collaboratorId, 'project-collaborator', projectId);
          RBACService.assignRole(viewerId, 'viewer', projectId);
          
          // Property: Owner should have all access
          const ownerAccess = RBACService.canAccessProject(ownerId, projectId, action);
          
          // Property: Collaborator access depends on action
          const collaboratorAccess = RBACService.canAccessProject(collaboratorId, projectId, action);
          const collaboratorShouldHaveAccess = ['read', 'update'].includes(action);
          
          // Property: Viewer should only have read access
          const viewerAccess = RBACService.canAccessProject(viewerId, projectId, action);
          const viewerShouldHaveAccess = action === 'read';
          
          return ownerAccess && 
                 (collaboratorAccess === collaboratorShouldHaveAccess) &&
                 (viewerAccess === viewerShouldHaveAccess);
        }
      ),
      { 
        numRuns: 50,
        timeout: 5000
      }
    );
  });

  test('Property 38: Permission checking should be consistent', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.record({
          userId: fc.string({ minLength: 1, maxLength: 50 }).filter(s => s.trim().length > 0),
          permissions: fc.array(
            fc.constantFrom('project:read', 'project:update', 'crawl:start', 'code:generate'),
            { minLength: 1, maxLength: 4 }
          ),
          projectId: fc.option(fc.string({ minLength: 1, maxLength: 20 }))
        }),
        async ({ userId, permissions, projectId }) => {
          // Assign user role to get some permissions
          RBACService.assignRole(userId, 'user', projectId);
          
          // Property: hasAnyPermission should return true if user has at least one permission
          const hasAny = RBACService.hasAnyPermission(userId, permissions, projectId);
          const individualChecks = permissions.map(p => RBACService.hasPermission(userId, p, projectId));
          const shouldHaveAny = individualChecks.some(check => check);
          
          if (hasAny !== shouldHaveAny) {
            return false;
          }
          
          // Property: hasAllPermissions should return true only if user has all permissions
          const hasAll = RBACService.hasAllPermissions(userId, permissions, projectId);
          const shouldHaveAll = individualChecks.every(check => check);
          
          return hasAll === shouldHaveAll;
        }
      ),
      { 
        numRuns: 50,
        timeout: 5000
      }
    );
  });

  test('Property 38: Role creation should validate permissions', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.record({
          roleId: fc.string({ minLength: 1, maxLength: 20 }).filter(s => s.trim().length > 0),
          roleName: fc.string({ minLength: 1, maxLength: 50 }),
          validPermissions: fc.array(
            fc.constantFrom('project:read', 'project:update', 'crawl:start'),
            { minLength: 1, maxLength: 3 }
          ),
          invalidPermissions: fc.array(
            fc.string({ minLength: 1, maxLength: 20 }).filter(s => !s.includes(':')),
            { minLength: 0, maxLength: 2 }
          )
        }),
        async ({ roleId, roleName, validPermissions, invalidPermissions }) => {
          // Skip if role already exists
          if (RBACService.getRole(roleId)) {
            return true;
          }
          
          // Property: Role with valid permissions should be created successfully
          const validRole: Role = {
            id: roleId + '_valid',
            name: roleName,
            description: 'Test role',
            permissions: validPermissions,
            isSystemRole: false
          };
          
          const validCreated = RBACService.createRole(validRole);
          if (!validCreated) {
            return false;
          }
          
          // Property: Role with invalid permissions should fail to create
          if (invalidPermissions.length > 0) {
            const invalidRole: Role = {
              id: roleId + '_invalid',
              name: roleName,
              description: 'Test role with invalid permissions',
              permissions: [...validPermissions, ...invalidPermissions],
              isSystemRole: false
            };
            
            const invalidCreated = RBACService.createRole(invalidRole);
            if (invalidCreated) {
              return false; // Should not succeed with invalid permissions
            }
          }
          
          return true;
        }
      ),
      { 
        numRuns: 30,
        timeout: 5000
      }
    );
  });

  test('Property 38: Project members should be tracked correctly', async () => {
    await fc.assert(
      fc.asyncProperty(
        fc.record({
          projectId: fc.string({ minLength: 1, maxLength: 20 }),
          userIds: fc.array(
            fc.string({ minLength: 1, maxLength: 20 }).filter(s => s.trim().length > 0),
            { minLength: 1, maxLength: 5 }
          ),
          roleId: fc.constantFrom('project-owner', 'project-collaborator', 'viewer')
        }),
        async ({ projectId, userIds, roleId }) => {
          // Remove duplicates
          const uniqueUserIds = [...new Set(userIds)];
          
          // Assign role to all users for the project
          const assignments = uniqueUserIds.map(userId => 
            RBACService.assignRole(userId, roleId, projectId)
          );
          
          // All assignments should succeed
          if (!assignments.every(assigned => assigned)) {
            return false;
          }
          
          // Property: All users should appear in project members
          const members = RBACService.getProjectMembers(projectId);
          const memberUserIds = members.map(m => m.userId);
          
          for (const userId of uniqueUserIds) {
            if (!memberUserIds.includes(userId)) {
              return false;
            }
          }
          
          // Property: Each member should have the correct role
          for (const member of members) {
            const hasCorrectRole = member.roles.some(role => 
              role.roleId === roleId && role.projectId === projectId
            );
            if (!hasCorrectRole) {
              return false;
            }
          }
          
          return true;
        }
      ),
      { 
        numRuns: 30,
        timeout: 5000
      }
    );
  });
});