/*
 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 {
  Configuration,
  encodeCrock,
  ExchangeWalletKycStatus,
  hashFullPaytoUri,
  j2s,
  LimitOperationType,
  TalerProtocolDuration,
  TalerProtocolTimestamp,
  TalerWireGatewayHttpClient,
  TransactionIdStr,
  TransactionMajorState,
  TransactionMinorState,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
  configureCommonKyc,
  createKycTestkudosEnvironment,
  postAmlDecision,
  withdrawViaBankV3,
} from "../harness/environments.js";
import { GlobalTestState } from "../harness/harness.js";

function adjustExchangeConfig(config: Configuration): void {
  configureCommonKyc(config);

  config.setString("KYC-RULE-R1", "operation_type", "balance");
  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:10");
  config.setString("KYC-RULE-R1", "timeframe", "forever");
  config.setString("KYC-RULE-R1", "next_measures", "M1");

  config.setString("KYC-RULE-R2", "operation_type", "balance");
  config.setString("KYC-RULE-R2", "enabled", "yes");
  config.setString("KYC-RULE-R2", "exposed", "yes");
  config.setString("KYC-RULE-R2", "is_and_combinator", "yes");
  config.setString("KYC-RULE-R2", "threshold", "TESTKUDOS:30");
  config.setString("KYC-RULE-R2", "timeframe", "forever");
  config.setString("KYC-RULE-R2", "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");
}

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

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

  // Withdraw digital cash into the wallet.

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

  t.logStep("waiting for transaction to be in pending(balance-kyc)");
  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: wres.transactionId as TransactionIdStr,
    txState: {
      major: TransactionMajorState.Pending,
      minor: TransactionMinorState.BalanceKycRequired,
    },
  });

  t.logStep("done waiting for transaction to be in pending(balance-kyc)");

  {
    const exchangeEntry = await walletClient.call(
      WalletApiOperation.GetExchangeEntryByUrl,
      {
        exchangeBaseUrl: exchange.baseUrl,
      },
    );
    console.log(j2s(exchangeEntry));
  }

  t.logStep("waiting for exchange state legi");

  await walletClient.call(WalletApiOperation.TestingWaitExchangeState, {
    exchangeBaseUrl: exchange.baseUrl,
    walletKycStatus: ExchangeWalletKycStatus.Legi,
  });

  t.logStep("done waiting for exchange state legi");

  {
    const exchangeEntry = await walletClient.call(
      WalletApiOperation.GetExchangeEntryByUrl,
      {
        exchangeBaseUrl: exchange.baseUrl,
      },
    );
    console.log(j2s(exchangeEntry));
    t.assertDeepEqual(
      exchangeEntry.walletKycStatus,
      ExchangeWalletKycStatus.Legi,
    );

    t.assertDeepEqual(
      exchangeEntry.walletKycRequestedThreshold,
      "TESTKUDOS:10",
    );

    const kycReservePub = exchangeEntry.walletKycReservePub;
    t.assertTrue(!!kycReservePub);

    // FIXME: Create/use helper function for this!
    const hPayto = hashFullPaytoUri(
      `payto://taler-reserve-http/localhost:${exchange.port}/${kycReservePub}`,
    );

    await postAmlDecision(t, {
      amlPriv: amlKeypair.priv,
      amlPub: amlKeypair.pub,
      exchangeBaseUrl: exchange.baseUrl,
      paytoHash: encodeCrock(hPayto),
      newRules: {
        expiration_time: TalerProtocolTimestamp.never(),
        rules: [
          {
            measures: ["verboten"],
            display_priority: 1,
            operation_type: LimitOperationType.balance,
            threshold: "TESTKUDOS:30",
            timeframe: TalerProtocolDuration.forever(),
            exposed: true,
          },
          {
            measures: ["verboten"],
            display_priority: 1,
            operation_type: LimitOperationType.deposit,
            threshold: "TESTKUDOS:5",
            timeframe: TalerProtocolDuration.fromSpec({ days: 1 }),
            exposed: true,
          },
        ],
        custom_measures: {},
      },
    });
  }

  t.logStep("waiting for withdrawal to finish");

  // Now after KYC is done for the balance, the withdrawal should finish
  await wres.withdrawalFinishedCond;

  t.logStep("done waiting for withdrawal to finish");

  // Now test the *next* threshold, but this time using manual withdrawal

  t.logStep("testing manual withdrawal");

  const mwResp = await walletClient.call(
    WalletApiOperation.AcceptManualWithdrawal,
    {
      // Specify larger amount than what will be in the reserve.
      amount: "TESTKUDOS:100",
      exchangeBaseUrl: exchange.baseUrl,
    },
  );

  const user = await bankClient.createRandomBankUser();

  // Next, do a manual withdrawal.
  const wireGatewayApiClient = new TalerWireGatewayHttpClient(
    exchangeBankAccount.wireGatewayApiBaseUrl,
  );

  const reservePub = mwResp.reservePub;

  await wireGatewayApiClient.addIncoming({
    auth: bank.getAdminAuth(),
    body: {
      amount: "TESTKUDOS:5",
      debit_account: user.accountPaytoUri,
      reserve_pub: reservePub,
    },
  });

  // Wallet should not require KYC, since the actual amount withdrawn
  // does not pass the threshold.
  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: mwResp.transactionId as TransactionIdStr,
    txState: {
      major: TransactionMajorState.Done,
    },
  });
}

runKycBalanceWithdrawalTest.suites = ["wallet"];
