/*
  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 Lesser General Public License as published by the Free Software
  Foundation; either version 2.1, 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 Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public License along with
  TALER; see the file COPYING.LGPL.  If not, see
  <http://www.gnu.org/licenses/>
*/
/**
 * @file merchant_api_get_transfers.c
 * @brief Implementation of the GET /transfers request of the merchant's HTTP API
 * @author Marcello Stanisci
 * @author Christian Grothoff
 */
#include "platform.h"
#include <curl/curl.h>
#include <jansson.h>
#include <microhttpd.h> /* just for HTTP status codes */
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_curl_lib.h>
#include "taler_merchant_service.h"
#include "merchant_api_common.h"
#include "merchant_api_curl_defaults.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_signatures.h>


/**
 * @brief A Handle for tracking wire transfers.
 */
struct TALER_MERCHANT_GetTransfersHandle
{

  /**
   * The url for this request.
   */
  char *url;

  /**
   * Handle for the request.
   */
  struct GNUNET_CURL_Job *job;

  /**
   * Function to call with the result.
   */
  TALER_MERCHANT_GetTransfersCallback cb;

  /**
   * Closure for @a cb.
   */
  void *cb_cls;

  /**
   * Reference to the execution context.
   */
  struct GNUNET_CURL_Context *ctx;
};


/**
 * Function called when we're done processing the
 * HTTP GET /transfers request.
 *
 * @param cls the `struct TALER_MERCHANT_GetTransfersHandle`
 * @param response_code HTTP response code, 0 on error
 * @param response response body, NULL if not in JSON
 */
static void
handle_transfers_get_finished (void *cls,
                               long response_code,
                               const void *response)
{
  struct TALER_MERCHANT_GetTransfersHandle *gth = cls;
  const json_t *json = response;
  struct TALER_MERCHANT_GetTransfersResponse gtr = {
    .hr.http_status = (unsigned int) response_code,
    .hr.reply = json
  };

  gth->job = NULL;
  switch (response_code)
  {
  case 0:
    gtr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
    break;
  case MHD_HTTP_OK:
    {
      const json_t *transfers;
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_array_const ("transfers",
                                      &transfers),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (json,
                             spec,
                             NULL, NULL))
      {
        GNUNET_break_op (0);
        gtr.hr.http_status = 0;
        gtr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
        break;
      }

      {
        size_t tds_length;
        struct TALER_MERCHANT_TransferData *tds;
        json_t *transfer;
        size_t i;
        bool ok;

        tds_length = json_array_size (transfers);
        tds = GNUNET_new_array (tds_length,
                                struct TALER_MERCHANT_TransferData);
        ok = true;
        json_array_foreach (transfers, i, transfer) {
          struct TALER_MERCHANT_TransferData *td = &tds[i];
          struct GNUNET_JSON_Specification ispec[] = {
            TALER_JSON_spec_amount_any ("credit_amount",
                                        &td->credit_amount),
            GNUNET_JSON_spec_fixed_auto ("wtid",
                                         &td->wtid),
            TALER_JSON_spec_full_payto_uri ("payto_uri",
                                            &td->payto_uri),
            TALER_JSON_spec_web_url ("exchange_url",
                                     &td->exchange_url),
            GNUNET_JSON_spec_uint64 ("transfer_serial_id",
                                     &td->credit_serial),
            GNUNET_JSON_spec_timestamp ("execution_time",
                                        &td->execution_time),
            GNUNET_JSON_spec_bool ("expected",
                                   &td->expected),
            GNUNET_JSON_spec_end ()
          };

          if (GNUNET_OK !=
              GNUNET_JSON_parse (transfer,
                                 ispec,
                                 NULL, NULL))
          {
            GNUNET_break_op (0);
            ok = false;
            break;
          }
        }

        if (! ok)
        {
          GNUNET_break_op (0);
          GNUNET_free (tds);
          gtr.hr.http_status = 0;
          gtr.hr.ec = TALER_EC_GENERIC_INVALID_RESPONSE;
          break;
        }
        gtr.details.ok.transfers = tds;
        gtr.details.ok.transfers_length = tds_length;
        gth->cb (gth->cb_cls,
                 &gtr);
        GNUNET_free (tds);
        TALER_MERCHANT_transfers_get_cancel (gth);
        return;
      }
    }
  case MHD_HTTP_UNAUTHORIZED:
    gtr.hr.ec = TALER_JSON_get_error_code (json);
    gtr.hr.hint = TALER_JSON_get_error_hint (json);
    /* Nothing really to verify, merchant says we need to authenticate. */
    break;
  case MHD_HTTP_NOT_FOUND:
    /* Nothing really to verify, this should never
       happen, we should pass the JSON reply to the application */
    gtr.hr.ec = TALER_JSON_get_error_code (json);
    gtr.hr.hint = TALER_JSON_get_error_hint (json);
    break;
  case MHD_HTTP_INTERNAL_SERVER_ERROR:
    /* Server had an internal issue; we should retry, but this API
       leaves this to the application */
    gtr.hr.ec = TALER_JSON_get_error_code (json);
    gtr.hr.hint = TALER_JSON_get_error_hint (json);
    break;
  default:
    /* unexpected response code */
    GNUNET_break_op (0);
    TALER_MERCHANT_parse_error_details_ (json,
                                         response_code,
                                         &gtr.hr);
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Unexpected response code %u/%d\n",
                (unsigned int) response_code,
                (int) gtr.hr.ec);
    gtr.hr.http_status = 0;
    break;
  }
  gth->cb (gth->cb_cls,
           &gtr);
  TALER_MERCHANT_transfers_get_cancel (gth);
}


