/*
 * Copyright 2019 ConsenSys AG.
 *
 * 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. See the License for the
 * specific language governing permissions and limitations under the License.
 */
package tech.pegasys.ethsigner.core.jsonrpc;

import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;

import tech.pegasys.ethsigner.core.EthSigner;
import tech.pegasys.ethsigner.core.requesthandler.sendtransaction.transaction.TransactionFactory;

import java.math.BigInteger;
import java.util.Optional;

import io.vertx.core.json.DecodeException;
import io.vertx.core.json.JsonObject;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.web3j.utils.Base64String;

public class EeaSendTransactionJsonParametersTest {

  private TransactionFactory factory;

  @BeforeEach
  public void setup() {
    // NOTE: the factory has been configured as per its use in the application.
    factory = new TransactionFactory(EthSigner.createJsonDecoder(), null);
  }

  @Test
  public void transactionStoredInJsonArrayCanBeDecoded() {
    final JsonObject parameters = validEeaTransactionParameters();

    final JsonRpcRequest request = wrapParametersInRequest(parameters);
    final EeaSendTransactionJsonParameters txnParams =
        factory.fromRpcRequestToJsonParam(EeaSendTransactionJsonParameters.class, request);

    assertThat(txnParams.gas()).isEqualTo(getStringAsOptionalBigInteger(parameters, "gas"));
    assertThat(txnParams.gasPrice())
        .isEqualTo(getStringAsOptionalBigInteger(parameters, "gasPrice"));
    assertThat(txnParams.nonce()).isEqualTo(getStringAsOptionalBigInteger(parameters, "nonce"));
    assertThat(txnParams.receiver()).isEqualTo(Optional.of(parameters.getString("to")));
    assertThat(txnParams.value()).isEqualTo(getStringAsOptionalBigInteger(parameters, "value"));
    assertThat(txnParams.privateFrom())
        .isEqualTo(Base64String.wrap(parameters.getString("privateFrom")));
    assertThat(txnParams.privateFor().get())
        .containsExactly(Base64String.wrap(parameters.getJsonArray("privateFor").getString(0)));
    assertThat(txnParams.restriction()).isEqualTo(parameters.getString("restriction"));
  }

  @Test
  public void transactionNotStoredInJsonArrayCanBeDecoded() {
    final JsonObject parameters = validEeaTransactionParameters();

    final JsonRpcRequest request = wrapParametersInRequest(parameters);
    final EeaSendTransactionJsonParameters txnParams =
        factory.fromRpcRequestToJsonParam(EeaSendTransactionJsonParameters.class, request);

    assertThat(txnParams.gas()).isEqualTo(getStringAsOptionalBigInteger(parameters, "gas"));
    assertThat(txnParams.gasPrice())
        .isEqualTo(getStringAsOptionalBigInteger(parameters, "gasPrice"));
    assertThat(txnParams.nonce()).isEqualTo(getStringAsOptionalBigInteger(parameters, "nonce"));
    assertThat(txnParams.receiver()).isEqualTo(Optional.of(parameters.getString("to")));
    assertThat(txnParams.value()).isEqualTo(getStringAsOptionalBigInteger(parameters, "value"));
    assertThat(txnParams.privateFrom())
        .isEqualTo(Base64String.wrap(parameters.getString("privateFrom")));
    assertThat(txnParams.privateFor().get())
        .containsExactly(Base64String.wrap(parameters.getJsonArray("privateFor").getString(0)));
    assertThat(txnParams.restriction()).isEqualTo(parameters.getString("restriction"));
  }

  @Test
  public void transactionWithNonZeroValueFails() {
    final JsonObject parameters = validEeaTransactionParameters();
    parameters.put("value", "0x9184e72a");

    final JsonRpcRequest request = wrapParametersInRequest(parameters);

    assertThatExceptionOfType(DecodeException.class)
        .isThrownBy(
            () ->
                factory.fromRpcRequestToJsonParam(EeaSendTransactionJsonParameters.class, request));
  }

