/*
 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 {
  Duration,
  ScopeType,
  TalerCorebankApiClient,
  j2s,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { defaultCoinConfig } from "../harness/denomStructures.js";
import {
  createWalletDaemonWithClient,
  makeTestPaymentV2,
  withdrawViaBankV3,
} from "../harness/environments.js";
import {
  BankService,
  ExchangeService,
  GlobalTestState,
  HarnessExchangeBankAccount,
  MerchantService,
  getTestHarnessPaytoForLabel,
  setupDb,
} from "../harness/harness.js";

/**
 * Run test for basic, bank-integrated withdrawal and payment.
 */
export async function runCurrencyScopeTest(t: GlobalTestState) {
  // Set up test environment
  const dbDefault = await setupDb(t);

  const dbExchangeTwo = await setupDb(t, {
    nameSuffix: "exchange2",
  });

  const bank = await BankService.create(t, {
    allowRegistrations: true,
    currency: "TESTKUDOS",
    database: dbDefault.connStr,
    httpPort: 8082,
  });

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

  const exchangeTwo = ExchangeService.create(t, {
    name: "testexchange-2",
    currency: "TESTKUDOS",
    httpPort: 8281,
    database: dbExchangeTwo.connStr,
  });

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

  let exchangeOneBankAccount: HarnessExchangeBankAccount = {
    wireGatewayApiBaseUrl: new URL(
      "accounts/myexchange/taler-wire-gateway/",
      bank.corebankApiBaseUrl,
    ).href,
    wireGatewayAuth: {
      username: "myexchange",
      password: "password",
    },
    accountPaytoUri: getTestHarnessPaytoForLabel("myexchange"),
  };

  let exchangeTwoBankAccount: HarnessExchangeBankAccount = {
    wireGatewayApiBaseUrl: new URL(
      "accounts/myexchange2/taler-wire-gateway/",
      bank.corebankApiBaseUrl,
    ).href,
    wireGatewayAuth: {
      username: "myexchange2",
      password: "password",
    },
    accountPaytoUri: getTestHarnessPaytoForLabel("myexchange2"),
  };

  bank.setSuggestedExchange(
    exchangeOne,
    exchangeOneBankAccount.accountPaytoUri,
  );

  await bank.start();

  await bank.pingUntilAvailable();

  const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
    auth: {
      username: "admin",
      password: "admin-password",
    },
  });

  await bankClient.registerAccountExtended({
    name: exchangeOneBankAccount.wireGatewayAuth.username,
    username: exchangeOneBankAccount.wireGatewayAuth.username,
    password: exchangeOneBankAccount.wireGatewayAuth.password,
    is_taler_exchange: true,
    payto_uri: exchangeOneBankAccount.accountPaytoUri,
  });
  await exchangeOne.addBankAccount("1", exchangeOneBankAccount);

  await bankClient.registerAccountExtended({
    name: exchangeTwoBankAccount.wireGatewayAuth.username,
    username: exchangeTwoBankAccount.wireGatewayAuth.username,
    password: exchangeTwoBankAccount.wireGatewayAuth.password,
    is_taler_exchange: true,
    payto_uri: exchangeTwoBankAccount.accountPaytoUri,
  });
  await exchangeTwo.addBankAccount("1", exchangeTwoBankAccount);

  // Set up the first exchange

  exchangeOne.addOfferedCoins(defaultCoinConfig);
  await exchangeOne.start();
  await exchangeOne.pingUntilAvailable();

  // Set up the second exchange

  exchangeTwo.addOfferedCoins(defaultCoinConfig);
  await exchangeTwo.start();
  await exchangeTwo.pingUntilAvailable();

  // Start and configure merchant

  merchant.addExchange(exchangeOne);
  merchant.addExchange(exchangeTwo);

  await merchant.start();
  await merchant.pingUntilAvailable();

  const { accessToken: adminAccessToken } = await merchant.addInstanceWithWireAccount({
    id: "admin",
    name: "Default Instance",
    paytoUris: [getTestHarnessPaytoForLabel("merchant-default")],
    defaultWireTransferDelay: Duration.toTalerProtocolDuration(
      Duration.fromSpec({ minutes: 1 }),
    ),
  });

  await merchant.addInstanceWithWireAccount({
    id: "minst1",
    name: "minst1",
    paytoUris: [getTestHarnessPaytoForLabel("minst1")],
    defaultWireTransferDelay: Duration.toTalerProtocolDuration(
      Duration.fromSpec({ minutes: 1 }),
    ),
  }, {adminAccessToken});

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

  console.log("setup done!");

  // Withdraw digital cash into the wallet.

  const w1 = await withdrawViaBankV3(t, {
    walletClient,
    bankClient,
    exchange: exchangeOne,
    amount: "TESTKUDOS:6",
  });

  const w2 = await withdrawViaBankV3(t, {
    walletClient,
    bankClient,
    exchange: exchangeTwo,
    amount: "TESTKUDOS:6",
  });

  await w1.withdrawalFinishedCond;
  await w2.withdrawalFinishedCond;

  const bal = await walletClient.call(WalletApiOperation.GetBalances, {});
  console.log(j2s(bal));

  // Separate balances, exchange-scope.
  t.assertDeepEqual(bal.balances.length, 2);

  await walletClient.call(WalletApiOperation.AddGlobalCurrencyExchange, {
    currency: "TESTKUDOS",
    exchangeBaseUrl: exchangeOne.baseUrl,
    exchangeMasterPub: exchangeOne.masterPub,
  });

  {
    const exch1 = await walletClient.call(WalletApiOperation.ListExchanges, {
      filterByScope: {
        type: ScopeType.Global,
        currency: "TESTKUDOS",
      },
    });
    t.assertDeepEqual(exch1.exchanges.length, 1);
  }

  await walletClient.call(WalletApiOperation.AddGlobalCurrencyExchange, {
    currency: "TESTKUDOS",
    exchangeBaseUrl: exchangeTwo.baseUrl,
    exchangeMasterPub: exchangeTwo.masterPub,
  });

  {
    const exch1 = await walletClient.call(WalletApiOperation.ListExchanges, {
      filterByScope: {
        type: ScopeType.Global,
        currency: "TESTKUDOS",
      },
    });
    t.assertDeepEqual(exch1.exchanges.length, 2);
  }

  const ex = walletClient.call(
    WalletApiOperation.ListGlobalCurrencyExchanges,
    {},
  );
  console.log("global currency exchanges:");
  console.log(j2s(ex));

  const bal2 = await walletClient.call(WalletApiOperation.GetBalances, {});
  console.log(j2s(bal2));

  // Global currencies are merged
  t.assertDeepEqual(bal2.balances.length, 1);

  const payRes = await makeTestPaymentV2(t, {
    merchant, merchantAdminAccessToken: adminAccessToken,
    walletClient,
    order: {
      amount: "TESTKUDOS:10",
      summary: "Test",
    },
  });

  const payTx = await walletClient.call(WalletApiOperation.GetTransactionById, {
    transactionId: payRes.transactionId,
  });

  console.log(`payment scopes: ${j2s(payTx.scopes)}`);

  t.assertDeepEqual(payTx.scopes.length, 1);
}

runCurrencyScopeTest.suites = ["wallet"];
