package com.atlassian.stash.internal.rest.user;

import com.atlassian.bitbucket.permission.*;
import com.atlassian.bitbucket.rest.RestErrors;
import com.atlassian.bitbucket.rest.user.RestDetailedGroup;
import com.atlassian.bitbucket.rest.user.RestPermittedGroup;
import com.atlassian.bitbucket.rest.user.RestPermittedUser;
import com.atlassian.bitbucket.rest.user.RestApplicationUser;
import com.atlassian.bitbucket.rest.util.ResponseFactory;
import com.atlassian.bitbucket.rest.util.RestPage;
import com.atlassian.bitbucket.rest.util.RestUtils;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.UserService;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.sun.jersey.spi.resource.Singleton;

import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.Set;

@Path("admin/permissions")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({RestUtils.APPLICATION_JSON_UTF8})
@Singleton
@AnonymousAllowed
public class GlobalPermissionResource extends AbstractPermissionResource {

    public GlobalPermissionResource(PermissionAdminService permissionAdminService,
                                    UserService userService,
                                    I18nService i18nService) {
        super(i18nService, permissionAdminService, userService);
    }

    /**
     * Retrieve a page of groups that have been granted at least one global permission.
     * <p>
     * The authenticated user must have <strong>ADMIN</strong> permission or higher to call this resource.
     *
     * @param filter if specified only group names containing the supplied string will be returned
     *
     * @response.representation.200.mediaType application/json
     * @response.representation.200.qname page
     * @response.representation.200.example {@link RestPermittedGroup#PAGE_EXAMPLE}
     * @response.representation.200.doc A page of groups and their highest global permissions.
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc The currently authenticated user is not an administrator.
     */
    @GET
    @Path("groups")
    public Response getGroupsWithAnyPermission(@QueryParam("filter") @DefaultValue("") String filter,
                                               @Context PageRequest pageRequest) {
        Page<PermittedGroup> page = permissionAdminService.findGroupsWithGlobalPermission(filter, pageRequest);

        return ResponseFactory.ok(new RestPage<>(page, RestPermittedGroup.REST_TRANSFORM)).build();
    }

    /**
     * Retrieve a page of groups that have no granted global permissions.
     * <p>
     * The authenticated user must have <strong>ADMIN</strong> permission or higher to call this resource.
     *
     * @param filter if specified only group names containing the supplied string will be returned
     *
     * @response.representation.200.mediaType application/json
     * @response.representation.200.qname page
     * @response.representation.200.example {@link RestDetailedGroup#PAGE_EXAMPLE}
     * @response.representation.200.doc A page of groups that have not been granted any global permissions.
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc The currently authenticated user is not an administrator.
     */
    @GET
    @Path("groups/none")
    public Response getGroupsWithoutAnyPermission(@QueryParam("filter") @DefaultValue("") String filter,
                                                  @Context PageRequest pageRequest) {
        Page<String> page = permissionAdminService.findGroupsWithoutGlobalPermission(filter, pageRequest);

        return ResponseFactory.ok(new RestPage<>(page, RestDetailedGroup.NAME_TRANSFORM)).build();
    }

    /**
     * Retrieve a page of users that have been granted at least one global permission.
     * <p>
     * The authenticated user must have <strong>ADMIN</strong> permission or higher to call this resource.
     *
     * @param filter if specified only user names containing the supplied string will be returned
     *
     * @response.representation.200.mediaType application/json
     * @response.representation.200.qname page
     * @response.representation.200.example {@link RestPermittedUser#PAGE_EXAMPLE}
     * @response.representation.200.doc A page of users and their highest global permissions.
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc The currently authenticated user is not an administrator.
     */
    @GET
    @Path("users")
    public Response getUsersWithAnyPermission(@QueryParam("filter") @DefaultValue("") String filter,
                                              @Context PageRequest pageRequest) {
        Page<PermittedUser> page = permissionAdminService.findUsersWithGlobalPermission(filter, pageRequest);

        return ResponseFactory.ok(new RestPage<>(page, RestPermittedUser.REST_TRANSFORM)).build();
    }

