/*
 This file is part of GNU Taler
 (C) 2022 Taler Systems S.A.

 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 <http://www.gnu.org/licenses/>
 */

/**
 * Imports.
 */
import {
  AmountString,
  buildCodecForObject,
  codecForNumber,
  codecForString,
  codecOptional,
  Logger,
} from "@gnu-taler/taler-util";
import { createPlatformHttpLib } from "@gnu-taler/taler-util/http";
import {
  CryptoDispatcher,
  SynchronousCryptoWorkerFactoryPlain,
} from "@gnu-taler/taler-wallet-core";
import {
  checkReserve,
  createTestingReserve,
  depositCoin,
  downloadExchangeInfo,
  findDenomOrThrow,
  refreshCoin,
  withdrawCoin,
} from "@gnu-taler/taler-wallet-core/dbless";

/**
 * Entry point for the benchmark.
 *
 * The benchmark runs against an existing Taler deployment and does not
 * set up its own services.
 */
export async function runBench2(configJson: any): Promise<void> {
  const logger = new Logger("Bench2");

  // Validate the configuration file for this benchmark.
  const benchConf = codecForBench2Config().decode(configJson);
  const curr = benchConf.currency;
  const cryptoDisp = new CryptoDispatcher(
    new SynchronousCryptoWorkerFactoryPlain(),
  );
  const cryptoApi = cryptoDisp.cryptoApi;

  const http = createPlatformHttpLib({
    enableThrottling: false,
  });

  const numIter = benchConf.iterations ?? 1;
  const numDeposits = benchConf.deposits ?? 5;

  const reserveAmount = (numDeposits + 1) * 10;

  const merchantPair = await cryptoApi.createEddsaKeypair({});
  const merchantPub = merchantPair.pub;
  const merchantPriv = merchantPair.priv;

  for (let i = 0; i < numIter; i++) {
    const exchangeInfo = await downloadExchangeInfo(benchConf.exchange, http);

    const reserveKeyPair = await cryptoApi.createEddsaKeypair({});

    console.log("creating fakebank reserve");

    await createTestingReserve({
      amount: `${curr}:${reserveAmount}`,
      exchangeInfo,
      corebankApiBaseUrl: benchConf.bank,
      http,
      reservePub: reserveKeyPair.pub,
    });

    console.log("waiting for reserve");

    await checkReserve(http, benchConf.exchange, reserveKeyPair.pub);

    console.log("reserve found");

    const d1 = findDenomOrThrow(exchangeInfo, `${curr}:8` as AmountString);

    for (let j = 0; j < numDeposits; j++) {
      console.log("withdrawing coin");
      const coin = await withdrawCoin({
        http,
        cryptoApi,
        reserveKeyPair: {
          reservePriv: reserveKeyPair.priv,
          reservePub: reserveKeyPair.pub,
        },
        denom: d1,
        exchangeBaseUrl: benchConf.exchange,
      });

      console.log("depositing coin");

      await depositCoin({
        amount: `${curr}:4` as AmountString,
        coin: coin,
        cryptoApi,
        exchangeBaseUrl: benchConf.exchange,
        http,
        depositPayto: benchConf.payto,
        merchantPriv,
      });

      const refreshDenoms = [
        findDenomOrThrow(exchangeInfo, `${curr}:1` as AmountString),
        findDenomOrThrow(exchangeInfo, `${curr}:1` as AmountString),
      ];

      console.log("refreshing coin");

      await refreshCoin({
        oldCoin: coin,
        cryptoApi,
        http,
        newDenoms: refreshDenoms,
      });

      console.log("refresh done");
    }
  }
}

/**
 * Format of the configuration file passed to the benchmark
 */
interface Bench2Config {
  /**
   * Base URL of the bank.
   */
  bank: string;

  /**
   * Payto url for deposits.
   */
  payto: string;

  /**
   * Base URL of the exchange.
   */
  exchange: string;

  /**
   * How many withdraw/deposit iterations should be made?
   * Defaults to 1.
   */
  iterations?: number;

  currency: string;

  deposits?: number;
}

/**
 * Schema validation codec for Bench1Config.
 */
const codecForBench2Config = () =>
  buildCodecForObject<Bench2Config>()
    .property("bank", codecForString())
    .property("payto", codecForString())
    .property("exchange", codecForString())
    .property("iterations", codecOptional(codecForNumber()))
    .property("deposits", codecOptional(codecForNumber()))
    .property("currency", codecForString())
    .build("Bench2Config");
