/*
 This file is part of GNU Taler
 (C) 2023 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,
  j2s,
  PreparePayResultType,
  succeedOrThrow,
  TalerCorebankApiClient,
  TalerMerchantInstanceHttpClient,
  TransactionType,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
  useSharedTestkudosEnvironment,
  withdrawViaBankV3,
} from "../harness/environments.js";
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";

/**
 * Repurchase detection with v1 contracts.
 */
export async function runRepurchaseV1Test(t: GlobalTestState) {
  // Set up test environment

  const { walletClient, bank, exchange, merchant, merchantAdminAccessToken } =
    await useSharedTestkudosEnvironment(t);

  // Withdraw digital cash into the wallet.

  const bankClient = new TalerCorebankApiClient(bank.baseUrl);

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

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

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

  const orderOneResp = succeedOrThrow(
    await merchantClient.createOrder(merchantAdminAccessToken, {
      order: {
        version: 1,
        summary: "Buy me",
        fulfillment_url: "https://example.com/test",
        choices: [
          {
            amount: "TESTKUDOS:5",
            description: "foo",
          },
          {
            amount: "TESTKUDOS:2",
            description: "bla",
          },
        ],
      },
    }),
  );

  let orderOneStatus = succeedOrThrow(
    await merchantClient.getOrderDetails(
      merchantAdminAccessToken,
      orderOneResp.order_id,
      {
        sessionId: "session1",
      },
    ),
  );

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

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

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

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

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

  {
    const resp = succeedOrThrow(
      await merchantClient.getOrderDetails(
        merchantAdminAccessToken,
        orderOneResp.order_id,
        {
          sessionId: "session1",
        },
      ),
    );
    console.log(`status orderId one, session session1`, resp);
  }

  const orderTwoResp = succeedOrThrow(
    await merchantClient.createOrder(merchantAdminAccessToken, {
      order: {
        version: 1,
        summary: "Buy me",
        fulfillment_url: "https://example.com/test",
        choices: [
          {
            amount: "TESTKUDOS:5",
            description: "foo",
          },
          {
            amount: "TESTKUDOS:2",
            description: "bla",
          },
        ],
      },
    }),
  );

  let orderTwoStatus = succeedOrThrow(
    await merchantClient.getOrderDetails(
      merchantAdminAccessToken,
      orderTwoResp.order_id,
      {
        sessionId: "session2",
      },
    ),
  );

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

  const orderLongpollUrl = new URL(
    `orders/${orderTwoResp.order_id}`,
    merchant.makeInstanceBaseUrl(),
  );
  if (orderTwoResp.token) {
    orderLongpollUrl.searchParams.set("token", orderTwoResp.token);
  }
  orderLongpollUrl.searchParams.set("timeout_ms", "60000");
  orderLongpollUrl.searchParams.set("session_id", "session2");

  const longpollPromise = harnessHttpLib.fetch(orderLongpollUrl.href);

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

  // Repurchase should be detected
  t.assertTrue(
    preparePayTwoResult.status === PreparePayResultType.AlreadyConfirmed,
  );

  t.logStep("start-wait-longpoll-promise");
  await longpollPromise;
  t.logStep("done-wait-longpoll-promise");

  {
    const resp1 = succeedOrThrow(
      await merchantClient.getOrderDetails(
        merchantAdminAccessToken,
        orderTwoResp.order_id,
        {
          sessionId: "session2",
        },
      ),
    );
    console.log("status orderId two, sessionId session2", j2s(resp1));

    const resp2 = succeedOrThrow(
      await merchantClient.getOrderDetails(
        merchantAdminAccessToken,
        orderOneResp.order_id,
        {
          sessionId: "session2",
        },
      ),
    );
    console.log("status orderId one, sessionId session2", j2s(resp2));
    console.log(resp2);
  }

  // Order three

  const orderThreeResp = succeedOrThrow(
    await merchantClient.createOrder(merchantAdminAccessToken, {
      order: {
        version: 1,
        summary: "Buy me",
        fulfillment_url: "https://example.com/test",
        choices: [
          {
            amount: "TESTKUDOS:5",
            description: "foo",
          },
          {
            amount: "TESTKUDOS:2",
            description: "bla",
          },
        ],
      },
    }),
  );

  let orderThreeStatus = succeedOrThrow(
    await merchantClient.getOrderDetails(
      merchantAdminAccessToken,
      orderThreeResp.order_id,
      {
        // Go back to session1
        sessionId: "session1",
      },
    ),
  );

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

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

  // Repurchase should be detected
  t.assertTrue(
    preparePayThreeResult.status === PreparePayResultType.AlreadyConfirmed,
  );

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

  {
    const txnsNormal = await walletClient.call(
      WalletApiOperation.GetTransactionsV2,
      {},
    );

    console.log(j2s(txnsNormal));

    t.assertDeepEqual(txnsNormal.transactions.length, 2);
  }

  // The list of all transactions should also include repurchases.
  {
    const txnsAll = await walletClient.call(
      WalletApiOperation.GetTransactionsV2,
      {
        includeAll: true,
      },
    );

    console.log(j2s(txnsAll));

    const numPayments = txnsAll.transactions.filter(
      (x) => x.type === TransactionType.Payment,
    ).length;

    t.assertDeepEqual(numPayments, 3);
  }

  // Test that deleting the original transaction also deletes the repurchase transaction.

  await walletClient.call(WalletApiOperation.DeleteTransaction, {
    transactionId: preparePayOneResult.transactionId,
  });

  {
    const txnsAll = await walletClient.call(
      WalletApiOperation.GetTransactionsV2,
      {
        includeAll: true,
      },
    );

    const numPayments = txnsAll.transactions.filter(
      (x) => x.type === TransactionType.Payment,
    ).length;

    t.assertDeepEqual(numPayments, 0);
  }
}

runRepurchaseV1Test.suites = ["wallet"];