    /**
     * Retrieve a page of users that have no granted global permissions.
     * <p>
     * The authenticated user must have <strong>ADMIN</strong> permission or higher to call this resource.
     *
     * @param filter if specified only user names containing the supplied string will be returned
     *
     * @response.representation.200.mediaType application/json
     * @response.representation.200.qname page
     * @response.representation.200.example {@link RestApplicationUser#PAGE_EXAMPLE}
     * @response.representation.200.doc A page of users that have not been granted any global permissions.
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc The currently authenticated user is not an administrator.
     */
    @GET
    @Path("users/none")
    public Response getUsersWithoutAnyPermission(@QueryParam("filter") @DefaultValue("") String filter,
                                                 @Context PageRequest pageRequest) {
        Page<ApplicationUser> page = permissionAdminService.findUsersWithoutGlobalPermission(filter, pageRequest);

        return ResponseFactory.ok(new RestPage<>(page, RestApplicationUser.REST_TRANSFORM)).build();
    }

    /**
     * Promote or demote a user's global permission level. Available global permissions are:
     * <ul>
     *     <li>LICENSED_USER</li>
     *     <li>PROJECT_CREATE</li>
     *     <li>ADMIN</li>
     *     <li>SYS_ADMIN</li>
     * </ul>
     * See the <a href="https://confluence.atlassian.com/display/BitbucketServer/Global+permissions">Bitbucket Server
     * documentation</a> for a detailed explanation of what each permission entails.
     * <p>
     * The authenticated user must have:
     * <ul>
     *     <li><strong>ADMIN</strong> permission or higher; and</li>
     *     <li>the permission they are attempting to grant or higher; and</li>
     *     <li>greater or equal permissions than the current permission level of the group (a user may not demote the
     *     permission level of a group with higher permissions than them)</li>
     * </ul>
     * to call this resource. In addition, a user may not demote a group's permission level if their own permission
     * level would be reduced as a result.
     *
     * @param permissionName the permission to grant
     * @param groupNames the names of the groups
     *
     * @response.representation.204.doc The specified permission was granted to the specified user.
     *
     * @response.representation.400.mediaType application/json
     * @response.representation.400.qname errors
     * @response.representation.400.example {@link RestErrors#VALIDATION_EXAMPLE}
     * @response.representation.400.doc The request was malformed or the specified permission does not exist.
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc The currently authenticated user is not an administrator or doesn't have the
     *                                  specified permission they are attempting to grant.
     *
     * @response.representation.403.mediaType application/json
     * @response.representation.403.qname errors
     * @response.representation.403.example {@link RestErrors#EXAMPLE}
     * @response.representation.403.doc The action was disallowed as it would exceed the server's license limits.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified group does not exist.
     *
     * @response.representation.409.mediaType application/json
     * @response.representation.409.qname errors
     * @response.representation.409.example {@link RestErrors#EXAMPLE}
     * @response.representation.409.doc The action was disallowed as it would reduce the currently authenticated user's
     *                                  permission level or the currently authenticated user has a lower permission
     *                                  level than the group they are attempting to modify.
     */
    @PUT
    @Path("groups")
    public Response setPermissionForGroups(@QueryParam("permission") String permissionName,
                                           @QueryParam("name") final Set<String> groupNames) {
        Permission permission = validatePermission(permissionName, null);
        Set<String> groups = validateGroups(groupNames, false);

        permissionAdminService.setPermission(new SetPermissionRequest.Builder()
                .globalPermission(permission)
                .groups(groups)
                .build());
        return ResponseFactory.noContent().build();
    }

