/*
 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,
  succeedOrThrow,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
  createFaultInjectedMerchantTestkudosEnvironment,
  withdrawViaBankV3,
} from "../harness/environments.js";
import { FaultInjectionResponseContext } from "../harness/faultInjection.js";
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";

/**
 * Run test for a payment where the merchant has a transient
 * failure in /pay
 */
export async function runPaymentTransientTest(t: GlobalTestState) {
  // Set up test environment

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

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

  // 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;

  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);

  const proposalTransactionId = preparePayResp.transactionId;

  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(),
  );

  let faultInjected = false;

  faultyMerchant.faultProxy.addFault({
    async modifyResponse(ctx: FaultInjectionResponseContext) {
      console.log("in modifyResponse");
      const url = new URL(ctx.request.requestUrl);
      console.log("pathname is", url.pathname);
      if (!url.pathname.endsWith("/pay")) {
        return;
      }
      if (faultInjected) {
        console.log("not injecting pay fault");
        return;
      }
      faultInjected = true;
      console.log("injecting pay fault");
      const err: TalerErrorDetail = {
        code: TalerErrorCode.GENERIC_DB_COMMIT_FAILED,
        hint: "something went wrong",
      };
      ctx.responseBody = Buffer.from(JSON.stringify(err));
      ctx.statusCode = 500;
    },
  });

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

  console.log(confirmPayResp);

  t.assertTrue(confirmPayResp.type === ConfirmPayResultType.Pending);
  t.assertTrue(faultInjected);

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

  t.assertTrue(confirmPayRespTwo.type === ConfirmPayResultType.Done);

  // Now ask the merchant if paid

  console.log("requesting", publicOrderStatusUrl);
  publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl);

  console.log(publicOrderStatusResp.json());

  if (publicOrderStatusResp.status != 200) {
    console.log(publicOrderStatusResp.json());
    throw Error(
      `expected status 200 (after paying), but got ${publicOrderStatusResp.status}`,
    );
  }
}

runPaymentTransientTest.suites = ["wallet"];
