/*
  This file is part of TALER
  Copyright (C) 2020, 2023, 2024 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 testing_api_cmd_post_transfers.c
 * @brief command to test POST /transfers
 * @author Christian Grothoff
 */
#include "platform.h"
#include <taler/taler_exchange_service.h>
#include <taler/taler_testing_lib.h>
#include "taler_merchant_service.h"
#include "taler_merchant_testing_lib.h"


/**
 * State of a "POST /transfers" CMD.
 */
struct PostTransfersState
{

  /**
   * Handle for a "POST /transfers" request.
   */
  struct TALER_MERCHANT_PostTransfersHandle *pth;

  /**
   * Handle for a "GET" bank account history request.
   */
  struct TALER_BANK_DebitHistoryHandle *dhh;

  /**
   * The interpreter state.
   */
  struct TALER_TESTING_Interpreter *is;

  /**
   * Base URL of the merchant serving the request.
   */
  const char *merchant_url;

  /**
   * URL of the bank to run history on.
   */
  char *exchange_url;

  /**
   * Credit account of the merchant.
   */
  struct TALER_FullPayto credit_account;

  /**
   * Payto URI to filter on.
   */
  struct TALER_FullPayto payto_uri;

  /**
   * Set to the hash of the @e payto_uri.
   */
  struct TALER_FullPaytoHashP h_payto;

  /**
   * Set to the hash of the normalized @e payto_uri.
   */
  struct TALER_NormalizedPaytoHashP h_normalized_payto;

  /**
   * Authentication details to authenticate to the bank.
   */
  struct TALER_BANK_AuthenticationData auth;

  /**
   * Set once we discovered the WTID.
   */
  struct TALER_WireTransferIdentifierRawP wtid;

  /**
   * the credit amount to look for at @e bank_url.
   */
  struct TALER_Amount credit_amount;

  /**
   * Expected HTTP response code.
   */
  unsigned int http_status;

  /**
   * Array of deposit command labels we expect to see aggregated.
   */
  const char **deposits;

  /**
   * Serial number of the wire transfer in the merchant backend,
   * set by #TALER_TESTING_cmd_merchant_get_transfers(). 0 if unknown.
   */
  uint64_t serial;

  /**
   * Length of @e deposits.
   */
  unsigned int deposits_length;

};


/**
 * Callback for a POST /transfers operation.
 *
 * @param cls closure for this function
 * @param ptr response details
 */
static void
transfers_cb (void *cls,
              const struct TALER_MERCHANT_PostTransfersResponse *ptr)
{
  struct PostTransfersState *pts = cls;

  pts->pth = NULL;
  if (pts->http_status != ptr->hr.http_status)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u (%d) to command %s\n",
                ptr->hr.http_status,
                (int) ptr->hr.ec,
                TALER_TESTING_interpreter_get_current_label (pts->is));
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (pts->is);
    return;
  }
  switch (ptr->hr.http_status)
  {
  case MHD_HTTP_NO_CONTENT:
    break;
  case MHD_HTTP_UNAUTHORIZED:
    break;
  case MHD_HTTP_NOT_FOUND:
    break;
  case MHD_HTTP_GATEWAY_TIMEOUT:
    break;
  default:
    GNUNET_break (0);
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "Unhandled HTTP status %u for POST /transfers.\n",
                ptr->hr.http_status);
  }
  TALER_TESTING_interpreter_next (pts->is);
}


/**
 * Offers information from the POST /transfers CMD state to other
 * commands.
 *
 * @param cls closure
 * @param[out] ret result (could be anything)
 * @param trait name of the trait
 * @param index index number of the object to extract.
 * @return #GNUNET_OK on success
 */
static enum GNUNET_GenericReturnValue
post_transfers_traits (void *cls,
                       const void **ret,
                       const char *trait,
                       unsigned int index)
{
  struct PostTransfersState *pts = cls;
  struct TALER_TESTING_Trait traits[] = {
    TALER_TESTING_make_trait_wtid (&pts->wtid),
    TALER_TESTING_make_trait_credit_payto_uri (&pts->credit_account),
    TALER_TESTING_make_trait_h_full_payto (&pts->h_payto),
    TALER_TESTING_make_trait_h_normalized_payto (&pts->h_normalized_payto),
    TALER_TESTING_make_trait_amount (&pts->credit_amount),
    TALER_TESTING_make_trait_exchange_url (pts->exchange_url),
    TALER_TESTING_make_trait_bank_row (&pts->serial),
    TALER_TESTING_trait_end (),
  };

  return TALER_TESTING_get_trait (traits,
                                  ret,
                                  trait,
                                  index);
}


