package com.atlassian.bitbucket.permission;

import com.atlassian.bitbucket.project.Project;
import com.atlassian.bitbucket.repository.Repository;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.SecurityService;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Set;

/**
 * Reads the permissions of users and groups.
 *
 * IMPORTANT: This should not be restricted by permissions, and is not intended for querying
 * access levels of users, but rather checking access of the current user at runtime
 *
 * @see PermissionAdminService
 */
public interface PermissionService {

    /**
     * @return {@code true} if the given user identified by {@code username} has the requested {@link Permission}.
     *  Will return true if the user is {@link ApplicationUser#isActive() active} and one of the following conditions is met:
     *  <ul>
     *      <li>permission is granted directly for the given user</li>
     *      <li>permission is granted to a group the given user is a member of</li>
     *  </ul>
     * @param username the user in question
     * @param permission the requested permission. Must be global.
     */
    boolean hasGlobalPermission(@Nullable String username, @Nonnull Permission permission);

    /**
     * @return {@code true} if the given {@link ApplicationUser} has the requested {@link Permission}.
     *  Will return true if the user is {@link ApplicationUser#isActive() active} and one of the following conditions is met:
     *  <ul>
     *      <li>permission is granted directly for the given user</li>
     *      <li>permission is granted to a group the given user is a member of</li>
     *  </ul>
     * @param user the user in question
     * @param permission the requested permission. Must be global.
     */
    boolean hasGlobalPermission(@Nullable ApplicationUser user, @Nonnull Permission permission);

    /**
     * @return true if the <em>current authentication session</em> has the requested {@link Permission}
     * @param permission the requested permission. Must be global.
     * @see PermissionService#hasGlobalPermission(ApplicationUser, Permission)
     */
    boolean hasGlobalPermission(@Nonnull Permission permission);

    /**
     * @return {@code true} if the given {@link ApplicationUser} has the requested {@link Permission} for the given {@link Project}.
     * Will return true if the user is {@link ApplicationUser#isActive() active} and one of the following conditions is met:
     * <ul>
     *     <li>permission is granted directly for the given user on the given project</li>
     *     <li>permission is granted for all logged in users on the given project</li>
     *     <li>permission is granted to a group the given user is a member of on the given project</li>
     *     <li>The given user is directly granted a permission on at least one repository within the given project
     *     and the given permission is implied as a result</li>
     *     <li>A group the given user is a member of is granted a permission on at least one repository within the given project
     *     and the given permission is implied as a result</li>
     *     <li>The given project is {@link #isPubliclyAccessible(Project) publicly accessible} and the permission is implied as a result</li>
     *     <li>the given user is directly granted {@link Permission#ADMIN administration rights}</li>
     *     <li>A group the given user is a member of is granted {@link Permission#ADMIN administration rights}</li>
     * </ul>
     * @param user the user in question
     * @param project the project in question
     * @param permission the requested permission. Must be non-global.
     */
    boolean hasProjectPermission(@Nullable ApplicationUser user, @Nonnull Project project, @Nonnull Permission permission);

    /**
     * @return true if the <em>current authentication token</em> has the requested {@link Permission} for the given {@link Project}
     * @param project the project in question
     * @param permission the requested permission. Must be non-global.
     * @see PermissionService#hasProjectPermission(ApplicationUser, Project, Permission)
     */
    boolean hasProjectPermission(@Nonnull Project project, @Nonnull Permission permission);

    /**
     * @return true if the given {@link ApplicationUser} has the requested {@link Permission} for the {@link Project} identified by projectId.
     * @param user the user in question
     * @param projectId the id of project
     * @param permission the requested permission. Must be non-global.
     * @see PermissionService#hasProjectPermission(ApplicationUser, Project, Permission)
     */
    boolean hasProjectPermission(@Nullable ApplicationUser user, int projectId, @Nonnull Permission permission);

