package com.atlassian.bitbucket.internal.ssh.rest;

import com.atlassian.bitbucket.auth.AuthenticationContext;
import com.atlassian.bitbucket.i18n.I18nService;
import com.atlassian.bitbucket.rest.RestErrors;
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.ssh.SshKey;
import com.atlassian.bitbucket.ssh.SshKeyService;
import com.atlassian.bitbucket.user.ApplicationUser;
import com.atlassian.bitbucket.user.NoSuchUserException;
import com.atlassian.bitbucket.user.UserService;
import com.atlassian.bitbucket.util.Page;
import com.atlassian.bitbucket.util.PageRequest;
import com.atlassian.plugins.rest.common.security.AnonymousAllowed;
import com.sun.jersey.spi.resource.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.ws.rs.*;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.net.URI;

@AnonymousAllowed
@Consumes({MediaType.APPLICATION_JSON})
@Produces({RestUtils.APPLICATION_JSON_UTF8})
@Singleton
@Path("keys")
public class SshKeysResource {

    private static final Logger log = LoggerFactory.getLogger(SshKeysResource.class);

    private final AuthenticationContext authContext;
    private final UserService userService;
    private final SshKeyService sshKeyService;
    private final I18nService i18nService;

    public SshKeysResource(AuthenticationContext authContext, UserService userService, SshKeyService sshKeyService,
                           I18nService i18nService) {
        this.authContext = authContext;
        this.userService = userService;
        this.sshKeyService = sshKeyService;
        this.i18nService = i18nService;
    }

    /**
     * Retrieve a page of ssh keys.
     *
     * @param userName the username of the user to retrieve the keys for.
     *                 If no username is specified, the ssh keys will be
     *                 retrieved for the current authenticated user.
     *
     * @response.representation.200.mediaType application/json
     * @response.representation.200.qname page
     * @response.representation.200.example {@link RestSshKey#EXAMPLE_PAGE}
     * @response.representation.200.doc A page of ssh keys.
     *
     * @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 has insufficient permissions
     *                                  to retrieve the ssh keys. This is only possible when a
     *                                  <strong>user</strong> is explicitly supplied.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc No user matches the supplied <strong>user</strong>
     */
    @GET
    public Response getSshKeys(
            @QueryParam("user") String userName,
            @Context PageRequest pageRequest) {
        log.debug("Entered getSshKeys method");
        ApplicationUser user = getUser(userName);
        Page<SshKey> keys = sshKeyService.findAllForUser(user, pageRequest);
        log.debug("Retrieved keys: {}", keys);
        Response response = ResponseFactory.ok(new RestPage<>(keys, RestSshKey.REST_TRANSFORM)).build();
        log.debug("Built response");
        return response;
    }

    /**
     * Add a new ssh key to a supplied user.
     *
     * @param userName the username of the user to add the ssh key for.
     *                 If no username is specified, the ssh key will
     *                 be added for the current authenticated user.
     *
     * @request.representation.example {@link RestSshKey#EXAMPLE_REQUEST}
     *
     * @response.representation.201.mediaType application/json
     * @response.representation.201.qname sshKey
     * @response.representation.201.example {@link RestSshKey#EXAMPLE_RESPONSE}
     * @response.representation.201.doc The newly created ssh key.
     *
     * @response.representation.400.mediaType application/json
     * @response.representation.400.qname errors
     * @response.representation.400.example {@link RestErrors#EXAMPLE}
     * @response.representation.400.doc The ssh key was not created because the key was not a valid
     *                                  RSA/DSA/ECDSA key of a supported length.
     *
     * @response.representation.401.mediaType application/json
     * @response.representation.401.qname errors
     * @response.representation.401.example {@link RestErrors#EXAMPLE}
     * @response.representation.401.doc Either there is no authenticated user or the currently authenticated user
     *                                  has insufficient permissions to add an ssh key. The latter is only
     *                                  possible when a <strong>user</strong> is explicitly supplied.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc No user matches the supplied <strong>user</strong>
     *
     * @response.representation.409.mediaType application/json
     * @response.representation.409.qname errors
     * @response.representation.409.example {@link RestErrors#EXAMPLE}
     * @response.representation.409.doc The ssh key already exists on the system.
     */
    @POST
    public Response addSshKey(
            @QueryParam("user") String userName,
            RestSshKey key,
            @Context UriInfo uriInfo) {
        SshKey sshKey = sshKeyService.addForUser(getUser(userName), key.getText());
        URI uri = uriInfo.getRequestUriBuilder()
                .replaceQuery(null) // remove query string
                .path(Integer.toString(sshKey.getId()))
                .build();
        return ResponseFactory
                .created(uri)
                .entity(new RestSshKey(sshKey))
                .build();
    }

    /**
     * Delete an ssh key.
     *
     * @param keyId the id of the key to delete.
     *
     * @response.representation.204.doc The ssh key matching the supplied <strong>id</strong> was deleted or
     *                                  did 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 has insufficient permissions
     *                                  to delete the ssh key.
     */
    @DELETE
    @Path("/{keyId}")
    public Response deleteSshKey(@PathParam("keyId") Integer keyId) {
        sshKeyService.remove(keyId);
        return ResponseFactory.noContent().build();
    }

    /**
     * Delete all ssh keys for a supplied user.
     *
     * @param userName the username of the user to delete the keys for.
     *                 If no username is specified, the ssh keys will
     *                 be deleted for the current authenticated user.
     *
     * @response.representation.204.doc The ssh keys matching the supplied <strong>user</strong> were deleted.
     *
     * @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 has insufficient permissions
     *                                  to delete the ssh keys. This is only possible when a
     *                                  <strong>user</strong> is explicitly supplied.
     *
     * @response.representation.404.mediaType application/json
     * @response.representation.404.qname errors
     * @response.representation.404.example {@link RestErrors#EXAMPLE}
     * @response.representation.404.doc No user matches the supplied <strong>user</strong>
     */
    @DELETE
    public Response deleteSshKeys(@QueryParam("user") String userName) {
        sshKeyService.removeAllForUser(getUser(userName));
        return ResponseFactory.noContent().build();
    }

    /**
     * Returns the ApplicationUser with the given name.
     *
     * @param username the name of the user to return
     * @return the user who has the given name
     */
    @Nonnull
    private ApplicationUser getUser(@Nullable String username) {
        ApplicationUser user = authContext.getCurrentUser();
        if (username != null) {
            user = userService.getUserByName(username);
            if (user == null) {
                user = userService.getServiceUserByName(username);
            }
        }

        if (user == null) {
            throw new NoSuchUserException(i18nService.createKeyedMessage("bitbucket.rest.ssh.user.notfound"), username);
        }
        return user;
    }
}
