/*
   This file is part of TALER
   Copyright (C) 2022 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 backenddb/pg_insert_transfer_details.c
 * @brief Implementation of the insert_transfer_details function for Postgres
 * @author Christian Grothoff
 */
#include "platform.h"
#include <taler/taler_error_codes.h>
#include <taler/taler_dbevents.h>
#include <taler/taler_pq_lib.h>
#include "pg_insert_transfer_details.h"
#include "pg_helper.h"


/**
 * How often do we re-try if we run into a DB serialization error?
 */
#define MAX_RETRIES 3


enum GNUNET_DB_QueryStatus
TMH_PG_insert_transfer_details (
  void *cls,
  const char *instance_id,
  const char *exchange_url,
  struct TALER_FullPayto payto_uri,
  const struct TALER_WireTransferIdentifierRawP *wtid,
  const struct TALER_EXCHANGE_TransferData *td)
{
  struct PostgresClosure *pg = cls;
  unsigned int len = td->details_length;
  struct TALER_Amount coin_values[GNUNET_NZL (len)];
  struct TALER_Amount deposit_fees[GNUNET_NZL (len)];
  const struct TALER_CoinSpendPublicKeyP *coin_pubs[GNUNET_NZL (len)];
  const struct TALER_PrivateContractHashP *contract_terms[GNUNET_NZL (len)];
  enum GNUNET_DB_QueryStatus qs;
  bool duplicate;

  for (unsigned int i = 0; i<len; i++)
  {
    const struct TALER_TrackTransferDetails *tdd = &td->details[i];

    coin_values[i] = tdd->coin_value;
    deposit_fees[i] = tdd->coin_fee;
    coin_pubs[i] = &tdd->coin_pub;
    contract_terms[i] = &tdd->h_contract_terms;
  }

  check_connection (pg);
  PREPARE (pg,
           "insert_transfer_details",
           "SELECT"
           " out_no_instance"
           ",out_no_account"
           ",out_no_exchange"
           ",out_duplicate"
           ",out_conflict"
           " FROM merchant_do_insert_transfer_details"
           " ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13);");

  for (unsigned int retries = 0;
       retries < MAX_RETRIES;
       retries++)
  {
    if (GNUNET_OK !=
        TMH_PG_start (pg,
                      "insert transfer details"))
    {
      GNUNET_break (0);
      return GNUNET_DB_STATUS_HARD_ERROR;
    }

    {
      struct GNUNET_PQ_QueryParam params[] = {
        GNUNET_PQ_query_param_string (instance_id),
        GNUNET_PQ_query_param_string (exchange_url),
        GNUNET_PQ_query_param_string (payto_uri.full_payto),
        GNUNET_PQ_query_param_auto_from_type (wtid),
        GNUNET_PQ_query_param_timestamp (&td->execution_time),
        GNUNET_PQ_query_param_auto_from_type (&td->exchange_pub),
        GNUNET_PQ_query_param_auto_from_type (&td->exchange_sig),
        TALER_PQ_query_param_amount_with_currency (pg->conn,
                                                   &td->total_amount),
        TALER_PQ_query_param_amount_with_currency (pg->conn,
                                                   &td->wire_fee),
        TALER_PQ_query_param_array_amount_with_currency (
          len,
          coin_values,
          pg->conn),
        TALER_PQ_query_param_array_amount_with_currency (
          len,
          deposit_fees,
          pg->conn),
        GNUNET_PQ_query_param_array_ptrs_auto_from_type (
          len,
          coin_pubs,
          pg->conn),
        GNUNET_PQ_query_param_array_ptrs_auto_from_type (
          len,
          contract_terms,
          pg->conn),
        GNUNET_PQ_query_param_end
      };
      bool no_instance;
      bool no_account;
      bool no_exchange;
      bool conflict;
      struct GNUNET_PQ_ResultSpec rs[] = {
        GNUNET_PQ_result_spec_bool ("out_no_instance",
                                    &no_instance),
        GNUNET_PQ_result_spec_bool ("out_no_account",
                                    &no_account),
        GNUNET_PQ_result_spec_bool ("out_no_exchange",
                                    &no_exchange),
        GNUNET_PQ_result_spec_bool ("out_duplicate",
                                    &duplicate),
        GNUNET_PQ_result_spec_bool ("out_conflict",
                                    &conflict),
        GNUNET_PQ_result_spec_end
      };

      qs = GNUNET_PQ_eval_prepared_singleton_select (pg->conn,
                                                     "insert_transfer_details",
                                                     params,
                                                     rs);
      GNUNET_PQ_cleanup_query_params_closures (params);
      if (0 >= qs)
      {
        GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
        TMH_PG_rollback (pg);
        if (GNUNET_DB_STATUS_SOFT_ERROR == qs)
          continue;
        GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                    "'insert_transfer_details' failed with status %d\n",
                    qs);
        return qs;
      }
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Transfer details inserted: %s%s%s%s%s\n",
                  no_instance ? "no instance " : "",
                  no_account ? "no account " : "",
                  no_exchange ? "no exchange ": "",
                  duplicate ? "duplicate ": "",
                  conflict ? "conflict" : "");
    }

    GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
                "Committing transaction...\n");
    qs = TMH_PG_commit (pg);
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
      return GNUNET_DB_STATUS_SUCCESS_ONE_RESULT;
    GNUNET_break (GNUNET_DB_STATUS_SOFT_ERROR == qs);
    if (GNUNET_DB_STATUS_SOFT_ERROR != qs)
      break;
  }
  if (duplicate)
    return GNUNET_DB_STATUS_SUCCESS_NO_RESULTS;
  return qs;
}
