/**
 * This file is part of TALER
 * Copyright (C) 2014-2023 Taler Systems SA
 *
 * TALER is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 3, or
 * (at your option) any later version.
 *
 * TALER is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with TALER; see the file COPYING.  If not, see
 * <http://www.gnu.org/licenses/>
 */
/**
 * @file test_merchant_api_twisted.c
 * @brief testcase to test exchange's HTTP API interface
 * @author Sree Harsha Totakura <sreeharsha@totakura.in>
 * @author Christian Grothoff
 * @author Marcello Stanisci
 * @author Florian Dold <dold@taler.net>
 */

#include "platform.h"
#include <taler/taler_util.h>
#include <taler/taler_signatures.h>
#include <taler/taler_exchange_service.h>
#include <taler/taler_json_lib.h>
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_testing_lib.h>
#include <microhttpd.h>
#include <taler/taler_bank_service.h>
#include <taler/taler_fakebank_lib.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_testing_lib.h"
#include <taler/taler_twister_testing_lib.h>
#include <taler/taler_twister_service.h>

/**
 * Configuration file we use.  One (big) configuration is used
 * for the various components for this test.
 */
static char *config_file;

/**
 * Account number of the exchange at the bank.
 */
#define EXCHANGE_ACCOUNT_NAME "2"

/**
 * Account number of the merchant at the bank.
 */
#define MERCHANT_ACCOUNT_NAME "3"

/**
 * Account number of some user.
 */
#define USER_ACCOUNT_NAME "62"

/**
 * Configuration file for the proxy between merchant and
 * exchange.  Not used directly here in the code (instead
 * used in the merchant config), but kept around for consistency.
 */
#define PROXY_EXCHANGE_config_file \
        "test_merchant_api_proxy_exchange.conf"

/**
 * Configuration file for the proxy between "lib" and merchant.
 */
#define PROXY_MERCHANT_config_file \
        "test_merchant_api_proxy_merchant.conf"

/**
 * Exchange base URL.  Could also be taken from config.
 */
#define EXCHANGE_URL "http://localhost:8081/"

/**
 * Twister URL that proxies the exchange.
 */
static char *twister_exchange_url;

/**
 * Twister URL that proxies the merchant.
 */
static char *twister_merchant_url;

/**
 * Twister URL that proxies the merchant.
 */
static char *twister_merchant_url_instance_nonexistent;

/**
 * Twister URL that proxies the merchant.
 */
static char *twister_merchant_url_instance_tor;

/**
 * Merchant base URL.
 */
static const char *merchant_url;

/**
 * Twister process that proxies the exchange.
 */
static struct GNUNET_OS_Process *twisterexchanged;

/**
 * Twister process that proxies the merchant.
 */
static struct GNUNET_OS_Process *twistermerchantd;

static struct TALER_FullPayto payer_payto;
static struct TALER_FullPayto exchange_payto;
static struct TALER_FullPayto merchant_payto;

static struct TALER_TESTING_Credentials cred;

/**
 * User name. Never checked by fakebank.
 */
#define USER_LOGIN_NAME "user42"

/**
 * User password. Never checked by fakebank.
 */
#define USER_LOGIN_PASS "pass42"

/**
 * Execute the taler-exchange-wirewatch command with
 * our configuration file.
 *
 * @param label label to use for the command.
 */
static struct TALER_TESTING_Command
CMD_EXEC_WIREWATCH (const char *label)
{
  return TALER_TESTING_cmd_exec_wirewatch (label, config_file);
}


/**
 * Execute the taler-exchange-aggregator, closer and transfer commands with
 * our configuration file.
 *
 * @param label label to use for the command.
 */
#define CMD_EXEC_AGGREGATOR(label) \
        TALER_TESTING_cmd_exec_aggregator (label "-aggregator", config_file), \
        TALER_TESTING_cmd_exec_transfer (label "-transfer", config_file)


/**
 * Run wire transfer of funds from some user's account to the
 * exchange.
 *
 * @param label label to use for the command.
 * @param amount amount to transfer, i.e. "EUR:1"
 * @param url exchange_url
 */
