/*
  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 taler-exchange-httpd_kyc-upload.c
 * @brief Handle /kyc-upload/$ID request
 * @author Christian Grothoff
 */
#include "platform.h"
#include "taler-exchange-httpd_common_kyc.h"
#include "taler-exchange-httpd_kyc-upload.h"


/**
 * Context used for processing the KYC upload req
 */
struct UploadContext
{

  /**
   * Kept in a DLL.
   */
  struct UploadContext *next;

  /**
   * Kept in a DLL.
   */
  struct UploadContext *prev;

  /**
   * Access token for the KYC data of the account.
   */
  struct TALER_AccountAccessTokenP access_token;

  /**
   * Index of the measure this upload is for.
   */
  unsigned int measure_index;

  /**
   * HTTP status code to use with @e response.
   */
  unsigned int response_code;

  /**
   * Index in the legitimization measures table this ID
   * refers to.
   */
  unsigned long long legitimization_measure_serial_id;

  /**
   * Response to return, NULL if none yet.
   */
  struct MHD_Response *response;

  /**
   * Request we are processing.
   */
  struct TEH_RequestContext *rc;

  /**
   * Handle for async KYC processing.
   */
  struct TEH_KycMeasureRunContext *kat;

  /**
   * Uploaded data, in JSON.
   */
  const json_t *result;

};


/**
 * Kept in a DLL.
 */
static struct UploadContext *uc_head;

/**
 * Kept in a DLL.
 */
static struct UploadContext *uc_tail;


void
TEH_kyc_upload_cleanup ()
{
  struct UploadContext *uc;

  while (NULL != (uc = uc_head))
  {
    MHD_resume_connection (uc->rc->connection);
    GNUNET_CONTAINER_DLL_remove (uc_head,
                                 uc_tail,
                                 uc);
  }
}


/**
 * Function called to clean up upload context.
 *
 * @param[in,out] rc context to clean up
 */
static void
upload_cleaner (struct TEH_RequestContext *rc)
{
  struct UploadContext *uc = rc->rh_ctx;

  if (NULL != uc->kat)
  {
    TEH_kyc_run_measure_cancel (uc->kat);
    uc->kat = NULL;
  }
  if (NULL != uc->response)
  {
    MHD_destroy_response (uc->response);
    uc->response = NULL;
  }
  GNUNET_free (uc);
}


/**
 * Function called after the KYC-AML trigger is done.
 *
 * @param cls closure
 * @param ec error code or 0 on success
 * @param detail error message or NULL on success / no info
 */
static void
aml_trigger_callback (
  void *cls,
  enum TALER_ErrorCode ec,
  const char *detail)
{
  struct UploadContext *uc = cls;

  uc->kat = NULL;
  GNUNET_assert (NULL == uc->response);
  if (TALER_EC_NONE != ec)
  {
    uc->response_code = TALER_ErrorCode_get_http_status (ec);
    if (0 == uc->response_code)
    {
      GNUNET_break (0);
      uc->response_code = MHD_HTTP_INTERNAL_SERVER_ERROR;
    }
    GNUNET_assert (uc->response_code != UINT_MAX);
    uc->response = TALER_MHD_make_error (
      ec,
      detail);
  }
  else
  {
    uc->response_code = MHD_HTTP_NO_CONTENT;
    uc->response = MHD_create_response_from_buffer_static (
      0,
      ""
      );
    TALER_MHD_add_global_headers (uc->response);
  }

  MHD_resume_connection (uc->rc->connection);
  GNUNET_CONTAINER_DLL_remove (uc_head,
                               uc_tail,
                               uc);
  TALER_MHD_daemon_trigger ();
}


