/*
  This file is part of TALER
  (C) 2020-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 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 taler-merchant-httpd_private-post-account.c
 * @brief implementing POST /private/accounts request handling
 * @author Christian Grothoff
 */
#include "platform.h"
#include "taler-merchant-httpd_private-post-account.h"
#include "taler-merchant-httpd_helper.h"
#include "taler_merchant_bank_lib.h"
#include <taler/taler_dbevents.h>
#include <taler/taler_json_lib.h>
#include "taler-merchant-httpd_mfa.h"
#include <regex.h>


MHD_RESULT
TMH_private_post_account (const struct TMH_RequestHandler *rh,
                          struct MHD_Connection *connection,
                          struct TMH_HandlerContext *hc)
{
  struct TMH_MerchantInstance *mi = hc->instance;
  const char *credit_facade_url = NULL;
  const json_t *credit_facade_credentials = NULL;
  struct TALER_FullPayto uri;
  struct GNUNET_JSON_Specification ispec[] = {
    TALER_JSON_spec_full_payto_uri ("payto_uri",
                                    &uri),
    GNUNET_JSON_spec_mark_optional (
      TALER_JSON_spec_web_url ("credit_facade_url",
                               &credit_facade_url),
      NULL),
    GNUNET_JSON_spec_mark_optional (
      GNUNET_JSON_spec_object_const ("credit_facade_credentials",
                                     &credit_facade_credentials),
      NULL),
    GNUNET_JSON_spec_end ()
  };
  struct TMH_WireMethod *wm;

  {
    enum GNUNET_GenericReturnValue res;

    res = TALER_MHD_parse_json_data (connection,
                                     hc->request_body,
                                     ispec);
    if (GNUNET_OK != res)
      return (GNUNET_NO == res)
             ? MHD_YES
             : MHD_NO;
  }

