/*
 * Copyright (C) 2018 LinkedIn Corp. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use
 * this file except in compliance with the License. You may obtain a copy of the
 * License at  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed
 * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
 * CONDITIONS OF ANY KIND, either express or implied.
 */
package azkaban.crypto;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

/**
 * Crypto class that actually delegates to version specific implementation of ICrypto interface. In
 * other words, it's a factory method class that implements ICrypto interface
 */
public class Crypto implements ICrypto {

  private static final ObjectMapper MAPPER = new ObjectMapper();
  private final Map<Version, ICrypto> cryptos;

  public Crypto() {
    this.cryptos = ImmutableMap.<Version, ICrypto>builder()
        .put(Version.V1_0, new CryptoV1())
        .put(Version.V1_1, new CryptoV1_1())
        .build();
  }

  public static String encode(final String s) {
    return Base64.getEncoder().encodeToString(s.getBytes(StandardCharsets.UTF_8));
  }

  public static String decode(final String s) {
    return new String(Base64.getDecoder().decode(s), StandardCharsets.UTF_8);
  }

  @Override
  public String encrypt(final String plaintext, final String passphrase,
      final Version cryptoVersion) {
    Preconditions.checkNotNull(cryptoVersion, "Crypto version is required.");
    Preconditions.checkArgument(!StringUtils.isEmpty(plaintext), "plaintext should not be empty");
    Preconditions.checkArgument(!StringUtils.isEmpty(passphrase), "passphrase should not be empty");

    final ICrypto crypto = this.cryptos.get(cryptoVersion);
    Preconditions.checkNotNull(crypto, cryptoVersion + " is not supported.");
    return crypto.encrypt(plaintext, passphrase, cryptoVersion);
  }

  @Override
  public String decrypt(final String cipheredText, final String passphrase) {
    Preconditions
        .checkArgument(!StringUtils.isEmpty(cipheredText), "cipheredText should not be empty");
    Preconditions.checkArgument(!StringUtils.isEmpty(passphrase), "passphrase should not be empty");

    try {
      final String jsonStr = decode(cipheredText);
      final JsonNode json = MAPPER.readTree(jsonStr);
      final String ver = json.get(VERSION_IDENTIFIER).asText();

      final ICrypto crypto = this.cryptos.get(Version.fromVerString(ver));
      Preconditions.checkNotNull(crypto);
      return crypto.decrypt(cipheredText, passphrase);
    } catch (final Exception e) {
      throw new RuntimeException(e);
    }
  }
}