/*
 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 {
  AbsoluteTime,
  AmountString,
  ContractTermsUtil,
  decodeCrock,
  Duration,
  encodeCrock,
  getRandomBytes,
  hash,
  j2s,
  PeerContractTerms,
  TalerError,
} from "@gnu-taler/taler-util";
import {
  CryptoDispatcher,
  EncryptContractRequest,
  SpendCoinDetails,
  SynchronousCryptoWorkerFactoryPlain,
} from "@gnu-taler/taler-wallet-core";
import {
  checkReserve,
  downloadExchangeInfo,
  findDenomOrThrow,
  topupReserveWithBank,
  withdrawCoin,
} from "@gnu-taler/taler-wallet-core/dbless";
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
import { createSimpleTestkudosEnvironmentV2 } from "../harness/environments.js";

/**
 * Test the exchange's purse API.
 */
export async function runExchangePurseTest(t: GlobalTestState) {
  // Set up test environment

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

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

  try {
    // Withdraw digital cash into the wallet.

    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,
      {},
    );

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

    const amount = "TESTKUDOS:5" as AmountString;

    const contractTerms: PeerContractTerms = {
      amount,
      summary: "Hello",
      purse_expiration: AbsoluteTime.toProtocolTimestamp(
        AbsoluteTime.addDuration(
          AbsoluteTime.now(),
          Duration.fromSpec({ minutes: 1 }),
        ),
      ),
    };

    const mergeReservePair = await cryptoApi.createEddsaKeypair({});
    const pursePair = await cryptoApi.createEddsaKeypair({});
    const mergePair = await cryptoApi.createEddsaKeypair({});
    const contractPair = await cryptoApi.createEddsaKeypair({});
    const contractEncNonce = encodeCrock(getRandomBytes(24));

    const pursePub = pursePair.pub;

    const hContractTerms = ContractTermsUtil.hashContractTerms(contractTerms);

    const purseSigResp = await cryptoApi.signPurseCreation({
      hContractTerms,
      mergePub: mergePair.pub,
      minAge: 0,
      purseAmount: amount,
      purseExpiration: contractTerms.purse_expiration,
      pursePriv: pursePair.priv,
    });

    const coinSpend: SpendCoinDetails = {
      ageCommitmentProof: undefined,
      coinPriv: coin.coinPriv,
      coinPub: coin.coinPub,
      contribution: amount,
      denomPubHash: coin.denomPubHash,
      denomSig: coin.denomSig,
      feeDeposit: d1.fees.feeDeposit,
    };

    const depositSigsResp = await cryptoApi.signPurseDeposits({
      exchangeBaseUrl: exchange.baseUrl,
      pursePub: pursePair.pub,
      coins: [coinSpend],
    });

    const encryptContractRequest: EncryptContractRequest = {
      contractTerms: contractTerms,
      mergePriv: mergePair.priv,
      pursePriv: pursePair.priv,
      pursePub: pursePair.pub,
      contractPriv: contractPair.priv,
      contractPub: contractPair.pub,
      nonce: contractEncNonce,
    };

    const econtractResp = await cryptoApi.encryptContractForMerge(
      encryptContractRequest,
    );

    const econtractHash = encodeCrock(
      hash(decodeCrock(econtractResp.econtract.econtract)),
    );

    const createPurseUrl = new URL(
      `purses/${pursePair.pub}/create`,
      exchange.baseUrl,
    );

    const reqBody = {
      amount: amount,
      merge_pub: mergePair.pub,
      purse_sig: purseSigResp.sig,
      h_contract_terms: hContractTerms,
      purse_expiration: contractTerms.purse_expiration,
      deposits: depositSigsResp.deposits,
      min_age: 0,
      econtract: econtractResp.econtract,
    };

    const httpResp = await http.fetch(createPurseUrl.href, {
      method: "POST",
      body: reqBody,
    });

    const respBody = await httpResp.json();

    console.log("status", httpResp.status);

    console.log(j2s(respBody));

    const mergeUrl = new URL(`purses/${pursePub}/merge`, exchange.baseUrl);
    mergeUrl.searchParams.set("timeout_ms", "300");
    const statusResp = await http.fetch(mergeUrl.href, {});

    const statusRespBody = await statusResp.json();

    console.log(j2s(statusRespBody));

    t.assertTrue(statusRespBody.merge_timestamp === undefined);
  } catch (e) {
    if (e instanceof TalerError) {
      console.log(e);
      console.log(j2s(e.errorDetail));
    } else {
      console.log(e);
    }
    throw e;
  }
}

runExchangePurseTest.suites = ["wallet"];