  {
    char *err;

    if (NULL !=
        (err = TALER_payto_validate (uri)))
    {
      MHD_RESULT mret;

      GNUNET_break_op (0);
      mret = TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
                                         err);
      GNUNET_free (err);
      return mret;
    }
  }
  {
    char *apt = GNUNET_strdup (TMH_allowed_payment_targets);
    char *method = TALER_payto_get_method (uri.full_payto);
    bool ok;

    ok = false;
    for (const char *tok = strtok (apt,
                                   " ");
         NULL != tok;
         tok = strtok (NULL,
                       " "))
    {
      if (0 == strcmp ("*",
                       tok))
        ok = true;
      if (0 == strcmp (method,
                       tok))
        ok = true;
      if (ok)
        break;
    }
    GNUNET_free (method);
    GNUNET_free (apt);
    if (! ok)
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
                                         "The payment target type is forbidden by policy");
    }
  }

  if ( (NULL != TMH_payment_target_regex) &&
       (0 !=
        regexec (&TMH_payment_target_re,
                 uri.full_payto,
                 0,
                 NULL,
                 0)) )
  {
    GNUNET_break_op (0);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_BAD_REQUEST,
                                       TALER_EC_GENERIC_PAYTO_URI_MALFORMED,
                                       "The specific account is forbidden by policy");
  }

  if ( (NULL == credit_facade_url) !=
       (NULL == credit_facade_credentials) )
  {
    GNUNET_break_op (0);
    return TALER_MHD_reply_with_error (connection,
                                       MHD_HTTP_BAD_REQUEST,
                                       TALER_EC_GENERIC_PARAMETER_MISSING,
                                       (NULL == credit_facade_url)
                                       ? "credit_facade_url"
                                       : "credit_facade_credentials");
  }
  if ( (NULL != credit_facade_url) ||
       (NULL != credit_facade_credentials) )
  {
    struct TALER_MERCHANT_BANK_AuthenticationData auth;

    if (GNUNET_OK !=
        TALER_MERCHANT_BANK_auth_parse_json (credit_facade_credentials,
                                             credit_facade_url,
                                             &auth))
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_BAD_REQUEST,
                                         TALER_EC_GENERIC_PARAMETER_MALFORMED,
                                         "credit_facade_url or credit_facade_credentials");
    }
    TALER_MERCHANT_BANK_auth_free (&auth);
  }

  /* FIXME: might want to do all of the DB interactions below in one transaction... */
  {
    enum GNUNET_DB_QueryStatus qs;
    enum GNUNET_GenericReturnValue ret;

    qs = TMH_db->select_accounts (TMH_db->cls,
                                  mi->settings.id,
                                  NULL,
                                  NULL);
    switch (qs)
    {
    case GNUNET_DB_STATUS_SOFT_ERROR:
    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,
                                         "select_accounts");
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      /* skip MFA */
      break;
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      ret = TMH_mfa_check_simple (hc,
                                  TALER_MERCHANT_MFA_CO_ACCOUNT_CONFIGURATION,
                                  mi);

      GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                  "Account creation MFA check returned %d\n",
                  (int) ret);
      if (GNUNET_OK != ret)
      {
        return (GNUNET_NO == ret)
        ? MHD_YES
        : MHD_NO;
      }
    }
  }

  /* convert provided payto URI into internal data structure with salts */
  wm = TMH_setup_wire_account (uri,
                               credit_facade_url,
                               credit_facade_credentials);
  GNUNET_assert (NULL != wm);
  {
    struct TALER_MERCHANTDB_AccountDetails ad = {
      .payto_uri = wm->payto_uri,
      .salt = wm->wire_salt,
      .instance_id = mi->settings.id,
      .h_wire = wm->h_wire,
      .credit_facade_url = wm->credit_facade_url,
      .credit_facade_credentials = wm->credit_facade_credentials,
      .active = wm->active
    };
    enum GNUNET_DB_QueryStatus qs;

    qs = TMH_db->insert_account (TMH_db->cls,
                                 &ad);
    switch (qs)
    {
    case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
      break;
    case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
      /* conflict: account exists */
      {
        struct TALER_MERCHANTDB_AccountDetails adx;

        qs = TMH_db->select_account_by_uri (TMH_db->cls,
                                            mi->settings.id,
                                            ad.payto_uri,
                                            &adx);
        switch (qs)
        {
        case GNUNET_DB_STATUS_SUCCESS_ONE_RESULT:
          if ( (0 == TALER_full_payto_cmp (adx.payto_uri,
                                           ad.payto_uri) ) &&
               ( (adx.credit_facade_credentials ==
                  ad.credit_facade_credentials) ||
                 ( (NULL != adx.credit_facade_credentials) &&
                   (NULL != ad.credit_facade_credentials) &&
                   (1 == json_equal (adx.credit_facade_credentials,
                                     ad.credit_facade_credentials)) ) ) &&
               ( (adx.credit_facade_url == ad.credit_facade_url) ||
                 ( (NULL != adx.credit_facade_url) &&
                   (NULL != ad.credit_facade_url) &&
                   (0 == strcmp (adx.credit_facade_url,
                                 ad.credit_facade_url)) ) ) )
          {
            TMH_wire_method_free (wm);
            GNUNET_free (adx.payto_uri.full_payto);
            GNUNET_free (adx.credit_facade_url);
            json_decref (adx.credit_facade_credentials);
            return TALER_MHD_REPLY_JSON_PACK (
              connection,
              MHD_HTTP_OK,
              GNUNET_JSON_pack_data_auto (
                "salt",
                &adx.salt),
              GNUNET_JSON_pack_data_auto (
                "h_wire",
                &adx.h_wire));
          }
          GNUNET_free (adx.payto_uri.full_payto);
          GNUNET_free (adx.credit_facade_url);
          json_decref (adx.credit_facade_credentials);
          break;
        case GNUNET_DB_STATUS_SUCCESS_NO_RESULTS:
        case GNUNET_DB_STATUS_SOFT_ERROR:
        case GNUNET_DB_STATUS_HARD_ERROR:
          GNUNET_break (0);
          TMH_wire_method_free (wm);
          return TALER_MHD_reply_with_error (connection,
                                             MHD_HTTP_INTERNAL_SERVER_ERROR,
                                             TALER_EC_GENERIC_DB_FETCH_FAILED,
                                             "select_account");
        }
      }
      TMH_wire_method_free (wm);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_CONFLICT,
                                         TALER_EC_MERCHANT_PRIVATE_ACCOUNT_EXISTS,
                                         uri.full_payto);
    case GNUNET_DB_STATUS_SOFT_ERROR:
    case GNUNET_DB_STATUS_HARD_ERROR:
      GNUNET_break (0);
      TMH_wire_method_free (wm);
      return TALER_MHD_reply_with_error (connection,
                                         MHD_HTTP_INTERNAL_SERVER_ERROR,
                                         TALER_EC_GENERIC_DB_STORE_FAILED,
                                         "insert_account");
    }
  }

  {
    struct GNUNET_DB_EventHeaderP es = {
      .size = htons (sizeof (es)),
      .type = htons (TALER_DBEVENT_MERCHANT_ACCOUNTS_CHANGED)
    };

    TMH_db->event_notify (TMH_db->cls,
                          &es,
                          NULL,
                          0);
  }
  /* Finally, also update our running process */
  GNUNET_CONTAINER_DLL_insert (mi->wm_head,
                               mi->wm_tail,
                               wm);
  /* Note: we may not need to do this, as we notified
     about the account change above. But also hardly hurts. */
  TMH_reload_instances (mi->settings.id);
  return TALER_MHD_REPLY_JSON_PACK (connection,
                                    MHD_HTTP_OK,
                                    GNUNET_JSON_pack_data_auto ("salt",
                                                                &wm->wire_salt),
                                    GNUNET_JSON_pack_data_auto ("h_wire",
                                                                &wm->h_wire));
}


/* end of taler-merchant-httpd_private-post-account.c */
