export interface Permission {
  id: string;
  name: string;
  description: string;
  resource: string;
  action: string;
}

export interface Role {
  id: string;
  name: string;
  description: string;
  permissions: string[]; // Permission IDs
  isSystemRole: boolean;
}

export interface UserRole {
  userId: string;
  roleId: string;
  projectId?: string; // For project-specific roles
  grantedAt: Date;
  grantedBy: string;
}

// In-memory storage for RBAC (in production, use database)
const permissions = new Map<string, Permission>();
const roles = new Map<string, Role>();
const userRoles = new Map<string, UserRole[]>(); // userId -> UserRole[]

export class RBACService {
  
  /**
   * Initialize default permissions and roles
   */
  static initialize(): void {
    // Define core permissions
    const corePermissions: Permission[] = [
      // Project permissions
      { id: 'project:create', name: 'Create Project', description: 'Create new projects', resource: 'project', action: 'create' },
      { id: 'project:read', name: 'Read Project', description: 'View project details', resource: 'project', action: 'read' },
      { id: 'project:update', name: 'Update Project', description: 'Modify project settings', resource: 'project', action: 'update' },
      { id: 'project:delete', name: 'Delete Project', description: 'Delete projects', resource: 'project', action: 'delete' },
      { id: 'project:share', name: 'Share Project', description: 'Share projects with others', resource: 'project', action: 'share' },
      
      // Crawling permissions
      { id: 'crawl:start', name: 'Start Crawl', description: 'Start website crawling', resource: 'crawl', action: 'start' },
      { id: 'crawl:stop', name: 'Stop Crawl', description: 'Stop running crawls', resource: 'crawl', action: 'stop' },
      { id: 'crawl:view', name: 'View Crawl Results', description: 'View crawling results', resource: 'crawl', action: 'view' },
      
      // Code generation permissions
      { id: 'code:generate', name: 'Generate Code', description: 'Generate code from crawled data', resource: 'code', action: 'generate' },
      { id: 'code:download', name: 'Download Code', description: 'Download generated code', resource: 'code', action: 'download' },
      
      // Admin permissions
      { id: 'admin:users', name: 'Manage Users', description: 'Manage user accounts', resource: 'admin', action: 'users' },
      { id: 'admin:roles', name: 'Manage Roles', description: 'Manage roles and permissions', resource: 'admin', action: 'roles' },
      { id: 'admin:system', name: 'System Administration', description: 'System-level administration', resource: 'admin', action: 'system' }
    ];
    
    // Add permissions to storage
    corePermissions.forEach(permission => {
      permissions.set(permission.id, permission);
    });
    
    // Define default roles
    const defaultRoles: Role[] = [
      {
        id: 'admin',
        name: 'Administrator',
        description: 'Full system access',
        permissions: corePermissions.map(p => p.id),
        isSystemRole: true
      },
      {
        id: 'user',
        name: 'Regular User',
        description: 'Standard user access',
        permissions: [
          'project:create', 'project:read', 'project:update', 'project:delete', 'project:share',
          'crawl:start', 'crawl:stop', 'crawl:view',
          'code:generate', 'code:download'
        ],
        isSystemRole: true
      },
      {
        id: 'viewer',
        name: 'Viewer',
        description: 'Read-only access',
        permissions: [
          'project:read',
          'crawl:view'
        ],
        isSystemRole: true
      },
      {
        id: 'project-owner',
        name: 'Project Owner',
        description: 'Full access to owned projects',
        permissions: [
          'project:read', 'project:update', 'project:delete', 'project:share',
          'crawl:start', 'crawl:stop', 'crawl:view',
          'code:generate', 'code:download'
        ],
        isSystemRole: false
      },
      {
        id: 'project-collaborator',
        name: 'Project Collaborator',
        description: 'Collaborate on projects',
        permissions: [
          'project:read', 'project:update',
          'crawl:start', 'crawl:view',
          'code:generate', 'code:download'
        ],
        isSystemRole: false
      }
    ];
    
    // Add roles to storage
    defaultRoles.forEach(role => {
      roles.set(role.id, role);
    });
  }
  
  /**
   * Create a new permission
   */
  static createPermission(permission: Permission): boolean {
    if (permissions.has(permission.id)) {
      return false; // Permission already exists
    }
    
    permissions.set(permission.id, permission);
    return true;
  }
  
  /**
   * Create a new role
   */
  static createRole(role: Role): boolean {
    if (roles.has(role.id)) {
      return false; // Role already exists
    }
    
    // Validate that all permissions exist
    for (const permissionId of role.permissions) {
      if (!permissions.has(permissionId)) {
        return false; // Invalid permission
      }
    }
    
    roles.set(role.id, role);
    return true;
  }
  
