/*
 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 {
  AbsoluteTime,
  AmountString,
  Duration,
  j2s,
  TransactionIdStr,
  TransactionMajorState,
  TransactionMinorState,
  TransactionType,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
  configureCommonKyc,
  createKycTestkudosEnvironment,
  createWalletDaemonWithClient,
  postAmlDecisionNoRules,
  withdrawViaBankV3,
} from "../harness/environments.js";
import { GlobalTestState, WalletClient } from "../harness/harness.js";

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

  const { walletClient, bankClient, exchange, amlKeypair } =
    await createKycTestkudosEnvironment(t, {
      adjustExchangeConfig(config) {
        configureCommonKyc(config);

        config.setString("KYC-RULE-R1", "operation_type", "merge");
        config.setString("KYC-RULE-R1", "enabled", "yes");
        config.setString("KYC-RULE-R1", "exposed", "yes");
        config.setString("KYC-RULE-R1", "is_and_combinator", "yes");
        config.setString("KYC-RULE-R1", "threshold", "TESTKUDOS:5");
        config.setString("KYC-RULE-R1", "timeframe", "1d");
        config.setString("KYC-RULE-R1", "next_measures", "M1");

        config.setString("KYC-MEASURE-M1", "check_name", "C1");
        config.setString("KYC-MEASURE-M1", "context", "{}");
        config.setString("KYC-MEASURE-M1", "program", "NONE");

        config.setString("KYC-CHECK-C1", "type", "INFO");
        config.setString("KYC-CHECK-C1", "description", "my check!");
        config.setString("KYC-CHECK-C1", "fallback", "FREEZE");
      },
    });

  // Origin wallet for the p2p transaction,
  // will pay for the invoice.
  const w0 = await createWalletDaemonWithClient(t, {
    name: "w0",
  });

  // Withdraw digital cash into the wallet.

  const wres1 = await withdrawViaBankV3(t, {
    bankClient,
    amount: "TESTKUDOS:20",
    exchange: exchange,
    walletClient: w0.walletClient,
  });

  await wres1.withdrawalFinishedCond;

  const wres2 = await withdrawViaBankV3(t, {
    bankClient,
    amount: "TESTKUDOS:1",
    exchange: exchange,
    walletClient: walletClient,
  });

  await wres2.withdrawalFinishedCond;

  const pullRes = await doPeerPullCredit(t, {
    walletClient,
    amount: "TESTKUDOS:10",
    summary: "test123",
  });

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

  console.log("tx details", j2s(txDet));

  const kycPaytoHash = txDet.kycPaytoHash;

  t.assertTrue(!!kycPaytoHash);

  t.assertTrue(!!txDet.kycUrl);
  t.assertTrue(!!txDet.kycAccessToken);
  t.assertTrue(txDet.kycUrl.includes(txDet.kycAccessToken));

  await postAmlDecisionNoRules(t, {
    amlPriv: amlKeypair.priv,
    amlPub: amlKeypair.pub,
    exchangeBaseUrl: exchange.baseUrl,
    paytoHash: kycPaytoHash,
  });

  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: pullRes.transactionId as TransactionIdStr,
    txState: {
      major: TransactionMajorState.Pending,
      minor: TransactionMinorState.Ready,
    },
  });

  const prepRes = await w0.walletClient.call(
    WalletApiOperation.PreparePeerPullDebit,
    {
      talerUri: pullRes.talerUri,
    },
  );

  await w0.walletClient.call(WalletApiOperation.ConfirmPeerPullDebit, {
    transactionId: prepRes.transactionId,
  });

  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: pullRes.transactionId as TransactionIdStr,
    txState: {
      major: TransactionMajorState.Done,
    },
  });
}

/**
 * Initiate a pull credit transaction, wait until the transaction
 * is ready.
 */
async function doPeerPullCredit(
  t: GlobalTestState,
  args: {
    walletClient: WalletClient;
    amount: AmountString;
    summary?: string;
  },
): Promise<{
  transactionId: string;
  talerUri: string;
}> {
  const purse_expiration = AbsoluteTime.toProtocolTimestamp(
    AbsoluteTime.addDuration(
      AbsoluteTime.now(),
      Duration.fromSpec({ days: 2 }),
    ),
  );
  const initRet = await args.walletClient.call(
    WalletApiOperation.InitiatePeerPullCredit,
    {
      partialContractTerms: {
        amount: args.amount,
        summary: args.summary ?? "Test P2P Payment",
        purse_expiration,
      },
    },
  );

  await args.walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: initRet.transactionId,
    txState: {
      major: TransactionMajorState.Pending,
      minor: TransactionMinorState.MergeKycRequired,
    },
  });

  const txDet = await args.walletClient.call(
    WalletApiOperation.GetTransactionById,
    {
      transactionId: initRet.transactionId,
    },
  );

  t.assertTrue(txDet.type === TransactionType.PeerPullCredit);
  const talerUri = txDet.talerUri;
  t.assertTrue(!!talerUri);

  return {
    transactionId: initRet.transactionId,
    talerUri,
  };
}

runKycPeerPullTest.suites = ["wallet"];
