/*
 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 {
  AgeRestriction,
  Amounts,
  AmountString,
  encodeCrock,
  ExchangeWithdrawRequest,
  getRandomBytes,
  succeedOrThrow,
  TalerExchangeHttpClient,
} from "@gnu-taler/taler-util";
import { HttpRequestLibrary } from "@gnu-taler/taler-util/http";
import {
  CryptoDispatcher,
  SynchronousCryptoWorkerFactoryPlain,
  TalerCryptoInterface,
} from "@gnu-taler/taler-wallet-core";
import {
  checkReserve,
  CoinInfo,
  downloadExchangeInfo,
  findDenomOrThrow,
  ReserveKeypair,
  topupReserveWithBank,
} from "@gnu-taler/taler-wallet-core/dbless";
import { DenominationRecord } from "../../../taler-wallet-core/src/db.js";
import { createSimpleTestkudosEnvironmentV2 } from "../harness/environments.js";
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";

/**
 * Run test for basic, bank-integrated withdrawal and payment.
 */
export async function runWithdrawalIdempotentTest(t: GlobalTestState) {
  // Set up test environment

  const { bank, exchange } = await createSimpleTestkudosEnvironmentV2(t);

  const http = harnessHttpLib;
  const cryptiDisp = new CryptoDispatcher(
    new SynchronousCryptoWorkerFactoryPlain(),
  );
  const cryptoApi = cryptiDisp.cryptoApi;

  const exchangeInfo = await downloadExchangeInfo(exchange.baseUrl, http);

  const reserveKeyPair = await cryptoApi.createEddsaKeypair({});

  let reserveUrl = new URL(`reserves/${reserveKeyPair.pub}`, exchange.baseUrl);
  reserveUrl.searchParams.set("timeout_ms", "30000");
  const longpollReq = http.fetch(reserveUrl.href, {
    method: "GET",
  });

  await topupReserveWithBank({
    amount: "TESTKUDOS:10" as AmountString,
    http,
    reservePub: reserveKeyPair.pub,
    corebankApiBaseUrl: bank.corebankApiBaseUrl,
    exchangeInfo,
  });

  console.log("waiting for longpoll request");
  const resp = await longpollReq;
  console.log(`got response, status ${resp.status}`);

  console.log(exchangeInfo);

  await checkReserve(http, exchange.baseUrl, reserveKeyPair.pub);

  const d1 = findDenomOrThrow(exchangeInfo, "TESTKUDOS:8" as AmountString);

  await myWithdrawCoin({
    http,
    cryptoApi,
    reserveKeyPair: {
      reservePriv: reserveKeyPair.priv,
      reservePub: reserveKeyPair.pub,
    },
    denom: d1,
    exchangeBaseUrl: exchange.baseUrl,
  });
}

async function myWithdrawCoin(args: {
  http: HttpRequestLibrary;
  cryptoApi: TalerCryptoInterface;
  reserveKeyPair: ReserveKeypair;
  denom: DenominationRecord;
  exchangeBaseUrl: string;
}): Promise<CoinInfo> {
  const { http, cryptoApi, reserveKeyPair, denom, exchangeBaseUrl } = args;
  const planchet = await cryptoApi.createPlanchet({
    coinIndex: 0,
    denomPub: denom.denomPub,
    feeWithdraw: Amounts.parseOrThrow(denom.fees.feeWithdraw),
    reservePriv: reserveKeyPair.reservePriv,
    reservePub: reserveKeyPair.reservePub,
    secretSeed: encodeCrock(getRandomBytes(32)),
    value: Amounts.parseOrThrow(denom.value),
  });

  const exchangeClient = new TalerExchangeHttpClient(exchangeBaseUrl);

  const sigResp = await cryptoApi.signWithdrawal({
    amount: Amounts.stringify(denom.value),
    fee: Amounts.stringify(denom.fees.feeWithdraw),
    coinEvs: [planchet.coinEv],
    denomsPubHashes: [planchet.denomPubHash],
    reservePriv: reserveKeyPair.reservePriv,
  });

  const reqBody: ExchangeWithdrawRequest = {
    cipher: "ED25519",
    coin_evs: [planchet.coinEv],
    denoms_h: [planchet.denomPubHash],
    reserve_pub: planchet.reservePub,
    reserve_sig: sigResp.sig,
  };

  const rBatch = succeedOrThrow(
    await exchangeClient.withdraw({
      body: reqBody,
    }),
  );

  {
    // Check for idempotency!
    succeedOrThrow(
      await exchangeClient.withdraw({
        body: reqBody,
      }),
    );
  }

  const ubSig = await cryptoApi.unblindDenominationSignature({
    planchet,
    evSig: rBatch.ev_sigs[0],
  });

  return {
    coinPriv: planchet.coinPriv,
    coinPub: planchet.coinPub,
    denomSig: ubSig,
    denomPub: denom.denomPub,
    denomPubHash: denom.denomPubHash,
    feeDeposit: Amounts.stringify(denom.fees.feeDeposit),
    feeRefresh: Amounts.stringify(denom.fees.feeRefresh),
    exchangeBaseUrl: args.exchangeBaseUrl,
    maxAge: AgeRestriction.AGE_UNRESTRICTED,
  };
}

runWithdrawalIdempotentTest.suites = ["wallet"];