    /**
     * @return true if the <em>current authentication token</em> has the requested {@link Permission} for the given {@link Project}
     * @param projectId the id project in question
     * @param permission the requested permission. Must be non-global.
     * @see PermissionService#hasProjectPermission(ApplicationUser, int, Permission)
     */
    boolean hasProjectPermission(int projectId, @Nonnull Permission permission);

    /**
     * @return true if the given {@link ApplicationUser} has the requested {@link Permission} for the given {@link Repository}.
     * Will return true if the user is {@link ApplicationUser#isActive() active} and one of the following conditions is met:
     * <ul>
     *     <li>permission is granted directly for the given user on the given repository</li>
     *     <li>permission is granted to a group the given user is a member of on the given repository</li>
     *     <li>The given user is directly granted a permission on the containing project and the given permission is implied as a result</li>
     *     <li>A group the given user is a member of is granted a permission on the containing project and the given permission is implied as a result</li>
     *     <li>All logged in users are granted a permission to the containing project and the given permission is implied as a result</li>
     *     <li>The given repository is {@link #isPubliclyAccessible(Repository) publicly accessible} and the permission is implied as a result</li>
     *     <li>the given user is directly granted {@link Permission#ADMIN administration rights}</li>
     *     <li>A group the given user is a member of is granted {@link Permission#ADMIN administration rights}</li>
     * </ul>
     * @param user the user in question
     * @param repository the repository in question
     * @param permission the requested permission. Must be non-global.
     */
    boolean hasRepositoryPermission(@Nullable ApplicationUser user, @Nonnull Repository repository, @Nonnull Permission permission);

    /**
     * @return true if the given {@link ApplicationUser} has the requested {@link Permission} for the given {@link Repository}.
     * @param user the user in question
     * @param repositoryId the repository in question
     * @param permission the requested permission. Must be non-global.
     * @see PermissionService#hasRepositoryPermission(ApplicationUser, Repository, Permission)
     */
    boolean hasRepositoryPermission(@Nullable ApplicationUser user, int repositoryId, @Nonnull Permission permission);

    /**
     * @return true if the <em>current AuthenticationToken</em> has the requested {@link Permission} for the given {@link Repository}
     * @param repository the repository in question
     * @param permission the requested permission. Must be non-global.
     * @see PermissionService#hasRepositoryPermission(ApplicationUser, Repository, Permission)
     */
    boolean hasRepositoryPermission(@Nonnull Repository repository, @Nonnull Permission permission);

    /**
     * @return true if the <em>current AuthenticationToken</em> has the requested {@link Permission} for the given {@link Repository} identified by repositoryid.
     * @param repositoryId the repository in question
     * @param permission the requested permission. Must be non-global.
     * @see PermissionService#hasRepositoryPermission(ApplicationUser, Repository, Permission)
     */
    boolean hasRepositoryPermission(int repositoryId, @Nonnull Permission permission);

    /**
     * @return true if the given {@link ApplicationUser} has the requested {@link Permission} for <em>any</em> project / repository
     * @param user the user in question
     * @param permission the requested permission. Must be non-global.
     */
    boolean hasAnyUserPermission(@Nonnull ApplicationUser user, @Nonnull Permission permission);

    /**
     * @return true if the <em>current user</em> has the requested {@link Permission} for <em>any</em> project / repository
     * @param permission the requested permission. Must be non-global.
     */
    boolean hasAnyUserPermission(@Nonnull Permission permission);

    /**
     * @return true if the current user has the given global permission through its membership of a group
     *
     * @param permission The permission required.
     * @param excludedGroups A Set of groups to be excluded from consideration.
     */
    boolean hasGlobalPermissionThroughGroupMembership(@Nonnull Permission permission,
                                                      @Nonnull Set<String> excludedGroups);


