/*
  This file is part of Anastasis
  Copyright (C) 2022 Anastasis SARL

  Anastasis 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.

  Anastasis 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
  Anastasis; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file anastasis-httpd_policy-meta.c
 * @brief functions to handle incoming requests on /policy/$PID/meta
 * @author Christian Grothoff
 */
#include "platform.h"
#include "anastasis-httpd.h"
#include "anastasis-httpd_policy-meta.h"
#include "anastasis_service.h"
#include <gnunet/gnunet_util_lib.h>
#include <gnunet/gnunet_rest_lib.h>
#include <taler/taler_json_lib.h>
#include <taler/taler_merchant_service.h>
#include <taler/taler_signatures.h>


/**
 * Function called on matching meta data.  Note that if the client did
 * not provide meta data for @a version, the function will be called
 * with @a recovery_meta_data being NULL.
 *
 * @param cls closure with a `json_t *` to build up
 * @param version the version of the recovery document
 * @param ts timestamp when the document was created
 * @param recovery_meta_data contains meta data about the encrypted recovery document
 * @param recovery_meta_data_size size of @a recovery_meta_data blob
 * @return #GNUNET_OK to continue to iterate, #GNUNET_NO to abort iteration
 */
static enum GNUNET_GenericReturnValue
build_meta_result (void *cls,
                   uint32_t version,
                   struct GNUNET_TIME_Timestamp ts,
                   const void *recovery_meta_data,
                   size_t recovery_meta_data_size)
{
  json_t *result = cls;
  char version_s[14];

  GNUNET_snprintf (version_s,
                   sizeof (version_s),
                   "%u",
                   (unsigned int) version);
  GNUNET_assert (0 ==
                 json_object_set_new (
                   result,
                   version_s,
                   GNUNET_JSON_PACK (
                     GNUNET_JSON_pack_data_varsize (
                       "meta",
                       recovery_meta_data,
                       recovery_meta_data_size),
                     GNUNET_JSON_pack_timestamp (
                       "upload_time",
                       ts))));
  return GNUNET_OK;
}


/**
 * Return the meta data on recovery documents of @a account on @a
 * connection.
 *
 * @param connection MHD connection to use
 * @param account_pub account to query
 * @return MHD result code
 */
static enum MHD_Result
return_policy_meta (
  struct MHD_Connection *connection,
  const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub)
{
  enum GNUNET_DB_QueryStatus qs;
  uint32_t max_version = INT32_MAX; /* Postgres is using signed ints... */
  json_t *result;

  {
    const char *version_s;

    version_s = MHD_lookup_connection_value (connection,
                                             MHD_GET_ARGUMENT_KIND,
                                             "max_version");
    if (NULL != version_s)
    {
      char dummy;

      if (1 != sscanf (version_s,
                       "%u%c",
                       &max_version,
                       &dummy))
      {
        return TALER_MHD_reply_with_error (connection,
                                           MHD_HTTP_BAD_REQUEST,
                                           TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                           "version");
      }
      if (max_version > INT32_MAX)
        max_version = INT32_MAX; /* cap to signed range */
    }
  }
  result = json_object ();
  GNUNET_assert (NULL != result);
  qs = ANASTASIS_DB_iterate_recovery_meta_data (
    account_pub,
    max_version,
    &build_meta_result,
    result);

  switch (qs)
  {
  case GNUNET_DB_STATUS_HARD_ERROR:
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
                                       "get_recovery_document");
  case GNUNET_DB_STATUS_SOFT_ERROR:
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_GENERIC_DB_SOFT_FAILURE,
                                       "get_recovery_document");
  case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_NOT_FOUND,
                                       TALER_EC_ANASTASIS_POLICY_NOT_FOUND,
                                       NULL);
  default:
    /* interesting case below */
    break;
  }

  return TALER_MHD_reply_json_steal (connection,
                                     result,
                                     MHD_HTTP_OK);
}


enum MHD_Result
AH_policy_meta_get (
  struct MHD_Connection *connection,
  const struct ANASTASIS_CRYPTO_AccountPublicKeyP *account_pub)
{
  struct GNUNET_HashCode recovery_data_hash;
  enum ANASTASIS_DB_AccountStatus as;
  uint32_t version;
  struct GNUNET_TIME_Timestamp expiration;

  as = ANASTASIS_DB_get_account (
    account_pub,
    &expiration,
    &recovery_data_hash,
    &version);
  switch (as)
  {
  case ANASTASIS_DB_ACCOUNT_STATUS_PAYMENT_REQUIRED:
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_NOT_FOUND,
                                       TALER_EC_ANASTASIS_POLICY_NOT_FOUND,
                                       "account expired: payment required");
  case ANASTASIS_DB_ACCOUNT_STATUS_HARD_ERROR:
    GNUNET_break (0);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_INTERNAL_SERVER_ERROR,
                                       TALER_EC_GENERIC_DB_FETCH_FAILED,
                                       "get account");
  case ANASTASIS_DB_ACCOUNT_STATUS_NO_RESULTS:
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_NOT_FOUND,
                                       TALER_EC_ANASTASIS_POLICY_NOT_FOUND,
                                       "no such account");
  case ANASTASIS_DB_ACCOUNT_STATUS_VALID_HASH_RETURNED:
    /* We have results, should fetch and return them! */
    break;
  }
  return return_policy_meta (connection,
                             account_pub);
}
