/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.
 GNU 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.
 GNU 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
 GNU Taler; see the file COPYING.  If not, see 
 */
/**
 * Imports.
 */
import {
  AmountJson,
  BalancesResponse,
  Amounts,
  Logger,
} from "@gnu-taler/taler-util";
import { CoinStatus, WalletStoresV1 } from "../db.js";
import { GetReadOnlyAccess } from "../util/query.js";
import { InternalWalletState } from "../common.js";
const logger = new Logger("operations/balance.ts");
interface WalletBalance {
  available: AmountJson;
  pendingIncoming: AmountJson;
  pendingOutgoing: AmountJson;
}
/**
 * Get balance information.
 */
export async function getBalancesInsideTransaction(
  ws: InternalWalletState,
  tx: GetReadOnlyAccess<{
    reserves: typeof WalletStoresV1.reserves;
    coins: typeof WalletStoresV1.coins;
    refreshGroups: typeof WalletStoresV1.refreshGroups;
    withdrawalGroups: typeof WalletStoresV1.withdrawalGroups;
  }>,
): Promise {
  const balanceStore: Record = {};
  /**
   * Add amount to a balance field, both for
   * the slicing by exchange and currency.
   */
  const initBalance = (currency: string): WalletBalance => {
    const b = balanceStore[currency];
    if (!b) {
      balanceStore[currency] = {
        available: Amounts.getZero(currency),
        pendingIncoming: Amounts.getZero(currency),
        pendingOutgoing: Amounts.getZero(currency),
      };
    }
    return balanceStore[currency];
  };
  // Initialize balance to zero, even if we didn't start withdrawing yet.
  await tx.reserves.iter().forEach((r) => {
    const b = initBalance(r.currency);
    if (!r.initialWithdrawalStarted) {
      b.pendingIncoming = Amounts.add(
        b.pendingIncoming,
        r.initialDenomSel.totalCoinValue,
      ).amount;
    }
  });
  await tx.coins.iter().forEach((c) => {
    // Only count fresh coins, as dormant coins will
    // already be in a refresh session.
    if (c.status === CoinStatus.Fresh) {
      const b = initBalance(c.currentAmount.currency);
      b.available = Amounts.add(b.available, c.currentAmount).amount;
    }
  });
  await tx.refreshGroups.iter().forEach((r) => {
    // Don't count finished refreshes, since the refresh already resulted
    // in coins being added to the wallet.
    if (r.timestampFinished) {
      return;
    }
    for (let i = 0; i < r.oldCoinPubs.length; i++) {
      const session = r.refreshSessionPerCoin[i];
      if (session) {
        const b = initBalance(session.amountRefreshOutput.currency);
        // We are always assuming the refresh will succeed, thus we
        // report the output as available balance.
        b.available = Amounts.add(
          b.available,
          session.amountRefreshOutput,
        ).amount;
      } else {
        const b = initBalance(r.inputPerCoin[i].currency);
        b.available = Amounts.add(
          b.available,
          r.estimatedOutputPerCoin[i],
        ).amount;
      }
    }
  });
  await tx.withdrawalGroups.iter().forEach((wds) => {
    if (wds.timestampFinish) {
      return;
    }
    const b = initBalance(wds.denomsSel.totalWithdrawCost.currency);
    b.pendingIncoming = Amounts.add(
      b.pendingIncoming,
      wds.denomsSel.totalCoinValue,
    ).amount;
  });
  const balancesResponse: BalancesResponse = {
    balances: [],
  };
  Object.keys(balanceStore)
    .sort()
    .forEach((c) => {
      const v = balanceStore[c];
      balancesResponse.balances.push({
        available: Amounts.stringify(v.available),
        pendingIncoming: Amounts.stringify(v.pendingIncoming),
        pendingOutgoing: Amounts.stringify(v.pendingOutgoing),
        hasPendingTransactions: false,
        requiresUserInput: false,
      });
    });
  return balancesResponse;
}
/**
 * Get detailed balance information, sliced by exchange and by currency.
 */
export async function getBalances(
  ws: InternalWalletState,
): Promise {
  logger.trace("starting to compute balance");
  const wbal = await ws.db
    .mktx((x) => ({
      coins: x.coins,
      refreshGroups: x.refreshGroups,
      reserves: x.reserves,
      purchases: x.purchases,
      withdrawalGroups: x.withdrawalGroups,
    }))
    .runReadOnly(async (tx) => {
      return getBalancesInsideTransaction(ws, tx);
    });
  logger.trace("finished computing wallet balance");
  return wbal;
}