/*
 This file is part of GNU Taler
 (C) 2020 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 {
  NotificationType,
  TransactionMajorState,
  TransactionMinorState,
  TransactionType,
  WithdrawalType,
  j2s,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { GlobalTestState } from "../harness/harness.js";
import { createSimpleTestkudosEnvironmentV3 } from "../harness/helpers.js";

/**
 * Run test for basic, bank-integrated withdrawal.
 */
export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
  // Set up test environment

  const { walletClient, bankClient, exchange } =
    await createSimpleTestkudosEnvironmentV3(t);

  // Create a withdrawal operation
  const user = await bankClient.createRandomBankUser();
  bankClient.setAuth(user);
  const wop = await bankClient.createWithdrawalOperation(
    user.username,
    "TESTKUDOS:10",
  );

  t.logStep("Hand it to the wallet")

  const r1 = await walletClient.client.call(
    WalletApiOperation.GetWithdrawalDetailsForUri,
    {
      talerWithdrawUri: wop.taler_withdraw_uri,
    },
  );

  t.logStep("Withdraw")

  const r2 = await walletClient.client.call(
    WalletApiOperation.AcceptBankIntegratedWithdrawal,
    {
      exchangeBaseUrl: exchange.baseUrl,
      talerWithdrawUri: wop.taler_withdraw_uri,
    },
  );

  t.logStep("wait confirmed")
  const withdrawalBankConfirmedCond = walletClient.waitForNotificationCond(
    (x) => {
      return (
        x.type === NotificationType.TransactionStateTransition &&
        x.transactionId === r2.transactionId &&
        x.newTxState.major === TransactionMajorState.Pending &&
        x.newTxState.minor === TransactionMinorState.ExchangeWaitReserve
      );
    },
  );

  t.logStep("wait finished")
  const withdrawalFinishedCond = walletClient.waitForNotificationCond((x) => {
    return (
      x.type === NotificationType.TransactionStateTransition &&
      x.transactionId === r2.transactionId &&
      x.newTxState.major === TransactionMajorState.Done
    );
  });

  t.logStep("wait withdraw coins")
  const withdrawalReserveReadyCond = walletClient.waitForNotificationCond(
    (x) => {
      return (
        x.type === NotificationType.TransactionStateTransition &&
        x.transactionId === r2.transactionId &&
        x.newTxState.major === TransactionMajorState.Pending &&
        x.newTxState.minor === TransactionMinorState.WithdrawCoins
      );
    },
  );

  t.logStep("Do it twice to check idempotency")
  const r3 = await walletClient.client.call(
    WalletApiOperation.AcceptBankIntegratedWithdrawal,
    {
      exchangeBaseUrl: exchange.baseUrl,
      talerWithdrawUri: wop.taler_withdraw_uri,
    },
  );

  t.logStep("stop wirewatch")
  await exchange.stopWirewatch();

  t.logStep("Check status before withdrawal is confirmed by bank.")
  {
    const txn = await walletClient.client.call(
      WalletApiOperation.GetTransactions,
      {},
    );
    console.log("transactions before confirmation:", j2s(txn));
    const tx0 = txn.transactions[0];
    t.assertTrue(tx0.type === TransactionType.Withdrawal);
    t.assertTrue(
      tx0.withdrawalDetails.type === WithdrawalType.TalerBankIntegrationApi,
    );
    t.assertTrue(tx0.withdrawalDetails.confirmed === false);
    t.assertTrue(tx0.withdrawalDetails.reserveIsReady === false);
  }

  t.logStep("Confirm it")

  await bankClient.confirmWithdrawalOperation(user.username, {
    withdrawalOperationId: wop.withdrawal_id,
  });

  await withdrawalBankConfirmedCond;

  // Check status after withdrawal is confirmed by bank,
  // but before funds are wired to the exchange.
  t.logStep("Check status after withdrawal")
  {
    const txn = await walletClient.client.call(
      WalletApiOperation.GetTransactions,
      {},
    );
    console.log("transactions after confirmation:", j2s(txn));
    const tx0 = txn.transactions[0];
    t.assertTrue(tx0.type === TransactionType.Withdrawal);
    t.assertTrue(
      tx0.withdrawalDetails.type === WithdrawalType.TalerBankIntegrationApi,
    );
    t.assertTrue(tx0.withdrawalDetails.confirmed === true);
    t.assertTrue(tx0.withdrawalDetails.reserveIsReady === false);
  }

  t.logStep("start wirewatch")
  await exchange.startWirewatch();

  t.logStep("wait reserve")
  await withdrawalReserveReadyCond;

  t.logStep("Check status after funds were wired.")
  {
    const txn = await walletClient.client.call(
      WalletApiOperation.GetTransactions,
      {},
    );
    console.log("transactions after reserve ready:", j2s(txn));
    const tx0 = txn.transactions[0];
    t.assertTrue(tx0.type === TransactionType.Withdrawal);
    t.assertTrue(
      tx0.withdrawalDetails.type === WithdrawalType.TalerBankIntegrationApi,
    );
    t.assertTrue(tx0.withdrawalDetails.confirmed === true);
    t.assertTrue(tx0.withdrawalDetails.reserveIsReady === true);
  }

  await withdrawalFinishedCond;

  t.logStep("Check balance")

  const balResp = await walletClient.client.call(
    WalletApiOperation.GetBalances,
    {},
  );
  t.assertAmountEquals("TESTKUDOS:9.72", balResp.balances[0].available);

  const txn = await walletClient.client.call(
    WalletApiOperation.GetTransactions,
    {},
  );
  console.log(`transactions: ${j2s(txn)}`);
}

runWithdrawalBankIntegratedTest.suites = ["wallet"];
