/*
 This file is part of GNU Taler
 (C) 2021 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,
  Duration,
  j2s,
  LoginTokenScope,
  MerchantAuthMethod,
  PreparePayResultType,
  succeedOrThrow,
  TalerCoreBankHttpClient,
  TalerMerchantInstanceHttpClient,
  TalerMerchantManagementHttpClient,
  TalerProtocolTimestamp,
  UserAndToken,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { defaultCoinConfig } from "../harness/denomStructures.js";
import {
  applyTimeTravelV2,
  createWalletDaemonWithClient,
  withdrawViaBankV4,
} from "../harness/environments.js";
import {
  BankService,
  ExchangeService,
  getTestHarnessPaytoForLabel,
  GlobalTestState,
  harnessBankAdminCreds,
  HarnessExchangeBankAccount,
  LibeufinBankService,
  MerchantService,
  setupDb,
  waitMs,
} from "../harness/harness.js";

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

  const db = await setupDb(t);

  const exchange = ExchangeService.create(t, {
    name: "testexchange-1",
    currency: "TESTKUDOS",
    httpPort: 8081,
    database: db.connStr,
  });

  const bc = {
    allowRegistrations: true,
    currency: "TESTKUDOS",
    database: db.connStr,
    httpPort: 8082,
  };

  const bank: BankService = await LibeufinBankService.create(t, bc);

  const receiverName = "Exchange";
  const exchangeBankUsername = "exchange";
  const exchangeBankPassword = "mypw-password";
  const exchangePaytoUri = getTestHarnessPaytoForLabel(exchangeBankUsername);
  const wireGatewayApiBaseUrl = new URL(
    `accounts/${exchangeBankUsername}/taler-wire-gateway/`,
    bank.corebankApiBaseUrl,
  ).href;

  const exchangeBankAccount: HarnessExchangeBankAccount = {
    wireGatewayApiBaseUrl,
    wireGatewayAuth: {
      username: exchangeBankUsername,
      password: exchangeBankPassword,
    },
    accountPaytoUri: exchangePaytoUri,
  };

  await exchange.addBankAccount("1", exchangeBankAccount);

  exchange.addOfferedCoins(defaultCoinConfig);

  bank.setSuggestedExchange(exchange, exchangeBankAccount.accountPaytoUri);

  await bank.start();

  const bankClient = new TalerCoreBankHttpClient(bank.corebankApiBaseUrl);
  const bankAdminTok = succeedOrThrow(
    await bankClient.createAccessToken("admin", harnessBankAdminCreds, {
      scope: "readwrite",
    }),
  ).access_token;

  await bankClient.createAccount(bankAdminTok, {
    name: receiverName,
    password: exchangeBankPassword,
    username: exchangeBankUsername,
    is_taler_exchange: true,
    payto_uri: exchangePaytoUri,
  });

  await exchange.start();

  const merchant = await MerchantService.create(t, {
    name: "testmerchant-1",
    httpPort: 8083,
    database: db.connStr,
  });
  const merchantMgmtClient = new TalerMerchantManagementHttpClient(
    merchant.makeInstanceBaseUrl(),
  );

  merchant.addExchange(exchange);

  await merchant.start();

  succeedOrThrow(
    await merchantMgmtClient.createInstance(undefined, {
      id: "admin",
      address: {},
      auth: {
        method: MerchantAuthMethod.TOKEN,
        password: "123",
      },
      jurisdiction: {},
      name: "Admin",
      use_stefan: true,
    }),
  );

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

  const merchantAdminTok = succeedOrThrow(
    await merchantAdminClient.createAccessToken("admin", "123", {
      scope: LoginTokenScope.All,
    }),
  ).access_token;

  console.log(`merchantAdminTok: ${merchantAdminTok}`);

  succeedOrThrow(
    await merchantMgmtClient.createInstance(merchantAdminTok, {
      id: "minst1",
      address: {},
      auth: {
        method: MerchantAuthMethod.TOKEN,
        password: "123",
      },
      jurisdiction: {},
      name: "My Instance One",
      use_stefan: true,
    }),
  );

  const merchantMinst1Client = new TalerMerchantInstanceHttpClient(
    merchant.makeInstanceBaseUrl("minst1"),
  );

  const merchantMinst1Tok = succeedOrThrow(
    await merchantMinst1Client.createAccessToken("minst1", "123", {
      scope: LoginTokenScope.All,
    }),
  ).access_token;

  succeedOrThrow(
    await bankClient.createAccount(bankAdminTok, {
      name: "minst1",
      password: "minst1-password",
      username: "minst1",
      payto_uri: getTestHarnessPaytoForLabel("minst1"),
    }),
  );

  succeedOrThrow(
    await merchantMinst1Client.addBankAccount(merchantMinst1Tok, {
      payto_uri: getTestHarnessPaytoForLabel("minst1"),
      extra_wire_subject_metadata: "foo123",
    }),
  );

  const { walletClient, walletService } = await createWalletDaemonWithClient(
    t,
    {
      name: "wallet",
      persistent: true,
    },
  );

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

  {
    const orderResp = succeedOrThrow(
      await merchantMinst1Client.createOrder(merchantMinst1Tok, {
        order: {
          amount: "TESTKUDOS:5",
          summary: "Hello",
          wire_transfer_deadline: TalerProtocolTimestamp.now(),
        },
      }),
    );

    let orderStatus = succeedOrThrow(
      await merchantMinst1Client.getOrderDetails(
        merchantMinst1Tok,
        orderResp.order_id,
      ),
    );

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

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

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

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

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

    // Check if payment was successful.

    orderStatus = succeedOrThrow(
      await merchantMinst1Client.getOrderDetails(
        merchantMinst1Tok,
        orderResp.order_id,
      ),
    );

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

  await applyTimeTravelV2(
    Duration.toMilliseconds(
      Duration.fromSpec({
        days: 14,
      }),
    ),
    {
      merchant,
      exchange,
      walletClient,
    },
  );

  {
    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.assertTrue(myTx.subject.includes("foo123"));
        break;
      }
      console.log("waiting for transaction...");
      await waitMs(1000);
      await exchange.runAggregatorOnce();
      await exchange.runTransferOnce();
    }
  }
}

runWireMetadataTest.suites = ["merchant"];