/**
 * Run the "POST /transfers" CMD. First, get the bank history to find
 * the wtid.
 *
 * @param cls closure.
 * @param cmd command being run now.
 * @param is interpreter state.
 */
static void
post_transfers_run2 (void *cls,
                     const struct TALER_TESTING_Command *cmd,
                     struct TALER_TESTING_Interpreter *is)
{
  struct PostTransfersState *pts = cls;

  pts->is = is;
  pts->pth = TALER_MERCHANT_transfers_post (
    TALER_TESTING_interpreter_get_context (pts->is),
    pts->merchant_url,
    &pts->credit_amount,
    &pts->wtid,
    pts->credit_account,
    pts->exchange_url,
    &transfers_cb,
    pts);
  GNUNET_assert (NULL != pts->pth);
}


/**
 * Callbacks of this type are used to serve the result of asking
 * the bank for the debit transaction history.
 *
 * @param cls closure with a `struct PostTransfersState *`
 * @param reply details from the HTTP response code
 */
static void
debit_cb (
  void *cls,
  const struct TALER_BANK_DebitHistoryResponse *reply)
{
  struct PostTransfersState *pts = cls;

  pts->dhh = NULL;
  switch (reply->http_status)
  {
  case MHD_HTTP_OK:
    /* handled below */
    break;
  case MHD_HTTP_NO_CONTENT:
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (pts->is);
    return;
  default:
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (pts->is);
    return;
  }
  for (unsigned int i = 0; i<reply->details.ok.details_length; i++)
  {
    const struct TALER_BANK_DebitDetails *details
      = &reply->details.ok.details[i];

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Bank reports transfer of %s to %s\n",
                TALER_amount2s (&details->amount),
                details->credit_account_uri.full_payto);
    if (0 != TALER_amount_cmp (&pts->credit_amount,
                               &details->amount))
      continue;
    pts->wtid = details->wtid;
    pts->credit_account.full_payto
      = GNUNET_strdup (details->credit_account_uri.full_payto);
    pts->exchange_url = GNUNET_strdup (details->exchange_base_url);
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Bank transfer found, checking with merchant backend at %s about %s from %s to %s with %s\n",
                pts->merchant_url,
                TALER_amount2s (&pts->credit_amount),
                pts->payto_uri.full_payto,
                pts->exchange_url,
                TALER_B2S (&pts->wtid));
    pts->pth = TALER_MERCHANT_transfers_post (
      TALER_TESTING_interpreter_get_context (pts->is),
      pts->merchant_url,
      &pts->credit_amount,
      &pts->wtid,
      pts->credit_account,
      pts->exchange_url,
      &transfers_cb,
      pts);
    GNUNET_assert (NULL != pts->pth);
    break;
  }
  if (NULL == pts->pth)
  {
    GNUNET_break (0);
    TALER_TESTING_interpreter_fail (pts->is);
    return;
  }
}


/**
 * Run the "POST /transfers" CMD. First, get the bank history to find
 * the wtid.
 *
 * @param cls closure.
 * @param cmd command being run now.
 * @param is interpreter state.
 */
static void
post_transfers_run (void *cls,
                    const struct TALER_TESTING_Command *cmd,
                    struct TALER_TESTING_Interpreter *is)
{
  struct PostTransfersState *pts = cls;

  pts->is = is;
  GNUNET_log (GNUNET_ERROR_TYPE_INFO,
              "Looking for transfer of %s from %s at bank\n",
              TALER_amount2s (&pts->credit_amount),
              pts->payto_uri.full_payto);
  pts->dhh = TALER_BANK_debit_history (TALER_TESTING_interpreter_get_context (
                                         is),
                                       &pts->auth,
                                       UINT64_MAX,
                                       -INT64_MAX,
                                       GNUNET_TIME_UNIT_ZERO,
                                       &debit_cb,
                                       pts);
  GNUNET_assert (NULL != pts->dhh);
}


