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.ResourcePatterns;
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.repository.Repository;
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(ResourcePatterns.REPOSITORY_URI + "/permissions")
@Consumes({MediaType.APPLICATION_JSON})
@Produces({RestUtils.APPLICATION_JSON_UTF8})
@Singleton
@AnonymousAllowed
public class RepositoryPermissionResource extends AbstractPermissionResource {

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

    /**
     * Retrieve a page of groups that have been granted at least one permission for the specified repository.
     * <p>
     * The authenticated user must have <strong>REPO_ADMIN</strong> permission for the specified repository or a higher
     * project or global permission 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 permissions for the specified repository.
     *
     * @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 a repository administrator for the
     *                                  specified repository.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified repository does not exist.
     */
    @GET
    @Path("groups")
    public Response getGroupsWithAnyPermission(@Context Repository repository,
                                               @QueryParam("filter") String filter,
                                               @Context PageRequest pageRequest) {
        Page<PermittedGroup> page = permissionAdminService.findGroupsWithRepositoryPermission(repository, filter,
                pageRequest);

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

    /**
     * Retrieve a page of groups that have no granted permissions for the specified repository.
     * <p>
     * The authenticated user must have <strong>REPO_ADMIN</strong> permission for the specified repository or a higher
     * project or global permission 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 permissions for the specified
     *                                  repository.
     *
     * @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 a repository administrator for the
     *                                  specified repository.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified repository does not exist.
     */
    @GET
    @Path("groups/none")
    public Response getGroupsWithoutAnyPermission(@Context Repository repository,
                                                  @QueryParam("filter") String filter,
                                                  @Context PageRequest pageRequest) {
        Page<String> page = permissionAdminService.findGroupsWithoutRepositoryPermission(repository, filter,
                pageRequest);

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

    /**
     * Retrieve a page of users that have been granted at least one permission for the specified repository.
     * <p>
     * The authenticated user must have <strong>REPO_ADMIN</strong> permission for the specified repository or a higher
     * project or global permission 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 RestPermittedUser#PAGE_EXAMPLE}
     * @response.representation.200.doc A page of users and their highest permissions for the specified repository.
     *
     * @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 a repository administrator for the
     *                                  specified repository.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified repository does not exist.
     */
    @GET
    @Path("users")
    public Response getUsersWithAnyPermission(@Context Repository repository,
                                              @QueryParam("filter") String filter,
                                              @Context PageRequest pageRequest) {
        Page<PermittedUser> page = permissionAdminService.findUsersWithRepositoryPermission(repository, filter,
                pageRequest);

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

    /**
     * Retrieve a page of <i>licensed</i> users that have no granted permissions for the specified repository.
     * <p>
     * The authenticated user must have <strong>REPO_ADMIN</strong> permission for the specified repository or a higher
     * project or global permission 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 users that have not been granted any permissions for the specified
     *                                  repository.
     *
     * @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 a repository administrator for the
     *                                  specified repository.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified repository does not exist.
     */
    @GET
    @Path("users/none")
    public Response getUsersWithoutPermission(@Context Repository repository,
                                              @QueryParam("filter") String filter,
                                              @Context PageRequest pageRequest) {
        Page<ApplicationUser> page = permissionAdminService.findLicensedUsersWithoutRepositoryPermission(repository, filter,
                pageRequest);

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

    /**
     * Promote or demote a group's permission level for the specified repository. Available repository permissions are:
     * <ul>
     *     <li>REPO_READ</li>
     *     <li>REPO_WRITE</li>
     *     <li>REPO_ADMIN</li>
     * </ul>
     * See the <a href="https://confluence.atlassian.com/display/BitbucketServer/Using+repository+permissions">Bitbucket
     * Server documentation</a> for a detailed explanation of what each permission entails.
     * <p>
     * The authenticated user must have <strong>REPO_ADMIN</strong> permission for the specified repository or a higher
     * project or global permission 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 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 for the specified
     *                                  repository.
     *
     * @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 reduce the currently authenticated user's
     *                                  permission level.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified repository or group does not exist.
     */
    @PUT
    @Path("groups")
    public Response setPermissionForGroup(@Context Repository repository,
                                          @QueryParam("permission") String permissionName,
                                          @QueryParam("name") Set<String> groupNames) {
        Permission permission = validatePermission(permissionName, Repository.class);
        Set<String> groups = validateGroups(groupNames, false);

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

    /**
     * Promote or demote a user's permission level for the specified repository. Available repository permissions are:
     * <ul>
     *     <li>REPO_READ</li>
     *     <li>REPO_WRITE</li>
     *     <li>REPO_ADMIN</li>
     * </ul>
     * See the <a href="https://confluence.atlassian.com/display/BitbucketServer/Using+repository+permissions">Bitbucket
     * Server documentation</a> for a detailed explanation of what each permission entails.
     * <p>
     * The authenticated user must have <strong>REPO_ADMIN</strong> permission for the specified repository or a higher
     * project or global permission to call this resource. In addition, a user may not reduce their own permission level unless
     * they have a project or global permission that already implies that permission.
     *
     * @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 for the specified
     *                                  repository.
     *
     * @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 reduce the currently authenticated user's
     *                                  permission level.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified repository or user does not exist.
     */
    @PUT
    @Path("users")
    public Response setPermissionForUser(@Context Repository repository,
                                         @QueryParam("name") Set<String> usernames,
                                         @QueryParam("permission") String permissionName) {
        Permission permission = validatePermission(permissionName, Repository.class);
        Set<ApplicationUser> users = validateUsers(usernames, false);

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

    /**
     * Revoke all permissions for the specified repository for a group.
     * <p>
     * The authenticated user must have <strong>REPO_ADMIN</strong> permission for the specified repository or a higher
     * project or global permission to call this resource.
     * <p>
     * In addition, a user may not revoke a group's permissions if it will reduce their own permission level.
     *
     * @param groupName the name of the group
     *
     * @response.representation.204.doc All repository permissions were revoked from the group for the specified repository.
     *
     * @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 of the specified
     *                                  repository.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified repository or 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.
     */
    @DELETE
    @Path("groups")
    public Response revokePermissionsForGroup(@Context Repository repository,
                                              @QueryParam("name") String groupName) {
        groupName = validateGroup(groupName, true);

        permissionAdminService.revokeAllRepositoryPermissions(repository, groupName);
        return ResponseFactory.noContent().build();
    }

    /**
     * Revoke all permissions for the specified repository for a user.
     * <p>
     * The authenticated user must have <strong>REPO_ADMIN</strong> permission for the specified repository or a higher
     * project or global permission to call this resource.
     * <p>
     * In addition, a user may not revoke their own repository permissions if they do not have a higher
     * project or global permission.
     *
     * @param username the name of the user
     *
     * @response.representation.204.doc All repository permissions were revoked from the user for the specified repository.
     *
     * @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 of the specified
     *                                  repository.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc The specified repository or 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.
     */
    @DELETE
    @Path("users")
    public Response revokePermissionsForUser(@Context Repository repository,
                                             @QueryParam("name") String username) {
        ApplicationUser user = validateUser(username, true);

        permissionAdminService.revokeAllRepositoryPermissions(repository, user);
        return ResponseFactory.noContent().build();
    }
}