static struct TALER_TESTING_Command
CMD_TRANSFER_TO_EXCHANGE (const char *label,
                          const char *amount)
{
  return TALER_TESTING_cmd_admin_add_incoming (label,
                                               amount,
                                               &cred.ba,
                                               payer_payto);
}


/**
 * Main function that will tell the interpreter what commands to
 * run.
 *
 * @param cls closure
 */
static void
run (void *cls,
     struct TALER_TESTING_Interpreter *is)
{
  /****** Covering /pay *******/
  struct TALER_TESTING_Command pay[] = {
    /**
     * Move money to the exchange's bank account.
     */
    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-abort-1",
                              "EUR:1.01"),

    /**
     * Make a reserve exist, according to the previous transfer.
     */
    CMD_EXEC_WIREWATCH ("wirewatch-abort-1"),
    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-abort-1",
                                                 "EUR:1.01",
                                                 payer_payto,
                                                 exchange_payto,
                                                 "create-reserve-abort-1"),
    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-abort-1",
                                       "create-reserve-abort-1",
                                       "EUR:1",
                                       0,
                                       MHD_HTTP_OK),
    TALER_TESTING_cmd_status ("withdraw-status-abort-1",
                              "create-reserve-abort-1",
                              "EUR:0",
                              MHD_HTTP_OK),
    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-abort-1",
                                            cred.cfg,
                                            twister_merchant_url,
                                            MHD_HTTP_OK,
                                            "abort-one",
                                            GNUNET_TIME_UNIT_ZERO_TS,
                                            GNUNET_TIME_UNIT_FOREVER_TS,
                                            "EUR:3.0"),
    /* Will only pay _half_ the supposed price,
     * so we'll then have the right to abort.  */
    TALER_TESTING_cmd_merchant_pay_order ("deposit-simple-for-abort",
                                          twister_merchant_url,
                                          MHD_HTTP_BAD_REQUEST,
                                          "create-proposal-abort-1",
                                          "withdraw-coin-abort-1",
                                          "EUR:1.01",
                                          "EUR:1.00",
                                          NULL), // no sense now
    TALER_TESTING_cmd_delete_object ("hack-abort-1",
                                     PROXY_MERCHANT_config_file,
                                     "merchant_pub"),
    TALER_TESTING_cmd_merchant_order_abort ("pay-abort-1",
                                            twister_merchant_url,
                                            "deposit-simple-for-abort",
                                            MHD_HTTP_OK),
    TALER_TESTING_cmd_delete_object ("hack-abort-2",
                                     PROXY_MERCHANT_config_file,
                                     "refund_permissions.0.rtransaction_id"),
    TALER_TESTING_cmd_merchant_order_abort ("pay-abort-2",
                                            twister_merchant_url,
                                            "deposit-simple-for-abort",
                                            MHD_HTTP_OK),
    TALER_TESTING_cmd_modify_object_dl ("hack-abort-3",
                                        PROXY_MERCHANT_config_file,
                                        "refund_permissions.0.coin_pub",
                                        /* dummy coin.  */
                                        "8YX10E41ZWHX0X2RK4XFAXB2D3M05M1HNG14ZFZZB8M7SA4QCKCG"),
    TALER_TESTING_cmd_merchant_order_abort ("pay-abort-3",
                                            twister_merchant_url,
                                            "deposit-simple-for-abort",
                                            MHD_HTTP_OK),
    TALER_TESTING_cmd_flip_download ("hack-abort-4",
                                     PROXY_MERCHANT_config_file,
                                     "refund_permissions.0.merchant_sig"),
    TALER_TESTING_cmd_merchant_order_abort ("pay-abort-4",
                                            twister_merchant_url,
                                            "deposit-simple-for-abort",
                                            MHD_HTTP_OK),
    /* just malforming the response.  */
    TALER_TESTING_cmd_malform_response ("malform-abortion",
                                        PROXY_MERCHANT_config_file),
    TALER_TESTING_cmd_merchant_order_abort ("pay-abort-5",
                                            twister_merchant_url,
                                            "deposit-simple-for-abort",
                                            0),
    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-double-spend",
                              "EUR:1.01"),
    CMD_EXEC_WIREWATCH ("wirewatch-double-spend"),
    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-double-spend",
                                            cred.cfg,
                                            twister_merchant_url,
                                            MHD_HTTP_OK,
                                            "DS-1",
                                            GNUNET_TIME_UNIT_ZERO_TS,
                                            GNUNET_TIME_UNIT_FOREVER_TS,
                                            "EUR:1.0"),
    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-double-spend-1",
                                            cred.cfg,
                                            twister_merchant_url,
                                            MHD_HTTP_OK,
                                            "DS-2",
                                            GNUNET_TIME_UNIT_ZERO_TS,
                                            GNUNET_TIME_UNIT_FOREVER_TS,
                                            "EUR:3.0"),

    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-double-spend",
                                       "create-reserve-double-spend",
                                       "EUR:1",
                                       0,
                                       MHD_HTTP_OK),
    TALER_TESTING_cmd_merchant_pay_order ("deposit-simple-ok",
                                          twister_merchant_url,
                                          MHD_HTTP_OK,
                                          "create-proposal-double-spend",
                                          "withdraw-coin-double-spend",
                                          "EUR:1.01",
                                          "EUR:1.00",
                                          NULL), // no sense now
    TALER_TESTING_cmd_flip_download ("hack-coin-history",
                                     PROXY_MERCHANT_config_file,
                                     "history.0.coin_sig"),
    /* Coin history check will fail,
     * due to coin's bad signature.  */
    TALER_TESTING_cmd_merchant_pay_order ("deposit-simple-fail",
                                          twister_merchant_url,
                                          MHD_HTTP_CONFLICT,
                                          "create-proposal-double-spend-1",
                                          "withdraw-coin-double-spend",
                                          "EUR:1.01",
                                          "EUR:1.00",
                                          NULL), // no sense now
    /* max uint64 number: 9223372036854775807; try to overflow! */
    TALER_TESTING_cmd_end ()
  };

  struct TALER_TESTING_Command commands[] = {
    /* general setup */
    TALER_TESTING_cmd_run_fakebank ("run-fakebank",
                                    cred.cfg,
                                    "exchange-account-exchange"),
    TALER_TESTING_cmd_system_start ("start-taler",
                                    config_file,
                                    "-ema",
                                    "-u", "exchange-account-exchange",
                                    "-r", "merchant-exchange-test",
                                    NULL),
    TALER_TESTING_cmd_get_exchange ("get-exchange",
                                    cred.cfg,
                                    NULL,
                                    true,
                                    true),
    TALER_TESTING_cmd_merchant_post_instances ("instance-create-admin",
                                               twister_merchant_url,
                                               "admin",
                                               MHD_HTTP_NO_CONTENT),
    TALER_TESTING_cmd_merchant_post_account (
      "instance-create-default-account",
      twister_merchant_url,
      merchant_payto,
      NULL, NULL,
      MHD_HTTP_OK),
    TALER_TESTING_cmd_batch ("pay",
                             pay),
    /* Malform the response from the exchange. */
    /**
     * Move money to the exchange's bank account.
     */
    CMD_TRANSFER_TO_EXCHANGE ("create-reserve-1",
                              "EUR:10.02"),
    /**
     * Make a reserve exist,
     * according to the previous
     * transfer.
     */
    CMD_EXEC_WIREWATCH ("wirewatch-1"),
    TALER_TESTING_cmd_check_bank_admin_transfer ("check_bank_transfer-2",
                                                 "EUR:10.02",
                                                 payer_payto,
                                                 exchange_payto,
                                                 "create-reserve-1"),
    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-1",
                                       "create-reserve-1",
                                       "EUR:5",
                                       0,
                                       MHD_HTTP_OK),
    TALER_TESTING_cmd_withdraw_amount ("withdraw-coin-2",
                                       "create-reserve-1",
                                       "EUR:5",
                                       0,
                                       MHD_HTTP_OK),

    TALER_TESTING_cmd_merchant_post_orders_no_claim (
      "create-proposal-1",
      merchant_url,
      MHD_HTTP_OK,
      "1",
      GNUNET_TIME_UNIT_ZERO_TS,
      GNUNET_TIME_UNIT_FOREVER_TS,
      "EUR:6.0"),
    TALER_TESTING_cmd_flip_upload ("hack-claim-token",
                                   PROXY_MERCHANT_config_file,
                                   "token"),
    TALER_TESTING_cmd_merchant_claim_order (
      "claim-1-incorrect-claim-token",
      twister_merchant_url,
      MHD_HTTP_NOT_FOUND,
      "create-proposal-1",
      NULL),
    TALER_TESTING_cmd_merchant_post_orders ("create-proposal-2",
                                            cred.cfg,
                                            merchant_url,
                                            MHD_HTTP_OK,
                                            "2",
                                            GNUNET_TIME_UNIT_ZERO_TS,
                                            GNUNET_TIME_UNIT_FOREVER_TS,
                                            "EUR:6.0"),
    TALER_TESTING_cmd_merchant_pay_order ("deposit-2",
                                          merchant_url,
                                          MHD_HTTP_BAD_REQUEST,
                                          "create-proposal-2",
                                          "withdraw-coin-1",
                                          "EUR:5",
                                          "EUR:4.99",
                                          NULL),
    TALER_TESTING_cmd_merchant_order_abort ("pay-abort-1",
                                            merchant_url,
                                            "deposit-2",
                                            MHD_HTTP_OK),
    TALER_TESTING_cmd_end ()
  };

  TALER_TESTING_run (is,
                     commands);
}


