/*
 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 {
  TalerCorebankApiClient,
  TransactionIdStr,
  TransactionMajorState,
  TransactionMinorState,
  TransactionStatePattern,
  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";

/**
 * Test two wallets scanning the same taler:// withdraw QR code.
 */
export async function runWithdrawalConflictTest(t: GlobalTestState) {
  // Set up test environment
  const [
    { walletClient: wallet1, bankClient, bank },
    { walletClient: wallet2 },
  ] = await Promise.all([
    createSimpleTestkudosEnvironmentV3(t),
    createWalletDaemonWithClient(t, {
      name: "w2",
    })
  ]);
  const user = await bankClient.createRandomBankUser();
  bankClient.setAuth(user);

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

  const [wdetails, wprepare1, wprepare2] = await Promise.all([
    wallet1.call(
      WalletApiOperation.GetWithdrawalDetailsForUri,
      {
        talerWithdrawUri: wop.taler_withdraw_uri,
      },
    ), wallet1.call(
      WalletApiOperation.PrepareBankIntegratedWithdrawal,
      {
        talerWithdrawUri: wop.taler_withdraw_uri,
      },
    ), wallet2.call(
      WalletApiOperation.PrepareBankIntegratedWithdrawal,
      {
        talerWithdrawUri: wop.taler_withdraw_uri,
      },
    )
  ]);

  t.logStep("stopping bank");

  // Make sure both wallets go into the state where they need
  // to register the reserve info with the bank.
  await bank.stop();

  await Promise.all([
    wallet1.call(WalletApiOperation.ConfirmWithdrawal, {
      transactionId: wprepare1.transactionId,
      amount,
      exchangeBaseUrl: wdetails.defaultExchangeBaseUrl!,
    }),
    wallet2.call(WalletApiOperation.ConfirmWithdrawal, {
      transactionId: wprepare2.transactionId,
      amount,
      exchangeBaseUrl: wprepare2.info.defaultExchangeBaseUrl!,
    })
  ]);

  t.logStep("withdrawals-confirmed-by-wallets");

  await Promise.all([
    wallet1.call(WalletApiOperation.TestingWaitTransactionState, {
      transactionId: wprepare1.transactionId as TransactionIdStr,
      txState: {
        major: TransactionMajorState.Pending,
        minor: TransactionMinorState.BankRegisterReserve,
      },
    }),
    wallet2.call(WalletApiOperation.TestingWaitTransactionState, {
      transactionId: wprepare2.transactionId as TransactionIdStr,
      txState: {
        major: TransactionMajorState.Pending,
        minor: TransactionMinorState.BankRegisterReserve,
      },
    })
  ]);

  await bank.start();

  // One wallet will succeed, another one will have an aborted transaction.
  // Order is non-determinstic.

  // The "aborted(bank)" state is only present because the taler-exchange-fakebank
  // returns 404 when one wallet completes the withdrawal, which is a
  // bug.
  const expectedFinalStates: TransactionStatePattern[] = [
    {
      major: TransactionMajorState.Done,
    },
    {
      major: TransactionMajorState.Aborted,
      minor: "*",
    },
    {
      major: TransactionMajorState.Failed,
      minor: "*",
    },
  ];

  await Promise.all([
    wallet1.call(WalletApiOperation.TestingWaitTransactionState, {
      transactionId: wprepare1.transactionId,
      txState: expectedFinalStates,
    }),
    wallet2.call(WalletApiOperation.TestingWaitTransactionState, {
      transactionId: wprepare2.transactionId,
      txState: expectedFinalStates,
    })
  ]);

  const [tx1, tx2] = await Promise.all([
    wallet1.call(WalletApiOperation.GetTransactionById, {
      transactionId: wprepare1.transactionId
    }),
    wallet2.call(WalletApiOperation.GetTransactionById, {
      transactionId: wprepare2.transactionId
    })
  ]);

  // FIXME one of them should succeed
  t.assertTrue(tx1.txState.major === TransactionMajorState.Failed)
  t.assertTrue(tx2.txState.major === TransactionMajorState.Failed)

  /*if (tx1.txState.major === TransactionMajorState.Done) {
    t.assertTrue(tx2.txState.major === TransactionMajorState.Aborted || tx2.txState.major === TransactionMajorState.Failed)
  } else {
    t.assertTrue(tx2.txState.major === TransactionMajorState.Done)
    t.assertTrue(tx1.txState.major === TransactionMajorState.Aborted || tx1.txState.major === TransactionMajorState.Failed)
  }*/
}

runWithdrawalConflictTest.suites = ["wallet"];
