/*
 This file is part of GNU Taler
 (C) 2024 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/>
 */

import {
  AbsoluteTime,
  Duration,
  j2s,
  TransactionMajorState,
  TransactionMinorState,
  TransactionPeerPushDebit,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { CoinConfig } from "../harness/denomStructures.js";
import {
  createSimpleTestkudosEnvironmentV3,
  createWalletDaemonWithClient,
  withdrawViaBankV3,
} from "../harness/environments.js";
import { GlobalTestState } from "../harness/harness.js";

const coinCommon = {
  cipher: "RSA" as const,
  durationLegal: "3 years",
  durationSpend: "2 years",
  durationWithdraw: "7 days",
  feeDeposit: "TESTKUDOS:0",
  feeRefresh: "TESTKUDOS:0",
  feeRefund: "TESTKUDOS:0",
  feeWithdraw: "TESTKUDOS:0",
  rsaKeySize: 1024,
};

const topsValues = [
  "TESTKUDOS:0.0025",
  "TESTKUDOS:0.005",
  "TESTKUDOS:0.01",
  "TESTKUDOS:0.02",
  "TESTKUDOS:0.04",
  "TESTKUDOS:0.08",
  "TESTKUDOS:0.16",
  "TESTKUDOS:0.32",
  "TESTKUDOS:0.64",
  "TESTKUDOS:1.28",
  "TESTKUDOS:2.56",
  "TESTKUDOS:5.12",
  "TESTKUDOS:10.24",
  "TESTKUDOS:20.48",
  "TESTKUDOS:40.96",
  "TESTKUDOS:81.92",
  "TESTKUDOS:163.84",
  "TESTKUDOS:327.68",
  "TESTKUDOS:655.36",
];

const coinConfigList: CoinConfig[] = topsValues.map(
  (value, idx): CoinConfig => ({
    ...coinCommon,
    name: `n${idx}`,
    value,
  }),
);

/**
 * Test deposit with a large number of coins.
 *
 * In particular, this checks that the wallet properly
 * splits deposits into batches with <=64 coins per batch.
 *
 * Since we use an artificially large number of coins, this
 * test is a bit slower than other tests.
 */
export async function runDepositTwiceTest(t: GlobalTestState) {
  // Set up test environment
  const {
    walletClient: aliceWallet,
    bankClient,
    exchange,
  } = await createSimpleTestkudosEnvironmentV3(t, coinConfigList);

  const { walletClient: bobWallet } = await createWalletDaemonWithClient(t, {
    name: "booWallet",
  });

  // I sent 50.- to device A and 5.-- to device B from differen bank accounts.
  const aliceWithdrawRes = await withdrawViaBankV3(t, {
    walletClient: aliceWallet,
    bankClient,
    exchange,
    amount: "TESTKUDOS:50",
  });
  const bobWithdrawRes = await withdrawViaBankV3(t, {
    walletClient: bobWallet,
    bankClient,
    exchange,
    amount: "TESTKUDOS:5",
  });

  await Promise.all([
    aliceWithdrawRes.withdrawalFinishedCond,
    bobWithdrawRes.withdrawalFinishedCond,
  ]);

  // A is validated by postal letter and B by phone number.
  // I made some transactions between the two devices (and no other).
  // I did send back 5.- from device B to my bank account (the 0.01 for the additional verification is no where but the 5.-- arrived in the bank account).
  const bobDepositResp = await bobWallet.call(
    WalletApiOperation.CreateDepositGroup,
    {
      amount: "TESTKUDOS:5",
      depositPaytoUri: bobWithdrawRes.accountPaytoUri,
    },
  );

  await bobWallet.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: bobDepositResp.transactionId,
    txState: {
      major: TransactionMajorState.Finalizing,
      minor: TransactionMinorState.Track,
    },
  });

  {
    const aliceBalance = await aliceWallet.call(
      WalletApiOperation.GetBalances,
      {},
    );
    const bobBalance = await bobWallet.call(WalletApiOperation.GetBalances, {});
    console.log(`BALANCES : ${j2s({ aliceBalance, bobBalance })}`);
  }

  const tomorrow = AbsoluteTime.toProtocolTimestamp(
    AbsoluteTime.addDuration(
      AbsoluteTime.now(),
      Duration.fromSpec({ days: 1 }),
    ),
  );

  // I did send an other 10.- from A to B.
  const initiate = await aliceWallet.call(
    WalletApiOperation.InitiatePeerPushDebit,
    {
      partialContractTerms: {
        summary: "sending 10 to bob",
        amount: "TESTKUDOS:10",
        purse_expiration: tomorrow,
      },
    },
  );

  await aliceWallet.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: initiate.transactionId,
    txState: {
      major: TransactionMajorState.Pending,
      minor: TransactionMinorState.Ready,
    },
  });

  const aliceSending10Transaction = (await aliceWallet.call(
    WalletApiOperation.GetTransactionById,
    {
      transactionId: initiate.transactionId,
    },
  )) as TransactionPeerPushDebit;

  const bobAcceptingTransaction = await bobWallet.call(
    WalletApiOperation.PreparePeerPushCredit,
    {
      talerUri: aliceSending10Transaction.talerUri!,
    },
  );

  await bobWallet.call(WalletApiOperation.ConfirmPeerPushCredit, {
    transactionId: bobAcceptingTransaction.transactionId,
  });

  await bobWallet.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: bobAcceptingTransaction.transactionId,
    txState: {
      major: TransactionMajorState.Done,
    },
  });

  {
    const aliceBalance = await aliceWallet.call(
      WalletApiOperation.GetBalances,
      {},
    );
    const bobBalance = await bobWallet.call(WalletApiOperation.GetBalances, {});
    console.log(`BALANCES : ${j2s({ aliceBalance, bobBalance })}`);
  }

  // I tried to do a deposite to the the same bank account from B again and get the error message
  const maxDepositResp = await bobWallet.call(
    WalletApiOperation.GetMaxDepositAmount,
    {
      currency: "TESTKUDOS",
      depositPaytoUri: bobWithdrawRes.accountPaytoUri,
    },
  );

  console.log(`DEPOSIT : ${j2s(maxDepositResp)}`);

  {
    const aliceBalance = await aliceWallet.call(
      WalletApiOperation.GetBalances,
      {},
    );
    const bobBalance = await bobWallet.call(WalletApiOperation.GetBalances, {});
    console.log(`BALNACES : ${j2s({ aliceBalance, bobBalance })}`);
  }
}

runDepositTwiceTest.suites = ["wallet"];
