/*
  This file is part of TALER
  Copyright (C) 2018, 2020, 2022 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_wallet_get_order.c
 * @brief Implementation of the GET /orders/$ID request
 * @author Christian Grothoff
 * @author Marcello Stanisci
 * @author Florian Dold
 */
#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_curl_defaults.h"
#include "merchant_api_common.h"
#include <taler/taler_json_lib.h>
#include <taler/taler_signatures.h>


/**
 * @brief A GET /orders/$ID handle
 */
struct TALER_MERCHANT_OrderWalletGetHandle
{

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

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

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

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

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


/**
 * Convenience function to call the callback in @a owgh with an error code of
 * @a ec and the exchange body being set to @a reply.
 *
 * @param owgh handle providing callback
 * @param ec error code to return to application
 * @param reply JSON reply we got from the exchange, can be NULL
 */
static void
cb_failure (struct TALER_MERCHANT_OrderWalletGetHandle *owgh,
            enum TALER_ErrorCode ec,
            const json_t *reply)
{
  struct TALER_MERCHANT_OrderWalletGetResponse owgr = {
    .hr.ec = ec,
    .hr.reply = reply
  };

  owgh->cb (owgh->cb_cls,
            &owgr);
}


/**
 * Function called when we're done processing the GET /check-payment request.
 *
 * @param cls the `struct TALER_MERCHANT_OrderWalletGetHandle`
 * @param response_code HTTP response code, 0 on error
 * @param response response body, should be NULL
 */
static void
handle_wallet_get_order_finished (void *cls,
                                  long response_code,
                                  const void *response)
{
  struct TALER_MERCHANT_OrderWalletGetHandle *owgh = cls;
  const json_t *json = response;

  owgh->job = NULL;
  switch (response_code)
  {
  case MHD_HTTP_OK:
    {
      struct TALER_MERCHANT_OrderWalletGetResponse owgr = {
        .hr.reply = json,
        .hr.http_status = MHD_HTTP_OK
      };
      struct GNUNET_JSON_Specification spec[] = {
        GNUNET_JSON_spec_bool ("refunded",
                               &owgr.details.ok.refunded),
        GNUNET_JSON_spec_bool ("refund_pending",
                               &owgr.details.ok.refund_pending),
        TALER_JSON_spec_amount_any ("refund_amount",
                                    &owgr.details.ok.refund_amount),
        GNUNET_JSON_spec_end ()
      };

      if (GNUNET_OK !=
          GNUNET_JSON_parse (json,
                             spec,
                             NULL, NULL))
      {
        GNUNET_break_op (0);
        cb_failure (owgh,
                    TALER_EC_GENERIC_REPLY_MALFORMED,
                    json);
        TALER_MERCHANT_wallet_order_get_cancel (owgh);
        return;
      }
      owgh->cb (owgh->cb_cls,
                &owgr);
      GNUNET_JSON_parse_free (spec);
      break;
    }
  case MHD_HTTP_PAYMENT_REQUIRED:
    {
      struct TALER_MERCHANT_OrderWalletGetResponse owgr = {
        .hr.reply = json,
        .hr.http_status = MHD_HTTP_PAYMENT_REQUIRED
      };

      /* Status is: unpaid */
      owgr.details.payment_required.taler_pay_uri
        = json_string_value (json_object_get (json,
                                              "taler_pay_uri"));
      owgr.details.payment_required.already_paid_order_id
        = json_string_value (json_object_get (json,
                                              "already_paid_order_id"));
      if (NULL == owgr.details.payment_required.taler_pay_uri)
      {
        GNUNET_break_op (0);
        cb_failure (owgh,
                    TALER_EC_GENERIC_REPLY_MALFORMED,
                    json);
        break;
      }
      owgh->cb (owgh->cb_cls,
                &owgr);
      break;
    }
  default:
    {
      struct TALER_MERCHANT_OrderWalletGetResponse owgr = {
        .hr.reply = json,
        .hr.http_status = response_code
      };

      TALER_MERCHANT_parse_error_details_ (response,
                                           response_code,
                                           &owgr.hr);
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Checking order status failed with HTTP status code %u/%d\n",
                  (unsigned int) response_code,
                  (int) owgr.hr.ec);
      GNUNET_break_op (0);
      owgh->cb (owgh->cb_cls,
                &owgr);
      break;
    }
  }
  TALER_MERCHANT_wallet_order_get_cancel (owgh);
}


