/*
 * Copyright (c) 2012-2018 Red Hat, Inc.
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *   Red Hat, Inc. - initial API and implementation
 */
package org.eclipse.che.api.ssh.server;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import static org.eclipse.che.api.ssh.shared.Constants.LINK_REL_GET_PAIR;
import static org.eclipse.che.api.ssh.shared.Constants.LINK_REL_REMOVE_PAIR;
import static org.eclipse.che.dto.server.DtoFactory.newDto;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.apache.commons.fileupload.FileItem;
import org.eclipse.che.api.core.BadRequestException;
import org.eclipse.che.api.core.ConflictException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.ServerException;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.core.rest.annotations.GenerateLink;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.util.LinksHelper;
import org.eclipse.che.api.ssh.server.model.impl.SshPairImpl;
import org.eclipse.che.api.ssh.shared.Constants;
import org.eclipse.che.api.ssh.shared.dto.GenerateSshPairRequest;
import org.eclipse.che.api.ssh.shared.dto.SshPairDto;
import org.eclipse.che.api.ssh.shared.model.SshPair;
import org.eclipse.che.commons.env.EnvironmentContext;

/**
 * Defines Ssh Rest API.
 *
 * @author Sergii Leschenko
 */
@Api(value = "/ssh", description = "Ssh REST API")
@Path("/ssh")
public class SshService extends Service {
  private final SshManager sshManager;

  @Inject
  public SshService(SshManager sshManager) {
    this.sshManager = sshManager;
  }

  @POST
  @Path("generate")
  @Consumes(APPLICATION_JSON)
  @Produces(APPLICATION_JSON)
  @GenerateLink(rel = Constants.LINK_REL_GENERATE_PAIR)
  @ApiOperation(
      value = "Generate and stores ssh pair based on the request",
      notes =
          "This operation can be performed only by authorized user,"
              + "this user will be the owner of the created ssh pair",
      response = SshPairDto.class)
  @ApiResponses({
    @ApiResponse(code = 201, message = "The ssh pair successfully generated"),
    @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
    @ApiResponse(
        code = 409,
        message =
            "Conflict error occurred during the ssh pair generation"
                + "(e.g. The Ssh pair with such name and service already exists)"),
    @ApiResponse(code = 500, message = "Internal server error occurred")
  })
  public Response generatePair(
      @ApiParam(value = "The configuration to generate the new ssh pair", required = true)
          GenerateSshPairRequest request)
      throws BadRequestException, ServerException, ConflictException {
    requiredNotNull(request, "Generate ssh pair request required");
    requiredNotNull(request.getService(), "Service name required");
    requiredNotNull(request.getName(), "Name required");
    final SshPairImpl generatedPair =
        sshManager.generatePair(getCurrentUserId(), request.getService(), request.getName());

    return Response.status(Response.Status.CREATED)
        .entity(asDto(injectLinks(asDto(generatedPair))))
        .build();
  }

  @POST
  @Consumes(MediaType.MULTIPART_FORM_DATA)
  @Produces(MediaType.TEXT_HTML)
  @GenerateLink(rel = Constants.LINK_REL_CREATE_PAIR)
  public Response createPair(Iterator<FileItem> formData)
      throws BadRequestException, ServerException, ConflictException {
    String service = null;
    String name = null;
    String privateKey = null;
    String publicKey = null;

    while (formData.hasNext()) {
      FileItem item = formData.next();
      String fieldName = item.getFieldName();
      switch (fieldName) {
        case "service":
          service = item.getString();
          break;
        case "name":
          name = item.getString();
          break;
        case "privateKey":
          privateKey = item.getString();
          break;
        case "publicKey":
          publicKey = item.getString();
          break;
        default:
          // do nothing
      }
    }

    requiredNotNull(service, "Service name required");
    requiredNotNull(name, "Name required");
    if (privateKey == null && publicKey == null) {
      throw new BadRequestException("Key content was not provided.");
    }

    sshManager.createPair(
        new SshPairImpl(getCurrentUserId(), service, name, publicKey, privateKey));

    // We should send 200 response code and body with empty line
    // through specific of html form that doesn't invoke complete submit handler
    return Response.ok("", MediaType.TEXT_HTML).build();
  }

  @POST
  @Consumes(APPLICATION_JSON)
  @GenerateLink(rel = Constants.LINK_REL_CREATE_PAIR)
  @ApiOperation(
      value = "Create a new ssh pair",
      notes =
          "This operation can be performed only by authorized user,"
              + "this user will be the owner of the created ssh pair")
  @ApiResponses({
    @ApiResponse(code = 204, message = "The ssh pair successfully created"),
    @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
    @ApiResponse(
        code = 409,
        message =
            "Conflict error occurred during the ssh pair creation"
                + "(e.g. The Ssh pair with such name and service already exists)"),
    @ApiResponse(code = 500, message = "Internal server error occurred")
  })
  public void createPair(
      @ApiParam(value = "The ssh pair to create", required = true) SshPairDto sshPair)
      throws BadRequestException, ServerException, ConflictException {
    requiredNotNull(sshPair, "Ssh pair required");
    requiredNotNull(sshPair.getService(), "Service name required");
    requiredNotNull(sshPair.getName(), "Name required");
    if (sshPair.getPublicKey() == null && sshPair.getPrivateKey() == null) {
      throw new BadRequestException("Key content was not provided.");
    }

    sshManager.createPair(new SshPairImpl(getCurrentUserId(), sshPair));
  }

