/*
  This file is part of TALER
  Copyright (C) 2019--2025 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 sync-httpd2_backup.c
 * @brief functions to handle incoming requests for backups
 * @author Christian Grothoff
 */
#include "platform.h"
#include "sync-httpd2.h"
#include <gnunet/gnunet_util_lib.h>
#include "sync-httpd2_backup.h"


/**
 * Handle request on @a request for retrieval of the latest
 * backup of @a account.
 *
 * @param request the MHD request to handle
 * @param account public key of the account the request is for
 * @return MHD action
 */
const struct MHD_Action *
SH_backup_get (struct MHD_Request *request,
               const struct SYNC_AccountPublicKeyP *account)
{
  struct GNUNET_HashCode backup_hash;
  enum SYNC_DB_QueryStatus qs;

  qs = db->lookup_account_TR (db->cls,
                              account,
                              &backup_hash);
  switch (qs)
  {
  case SYNC_DB_OLD_BACKUP_MISSING:
    GNUNET_break (0);
    return TALER_MHD2_reply_with_error (request,
                                        MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR,
                                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                                        NULL);
  case SYNC_DB_OLD_BACKUP_MISMATCH:
    GNUNET_break (0);
    return TALER_MHD2_reply_with_error (request,
                                        MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR,
                                        TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                                        NULL);
  case SYNC_DB_PAYMENT_REQUIRED:
    return TALER_MHD2_reply_with_error (request,
                                        MHD_HTTP_STATUS_NOT_FOUND,
                                        TALER_EC_SYNC_ACCOUNT_UNKNOWN,
                                        NULL);
  case SYNC_DB_HARD_ERROR:
    return TALER_MHD2_reply_with_error (request,
                                        MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR,
                                        TALER_EC_GENERIC_DB_FETCH_FAILED,
                                        NULL);
  case SYNC_DB_SOFT_ERROR:
    GNUNET_break (0);
    return TALER_MHD2_reply_with_error (request,
                                        MHD_HTTP_STATUS_INTERNAL_SERVER_ERROR,
                                        TALER_EC_GENERIC_DB_SOFT_FAILURE,
                                        NULL);
  case SYNC_DB_NO_RESULTS:
    {
      struct MHD_Response *resp;

      resp = MHD_response_from_empty (MHD_HTTP_STATUS_NO_CONTENT);
      TALER_MHD2_add_global_headers (resp,
                                     false);
      return MHD_action_from_response (request,
                                       resp);
    }
  case SYNC_DB_ONE_RESULT:
    {
      const struct MHD_StringNullable *inm;

      inm = MHD_request_get_value (request,
                                   MHD_VK_HEADER,
                                   MHD_HTTP_HEADER_IF_NONE_MATCH);
      if ( (NULL != inm) &&
           (2 < inm->len) &&
           ('"' == inm->cstr[0]) &&
           ('=' == inm->cstr[inm->len - 1]) )
      {
        struct GNUNET_HashCode inm_h;

        if (GNUNET_OK !=
            GNUNET_STRINGS_string_to_data (inm->cstr + 1,
                                           inm->len - 2,
                                           &inm_h,
                                           sizeof (inm_h)))
        {
          GNUNET_break_op (0);
          return TALER_MHD2_reply_with_error (request,
                                              MHD_HTTP_STATUS_BAD_REQUEST,
                                              TALER_EC_SYNC_BAD_IF_NONE_MATCH,
                                              "Etag does not include a base32-encoded SHA-512 hash");
        }
        if (0 == GNUNET_memcmp (&inm_h,
                                &backup_hash))
        {
          struct MHD_Response *resp;

          resp = MHD_response_from_empty (MHD_HTTP_STATUS_NOT_MODIFIED);
          TALER_MHD2_add_global_headers (resp,
                                         false);
          return MHD_action_from_response (request,
                                           resp);
        }
      }
    }
    /* We have a result, should fetch and return it! */
    break;
  }
  return MHD_action_from_response (request,
                                   SH_make_backup (account,
                                                   MHD_HTTP_STATUS_OK));
}