/**
 * Free the state of a "POST product" CMD, and possibly
 * cancel a pending operation thereof.
 *
 * @param cls closure.
 * @param cmd command being run.
 */
static void
post_transfers_cleanup (void *cls,
                        const struct TALER_TESTING_Command *cmd)
{
  struct PostTransfersState *pts = cls;

  if (NULL != pts->pth)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "POST /transfers operation did not complete\n");
    TALER_MERCHANT_transfers_post_cancel (pts->pth);
  }
  if (NULL != pts->dhh)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                "GET debit history operation did not complete\n");
    TALER_BANK_debit_history_cancel (pts->dhh);
  }
  GNUNET_array_grow (pts->deposits,
                     pts->deposits_length,
                     0);
  GNUNET_free (pts->exchange_url);
  GNUNET_free (pts->credit_account.full_payto);
  GNUNET_free (pts);
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_transfer (
  const char *label,
  const struct TALER_BANK_AuthenticationData *auth,
  struct TALER_FullPayto payto_uri,
  const char *merchant_url,
  const char *credit_amount,
  unsigned int http_code,
  ...)
{
  struct PostTransfersState *pts;

  pts = GNUNET_new (struct PostTransfersState);
  pts->merchant_url = merchant_url;
  pts->auth = *auth;
  pts->payto_uri = payto_uri;
  TALER_full_payto_hash (payto_uri,
                         &pts->h_payto);
  TALER_full_payto_normalize_and_hash (payto_uri,
                                       &pts->h_normalized_payto);
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (credit_amount,
                                         &pts->credit_amount));
  pts->http_status = http_code;
  {
    const char *clabel;
    va_list ap;

    va_start (ap, http_code);
    while (NULL != (clabel = va_arg (ap, const char *)))
    {
      GNUNET_array_append (pts->deposits,
                           pts->deposits_length,
                           clabel);
    }
    va_end (ap);
  }
  {
    struct TALER_TESTING_Command cmd = {
      .cls = pts,
      .label = label,
      .run = &post_transfers_run,
      .cleanup = &post_transfers_cleanup,
      .traits = &post_transfers_traits
    };

    return cmd;
  }
}


struct TALER_TESTING_Command
TALER_TESTING_cmd_merchant_post_transfer2 (
  const char *label,
  const char *merchant_url,
  struct TALER_FullPayto payto_uri,
  const char *credit_amount,
  const char *wtid,
  const char *exchange_url,
  unsigned int http_code)
{
  struct PostTransfersState *pts;

  pts = GNUNET_new (struct PostTransfersState);
  pts->merchant_url = merchant_url;
  pts->credit_account.full_payto
    = GNUNET_strdup (payto_uri.full_payto);
  pts->exchange_url = GNUNET_strdup (exchange_url);
  GNUNET_assert (GNUNET_OK ==
                 TALER_string_to_amount (credit_amount,
                                         &pts->credit_amount));
  if (NULL == wtid)
  {
    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                                &pts->wtid,
                                sizeof (pts->wtid));
  }
  else
  {
    GNUNET_assert (GNUNET_OK ==
                   GNUNET_STRINGS_string_to_data (wtid,
                                                  strlen (wtid),
                                                  &pts->wtid,
                                                  sizeof (pts->wtid)));
  }
  pts->http_status = http_code;
  {
    struct TALER_TESTING_Command cmd = {
      .cls = pts,
      .label = label,
      .run = &post_transfers_run2,
      .cleanup = &post_transfers_cleanup,
      .traits = &post_transfers_traits
    };

    return cmd;
  }
}


void
TALER_TESTING_cmd_merchant_post_transfer_set_serial (
  struct TALER_TESTING_Command *cmd,
  uint64_t serial)
{
  struct PostTransfersState *pts = cmd->cls;

  GNUNET_assert (cmd->run = &post_transfers_run);
  pts->serial = serial;
}


/* end of testing_api_cmd_post_transfers.c */