    /**
     * @return true if the current user has the given project permission through its membership of a group
     *
     * @param project The project on which permission is being checked.
     * @param permission The permission required.
     * @param excludedGroups A Set of groups to be excluded from consideration.
     */
    boolean hasProjectPermissionThroughGroupMembership(@Nonnull Project project, @Nonnull Permission permission,
                                                       @Nonnull Set<String> excludedGroups);

    /**
     * @return true if the current user has the given repository permission through its membership of a group
     *
     * @param repository The repository on which permission is being checked.
     * @param permission The permission required.
     * @param excludedGroups A Set of groups to be excluded from consideration.
     */
    boolean hasRepositoryPermissionThroughGroupMembership(@Nonnull Repository repository, @Nonnull Permission permission,
                                                          @Nonnull Set<String> excludedGroups);

    /**
     * @return true if the current user has the given global permission directly granted
     *         (i.e. not through their group membership)
     * @param permission the permission required.
     */
    boolean hasDirectGlobalUserPermission(@Nonnull Permission permission);


    /**
     * @return true if the current user has the given project permission directly granted
     *         (i.e. not through their group membership)
     *
     * @param project the project on which permission is being checked.
     * @param permission the permission required.
     */
    boolean hasDirectProjectUserPermission(@Nonnull Project project, @Nonnull Permission permission);

    /**
     * @return true if the current user has the given repository permission directly granted
     *         (i.e. not through their group membership)
     *
     * @param repository the repository on which permission is being checked.
     * @param permission the permission required.
     */
    boolean hasDirectRepositoryUserPermission(@Nonnull Repository repository, @Nonnull Permission permission);

    /**
     * @return true if the given group has the given permission.
     *
     * @param permission the permission required.
     * @param group the group to be checked.
     */
    boolean hasGlobalGroupPermission(@Nonnull Permission permission, @Nonnull String group);

    /**
     * Retrieve whether the current user (authenticated or not) has access to the given project.
     * <p>
     * The user may have access as a result of the following:
     * <ul>
     *     <li>The current authenticated user has {@link Permission#PROJECT_VIEW} permission for the given project; or</li>
     *     <li>The current thread is
     *     {@link SecurityService#withPermission(Permission, String)} running with}
     *     {@link Permission#PROJECT_VIEW} permission; or</li>
     *     <li>Public access is enabled for the system; and
     *     <ul>
     *         <li>The given project {@link Project#isPublic() is public}; or</li>
     *         <li>At least one of the repositories contained within the project {@link Repository#isPublic() are public}</li>
     *     </ul>
     *     </li>
     * </ul>
     *
     * @param project the project on which to check
     * @return {@code true} if the current user has access to the given project, {@code false} otherwise
     */
    boolean isProjectAccessible(@Nonnull Project project);

    /**
     * @param projectId the id of the project on which to check
     * @return {@code true} if the current user has access to the given project, {@code false} otherwise
     * @see #isProjectAccessible(Project)
     */
    boolean isProjectAccessible(int projectId);

    /**
     * Retrieve whether the current user (authenticated or not) has access to the given repository.
     * <p>
     * The user may have access as a result of the following:
     * <ul>
     *     <li>The current authenticated user has {@link Permission#REPO_READ} permission for the given repository; or</li>
     *     <li>The current thread is
     *     {@link SecurityService#withPermission(Permission, String)}  running with}
     *     {@link Permission#REPO_READ} permission; or</li>
     *     <li>Public access is enabled for the system; and
     *     <ul>
     *         <li>The given repository {@link Repository#isPublic() is public}; or</li>
     *         <li>The containing project for the given repository {@link Project#isPublic() is public}</li>
     *     </ul>
     *     </li>
     * </ul>
     *
     * @param repository the repository on which to check
     * @return {@code true} if the current user has access to the given repository, {@code false} otherwise
     */
    boolean isRepositoryAccessible(@Nonnull Repository repository);

    /**
     * @param repositoryId the id of the repository on which to check
     * @return {@code true} if the current user has access to the given repository, {@code false} otherwise
     * @see #isRepositoryAccessible(Repository)
     */
    boolean isRepositoryAccessible(int repositoryId);