/**
 * Return the current backup of @a account on @a request
 * using @a default_http_status on success.
 *
 * @param account account to query
 * @param default_http_status HTTP status to queue response
 *  with on success (#MHD_HTTP_STATUS_OK or #MHD_HTTP_STATUS_CONFLICT)
 * @return MHD response
 */
struct MHD_Response *
SH_make_backup (const struct SYNC_AccountPublicKeyP *account,
                enum MHD_HTTP_StatusCode default_http_status)
{
  enum SYNC_DB_QueryStatus qs;
  struct MHD_Response *resp;
  struct SYNC_AccountSignatureP account_sig;
  struct GNUNET_HashCode backup_hash;
  struct GNUNET_HashCode prev_hash;
  size_t backup_size;
  void *backup;

  qs = db->lookup_backup_TR (db->cls,
                             account,
                             &account_sig,
                             &prev_hash,
                             &backup_hash,
                             &backup_size,
                             &backup);
  switch (qs)
  {
  case SYNC_DB_OLD_BACKUP_MISSING:
    GNUNET_break (0);
    return TALER_MHD2_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                                  "unexpected return status (backup missing)");
  case SYNC_DB_OLD_BACKUP_MISMATCH:
    GNUNET_break (0);
    return TALER_MHD2_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                                  "unexpected return status (backup mismatch)");
  case SYNC_DB_PAYMENT_REQUIRED:
    GNUNET_break (0);
    return TALER_MHD2_make_error (TALER_EC_GENERIC_INTERNAL_INVARIANT_FAILURE,
                                  "unexpected return status (payment required)")
    ;
  case SYNC_DB_HARD_ERROR:
    GNUNET_break (0);
    return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_FETCH_FAILED,
                                  NULL);
  case SYNC_DB_SOFT_ERROR:
    GNUNET_break (0);
    return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_SOFT_FAILURE,
                                  NULL);
  case SYNC_DB_NO_RESULTS:
    GNUNET_break (0);
    /* Note: can theoretically happen due to non-transactional nature if
       the backup expired / was gc'ed JUST between the two SQL calls.
       But too rare to handle properly, as doing a transaction would be
       expensive. Just admit to failure ;-) */
    return TALER_MHD2_make_error (TALER_EC_GENERIC_DB_INVARIANT_FAILURE,
                                  NULL);
  case SYNC_DB_ONE_RESULT:
    /* interesting case below */
    break;
  }
  resp = MHD_response_from_buffer (default_http_status,
                                   backup_size,
                                   backup,
                                   &free,
                                   backup);
  TALER_MHD2_add_global_headers (resp,
                                 false);
  {
    char *sig_s;
    char *prev_s;
    char *etag;
    char *etagq;

    sig_s = GNUNET_STRINGS_data_to_string_alloc (&account_sig,
                                                 sizeof (account_sig));
    prev_s = GNUNET_STRINGS_data_to_string_alloc (&prev_hash,
                                                  sizeof (prev_hash));
    etag = GNUNET_STRINGS_data_to_string_alloc (&backup_hash,
                                                sizeof (backup_hash));
    GNUNET_break (MHD_SC_OK ==
                  MHD_response_add_header (resp,
                                           "Sync-Signature",
                                           sig_s));
    GNUNET_break (MHD_SC_OK ==
                  MHD_response_add_header (resp,
                                           "Sync-Previous",
                                           prev_s));
    GNUNET_asprintf (&etagq,
                     "\"%s\"",
                     etag);
    GNUNET_break (MHD_SC_OK ==
                  MHD_response_add_header (resp,
                                           MHD_HTTP_HEADER_ETAG,
                                           etagq));
    GNUNET_free (etagq);
    GNUNET_free (etag);
    GNUNET_free (prev_s);
    GNUNET_free (sig_s);
  }
  return resp;
}