/**
 * Kill, wait, and destroy convenience function.
 *
 * @param process process to purge.
 */
static void
purge_process (struct GNUNET_OS_Process *process)
{
  GNUNET_OS_process_kill (process,
                          SIGINT);
  GNUNET_OS_process_wait (process);
  GNUNET_OS_process_destroy (process);
}


int
main (int argc,
      char *const *argv)
{
  int ret;

  {
    char *cipher;

    cipher = GNUNET_STRINGS_get_suffix_from_binary_name (argv[0]);
    GNUNET_assert (NULL != cipher);
    GNUNET_asprintf (&config_file,
                     "test_merchant_api_twisted-%s.conf",
                     cipher);
    GNUNET_free (cipher);
  }
  payer_payto.full_payto =
    (char *) "payto://x-taler-bank/localhost/" USER_ACCOUNT_NAME
    "?receiver-name=" USER_ACCOUNT_NAME;
  exchange_payto.full_payto =
    (char *) "payto://x-taler-bank/localhost/" EXCHANGE_ACCOUNT_NAME
    "?receiver-name=" EXCHANGE_ACCOUNT_NAME;
  merchant_payto.full_payto =
    (char *) "payto://x-taler-bank/localhost/" MERCHANT_ACCOUNT_NAME
    "?receiver-name=" MERCHANT_ACCOUNT_NAME;
  merchant_url = "http://localhost:8080/";
  if (NULL == (twister_exchange_url = TALER_TWISTER_prepare_twister (
                 PROXY_EXCHANGE_config_file)))
    return 77;

  if (NULL == (twister_merchant_url = TALER_TWISTER_prepare_twister (
                 PROXY_MERCHANT_config_file)))
    return 77;
  twister_merchant_url_instance_nonexistent = TALER_url_join (
    twister_merchant_url, "instances/foo/", NULL);
  twister_merchant_url_instance_tor = TALER_url_join (
    twister_merchant_url, "instances/tor/", NULL);
  if (NULL == (twisterexchanged = TALER_TWISTER_run_twister
                                    (PROXY_EXCHANGE_config_file)))
    return 77;
  if (NULL == (twistermerchantd = TALER_TWISTER_run_twister
                                    (PROXY_MERCHANT_config_file)))
    return 77;
  ret = TALER_TESTING_main (argv,
                            "INFO",
                            config_file,
                            "exchange-account-exchange",
                            TALER_TESTING_BS_FAKEBANK,
                            &cred,
                            &run,
                            NULL);
  purge_process (twisterexchanged);
  purge_process (twistermerchantd);
  return ret;
}


/* end of test_merchant_api_twisted.c */