struct TALER_MERCHANT_OrderWalletGetHandle *
TALER_MERCHANT_wallet_order_get (
  struct GNUNET_CURL_Context *ctx,
  const char *backend_url,
  const char *order_id,
  const struct TALER_PrivateContractHashP *h_contract,
  struct GNUNET_TIME_Relative timeout,
  const char *session_id,
  const struct TALER_Amount *min_refund,
  bool await_refund_obtained,
  TALER_MERCHANT_OrderWalletGetCallback cb,
  void *cb_cls)
{
  struct TALER_MERCHANT_OrderWalletGetHandle *owgh;
  unsigned int tms;

  GNUNET_assert (NULL != backend_url);
  GNUNET_assert (NULL != order_id);
  owgh = GNUNET_new (struct TALER_MERCHANT_OrderWalletGetHandle);
  owgh->ctx = ctx;
  owgh->cb = cb;
  owgh->cb_cls = cb_cls;
  tms = (unsigned int) (timeout.rel_value_us
                        / GNUNET_TIME_UNIT_MILLISECONDS.rel_value_us);
  {
    char timeout_ms[32];
    struct GNUNET_CRYPTO_HashAsciiEncoded h_contract_s;
    char *path;

    GNUNET_CRYPTO_hash_to_enc (&h_contract->hash,
                               &h_contract_s);
    GNUNET_snprintf (timeout_ms,
                     sizeof (timeout_ms),
                     "%u",
                     tms);
    GNUNET_asprintf (&path,
                     "orders/%s",
                     order_id);
    owgh->url = TALER_url_join (backend_url,
                                path,
                                "h_contract",
                                h_contract_s.encoding,
                                "session_id",
                                session_id,
                                "timeout_ms",
                                (0 != tms)
                                ? timeout_ms
                                : NULL,
                                "refund",
                                (NULL != min_refund)
                                ? TALER_amount2s (min_refund)
                                : NULL,
                                "await_refund_obtained",
                                await_refund_obtained
                                ? "yes"
                                : NULL,
                                NULL);
    GNUNET_free (path);
  }
  if (NULL == owgh->url)
  {
    GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
                "Could not construct request URL.\n");
    GNUNET_free (owgh);
    return NULL;
  }

  {
    CURL *eh;

    eh = TALER_MERCHANT_curl_easy_get_ (owgh->url);
    if (0 != tms)
    {
      GNUNET_break (CURLE_OK ==
                    curl_easy_setopt (eh,
                                      CURLOPT_TIMEOUT_MS,
                                      (long) (tms + 100L)));
    }

    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Checking order status at %s\n",
                owgh->url);
    if (NULL == (owgh->job =
                   GNUNET_CURL_job_add (ctx,
                                        eh,
                                        &handle_wallet_get_order_finished,
                                        owgh)))
    {
      GNUNET_break (0);
      GNUNET_free (owgh->url);
      GNUNET_free (owgh);
      return NULL;
    }
  }
  return owgh;
}


void
TALER_MERCHANT_wallet_order_get_cancel (
  struct TALER_MERCHANT_OrderWalletGetHandle *owgh)
{
  if (NULL != owgh->job)
  {
    GNUNET_CURL_job_cancel (owgh->job);
    owgh->job = NULL;
  }
  GNUNET_free (owgh->url);
  GNUNET_free (owgh);
}


/* end of merchant_api_wallet_get_order.c */
