/*
 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 {
  AbsoluteTime,
  AmountString,
  Duration,
  TransactionMajorState,
  TransactionMinorState,
  j2s,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
  createSimpleTestkudosEnvironmentV3,
  withdrawViaBankV3,
} from "../harness/environments.js";
import {
  GlobalTestState,
  getTestHarnessPaytoForLabel,
} from "../harness/harness.js";

export async function runDepositMergeTest(t: GlobalTestState) {
  // Set up test environment

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

  // Withdraw digital cash into the wallet.

  const withdrawalResult = await withdrawViaBankV3(t, {
    walletClient,
    bankClient,
    exchange,
    amount: "TESTKUDOS:50",
  });

  await withdrawalResult.withdrawalFinishedCond;

  const depositPaytoUri = getTestHarnessPaytoForLabel("foo");
  const bal = await walletClient.call(WalletApiOperation.GetBalances, {});
  t.assertAmountEquals(bal.balances[0].available, "TESTKUDOS:49.83");

  t.logStep("before first deposit");

  /**
   * first deposit
   */
  const depositGroupResult1 = await walletClient.client.call(
    WalletApiOperation.CreateDepositGroup,
    {
      amount: "TESTKUDOS:3" as AmountString,
      depositPaytoUri,
      wireDeadline: AbsoluteTime.toProtocolTimestamp(
        AbsoluteTime.addDuration(
          AbsoluteTime.now(),
          Duration.fromSpec({ minutes: 5 }),
        ),
      ),
    },
  );
  const d1Id = depositGroupResult1.transactionId;

  t.logStep("after first deposit");

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

  await exchange.stop();
  // @ts-ignore duration is not forever
  exchange.setTimetravel(Duration.fromSpec({ minutes: 1 }).d_ms);
  await exchange.start();
  await exchange.pingUntilAvailable();
  // total time: 1 minute

  t.logStep("done with first timetravel");

  /**
   * second deposit after 1 minute
   */

  const depositGroupResult2 = await walletClient.client.call(
    WalletApiOperation.CreateDepositGroup,
    {
      amount: "TESTKUDOS:3" as AmountString,
      depositPaytoUri,
      wireDeadline: AbsoluteTime.toProtocolTimestamp(
        AbsoluteTime.addDuration(
          AbsoluteTime.now(),
          Duration.fromSpec({ minutes: 5 }),
        ),
      ),
    },
  );

  const d2Id = depositGroupResult2.transactionId;

  t.logStep("done with second deposit");

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

  t.logStep("done with second deposit (track)");
  await exchange.stop();
  // @ts-ignore duration is not forever
  exchange.setTimetravel(Duration.fromSpec({ minutes: 2 }).d_ms);
  await exchange.start();
  await exchange.pingUntilAvailable();
  // total time: 2 minute

  /**
   * third deposit after 2 minute
   */
  const depositGroupResult3 = await walletClient.client.call(
    WalletApiOperation.CreateDepositGroup,
    {
      amount: "TESTKUDOS:3" as AmountString,
      depositPaytoUri,
      wireDeadline: AbsoluteTime.toProtocolTimestamp(
        AbsoluteTime.addDuration(
          AbsoluteTime.now(),
          Duration.fromSpec({ minutes: 5 }),
        ),
      ),
    },
  );

  const d3Id = depositGroupResult3.transactionId;

  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: d3Id,
    txState: {
      major: TransactionMajorState.Finalizing,
      minor: TransactionMinorState.Track,
    },
  });
  await exchange.stop();
  // @ts-ignore duration is not forever
  exchange.setTimetravel(Duration.fromSpec({ minutes: 3 }).d_ms);
  await exchange.start();
  await exchange.pingUntilAvailable();
  // total time: 3 minute

  /**
   * otherwise we get ECONNRESET in tx state because of the exchange restart
   */
  await walletClient.call(WalletApiOperation.RetryTransaction, {
    transactionId: d1Id,
  });
  await walletClient.call(WalletApiOperation.RetryTransaction, {
    transactionId: d2Id,
  });
  await walletClient.call(WalletApiOperation.RetryTransaction, {
    transactionId: d3Id,
  });

  /**
   * check deposit tx after 3 minute, all pending
   */
  {
    const d1Details = await walletClient.call(
      WalletApiOperation.GetTransactionById,
      { transactionId: d1Id },
    );

    const d2Details = await walletClient.call(
      WalletApiOperation.GetTransactionById,
      { transactionId: d2Id },
    );

    const d3Details = await walletClient.call(
      WalletApiOperation.GetTransactionById,
      { transactionId: d3Id },
    );
    console.log(j2s({ d3Details, d2Details, d1Details }));

    t.assertTrue(d1Details.txState.major === TransactionMajorState.Finalizing);
    t.assertTrue(d1Details.txState.minor === TransactionMinorState.Track);
    t.assertTrue(d2Details.txState.major === TransactionMajorState.Finalizing);
    t.assertTrue(d2Details.txState.minor === TransactionMinorState.Track);
    t.assertTrue(d3Details.txState.major === TransactionMajorState.Finalizing);
    t.assertTrue(d3Details.txState.minor === TransactionMinorState.Track);
  }

  ///////////////////////////////////////////

  await exchange.stop();
  // @ts-ignore duration is not forever
  exchange.setTimetravel(Duration.fromSpec({ minutes: 6 }).d_ms);
  await exchange.start();
  await exchange.pingUntilAvailable();
  // total time: 6 minute

  await walletClient.call(WalletApiOperation.RetryTransaction, {
    transactionId: d1Id,
  });
  await walletClient.call(WalletApiOperation.RetryTransaction, {
    transactionId: d2Id,
  });
  await walletClient.call(WalletApiOperation.RetryTransaction, {
    transactionId: d3Id,
  });

  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: d1Id,
    txState: {
      major: TransactionMajorState.Done,
    },
  });
  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: d2Id,
    txState: {
      major: TransactionMajorState.Done,
    },
  });
  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: d3Id,
    txState: {
      major: TransactionMajorState.Done,
    },
  });
  /* check deposit tx after 6 minute, first one should already be wired since default
   * wire deadline is 5 minutes
   *
   * other deposit should already be completed since it should use the same wire transfer
   */
  {
    const d1Details = await walletClient.call(
      WalletApiOperation.GetTransactionById,
      { transactionId: d1Id },
    );
    const d2Details = await walletClient.call(
      WalletApiOperation.GetTransactionById,
      { transactionId: d2Id },
    );
    const d3Details = await walletClient.call(
      WalletApiOperation.GetTransactionById,
      { transactionId: d3Id },
    );
    console.log(j2s({ d3Details, d2Details, d1Details }));
    t.assertTrue(d1Details.txState.major === TransactionMajorState.Done);
    t.assertTrue(d2Details.txState.major === TransactionMajorState.Done);
    t.assertTrue(d3Details.txState.major === TransactionMajorState.Done);
  }
}

runDepositMergeTest.suites = ["wallet"];