  /**
   * Assign role to user
   */
  static assignRole(userId: string, roleId: string, projectId?: string, grantedBy?: string): boolean {
    if (!roles.has(roleId)) {
      return false; // Role doesn't exist
    }
    
    const userRole: UserRole = {
      userId,
      roleId,
      projectId,
      grantedAt: new Date(),
      grantedBy: grantedBy || 'system'
    };
    
    const existingRoles = userRoles.get(userId) || [];
    
    // Check if user already has this role for this project/globally
    const existingRole = existingRoles.find(ur => 
      ur.roleId === roleId && ur.projectId === projectId
    );
    
    if (existingRole) {
      return false; // User already has this role
    }
    
    existingRoles.push(userRole);
    userRoles.set(userId, existingRoles);
    return true;
  }
  
  /**
   * Remove role from user
   */
  static removeRole(userId: string, roleId: string, projectId?: string): boolean {
    const existingRoles = userRoles.get(userId) || [];
    
    const roleIndex = existingRoles.findIndex(ur => 
      ur.roleId === roleId && ur.projectId === projectId
    );
    
    if (roleIndex === -1) {
      return false; // User doesn't have this role
    }
    
    existingRoles.splice(roleIndex, 1);
    userRoles.set(userId, existingRoles);
    return true;
  }
  
  /**
   * Check if user has permission
   */
  static hasPermission(userId: string, permissionId: string, projectId?: string): boolean {
    const userRolesList = userRoles.get(userId) || [];
    
    // Check global roles first
    const globalRoles = userRolesList.filter(ur => !ur.projectId);
    for (const userRole of globalRoles) {
      const role = roles.get(userRole.roleId);
      if (role && role.permissions.includes(permissionId)) {
        return true;
      }
    }
    
    // Check project-specific roles if projectId is provided
    if (projectId) {
      const projectRoles = userRolesList.filter(ur => ur.projectId === projectId);
      for (const userRole of projectRoles) {
        const role = roles.get(userRole.roleId);
        if (role && role.permissions.includes(permissionId)) {
          return true;
        }
      }
    }
    
    return false;
  }
  
  /**
   * Check if user has any of the specified permissions
   */
  static hasAnyPermission(userId: string, permissionIds: string[], projectId?: string): boolean {
    return permissionIds.some(permissionId => 
      this.hasPermission(userId, permissionId, projectId)
    );
  }
  
  /**
   * Check if user has all of the specified permissions
   */
  static hasAllPermissions(userId: string, permissionIds: string[], projectId?: string): boolean {
    return permissionIds.every(permissionId => 
      this.hasPermission(userId, permissionId, projectId)
    );
  }
  
  /**
   * Get user's roles
   */
  static getUserRoles(userId: string, projectId?: string): UserRole[] {
    const userRolesList = userRoles.get(userId) || [];
    
    if (projectId) {
      return userRolesList.filter(ur => ur.projectId === projectId || !ur.projectId);
    }
    
    return userRolesList;
  }
  
  /**
   * Get user's permissions
   */
  static getUserPermissions(userId: string, projectId?: string): Permission[] {
    const userRolesList = this.getUserRoles(userId, projectId);
    const userPermissions = new Set<string>();
    
    for (const userRole of userRolesList) {
      const role = roles.get(userRole.roleId);
      if (role) {
        role.permissions.forEach(permissionId => {
          userPermissions.add(permissionId);
        });
      }
    }
    
    return Array.from(userPermissions)
      .map(permissionId => permissions.get(permissionId))
      .filter(permission => permission !== undefined) as Permission[];
  }
  
  /**
   * Get all permissions
   */
  static getAllPermissions(): Permission[] {
    return Array.from(permissions.values());
  }
  
  /**
   * Get all roles
   */
  static getAllRoles(): Role[] {
    return Array.from(roles.values());
  }
  
  /**
   * Get role by ID
   */
  static getRole(roleId: string): Role | null {
    return roles.get(roleId) || null;
  }
  
  /**
   * Get permission by ID
   */
  static getPermission(permissionId: string): Permission | null {
    return permissions.get(permissionId) || null;
  }
  
  /**
   * Check if user is admin
   */
  static isAdmin(userId: string): boolean {
    return this.hasPermission(userId, 'admin:system');
  }
  
  /**
   * Check if user can access project
   */
  static canAccessProject(userId: string, projectId: string, action: 'read' | 'update' | 'delete' | 'share' = 'read'): boolean {
    const permissionId = `project:${action}`;
    return this.hasPermission(userId, permissionId, projectId) || this.hasPermission(userId, permissionId);
  }
  
  /**
   * Get project members (users with any role in the project)
   */
  static getProjectMembers(projectId: string): { userId: string; roles: UserRole[] }[] {
    const members = new Map<string, UserRole[]>();
    
    for (const [userId, userRolesList] of userRoles.entries()) {
      const projectRoles = userRolesList.filter(ur => ur.projectId === projectId);
      if (projectRoles.length > 0) {
        members.set(userId, projectRoles);
      }
    }
    
    return Array.from(members.entries()).map(([userId, roles]) => ({ userId, roles }));
  }
  
  /**
   * Clear all data (for testing)
   */
  static clearAll(): void {
    permissions.clear();
    roles.clear();
    userRoles.clear();
  }
}

// Initialize default permissions and roles
RBACService.initialize();