/*
 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,
  ConfirmPayResultType,
  Duration,
  j2s,
  PreparePayResultType,
  succeedOrThrow,
  TalerCoreBankHttpClient,
  TalerMerchantApi,
  TalerMerchantInstanceHttpClient,
  TalerProtocolTimestamp,
  UserAndToken,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { CoinConfig } from "../harness/denomStructures.js";
import {
  createSimpleTestkudosEnvironmentV3,
  withdrawViaBankV3,
} from "../harness/environments.js";
import { GlobalTestState, waitMs } 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 coinConfigList: CoinConfig[] = [
  {
    ...coinCommon,
    name: "n1",
    value: "TESTKUDOS:1",
  },
];

/**
 * 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 runMerchantDepositLargeTest(t: GlobalTestState) {
  // Set up test environment
  const {
    walletClient,
    bankClient,
    exchange,
    bank,
    exchangeBankAccount,
    merchantAdminAccessToken,
    merchant,
  } = await createSimpleTestkudosEnvironmentV3(t, coinConfigList, {
    forceLibeufin: true,
    exchangeConfig: {
      roundUnit: "TESTKUDOS:2",
      tinyAmount: "TESTKUDOS:2",
      overrideWireFee: "TESTKUDOS:0",
    },
  });

  // Withdraw digital cash into the wallet.
  const withdrawRes = await withdrawViaBankV3(t, {
    walletClient,
    bankClient,
    exchange,
    amount: "TESTKUDOS:200",
  });

  await withdrawRes.withdrawalFinishedCond;

  const order = {
    summary: "Buy me!",
    amount: "TESTKUDOS:100",
    fulfillment_url: "taler://fulfillment-success/thx",
    pay_deadline: AbsoluteTime.toProtocolTimestamp(
      AbsoluteTime.addDuration(
        AbsoluteTime.now(),
        Duration.fromSpec({ seconds: 20 }),
      ),
    ),
    wire_transfer_deadline: TalerProtocolTimestamp.now(),
  } satisfies TalerMerchantApi.Order;

  const merchantClient = new TalerMerchantInstanceHttpClient(
    merchant.makeInstanceBaseUrl(),
  );

  const orderResp = succeedOrThrow(
    await merchantClient.createOrder(merchantAdminAccessToken, {
      order: order,
      refund_delay: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ seconds: 1 }),
      ),
    }),
  );

  let orderStatus = succeedOrThrow(
    await merchantClient.getOrderDetails(
      merchantAdminAccessToken,
      orderResp.order_id,
    ),
  );

  t.assertTrue(orderStatus.order_status === "unpaid");

  // Make wallet pay for the order

  const preparePayResult = await walletClient.call(
    WalletApiOperation.PreparePayForUri,
    {
      talerPayUri: orderStatus.taler_pay_uri,
    },
  );

  t.assertTrue(
    preparePayResult.status === PreparePayResultType.PaymentPossible,
  );

  const diff = AbsoluteTime.difference(
    AbsoluteTime.fromProtocolTimestamp(
      preparePayResult.contractTerms.wire_transfer_deadline,
    ),
    AbsoluteTime.now(),
  );

  console.log(`wire transfer deadline in ${diff.d_ms}ms`);

  const r2 = await walletClient.call(WalletApiOperation.ConfirmPay, {
    transactionId: preparePayResult.transactionId,
  });

  t.assertDeepEqual(r2.type, ConfirmPayResultType.Done);

  // Check if payment was successful.

  orderStatus = succeedOrThrow(
    await merchantClient.getOrderDetails(
      merchantAdminAccessToken,
      orderResp.order_id,
    ),
  );

  t.assertDeepEqual(orderStatus.order_status, "paid");

  await walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});

  const client = new TalerCoreBankHttpClient(bank.corebankApiBaseUrl);
  const btok = succeedOrThrow(
    await client.createAccessToken(
      "exchange",
      {
        type: "basic",
        password: "mypw-password",
      },
      {
        scope: "readwrite",
      },
    ),
  ).access_token;
  const bankAuth: UserAndToken = {
    token: btok,
    username: "exchange",
  };

  while (1) {
    const txns = succeedOrThrow(await client.getTransactions(bankAuth));
    console.log(`bank txns for exchange: ${j2s(txns)}`);
    if (txns.transactions.length >= 2) {
      const myTx = txns.transactions.find((x) => x.direction === "debit");
      if (!myTx) {
        throw Error("unexpected transaction");
      }
      t.assertAmountEquals(myTx.amount, "TESTKUDOS:100");
      break;
    }
    console.log("waiting for transaction...");
    await waitMs(1000);
    await exchange.runAggregatorOnce();
    await exchange.runTransferOnce();
  }
}

runMerchantDepositLargeTest.suites = ["merchant"];