MHD_RESULT
TEH_handler_kyc_upload (
  struct TEH_RequestContext *rc,
  const json_t *root,
  const char *const args[1])
{
  struct UploadContext *uc = rc->rh_ctx;
  const char *id = args[0];

  if (NULL == uc)
  {
    const char *slash;
    char dummy;

    uc = GNUNET_new (struct UploadContext);
    uc->rc = rc;
    uc->result = root;
    rc->rh_ctx = uc;
    rc->rh_cleaner = &upload_cleaner;
    slash = strchr (id, '-');
    if (NULL == slash)
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (
        rc->connection,
        MHD_HTTP_NOT_FOUND,
        TALER_EC_GENERIC_ENDPOINT_UNKNOWN,
        rc->url);
    }
    if (GNUNET_OK !=
        GNUNET_STRINGS_string_to_data (id,
                                       slash - id,
                                       &uc->access_token,
                                       sizeof (uc->access_token)))
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (
        rc->connection,
        MHD_HTTP_BAD_REQUEST,
        TALER_EC_GENERIC_PARAMETER_MALFORMED,
        "Access token in ID is malformed");
    }
    if (2 !=
        sscanf (slash + 1,
                "%u-%llu%c",
                &uc->measure_index,
                &uc->legitimization_measure_serial_id,
                &dummy))
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (
        rc->connection,
        MHD_HTTP_BAD_REQUEST,
        TALER_EC_GENERIC_PARAMETER_MALFORMED,
        "ID is malformed");
    }
    GNUNET_log (GNUNET_ERROR_TYPE_INFO,
                "/kyc-upload received form submission\n");
    json_dumpf (root,
                stderr,
                JSON_INDENT (2));
  }
  if (NULL != uc->response)
  {
    return MHD_queue_response (rc->connection,
                               uc->response_code,
                               uc->response);

  }

  {
    uint64_t legi_process_row;
    struct TALER_NormalizedPaytoHashP h_payto;
    enum GNUNET_DB_QueryStatus qs;
    json_t *jmeasures;
    bool is_finished = false;
    size_t enc_attributes_len;
    void *enc_attributes;
    const char *error_message;
    char *form_name;
    enum TALER_ErrorCode ec;
    bool is_wallet;

    qs = TEH_plugin->lookup_completed_legitimization (
      TEH_plugin->cls,
      uc->legitimization_measure_serial_id,
      uc->measure_index,
      &uc->access_token,
      &h_payto,
      &is_wallet,
      &jmeasures,
      &is_finished,
      &enc_attributes_len,
      &enc_attributes);
    /* FIXME: not exactly performant/elegant, should eventually
       modify lookup_completed_legitimization to
       return something if we are purely pending? */
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
      qs = TEH_plugin->lookup_pending_legitimization (
        TEH_plugin->cls,
        uc->legitimization_measure_serial_id,
        &uc->access_token,
        &h_payto,
        &jmeasures,
        &is_finished,
        &is_wallet);
    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,
        "lookup_pending_legitimization");
    }
    if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS == qs)
    {
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (
        rc->connection,
        MHD_HTTP_NOT_FOUND,
        TALER_EC_EXCHANGE_KYC_CHECK_REQUEST_UNKNOWN,
        NULL);
    }

    if (is_finished)
    {
      if (NULL != enc_attributes)
      {
        json_t *xattributes;

        xattributes
          = TALER_CRYPTO_kyc_attributes_decrypt (
              &TEH_attribute_key,
              enc_attributes,
              enc_attributes_len);
        if (json_equal (xattributes,
                        uc->result))
        {
          /* Request is idempotent! */
          json_decref (xattributes);
          GNUNET_free (enc_attributes);
          return TALER_MHD_reply_static (
            rc->connection,
            MHD_HTTP_NO_CONTENT,
            NULL,
            NULL,
            0);
        }
        json_decref (xattributes);
        GNUNET_free (enc_attributes);
      }
      /* Finished, and with no or different attributes, conflict! */
      GNUNET_break_op (0);
      return TALER_MHD_reply_with_error (
        rc->connection,
        MHD_HTTP_CONFLICT,
        TALER_EC_EXCHANGE_KYC_FORM_ALREADY_UPLOADED,
        NULL);
    }
    /* This _should_ not be possible (! is_finished but non-null enc_attributes),
       but also cannot exactly hurt... */
    GNUNET_free (enc_attributes);
    ec = TALER_KYCLOGIC_check_form (jmeasures,
                                    uc->measure_index,
                                    uc->result,
                                    &form_name,
                                    &error_message);
    if (TALER_EC_NONE != ec)
    {
      GNUNET_break_op (0);
      json_decref (jmeasures);
      return TALER_MHD_reply_with_ec (
        rc->connection,
        ec,
        error_message);
    }
    json_decref (jmeasures);

    /* Setup KYC process (which we will then immediately 'finish') */
    qs = TEH_plugin->insert_kyc_requirement_process (
      TEH_plugin->cls,
      &h_payto,
      uc->measure_index,
      uc->legitimization_measure_serial_id,
      form_name,
      NULL,     /* provider account ID */
      NULL,     /* provider legi ID */
      &legi_process_row);
    if (qs <= 0)
    {
      GNUNET_break (0);
      GNUNET_free (form_name);
      return TALER_MHD_reply_with_error (
        rc->connection,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        TALER_EC_GENERIC_DB_STORE_FAILED,
        "insert_kyc_requirement_process");
    }
    qs = TEH_kyc_store_attributes (
      legi_process_row,
      &h_payto,
      form_name,
      NULL /* provider account */,
      NULL /* provider legi ID */,
      GNUNET_TIME_UNIT_FOREVER_ABS, /* expiration time */
      uc->result);
    GNUNET_free (form_name);
    if (0 >= qs)
    {
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (
        rc->connection,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        TALER_EC_GENERIC_DB_STORE_FAILED,
        "kyc_store_attributes");
    }

    uc->kat = TEH_kyc_run_measure_for_attributes (
      &rc->async_scope_id,
      legi_process_row,
      &h_payto,
      is_wallet,
      &aml_trigger_callback,
      uc);
    if (NULL == uc->kat)
    {
      GNUNET_break (0);
      return TALER_MHD_reply_with_error (
        rc->connection,
        MHD_HTTP_INTERNAL_SERVER_ERROR,
        TALER_EC_EXCHANGE_KYC_GENERIC_AML_LOGIC_BUG,
        "TEH_kyc_finished");
    }
    MHD_suspend_connection (uc->rc->connection);
    GNUNET_CONTAINER_DLL_insert (uc_head,
                                 uc_tail,
                                 uc);
    return MHD_YES;
  }
}
