/*
  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_post_orders.c
 * @brief Implementation of the POST /orders
 * @author Christian Grothoff
 * @author Marcello Stanisci
 */
#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>
#include <taler/taler_curl_lib.h>


/**
 * @brief A POST /orders Handle
 */
struct TALER_MERCHANT_PostOrdersHandle
{

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

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

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

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

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

  /**
   * Minor context that holds body and headers.
   */
  struct TALER_CURL_PostContext post_ctx;
};


/**
 * Function called when we're done processing the
 * HTTP POST /orders request.
 *
 * @param cls the `struct TALER_MERCHANT_PostOrdersHandle`
 * @param response_code HTTP response code, 0 on error
 * @param response response body, NULL if not JSON
 */
static void
handle_post_order_finished (void *cls,
                            long response_code,
                            const void *response)
{
  struct TALER_MERCHANT_PostOrdersHandle *po = cls;
  const json_t *json = response;

  po->job = NULL;
  TALER_MERCHANT_handle_order_creation_response_ (po->cb,
                                                  po->cb_cls,
                                                  response_code,
                                                  json);
  TALER_MERCHANT_orders_post_cancel (po);
}


struct TALER_MERCHANT_PostOrdersHandle *
TALER_MERCHANT_orders_post (struct GNUNET_CURL_Context *ctx,
                            const char *backend_url,
                            const json_t *order,
                            struct GNUNET_TIME_Relative refund_delay,
                            TALER_MERCHANT_PostOrdersCallback cb,
                            void *cb_cls)
{
  static const char *no_uuids[GNUNET_NZL (0)];

  return TALER_MERCHANT_orders_post2 (ctx,
                                      backend_url,
                                      order,
                                      refund_delay,
                                      NULL,
                                      0,
                                      NULL,
                                      0,
                                      no_uuids,
                                      true,
                                      cb,
                                      cb_cls);
}


struct TALER_MERCHANT_PostOrdersHandle *
TALER_MERCHANT_orders_post2 (
  struct GNUNET_CURL_Context *ctx,
  const char *backend_url,
  const json_t *order,
  struct GNUNET_TIME_Relative refund_delay,
  const char *payment_target,
  unsigned int inventory_products_length,
  const struct TALER_MERCHANT_InventoryProduct inventory_products[],
  unsigned int uuids_length,
  const char *uuids[static uuids_length],
  bool create_token,
  TALER_MERCHANT_PostOrdersCallback cb,
  void *cb_cls)
{
  return TALER_MERCHANT_orders_post3 (
    ctx,
    backend_url,
    order,
    NULL, /* session ID */
    refund_delay,
    payment_target,
    inventory_products_length,
    inventory_products,
    uuids_length,
    uuids,
    create_token,
    cb,
    cb_cls);
}


struct TALER_MERCHANT_PostOrdersHandle *
TALER_MERCHANT_orders_post3 (
  struct GNUNET_CURL_Context *ctx,
  const char *backend_url,
  const json_t *order,
  const char *session_id,
  struct GNUNET_TIME_Relative refund_delay,
  const char *payment_target,
  unsigned int inventory_products_length,
  const struct TALER_MERCHANT_InventoryProduct inventory_products[],
  unsigned int uuids_length,
  const char *uuids[static uuids_length],
  bool create_token,
  TALER_MERCHANT_PostOrdersCallback cb,
  void *cb_cls)
{
  struct TALER_MERCHANT_PostOrdersHandle *po;
  json_t *req;
  CURL *eh;

  po = GNUNET_new (struct TALER_MERCHANT_PostOrdersHandle);
  po->ctx = ctx;
  po->cb = cb;
  po->cb_cls = cb_cls;
  po->url = TALER_url_join (backend_url,
                            "private/orders",
                            NULL);
  req = GNUNET_JSON_PACK (
    GNUNET_JSON_pack_object_incref ("order",
                                    (json_t *) order),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("session_id",
                               session_id)),
    GNUNET_JSON_pack_allow_null (
      GNUNET_JSON_pack_string ("payment_target",
                               payment_target)));
  if (0 != refund_delay.rel_value_us)
  {
    GNUNET_assert (0 ==
                   json_object_set_new (req,
                                        "refund_delay",
                                        GNUNET_JSON_from_time_rel (
                                          refund_delay)));
  }
  if (0 != inventory_products_length)
  {
    json_t *ipa = json_array ();

    GNUNET_assert (NULL != ipa);
    for (unsigned int i = 0; i<inventory_products_length; i++)
    {
      json_t *ip;
      char unit_quantity_buf[64];

      TALER_MERCHANT_format_quantity_string (inventory_products[i].quantity,
                                             inventory_products[i].quantity_frac,
                                             unit_quantity_buf,
                                             sizeof (unit_quantity_buf));

      ip = GNUNET_JSON_PACK (
        GNUNET_JSON_pack_string ("product_id",
                                 inventory_products[i].product_id),
        GNUNET_JSON_pack_string ("unit_quantity",
                                 unit_quantity_buf));
      GNUNET_assert (NULL != ip);
      GNUNET_assert (0 ==
                     json_array_append_new (ipa,
                                            ip));
    }
    GNUNET_assert (0 ==
                   json_object_set_new (req,
                                        "inventory_products",
                                        ipa));
  }
  if (0 != uuids_length)
  {
    json_t *ua = json_array ();

    GNUNET_assert (NULL != ua);
    for (unsigned int i = 0; i<uuids_length; i++)
    {
      json_t *u;

      u = json_string (uuids[i]);
      GNUNET_assert (0 ==
                     json_array_append_new (ua,
                                            u));
    }
    GNUNET_assert (0 ==
                   json_object_set_new (req,
                                        "lock_uuids",
                                        ua));
  }
  if (! create_token)
  {
    GNUNET_assert (0 ==
                   json_object_set_new (req,
                                        "create_token",
                                        json_boolean (create_token)));
  }
  eh = TALER_MERCHANT_curl_easy_get_ (po->url);
  if (GNUNET_OK !=
      TALER_curl_easy_post (&po->post_ctx,
                            eh,
                            req))
  {
    GNUNET_break (0);
    curl_easy_cleanup (eh);
    json_decref (req);
    GNUNET_free (po);
    return NULL;
  }
  json_decref (req);
  po->job = GNUNET_CURL_job_add2 (ctx,
                                  eh,
                                  po->post_ctx.headers,
                                  &handle_post_order_finished,
                                  po);
  return po;
}


void
TALER_MERCHANT_orders_post_cancel (
  struct TALER_MERCHANT_PostOrdersHandle *po)
{
  if (NULL != po->job)
  {
    GNUNET_CURL_job_cancel (po->job);
    po->job = NULL;
  }
  GNUNET_free (po->url);
  TALER_curl_easy_post_finished (&po->post_ctx);
  GNUNET_free (po);
}


/* end of merchant_api_post_orders.c */