  @Test
  public void transactionWithInvalidPrivateFromThrowsIllegalArgumentException() {
    final JsonObject parameters = validEeaTransactionParameters();
    parameters.put("privateFrom", "invalidThirtyTwoByteData=");

    final JsonRpcRequest request = wrapParametersInRequest(parameters);

    assertThatExceptionOfType(DecodeException.class)
        .isThrownBy(
            () ->
                factory.fromRpcRequestToJsonParam(EeaSendTransactionJsonParameters.class, request));
  }

  @Test
  public void transactionWithInvalidPrivateForThrowsIllegalArgumentException() {
    final JsonObject parameters = validEeaTransactionParameters();
    parameters.put("privateFor", singletonList("invalidThirtyTwoByteData="));

    final JsonRpcRequest request = wrapParametersInRequest(parameters);

    assertThatExceptionOfType(DecodeException.class)
        .isThrownBy(
            () ->
                factory.fromRpcRequestToJsonParam(EeaSendTransactionJsonParameters.class, request));
  }

  @Test
  public void transactionWithInvalidRestrictionCanBeDecoded() {
    final JsonObject parameters = validEeaTransactionParameters();
    parameters.put("restriction", "invalidRestriction");

    final JsonRpcRequest request = wrapParametersInRequest(parameters);
    final EeaSendTransactionJsonParameters txnParams =
        factory.fromRpcRequestToJsonParam(EeaSendTransactionJsonParameters.class, request);

    assertThat(txnParams.restriction()).isEqualTo("invalidRestriction");
  }

  @Test
  public void transactionWithInvalidFromCanBeDecoded() {
    final JsonObject parameters = validEeaTransactionParameters();
    parameters.put("from", "invalidFromAddress");

    final JsonRpcRequest request = wrapParametersInRequest(parameters);
    final EeaSendTransactionJsonParameters txnParams =
        factory.fromRpcRequestToJsonParam(EeaSendTransactionJsonParameters.class, request);

    assertThat(txnParams.sender()).isEqualTo("invalidFromAddress");
  }

  @Test
  public void transactionWithInvalidToCanBeDecoded() {
    final JsonObject parameters = validEeaTransactionParameters();
    parameters.put("to", "invalidToAddress");

    final JsonRpcRequest request = wrapParametersInRequest(parameters);
    final EeaSendTransactionJsonParameters txnParams =
        factory.fromRpcRequestToJsonParam(EeaSendTransactionJsonParameters.class, request);

    assertThat(txnParams.receiver()).contains("invalidToAddress");
  }

  private Optional<BigInteger> getStringAsOptionalBigInteger(
      final JsonObject object, final String key) {
    final String value = object.getString(key);
    return Optional.of(new BigInteger(value.substring(2), 16));
  }

  private JsonObject validEeaTransactionParameters() {
    final JsonObject parameters = new JsonObject();
    parameters.put("from", "0xb60e8dd61c5d32be8058bb8eb970870f07233155");
    parameters.put("to", "0xd46e8dd67c5d32be8058bb8eb970870f07244567");
    parameters.put("nonce", "0x1");
    parameters.put("gas", "0x76c0");
    parameters.put("gasPrice", "0x9184e72a000");
    parameters.put("value", "0x0");
    parameters.put(
        "data",
        "0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675");
    parameters.put("privateFrom", "ZlapEsl9qDLPy/e88+/6yvCUEVIvH83y0N4A6wHuKXI=");
    parameters.put("privateFor", singletonList("GV8m0VZAccYGAAYMBuYQtKEj0XtpXeaw2APcoBmtA2w="));
    parameters.put("restriction", "restricted");

    return parameters;
  }

  private <T> JsonRpcRequest wrapParametersInRequest(final T parameters) {
    final JsonObject input = new JsonObject();
    input.put("jsonrpc", 2.0);
    input.put("method", "mine");
    input.put("params", parameters);

    return input.mapTo(JsonRpcRequest.class);
  }
}