struct TALER_MERCHANT_GetTransfersHandle *
TALER_MERCHANT_transfers_get (
  struct GNUNET_CURL_Context *ctx,
  const char *backend_url,
  struct TALER_FullPayto payto_uri,
  const struct GNUNET_TIME_Timestamp before,
  const struct GNUNET_TIME_Timestamp after,
  int64_t limit,
  uint64_t offset,
  enum TALER_EXCHANGE_YesNoAll expected,
  TALER_MERCHANT_GetTransfersCallback cb,
  void *cb_cls)
{
  struct TALER_MERCHANT_GetTransfersHandle *gth;
  CURL *eh;
  const char *expected_s = NULL;
  char limit_s[30];
  char offset_s[30];
  char before_s[30];
  char after_s[30];

  gth = GNUNET_new (struct TALER_MERCHANT_GetTransfersHandle);
  gth->ctx = ctx;
  gth->cb = cb;
  gth->cb_cls = cb_cls;
  expected_s = TALER_yna_to_string (expected);
  GNUNET_snprintf (limit_s,
                   sizeof (limit_s),
                   "%lld",
                   (long long) limit);
  GNUNET_snprintf (offset_s,
                   sizeof (offset_s),
                   "%lld",
                   (unsigned long long) offset);
  GNUNET_snprintf (before_s,
                   sizeof (before_s),
                   "%llu",
                   (unsigned long long) GNUNET_TIME_timestamp_to_s (before));
  GNUNET_snprintf (after_s,
                   sizeof (after_s),
                   "%llu",
                   (unsigned long long) GNUNET_TIME_timestamp_to_s (after));
  {
    char *enc_payto = TALER_urlencode (payto_uri.full_payto);

    gth->url = TALER_url_join (backend_url,
                               "private/transfers",
                               "payto_uri",
                               enc_payto,
                               "expected",
                               (TALER_EXCHANGE_YNA_ALL != expected)
                               ? expected_s
                               : NULL,
                               "limit",
                               0 != limit
                               ? limit_s
                               : NULL,
                               "offset",
                               ((0 != offset) && (UINT64_MAX != offset))
                               ? offset_s
                               : NULL,
                               "before",
                               GNUNET_TIME_absolute_is_never (before.abs_time)
                               ? NULL
                               : before_s,
                               "after",
                               GNUNET_TIME_absolute_is_zero (after.abs_time)
                               ? NULL
                               : after_s,
                               NULL);
    GNUNET_free (enc_payto);
  }
  if (NULL == gth->url)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Could not construct request URL.\n");
    GNUNET_free (gth);
    return NULL;
  }
  eh = TALER_MERCHANT_curl_easy_get_ (gth->url);
  gth->job = GNUNET_CURL_job_add (ctx,
                                  eh,
                                  &handle_transfers_get_finished,
                                  gth);
  return gth;
}


void
TALER_MERCHANT_transfers_get_cancel (
  struct TALER_MERCHANT_GetTransfersHandle *gth)
{
  if (NULL != gth->job)
  {
    GNUNET_CURL_job_cancel (gth->job);
    gth->job = NULL;
  }
  GNUNET_free (gth->url);
  GNUNET_free (gth);
}


/* end of merchant_api_get_transfers.c */