    /**
     * Promote or demote the global permission level of a user. Available global permissions are:
     * <ul>
     *     <li>LICENSED_USER</li>
     *     <li>PROJECT_CREATE</li>
     *     <li>ADMIN</li>
     *     <li>SYS_ADMIN</li>
     * </ul>
     * See the <a href="https://confluence.atlassian.com/display/BitbucketServer/Global+permissions">Bitbucket Server
     * documentation</a> for a detailed explanation of what each permission entails.
     * <p>
     * The authenticated user must have:
     * <ul>
     *     <li><strong>ADMIN</strong> permission or higher; and</li>
     *     <li>the permission they are attempting to grant; and</li>
     *     <li>greater or equal permissions than the current permission level of the user (a user may not demote the
     *     permission level of a user with higher permissions than them)</li>
     * </ul>
     * to call this resource. In addition, a user may not demote their own permission level.
     *
     * @param permissionName the permission to grant
     * @param usernames the names of the users
     *
     * @response.representation.204.doc The requested permission was granted.
     *
     * @response.representation.400.mediaType application/json
     * @response.representation.400.qname errors
     * @response.representation.400.example {@link RestErrors#VALIDATION_EXAMPLE}
     * @response.representation.400.doc The request was malformed or the specified permission does not exist.
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc The currently authenticated user is not an administrator or doesn't have the
     *                                  specified permission they are attempting to grant.
     *
     * @response.representation.403.mediaType application/json
     * @response.representation.403.qname errors
     * @response.representation.403.example {@link RestErrors#EXAMPLE}
     * @response.representation.403.doc The action was disallowed as it would exceed the server's license limits.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified user does not exist.
     *
     * @response.representation.409.mediaType application/json
     * @response.representation.409.qname errors
     * @response.representation.409.example {@link RestErrors#EXAMPLE}
     * @response.representation.409.doc The action was disallowed as it would reduce the currently authenticated user's
     *                                  permission level or the currently authenticated user has a lower permission
     *                                  level than the user they are attempting to modify.
     */
    @PUT
    @Path("users")
    public Response setPermissionForUsers(@QueryParam("name") Set<String> usernames,
                                          @QueryParam("permission") String permissionName) {
        Permission permission = validatePermission(permissionName, null);
        Set<ApplicationUser> users = validateUsers(usernames, false);

        permissionAdminService.setPermission(new SetPermissionRequest.Builder()
                .globalPermission(permission)
                .users(users)
                .build());
        return ResponseFactory.noContent().build();
    }

    /**
     * Revoke all global permissions for a group.
     *
     * <p>
     * The authenticated user must have:
     * <ul>
     *     <li><strong>ADMIN</strong> permission or higher; and</li>
     *     <li>greater or equal permissions than the current permission level of the group (a user may not demote the
     *     permission level of a group with higher permissions than them)</li>
     * </ul>
     * to call this resource. In addition, a user may not revoke a group's permissions if their own permission level
     * would be reduced as a result.
     *
     * @param groupName the name of the group
     *
     * @response.representation.204.doc All global permissions were revoked from the group.
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc The currently authenticated user is not an administrator.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified group does not exist.
     *
     * @response.representation.409.mediaType application/json
     * @response.representation.409.qname errors
     * @response.representation.409.example {@link RestErrors#EXAMPLE}
     * @response.representation.409.doc The action was disallowed as it would reduce the currently authenticated user's
     *                                  permission level or the currently authenticated user has a lower permission
     *                                  level than the group they are attempting to modify.
     */
    @DELETE
    @Path("groups")
    public Response revokePermissionsForGroup(@QueryParam("name") String groupName) {
        groupName = validateGroup(groupName, true);

        permissionAdminService.revokeAllGlobalPermissions(groupName);
        return ResponseFactory.noContent().build();
    }

    /**
     * Revoke all global permissions for a user.
     * <p>
     * The authenticated user must have:
     * <ul>
     *     <li><strong>ADMIN</strong> permission or higher; and</li>
     *     <li>greater or equal permissions than the current permission level of the user (a user may not demote the
     *     permission level of a user with higher permissions than them)</li>
     * </ul>
     * to call this resource. In addition, a user may not demote their own permission level.
     *
     * @param username the name of the user
     *
     * @response.representation.204.doc All global permissions were revoked from the user.
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc The currently authenticated user is not an administrator.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified user does not exist.
     *
     * @response.representation.409.mediaType application/json
     * @response.representation.409.qname errors
     * @response.representation.409.example {@link RestErrors#EXAMPLE}
     * @response.representation.409.doc The action was disallowed as it would reduce the currently authenticated user's
     *                                  permission level or the currently authenticated user has a lower permission
     *                                  level than the user they are attempting to modify.
     */
    @DELETE
    @Path("users")
    public Response revokePermissionsForUser(@QueryParam("name") String username) {
        ApplicationUser user = validateUser(username, true);

        permissionAdminService.revokeAllGlobalPermissions(user);
        return ResponseFactory.noContent().build();
    }
}
