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

/**
 * Run test for basic, bank-integrated withdrawal.
 */
export async function runWithdrawalBankIntegratedTest(t: GlobalTestState) {
  // Set up test environment
  const [
    { walletClient: wallet1, bankClient, exchange },
    { walletClient: wallet2 },
  ] = await Promise.all([
    createSimpleTestkudosEnvironmentV3(t),
    createWalletDaemonWithClient(t, {
      name: "w2",
    }),
  ]);
  const user = await bankClient.createRandomBankUser();
  bankClient.setAuth(user);

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

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

  await wallet1.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
    talerWithdrawUri: withdrawal.taler_withdraw_uri,
  });

  t.logStep("Withdraw");

  const { transactionId } = await wallet1.call(
    WalletApiOperation.AcceptBankIntegratedWithdrawal,
    {
      exchangeBaseUrl: exchange.baseUrl,
      talerWithdrawUri: withdrawal.taler_withdraw_uri,
    },
  );

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

  t.logStep("wait finished");
  const withdrawalFinishedCond = wallet1.waitForNotificationCond(
    (x) =>
      x.type === NotificationType.TransactionStateTransition &&
      x.transactionId === transactionId &&
      x.newTxState.major === TransactionMajorState.Done,
  );

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

  t.logStep("Do it twice to check idempotency");
  {
    const idempotent = await wallet1.call(
      WalletApiOperation.AcceptBankIntegratedWithdrawal,
      {
        exchangeBaseUrl: exchange.baseUrl,
        talerWithdrawUri: withdrawal.taler_withdraw_uri,
      },
    );
    t.assertTrue(idempotent.transactionId === transactionId);
  }

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

  t.logStep("Check status before withdrawal is confirmed by bank.");
  {
    const tx = await wallet1.call(WalletApiOperation.GetTransactionById, {
      transactionId,
    });
    console.log("transaction before confirmation:", j2s(tx));
    t.assertTrue(tx.type === TransactionType.Withdrawal);
    t.assertTrue(
      tx.withdrawalDetails.type === WithdrawalType.TalerBankIntegrationApi,
    );
    t.assertTrue(tx.withdrawalDetails.confirmed === false);
    t.assertTrue(tx.withdrawalDetails.reserveIsReady === false);
  }

  t.logStep("Confirm it");

  await wallet1.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId,
    txState: {
      major: TransactionMajorState.Pending,
      minor: TransactionMinorState.BankConfirmTransfer,
    },
  });

  await bankClient.confirmWithdrawalOperation(user.username, {
    withdrawalOperationId: withdrawal.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 tx = await wallet1.call(WalletApiOperation.GetTransactionById, {
      transactionId,
    });
    console.log("transactions after confirmation:", j2s(tx));
    t.assertTrue(tx.type === TransactionType.Withdrawal);
    t.assertTrue(
      tx.withdrawalDetails.type === WithdrawalType.TalerBankIntegrationApi,
    );
    t.assertTrue(tx.withdrawalDetails.confirmed === true);
    t.assertTrue(tx.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 tx = await wallet1.call(WalletApiOperation.GetTransactionById, {
      transactionId,
    });
    console.log("transactions after reserve ready:", j2s(tx));
    t.assertTrue(tx.type === TransactionType.Withdrawal);
    t.assertTrue(
      tx.withdrawalDetails.type === WithdrawalType.TalerBankIntegrationApi,
    );
    t.assertTrue(tx.withdrawalDetails.confirmed === true);
    t.assertTrue(tx.withdrawalDetails.reserveIsReady === true);
  }

  await withdrawalFinishedCond;

  t.logStep("Check rescan idempotent");

  {
    await wallet1.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
      talerWithdrawUri: withdrawal.taler_withdraw_uri,
    });

    await wallet1.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
      exchangeBaseUrl: exchange.baseUrl,
      talerWithdrawUri: withdrawal.taler_withdraw_uri,
    });
  }

  t.logStep("Check other wallet scan after completion");

  {
    await wallet2.call(WalletApiOperation.GetWithdrawalDetailsForUri, {
      talerWithdrawUri: withdrawal.taler_withdraw_uri,
    });
    // FIXME #9683 wallet should already know withdrawal already completed by wallet1

    const e = await t.assertThrowsTalerErrorAsync(async () => {
      await wallet2.call(WalletApiOperation.AcceptBankIntegratedWithdrawal, {
        exchangeBaseUrl: exchange.baseUrl,
        talerWithdrawUri: withdrawal.taler_withdraw_uri,
      });
    });
    t.assertTrue(
      e.errorDetail.code ===
        TalerErrorCode.WALLET_WITHDRAWAL_OPERATION_ABORTED_BY_BANK,
    );
    // FIXME #9683 can we have a more proper error code than this ?
  }

  t.logStep("Check balance");

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

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

runWithdrawalBankIntegratedTest.suites = ["wallet"];
