/*
 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 {
  Logger,
  TalerBankConversionHttpClient,
  TalerCoreBankHttpClient,
  TalerCorebankApiClient,
  TalerErrorCode,
  j2s,
  succeedOrThrow,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { defaultCoinConfig } from "../harness/denomStructures.js";
import { createWalletDaemonWithClient } from "../harness/environments.js";
import {
  ExchangeService,
  GlobalTestState,
  LibeufinBankService,
  LibeufinNexusService,
  MerchantService,
  getTestHarnessPaytoForLabel,
  setupDb,
} from "../harness/harness.js";

const logger = new Logger("test-libeufin-bank.ts");

/**
 * Run test for conversion functionality of libeufin-bank.
 */
export async function runLibeufinConversionTest(t: GlobalTestState) {
  // Set up test environment

  const db = await setupDb(t);

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

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

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

  const nexus = await LibeufinNexusService.create(t, {
    currency: "FOO",
    database: db.connStr,
  });

  await nexus.dbinit();

  const exchangeBankUsername = "exchange";
  const exchangeBankPw = "mypw-password";
  const exchangePayto = getTestHarnessPaytoForLabel(exchangeBankUsername);
  const wireGatewayApiBaseUrl = new URL(
    "accounts/exchange/taler-wire-gateway/",
    bank.baseUrl,
  ).href;

  logger.info("creating bank account for the exchange");

  exchange.addBankAccount("1", {
    wireGatewayApiBaseUrl,
    wireGatewayAuth: {
      username: exchangeBankUsername,
      password: exchangeBankPw,
    },
    accountPaytoUri: exchangePayto,
    conversionUrl: new URL("conversion-info/", bank.baseUrl).href,
  });

  exchange.addBankAccount("conv", {
    wireGatewayApiBaseUrl,
    wireGatewayAuth: {
      username: exchangeBankUsername,
      password: exchangeBankPw,
    },
    accountPaytoUri: exchangePayto,
    conversionUrl: new URL("conversion-info/", bank.baseUrl).href,
  });

  bank.setSuggestedExchange(exchange);

  await bank.start();

  await bank.pingUntilAvailable();

  exchange.addOfferedCoins(defaultCoinConfig);

  await exchange.start();
  await exchange.pingUntilAvailable();

  merchant.addExchange(exchange);

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

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

  await merchant.addInstanceWithWireAccount(
    {
      id: "minst1",
      name: "minst1",
      paytoUris: [getTestHarnessPaytoForLabel("minst1")],
    },
    { adminAccessToken },
  );

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

  console.log("setup done!");

  const adminUser = "admin";
  const adminPassword = "admin-password";

  const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
    auth: {
      username: adminUser,
      password: adminPassword,
    },
  });

  // register exchange bank account
  await bankClient.registerAccountExtended({
    name: "exchange",
    password: exchangeBankPw,
    username: exchangeBankUsername,
    is_taler_exchange: true,
    payto_uri: exchangePayto,
  });

  const bankUser = await bankClient.registerAccount("user1", "password1");
  bankClient.setAuth({
    username: "user1",
    password: "password1",
  });

  const cbc = new TalerCoreBankHttpClient(bank.baseUrl);
  const adminTokResp = succeedOrThrow(
    await cbc.createAccessToken(
      adminUser,
      {
        type: "basic",
        password: adminPassword,
      },
      {
        scope: "readwrite",
      },
    ),
  );

  await bank.stop();

  // libeufin needs the exchange account to be created *before* conversion
  // is activated.
  bank.changeConfig((conf) => {
    conf.setString("libeufin-bank", "allow_conversion", "yes");
    conf.setString("libeufin-bank", "fiat_currency", "FOO");
  });

  await bank.start({
    noReset: true,
  });

  const adminTok = adminTokResp.access_token;

  const cc = new TalerBankConversionHttpClient(
    bank.baseUrl + `conversion-info/`,
  );

  succeedOrThrow(
    await cc.updateConversionRate(adminTok, {
      cashin_fee: "TESTKUDOS:0",
      cashin_min_amount: "FOO:5",
      cashin_ratio: "1",
      cashin_rounding_mode: "nearest",
      cashin_tiny_amount: "TESTKUDOS:0.01",
      cashout_fee: "FOO:0",
      cashout_min_amount: "TESTKUDOS:0",
      cashout_ratio: "1",
      cashout_rounding_mode: "nearest",
      cashout_tiny_amount: "FOO:0.01",
    }),
  );

  const detRes = await walletClient.call(
    WalletApiOperation.GetWithdrawalDetailsForAmount,
    {
      amount: "TESTKUDOS:1",
      exchangeBaseUrl: exchange.baseUrl,
    },
  );

  console.log(j2s(detRes));

  const ec = detRes.withdrawalAccountsList[0].conversionError?.code;

  switch (ec) {
    case TalerErrorCode.BANK_CONVERSION_AMOUNT_TO_SMALL:
    case TalerErrorCode.BANK_BAD_CONVERSION:
      break;
    default:
      t.fail(`unexpected conversion error code: ${ec}`);
  }
}

runLibeufinConversionTest.suites = ["fakebank"];
