import groupBy from 'lodash/groupBy';
import invertBy from 'lodash/invertBy';
import pick from 'lodash/pick';

import {Permissions} from 'sentry/types';

const PERMISSION_LEVELS = {
  read: 0,
  write: 1,
  admin: 2,
};

const HUMAN_RESOURCE_NAMES = {
  project: 'Project',
  team: 'Team',
  release: 'Release',
  event: 'Event',
  org: 'Organization',
  member: 'Member',
};

const DEFAULT_RESOURCE_PERMISSIONS: Permissions = {
  Project: 'no-access',
  Team: 'no-access',
  Release: 'no-access',
  Event: 'no-access',
  Organization: 'no-access',
  Member: 'no-access',
};

const PROJECT_RELEASES = 'project:releases';

type PermissionLevelResources = {
  admin: string[];
  read: string[];
  write: string[];
};
/**
 * Numerical value of the scope where Admin is higher than Write,
 * which is higher than Read. Used to sort scopes by access.
 */
const permissionLevel = (scope: string): number => {
  const permission = scope.split(':')[1];
  return PERMISSION_LEVELS[permission];
};

const compareScopes = (a: string, b: string) => permissionLevel(a) - permissionLevel(b);

/**
 * Return the most permissive scope for each resource.
 *
 * Example:
 *    Given the full list of scopes:
 *      ['project:read', 'project:write', 'team:read', 'team:write', 'team:admin']
 *
 *    this would return:
 *      ['project:write', 'team:admin']
 */
function topScopes(scopeList: string[]) {
  return Object.values(groupBy(scopeList, scope => scope.split(':')[0]))
    .map(scopes => scopes.sort(compareScopes))
    .map(scopes => scopes.pop());
}

/**
 * Convert into a list of Permissions, grouped by resource.
 *
 * This is used in the new/edit Sentry App form. That page displays permissions
 * in a per-Resource manner, meaning one row for Project, one for Organization, etc.
 *
 * This exposes scopes in a way that works for that UI.
 *
 * Example:
 *    {
 *      'Project': 'read',
 *      'Organization': 'write',
 *      'Team': 'no-access',
 *      ...
 *    }
 */
function toResourcePermissions(scopes: string[]): Permissions {
  const permissions = {...DEFAULT_RESOURCE_PERMISSIONS};
  let filteredScopes = [...scopes];
  // The scope for releases is `project:releases`, but instead of displaying
  // it as a permission of Project, we want to separate it out into its own
  // row for Releases.
  if (scopes.includes(PROJECT_RELEASES)) {
    permissions.Release = 'admin';
    filteredScopes = scopes.filter((scope: string) => scope !== PROJECT_RELEASES); // remove project:releases
  }

  topScopes(filteredScopes).forEach((scope: string | undefined) => {
    if (scope) {
      const [resource, permission] = scope.split(':');
      permissions[HUMAN_RESOURCE_NAMES[resource]] = permission;
    }
  });

  return permissions;
}

/**
 * Convert into a list of Permissions, grouped by access and including a
 * list of resources per access level.
 *
 * This is used in the Permissions Modal when installing an App. It displays
 * scopes in a per-Permission way, meaning one row for Read, one for Write,
 * and one for Admin.
 *
 * This exposes scopes in a way that works for that UI.
 *
 * Example:
 *    {
 *      read:  ['Project', 'Organization'],
 *      write: ['Member'],
 *      admin: ['Release']
 *    }
 */
function toPermissions(scopes: string[]): PermissionLevelResources {
  const defaultPermissions = {read: [], write: [], admin: []};
  const resourcePermissions = toResourcePermissions(scopes);

  // Filter out the 'no-access' permissions
  const permissions = pick(invertBy(resourcePermissions), ['read', 'write', 'admin']);
  return {...defaultPermissions, ...permissions};
}

export {toPermissions, toResourcePermissions};
