/*
 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 {
  AccessToken,
  Duration,
  PreparePayResultType,
  succeedOrThrow,
  TalerMerchantInstanceHttpClient,
  URL,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
  createSimpleTestkudosEnvironmentV3,
  withdrawViaBankV3,
} from "../harness/environments.js";
import {
  ExchangeServiceInterface,
  GlobalTestState,
  harnessHttpLib,
  MerchantServiceInterface,
  WalletClient,
} from "../harness/harness.js";

async function testRefundApiWithFulfillmentUrl(
  t: GlobalTestState,
  env: {
    merchant: MerchantServiceInterface;
    walletClient: WalletClient;
    merchantAdminAccessToken: AccessToken;
    exchange: ExchangeServiceInterface;
  },
): Promise<void> {
  const { walletClient, merchant, merchantAdminAccessToken } = env;

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

  // Set up order.
  const orderResp = succeedOrThrow(
    await merchantClient.createOrder(merchantAdminAccessToken, {
      order: {
        summary: "Buy me!",
        amount: "TESTKUDOS:5",
        fulfillment_url: "https://example.com/fulfillment",
      },
      refund_delay: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ minutes: 5 }),
      ),
    }),
  );

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

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

  const talerPayUri = orderStatus.taler_pay_uri;
  const orderId = orderResp.order_id;

  // Make wallet pay for the order

  let preparePayResult = await walletClient.call(
    WalletApiOperation.PreparePayForUri,
    {
      talerPayUri,
    },
  );

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

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

  // Check if payment was successful.

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

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

  preparePayResult = await walletClient.call(
    WalletApiOperation.PreparePayForUri,
    {
      talerPayUri,
    },
  );

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

  await merchantClient.addRefund(merchantAdminAccessToken, orderResp.order_id, {
    refund: "TESTKUDOS:5",
    reason: "foo",
  });

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

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

  t.assertAmountEquals(orderStatus.refund_amount, "TESTKUDOS:5");

  // Now test what the merchant gives as a response for various requests to the
  // public order status URL!

  let publicOrderStatusUrl = new URL(
    `orders/${orderId}`,
    merchant.makeInstanceBaseUrl(),
  );
  publicOrderStatusUrl.searchParams.set(
    "h_contract",
    preparePayResult.contractTermsHash,
  );

  let publicOrderStatusResp = await harnessHttpLib.fetch(
    publicOrderStatusUrl.href,
  );
  const respData = await publicOrderStatusResp.json();
  t.assertTrue(publicOrderStatusResp.status === 200);
  t.assertAmountEquals(respData.refund_amount, "TESTKUDOS:5");

  publicOrderStatusUrl = new URL(
    `orders/${orderId}`,
    merchant.makeInstanceBaseUrl(),
  );
  console.log(`requesting order status via '${publicOrderStatusUrl.href}'`);
  publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
  console.log(publicOrderStatusResp.status);
  console.log(await publicOrderStatusResp.json());
  // We didn't give any authentication, so we should get a fulfillment URL back
  t.assertTrue(publicOrderStatusResp.status === 403);
}

async function testRefundApiWithFulfillmentMessage(
  t: GlobalTestState,
  env: {
    merchant: MerchantServiceInterface;
    walletClient: WalletClient;
    merchantAdminAccessToken: AccessToken;
    exchange: ExchangeServiceInterface;
  },
): Promise<void> {
  const { walletClient, merchant, merchantAdminAccessToken } = env;

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

  // Set up order.
  const orderResp = succeedOrThrow(
    await merchantClient.createOrder(merchantAdminAccessToken, {
      order: {
        summary: "Buy me!",
        amount: "TESTKUDOS:5",
        fulfillment_message: "Thank you for buying foobar",
      },
      refund_delay: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ minutes: 5 }),
      ),
    }),
  );

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

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

  const talerPayUri = orderStatus.taler_pay_uri;
  const orderId = orderResp.order_id;

  // Make wallet pay for the order

  let preparePayResult = await walletClient.call(
    WalletApiOperation.PreparePayForUri,
    {
      talerPayUri,
    },
  );

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

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

  // Check if payment was successful.

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

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

  preparePayResult = await walletClient.call(
    WalletApiOperation.PreparePayForUri,
    {
      talerPayUri,
    },
  );

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

  await merchantClient.addRefund(merchantAdminAccessToken, orderId, {
    refund: "TESTKUDOS:5",
    reason: "foo",
  });

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

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

  t.assertAmountEquals(orderStatus.refund_amount, "TESTKUDOS:5");

  // Now test what the merchant gives as a response for various requests to the
  // public order status URL!

  let publicOrderStatusUrl = new URL(
    `orders/${orderId}`,
    merchant.makeInstanceBaseUrl(),
  );
  publicOrderStatusUrl.searchParams.set(
    "h_contract",
    preparePayResult.contractTermsHash,
  );

  let publicOrderStatusResp = await harnessHttpLib.fetch(
    publicOrderStatusUrl.href,
  );
  let respData = await publicOrderStatusResp.json();
  console.log(respData);
  t.assertTrue(publicOrderStatusResp.status === 200);
  t.assertAmountEquals(respData.refund_amount, "TESTKUDOS:5");

  publicOrderStatusUrl = new URL(
    `orders/${orderId}`,
    merchant.makeInstanceBaseUrl(),
  );

  publicOrderStatusResp = await harnessHttpLib.fetch(publicOrderStatusUrl.href);
  respData = await publicOrderStatusResp.json();
  console.log(respData);
  // We didn't give any authentication, so we should get a fulfillment URL back
  t.assertTrue(publicOrderStatusResp.status === 403);
}

/**
 * Test case for the refund API of the merchant backend.
 */
export async function runMerchantRefundApiTest(t: GlobalTestState) {
  // Set up test environment

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

  // Withdraw digital cash into the wallet.

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

  await testRefundApiWithFulfillmentUrl(t, {
    walletClient,
    exchange,
    merchant,
    merchantAdminAccessToken,
  });

  await testRefundApiWithFulfillmentMessage(t, {
    walletClient,
    exchange,
    merchant,
    merchantAdminAccessToken,
  });
}

runMerchantRefundApiTest.suites = ["merchant"];
