/*
  This file is part of Challenger
  Copyright (C) 2023, 2024 Taler Systems SA

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

  Challenger 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
  Challenger; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
*/
/**
 * @file challenger-httpd_setup.c
 * @brief functions to handle incoming requests for setups
 * @author Christian Grothoff
 */
#include "platform.h"
#include "challenger-httpd.h"
#include <gnunet/gnunet_util_lib.h>
#include "challenger-httpd_setup.h"
#include "challenger-httpd_common.h"

/**
 * Maximum number of retries for the database interaction.
 */
#define MAX_RETRIES 3

/**
 * Set to 1 to dump addresses into the log.
 */
#define DEBUG 0

struct SetupContext
{

  /**
   * Our handler context.
   */
  struct CH_HandlerContext *hc;

  /**
   * Opaque parsing context.
   */
  void *opaque_post_parsing_context;

  /**
   * Uploaded JSON data, NULL if upload is not yet complete.
   */
  json_t *root;

};


/**
 * Callback to clean up the request context.
 *
 * @param[in,out] ctx a `struct SetupContext` to clean up
 */
static void
request_done (void *ctx)
{
  struct SetupContext *sc = ctx;

  if (NULL == ctx)
    return;
  if (NULL != sc->root)
  {
    json_decref (sc->root);
    sc->root = NULL;
  }
  TALER_MHD_parse_post_cleanup_callback (sc->opaque_post_parsing_context);
  GNUNET_free (sc);
}


MHD_RESULT
CH_handler_setup (struct CH_HandlerContext *hc,
                  const char *upload_data,
                  size_t *upload_data_size)
{
  struct SetupContext *sc = hc->ctx;
  unsigned long long client_id;
  const char *client_secret;
  const char *cl;

  if (NULL == sc)
  {
    /* Fresh request, do initial setup */
    sc = GNUNET_new (struct SetupContext);
    sc->hc = hc;
    hc->cc = &request_done;
    hc->ctx = sc;
    return MHD_YES;
  }
  cl = MHD_lookup_connection_value (hc->connection,
                                    MHD_HEADER_KIND,
                                    MHD_HTTP_HEADER_CONTENT_LENGTH);
  /* If we have a body (Content-length set and not zero) and we
     are not yet done parsing it, try to parse it! */
  if ( (NULL == sc->root) &&
       (NULL != cl) &&
       (0 != strcmp ("0",
                     cl)) )
  {
    /* parse byte stream upload into JSON */
    enum GNUNET_GenericReturnValue res;

#if DEBUG
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Handling upload of `%.*s' (%s)\n",
                (int) *upload_data_size,
                upload_data,
                cl);
#endif
    res = TALER_MHD_parse_post_json (hc->connection,
                                     &sc->opaque_post_parsing_context,
                                     upload_data,
                                     upload_data_size,
                                     &sc->root);
    if (GNUNET_SYSERR == res)
    {
      GNUNET_assert (NULL == sc->root);
      return MHD_NO; /* bad upload, could not even generate error */
    }
    if ( (GNUNET_NO == res) ||
         (NULL == sc->root) )
    {
      GNUNET_assert (NULL == sc->root);
      return MHD_YES; /* so far incomplete upload or parser error */
    }
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "Address data uploaded in /setup\n");
#if DEBUG
    json_dumpf (sc->root,
                stderr,
                JSON_INDENT (2));
#endif
  }

  {
    char dummy;

    if (1 != sscanf (hc->path,
                     "%llu%c",
                     &client_id,
                     &dummy))
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (hc->connection,
                                         MHD_HTTP_NOT_FOUND,
                                         TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
                                         hc->path);
    }
  }
  client_secret = CH_get_client_secret (hc->connection);
  if (NULL == client_secret)
  {
    GNUNET_break_op (0);
    return TALER_MHD_reply_with_error (hc->connection,
                                       MHD_HTTP_NOT_FOUND,
                                       TALER_EC_GENERIC_PARAMETER_MISSING,
                                       MHD_HTTP_HEADER_AUTHORIZATION);
  }
  {
    const json_t *ro;

    ro = json_object_get (sc->root,
                          "read_only");
    if ( (NULL != ro) &&
         (! json_is_boolean (ro)) )
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (hc->connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                         "read_only");
    }
  }
  for (unsigned int r = 0; r<MAX_RETRIES; r++)
  {
    enum GNUNET_DB_QueryStatus qs;
    char *client_url = NULL;

    qs = CH_db->client_check (CH_db->cls,
                              (uint64_t) client_id,
                              client_secret,
                              1,
                              &client_url);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (hc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
                                         NULL);
    case GNUNET_DB_STATUS_SOFT_ERROR:
      if (r < MAX_RETRIES - 1)
        continue;
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (hc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_FETCH_FAILED,
                                         NULL);
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      return TALER_MHD_reply_with_error (hc->connection,
                                         MHD_HTTP_NOT_FOUND,
                                         TALER_EC_CHALLENGER_GENERIC_CLIENT_UNKNOWN,
                                         NULL);
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      break;
    }
    GNUNET_free (client_url);
    break;
  }

  {
    struct CHALLENGER_ValidationNonceP nonce;
    struct GNUNET_TIME_Absolute expiration_time;
    enum GNUNET_DB_QueryStatus qs;

    expiration_time = GNUNET_TIME_relative_to_absolute (CH_validation_duration);
    GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE,
                                &nonce,
                                sizeof (nonce));
    qs = CH_db->setup_nonce (CH_db->cls,
                             client_id,
                             &nonce,
                             expiration_time,
                             sc->root);
    switch (qs)
    {
    case GNUNET_DB_STATUS_HARD_ERROR:
    case GNUNET_DB_STATUS_SOFT_ERROR:
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (hc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                                         NULL);
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (hc->connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                                         NULL);
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      break;
    }
    {
      char *nstr;

      nstr = GNUNET_STRINGS_data_to_string_alloc (&nonce,
                                                  sizeof (nonce));
      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "/setup returns nonce `%s'\n",
                  nstr);
      GNUNET_free (nstr);
    }
    return TALER_MHD_REPLY_JSON_PACK (
      hc->connection,
      MHD_HTTP_OK,
      GNUNET_JSON_pack_data_auto ("nonce",
                                  &nonce));
  }
}
