/*
   This file is part of TALER
   Copyright (C) 2026 Taler Systems SA

   TALER is free software; you can redistribute it and/or modify it under the
   terms of the GNU 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 src/exchangedb/get_pending_aggregation.c
 * @brief Implementation of the get_pending_aggregation function for Postgres
 * @author Christian Grothoff
 */
#include "taler/taler_pq_lib.h"
#include "helper.h"
#include "exchange-database/get_pending_aggregation.h"


enum GNUNET_DB_QueryStatus
TALER_EXCHANGEDB_get_pending_aggregation (
  struct TALER_EXCHANGEDB_PostgresContext *pg,
  const struct TALER_WireTransferIdentifierRawP *wtid,
  struct TALER_FullPaytoHashP *wire_target_h_payto,
  struct TALER_FullPayto *payto_uri,
  struct TALER_Amount *total_deposited,
  struct TALER_Amount *total_refunded,
  struct TALER_Amount *total_deposit_fee)
{
  struct GNUNET_PQ_QueryParam params[] = {
    GNUNET_PQ_query_param_auto_from_type (wtid),
    GNUNET_PQ_query_param_end
  };
  struct GNUNET_PQ_ResultSpec rs[] = {
    GNUNET_PQ_result_spec_auto_from_type ("wire_target_h_payto",
                                          wire_target_h_payto),
    GNUNET_PQ_result_spec_string ("payto_uri",
                                  &payto_uri->full_payto),
    TALER_PQ_RESULT_SPEC_AMOUNT ("total_deposited",
                                 total_deposited),
    TALER_PQ_RESULT_SPEC_AMOUNT ("total_refunded",
                                 total_refunded),
    TALER_PQ_RESULT_SPEC_AMOUNT ("total_deposit_fee",
                                 total_deposit_fee),
    GNUNET_PQ_result_spec_end
  };

  /* This mirrors the arithmetic of `exchange_do_aggregate' (see
     do_aggregate.c) so that the result can be compared with what the exchange
     itself computed.  The one subtlety worth preserving is
     `fully_refunded_coins': the exchange does not keep the deposit fee of a
     coin whose deposit was refunded in full, so neither may we.

     The `NOT EXISTS' on wire_out is what makes this a *pending* aggregation;
     once the transfer is executed the query returns no rows and the caller
     drops the hold. */
  PREPARE (pg,
           "get_pending_aggregation",
           "WITH bd AS ("
           "  SELECT bdep.batch_deposit_serial_id"
           "        ,bdep.wire_target_h_payto"
           "    FROM aggregation_tracking atr"
           "    JOIN batch_deposits bdep"
           "      USING (batch_deposit_serial_id)"
           "   WHERE atr.wtid_raw=$1)"
           " ,cd AS ("
           "  SELECT cdep.coin_pub"
           "        ,cdep.batch_deposit_serial_id"
           "        ,cdep.amount_with_fee AS amount"
           "    FROM coin_deposits cdep"
           "   WHERE cdep.batch_deposit_serial_id IN"
           "         (SELECT batch_deposit_serial_id FROM bd))"
           " ,ref AS ("
           "  SELECT r.amount_with_fee AS refund"
           "        ,r.coin_pub"
           "        ,r.batch_deposit_serial_id"
           "    FROM refunds r"
           "   WHERE r.batch_deposit_serial_id IN"
           "         (SELECT batch_deposit_serial_id FROM bd))"
           " ,ref_by_coin AS ("
           "  SELECT SUM((ref.refund).val) AS sum_val"
           "        ,SUM((ref.refund).frac) AS sum_frac"
           "        ,coin_pub"
           "        ,batch_deposit_serial_id"
           "    FROM ref"
           "   GROUP BY coin_pub, batch_deposit_serial_id)"
           " ,fully_refunded_coins AS ("
           "  SELECT cd.coin_pub"
           "        ,cd.batch_deposit_serial_id"
           "    FROM ref_by_coin n"
           "    JOIN cd"
           "      ON (n.coin_pub = cd.coin_pub"
           "      AND n.batch_deposit_serial_id = cd.batch_deposit_serial_id"
           "      AND n.sum_val + n.sum_frac / 100000000 = (cd.amount).val"
           "      AND n.sum_frac % 100000000 = (cd.amount).frac))"
           " ,fees AS ("
           "  SELECT denom.fee_deposit AS fee"
           "    FROM cd"
           "    JOIN known_coins kc"
           "      USING (coin_pub)"
           "    JOIN denominations denom"
           "      USING (denominations_serial)"
           "   WHERE (cd.coin_pub, cd.batch_deposit_serial_id) NOT IN"
           "         (SELECT coin_pub, batch_deposit_serial_id"
           "            FROM fully_refunded_coins))"
           "SELECT"
           "  (SELECT wire_target_h_payto FROM bd LIMIT 1)"
           "    AS wire_target_h_payto"
           " ,(SELECT wt.payto_uri"
           "     FROM wire_targets wt"
           "    WHERE wt.wire_target_h_payto="
           "          (SELECT wire_target_h_payto FROM bd LIMIT 1))"
           "    AS payto_uri"
           " ,ROW(COALESCE(SUM((cd.amount).val),0)"
           "      + COALESCE(SUM((cd.amount).frac),0) / 100000000"
           "     ,COALESCE(SUM((cd.amount).frac),0) % 100000000)::taler_amount"
           "    AS total_deposited"
           " ,(SELECT ROW(COALESCE(SUM((ref.refund).val),0)"
           "              + COALESCE(SUM((ref.refund).frac),0) / 100000000"
           "             ,COALESCE(SUM((ref.refund).frac),0)"
           "              % 100000000)::taler_amount"
           "     FROM ref)"
           "    AS total_refunded"
           " ,(SELECT ROW(COALESCE(SUM((fees.fee).val),0)"
           "              + COALESCE(SUM((fees.fee).frac),0) / 100000000"
           "             ,COALESCE(SUM((fees.fee).frac),0)"
           "              % 100000000)::taler_amount"
           "     FROM fees)"
           "    AS total_deposit_fee"
           "  FROM cd"
           " WHERE NOT EXISTS ("
           "   SELECT 1 FROM wire_out wo WHERE wo.wtid_raw=$1)"
           " HAVING COUNT(*) > 0;");
  return GNUNET_PQ_eval_prepared_singleton_select (
    pg->conn,
    "get_pending_aggregation",
    params,
    rs);
}