    /**
     * Retrieves whether the repository is <em>publicly accessible</em>.
     * <p>
     * Note that a repository will be public if:
     * <ul>
     *     <li>public access is enabled for the instance; and
     *     <ul>
     *          <li>the repository itself {@link Repository#isPublic() is public}; or</li>
     *          <li>the containing project for the repository {@link Project#isPublic() is public}</li>
     *     </ul>
     * </ul>
     * <p>
     * To check whether the current user (authenticated or not) can access the repository,
     * use {@link #isRepositoryAccessible(Repository)} instead.
     *
     * @param repository the repository on which to check
     * @return {@code true} if unauthenticated users can access the given repository, {@code false} otherwise
     */
    boolean isPubliclyAccessible(@Nonnull Repository repository);

    /**
     * Retrieves whether the project is <em>publicly accessible</em>.
     * <p>
     * Note: to check whether the current user (authenticated or not) can access the project,
     * use {@link #isProjectAccessible(Project)} instead.
     *
     * @param project the project on which to check
     * @return {@code true} if unauthenticated users can access the given project, {@code false} otherwise
     */
    boolean isPubliclyAccessible(@Nonnull Project project);

    /**
     * Get the users which are granted a permission
     *
     * @param permission the permission in question
     * @param request a page request
     * @return the page of users who have been granted a permission or an inheriting permission
     */
    @Nonnull
    Page<ApplicationUser> getGrantedUsers(@Nonnull Permission permission, @Nonnull PageRequest request);

    /**
     * Get the groups which are granted a permission
     *
     * @param permission the permission in question
     * @param request a page request
     * @return the page of group names which have been granted a permission or an inheriting permission
     */
    @Nonnull
    Page<String> getGrantedGroups(@Nonnull Permission permission, @Nonnull PageRequest request);

    /**
     * Get the usernames of the users with the given permission (whether directly or though permission inheritance)
     * This includes users granted the permission directly and those who have the permission through their group
     * membership.
     *
     * @param permission the permission for which the users are being fetched. It must be {@link Permission#isGlobal() global}
     * @return The lowercase usernames of all the users who currently have the given permission
     */
    @Nonnull
    Set<String> getUsersWithPermission(@Nonnull Permission permission);

    /**
     * Get the highest global permission for a user.
     *
     * @param user the user
     * @return the highest global permission for the user or null if they have no permissions
     */
    @Nullable
    Permission getHighestGlobalPermission(@Nullable ApplicationUser user);

    /**
     * Get the highest global permission for a user.
     *
     * @param username the user name
     * @return the highest global permission for the user or null if they have no permissions
     */
    @Nullable
    Permission getHighestGlobalPermission(@Nullable String username);

    /**
     * Get the highest global permission for a group.
     *
     * @param groupName the group name
     * @return the highest global permission for the group or null if the group has no permissions
     */
    @Nullable
    Permission getHighestGlobalGroupPermission(@Nullable String groupName);

    /**
     * The complete set of {@link EffectivePermission effective permissions} for the supplied user.
     * Only the minimal set of effective permissions is returned by the iterator such that any effective permission
     * that can be {@link Permission#getImplyingPermissions() inferred} from other permissions is not returned.
     * <p>
     * E.g. if a user is granted {@code SYS_ADMIN} permission, a group they belong to is granted {@code REPO_READ}
     * on repository with id 1 and project 7 has a default permission of {@code PROJ_READ}, the only {@code EffectivePermission}
     * to be returned from the iterator will be a {@code GlobalEffectivePermission} with permissions {@code SYS_ADMIN}
     * since all other permissions can be inferred.
     *
     * @param user the user
     * @return a minimal sequence of effective permissions
     */
    @Nonnull
    Iterable<EffectivePermission> getEffectivePermissions(@Nonnull ApplicationUser user);
}