  @GET
  @Path("{service}/find")
  @Produces(APPLICATION_JSON)
  @ApiOperation(
      value = "Get the ssh pair by the name of pair and name of service owned by the current user",
      notes = "This operation can be performed only by authorized user.")
  @ApiResponses({
    @ApiResponse(code = 200, message = "The ssh pair successfully fetched"),
    @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
    @ApiResponse(
        code = 404,
        message = "The ssh pair with specified name and service does not exist for current user"),
    @ApiResponse(code = 500, message = "Internal server error occurred")
  })
  public SshPairDto getPair(
      @ApiParam("Name of service") @PathParam("service") String service,
      @ApiParam(value = "Name of ssh pair", required = true) @QueryParam("name") String name)
      throws NotFoundException, ServerException, BadRequestException {
    requiredNotNull(name, "Name of ssh pair");
    return injectLinks(asDto(sshManager.getPair(getCurrentUserId(), service, name)));
  }

  @DELETE
  @Path("{service}")
  @ApiOperation(
      value =
          "Remove the ssh pair by the name of pair and name of service owned by the current user")
  @ApiResponses({
    @ApiResponse(code = 204, message = "The ssh pair successfully removed"),
    @ApiResponse(code = 400, message = "Missed required parameters, parameters are not valid"),
    @ApiResponse(code = 404, message = "The ssh pair doesn't exist"),
    @ApiResponse(code = 500, message = "Internal server error occurred")
  })
  public void removePair(
      @ApiParam("Name of service") @PathParam("service") String service,
      @ApiParam(value = "Name of ssh pair", required = true) @QueryParam("name") String name)
      throws ServerException, NotFoundException, BadRequestException {
    requiredNotNull(name, "Name of ssh pair");
    sshManager.removePair(getCurrentUserId(), service, name);
  }

  @GET
  @Path("{service}")
  @Produces(APPLICATION_JSON)
  @ApiOperation(
      value = "Get the ssh pairs by name of service owned by the current user",
      notes = "This operation can be performed only by authorized user.",
      response = SshPairDto.class,
      responseContainer = "List")
  @ApiResponses({
    @ApiResponse(code = 200, message = "The ssh pairs successfully fetched"),
    @ApiResponse(code = 500, message = "Internal server error occurred")
  })
  public List<SshPairDto> getPairs(
      @ApiParam("Name of service") @PathParam("service") String service) throws ServerException {
    return sshManager
        .getPairs(getCurrentUserId(), service)
        .stream()
        .map(sshPair -> injectLinks(asDto(sshPair)))
        .collect(Collectors.toList());
  }

  private static String getCurrentUserId() {
    return EnvironmentContext.getCurrent().getSubject().getUserId();
  }

  private static SshPairDto asDto(SshPair pair) {
    return newDto(SshPairDto.class)
        .withService(pair.getService())
        .withName(pair.getName())
        .withPublicKey(pair.getPublicKey())
        .withPrivateKey(pair.getPrivateKey());
  }

  private SshPairDto injectLinks(SshPairDto sshPairDto) {
    final UriBuilder uriBuilder = getServiceContext().getServiceUriBuilder();
    final Link getPairsLink =
        LinksHelper.createLink(
            "GET",
            uriBuilder
                .clone()
                .path(getClass(), "getPairs")
                .build(sshPairDto.getService())
                .toString(),
            APPLICATION_JSON,
            LINK_REL_GET_PAIR);

    final Link removePairLink =
        LinksHelper.createLink(
            "DELETE",
            uriBuilder
                .clone()
                .path(getClass(), "removePair")
                .build(sshPairDto.getService(), sshPairDto.getName())
                .toString(),
            APPLICATION_JSON,
            LINK_REL_REMOVE_PAIR);

    final Link getPairLink =
        LinksHelper.createLink(
            "GET",
            uriBuilder
                .clone()
                .path(getClass(), "getPair")
                .build(sshPairDto.getService(), sshPairDto.getName())
                .toString(),
            APPLICATION_JSON,
            LINK_REL_GET_PAIR);

    return sshPairDto.withLinks(Arrays.asList(getPairsLink, removePairLink, getPairLink));
  }

  /**
   * Checks object reference is not {@code null}
   *
   * @param object object reference to check
   * @param subject used as subject of exception message "{subject} required"
   * @throws BadRequestException when object reference is {@code null}
   */
  private void requiredNotNull(Object object, String subject) throws BadRequestException {
    if (object == null) {
      throw new BadRequestException(subject + " required");
    }
  }
}
