/*
  This file is part of TALER
  Copyright (C) 2024 Taler Systems SA

  TALER is free software; you can redistribute it and/or modify it under the
  terms of the GNU Affero 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 Affero General Public License for more details.

  You should have received a copy of the GNU Affero General Public License along with
  TALER; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file donau-httpd_batch-submit.c
 * @brief Handle request to insert a submitted receipt.
 * @author Johannes Casaburi
 */
#include "donau_config.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_json_lib.h>
#include <jansson.h>
#include <microhttpd.h>
#include <pthread.h>
#include "taler/taler_json_lib.h"
#include "taler/taler_mhd_lib.h"
#include "taler/taler_signatures.h"
#include "donaudb_plugin.h"
#include "donau-httpd_batch-submit.h"
#include "donau-httpd_keys.h"


/**
 * Closure for #insert_submitted_receipts()
 */
struct InsertReceiptContext
{
  struct DONAU_HashDonorTaxId h_donor_tax_id;
  struct DONAU_DonationReceipt *donation_receipts;
  size_t num_dr;
  uint64_t donation_year;
};

/**
 * Parse a donation receipt encoded in JSON.
 *
 * @param[out] dr where to return the result
 * @param dr_obj json to parse
 * @return #GNUNET_OK if all is fine, #GNUNET_SYSERR if @a dr_obj
 * is malformed.
 */
static enum GNUNET_GenericReturnValue
parse_json_dr (struct DONAU_DonationReceipt *dr,
               const json_t *dr_obj)
{
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_fixed_auto ("h_donation_unit_pub",
                                 &dr->h_donation_unit_pub),
    GNUNET_JSON_spec_fixed_auto ("nonce",
                                 &dr->nonce),
    GNUNET_JSON_spec_unblinded_signature ("donation_unit_sig",
                                          &dr->donation_unit_sig.unblinded_sig),
    GNUNET_JSON_spec_end ()
  };

  if (GNUNET_OK !=
      GNUNET_JSON_parse (dr_obj,
                         spec,
                         NULL,
                         NULL))
  {
    GNUNET_break_op (0);
    return GNUNET_SYSERR;
  }

  return GNUNET_OK;
}


/**
 * Free data in @a irc, but not @a irc itself
 *
 * @param[in,out] irc data structure to clean up
 */
static void
free_irc (struct InsertReceiptContext *irc)
{
  for (size_t i = 0; i<irc->num_dr; i++)
    GNUNET_CRYPTO_unblinded_sig_decref (irc->donation_receipts[i].
                                        donation_unit_sig.unblinded_sig);
  GNUNET_free (irc->donation_receipts);
}


MHD_RESULT
DH_handler_submit_receipts_post (struct DH_RequestContext *rc,
                                 const json_t *root,
                                 const char *const args[])
{
  struct InsertReceiptContext irc = {0};
  const json_t *donation_receipts;
  struct GNUNET_JSON_Specification spec[] = {
    GNUNET_JSON_spec_fixed_auto ("h_donor_tax_id",
                                 &irc.h_donor_tax_id),
    GNUNET_JSON_spec_array_const ("donation_receipts",
                                  &donation_receipts),
    GNUNET_JSON_spec_uint64 ("donation_year",
                             &irc.donation_year),
    GNUNET_JSON_spec_end ()
  };
  size_t num_dr;

  (void) args;
  {
    enum GNUNET_GenericReturnValue res;

    res = TALER_MHD_parse_json_data (rc->connection,
                                     root,
                                     spec);
    if (GNUNET_SYSERR == res)
      return MHD_NO; /* hard failure */
    if (GNUNET_NO == res)
    {
      GNUNET_break_op (0);
      return MHD_YES; /* failure */
    }
  }

  /* parse the donation receipts */
  num_dr = json_array_size (donation_receipts);

  if (0 == num_dr)
  {
    GNUNET_break_op (0);
    return TALER_MHD_reply_with_error (rc->connection,
                                       MHD_HTTP_BAD_REQUEST,
                                       TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                       "donation_receipts");
  }
  {
    json_t *dr_obj;
    size_t index;

    irc.num_dr = num_dr;
    irc.donation_receipts = GNUNET_new_array (num_dr,
                                              struct DONAU_DonationReceipt);

    json_array_foreach (donation_receipts,
                        index,
                        dr_obj)
    {
      if (GNUNET_SYSERR ==
          parse_json_dr (&irc.donation_receipts[index],
                         dr_obj))
      {
        GNUNET_break_op (0);
        free_irc (&irc);
        return TALER_MHD_reply_with_error (rc->connection,
                                           MHD_HTTP_BAD_REQUEST,
                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                           "donation_receipts");
      }
    }
  }

  for (size_t i = 0; i < num_dr; i++)
  {
    struct DONAU_UniqueDonorIdentifierHashP udi_hash;
    struct DH_DonationUnitKey *dk;

    /* Check nonce unique*/
    for (size_t j = i + 1; j < num_dr; j++)
    {
      if (0 ==
          GNUNET_memcmp (&irc.donation_receipts[i].nonce,
                         &irc.donation_receipts[j].nonce))
      {
        GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                    "Donation receipt nonce is not unique!\n");
        free_irc (&irc);
        return TALER_MHD_reply_with_error (rc->connection,
                                           MHD_HTTP_CONFLICT,
                                           TALER_EC_DONAU_DONOR_IDENTIFIER_NONCE_REUSE,
                                           NULL);
      }
    }

    /* Check if donation unit exists*/
    if (NULL == (dk = DH_keys_donation_unit_by_hash (
                   &irc.donation_receipts[i].h_donation_unit_pub)))
    {
      GNUNET_break_op (0);
      free_irc (&irc);
      return TALER_MHD_reply_with_error (rc->connection,
                                         MHD_HTTP_NOT_FOUND,
                                         TALER_EC_DONAU_GENERIC_DONATION_UNIT_UNKNOWN,
                                         NULL);
    }

    DONAU_unique_donor_id_hash (
      &irc.h_donor_tax_id,
      &irc.donation_receipts[i].nonce,
      &udi_hash);

    /* Check signature*/
    if (GNUNET_OK != DONAU_donation_receipt_verify (
          &dk->donation_unit_pub,
          &udi_hash,
          &irc.donation_receipts[i].donation_unit_sig))
    {
      GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
                  "Donation receipt signature invalid!\n");
      free_irc (&irc);
      return TALER_MHD_reply_with_error (rc->connection,
                                         MHD_HTTP_FORBIDDEN,
                                         TALER_EC_DONAU_DONATION_RECEIPT_SIGNATURE_INVALID,
                                         NULL);

    }
  }

  {
    enum GNUNET_DB_QueryStatus qs;

    qs = DH_plugin->insert_submitted_receipts (
      DH_plugin->cls,
      &irc.h_donor_tax_id,
      num_dr,
      irc.donation_receipts,
      irc.donation_year);
    free_irc (&irc);
    if (qs < 0)
    {
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (rc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
                                         NULL);
    }
  }
  GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
              "submitted receipts inserted!\n");

  return TALER_MHD_reply_static (
    rc->connection,
    MHD_HTTP_CREATED,
    NULL,
    NULL,
    0);
}


/* end of donau-httpd_batch-submit.c */
