/*
 * Copyright 2018 Nordnet Bank AB
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is furnished to do
 * so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import java.util.Base64;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;

import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;

public class SimpleRestClient {

  private final WebTarget baseResource;
  private static final MediaType responseType = MediaType.APPLICATION_JSON_TYPE;

  public SimpleRestClient() {
    baseResource = ClientBuilder.newClient().target(Main.URL + Main.API_VERSION);
  }

  public JsonNode httpRequestNoParameter(String req) throws IOException {
    return new ObjectMapper()
        .readTree(baseResource.path(req).request(responseType).get(String.class));
  }

  public JsonNode login(String user, String password)
      throws InvalidKeyException, NoSuchAlgorithmException, InvalidKeySpecException,
          NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException, IOException {

    String authParameters = encryptAuthParameter(user, password);

    // Send login request
    Response response =
        baseResource
            .path("login")
            .queryParam("service", Main.SERVICE)
            .queryParam("auth", authParameters)
            .request(responseType)
            .post(null);

    ObjectNode json = response.readEntity(ObjectNode.class);

    JsonNode node = new ObjectMapper().readTree(json.toString());

    String sessionKey = json.get("session_key").asText();

    // Add the session key to basic auth for all calls
    baseResource.register(HttpAuthenticationFeature.basic(sessionKey, sessionKey));

    return node;
  }

  public String logout(String sessionKey) {
    return baseResource.path("login").path(sessionKey).request(responseType).delete(String.class);
  }

  private String encryptAuthParameter(String user, String password)
      throws NoSuchAlgorithmException, InvalidKeySpecException, IOException, NoSuchPaddingException,
          InvalidKeyException, IllegalBlockSizeException, BadPaddingException {

    // Construct the base for the auth parameter
    String login =
        Base64.getEncoder().encodeToString(user.getBytes())
            + ":"
            + Base64.getEncoder().encodeToString(password.getBytes())
            + ":"
            + Base64.getEncoder()
                .encodeToString(String.valueOf(System.currentTimeMillis()).getBytes());

    // RSA encrypt it using NNAPI public key
    PublicKey pubKey = getKeyFromPEM(Main.PUBLIC_KEY_FILENAME);
    Cipher cipher = Cipher.getInstance("RSA");
    cipher.init(Cipher.ENCRYPT_MODE, pubKey);

    byte[] encryptedBytes = cipher.doFinal(login.getBytes("UTF-8"));

    // Encode the encrypted data in Base64
    String encodedEncryptedBytes = Base64.getEncoder().encodeToString(encryptedBytes);

    return URLEncoder.encode(encodedEncryptedBytes, "UTF-8");
  }

  private static PublicKey getKeyFromPEM(String filename)
      throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
    BufferedReader reader = null;
    try {
      reader = new BufferedReader(new FileReader(filename));
      String line = null;
      String key = "";
      while (true) {
        line = reader.readLine();
        if (line == null) break;
        else if (line.startsWith("-----BEGIN PUBLIC KEY-----")) continue;
        else if (line.startsWith("-----END PUBLIC KEY-----")) continue;
        else key += line.trim();
      }
      byte[] binary = Base64.getDecoder().decode(key);
      X509EncodedKeySpec spec = new X509EncodedKeySpec(binary);
      KeyFactory kf = KeyFactory.getInstance("RSA");
      return kf.generatePublic(spec);
    } finally {
      if (reader != null) {
        reader.close();
      }
    }
  }
}