/*
 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 {
  ConfirmPayResultType,
  PreparePayResultType,
  TalerCorebankApiClient,
  TalerErrorCode,
  TalerErrorDetail,
  TalerMerchantInstanceHttpClient,
  URL,
  codecForMerchantOrderStatusUnpaid,
  j2s,
  succeedOrThrow,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
  createFaultInjectedMerchantTestkudosEnvironment,
  withdrawViaBankV3,
} from "../harness/environments.js";
import { FaultInjectionRequestContext } from "../harness/faultInjection.js";
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";

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

  const { walletClient, bank, exchange, faultyMerchant, faultyExchange, merchantAdminAccessToken } =
    await createFaultInjectedMerchantTestkudosEnvironment(t);

  // Withdraw digital cash into the wallet.

  const bankClient = new TalerCorebankApiClient(bank.baseUrl);

  const wres = await withdrawViaBankV3(t, {
    walletClient,
    bankClient,
    exchange: faultyExchange,
    amount: "TESTKUDOS:20",
  });

  await wres.withdrawalFinishedCond;

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

  let orderResp = succeedOrThrow(
    await merchantClient.createOrder(merchantAdminAccessToken, {
      order: {
        summary: "Buy me!",
        amount: "TESTKUDOS:5",
        fulfillment_url: "https://example.com/article42",
        public_reorder_url: "https://example.com/article42-share",
      },
    }),
  );

  let orderStatus = succeedOrThrow(
    await merchantClient.getOrderDetails(merchantAdminAccessToken, orderResp.order_id, {
      sessionId: "mysession-one",
    }),
  );

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

  t.assertTrue(orderStatus.already_paid_order_id === undefined);
  let publicOrderStatusUrl = orderStatus.order_status_url;

  let publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);

  if (publicOrderStatusResp.status != 402) {
    throw Error(
      `expected status 402 (before claiming), but got ${publicOrderStatusResp.status}`,
    );
  }

  let pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
    publicOrderStatusResp.json(),
  );

  console.log(pubUnpaidStatus);

  let preparePayResp = await walletClient.call(
    WalletApiOperation.PreparePayForUri,
    {
      talerPayUri: pubUnpaidStatus.taler_pay_uri,
    },
  );

  t.assertTrue(preparePayResp.status === PreparePayResultType.PaymentPossible);

  publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);

  if (publicOrderStatusResp.status != 402) {
    throw Error(
      `expected status 402 (after claiming), but got ${publicOrderStatusResp.status}`,
    );
  }

  pubUnpaidStatus = codecForMerchantOrderStatusUnpaid().decode(
    publicOrderStatusResp.json(),
  );

  faultyMerchant.faultProxy.addFault({
    async modifyRequest(ctx: FaultInjectionRequestContext) {
      const url = new URL(ctx.requestUrl);
      if (!url.pathname.endsWith("/pay")) {
        return;
      }
      ctx.dropRequest = true;
      const err: TalerErrorDetail = {
        code: TalerErrorCode.GENERIC_CONFIGURATION_INVALID,
        hint: "something went wrong",
      };
      ctx.substituteResponseStatusCode = 404;
      ctx.substituteResponseBody = Buffer.from(JSON.stringify(err));
      console.log("injecting pay fault");
    },
  });

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

  // Can't have succeeded yet, but network error results in "pending" state.
  t.assertDeepEqual(confirmPayResp.type, ConfirmPayResultType.Pending);

  const txns = await walletClient.call(WalletApiOperation.GetTransactions, {
    sort: "stable-ascending",
  });
  console.log(j2s(txns));

  await walletClient.call(WalletApiOperation.AbortTransaction, {
    transactionId: txns.transactions[1].transactionId,
  });

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

  const txns2 = await walletClient.call(WalletApiOperation.GetTransactions, {
    sort: "stable-ascending",
  });
  console.log(j2s(txns2));

  const txTypes = txns2.transactions.map((x) => x.type);
  console.log(txTypes);
  // No refund, as the deposit never reached the merchant.
  t.assertDeepEqual(txTypes, ["withdrawal", "payment"]);

  // FIXME: check a case where there is indeed a refund!
  // That's easiest to simulate with two exchanges,
  // where only one of them works.

  // FIXME: also check extended transaction list for refresh.
  // FIXME: also check balance
}

runPaymentAbortTest.suites = ["wallet"];
