/*
 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 {
  codecForAny,
  codecForKycProcessClientInformation,
  decodeCrock,
  encodeCrock,
  j2s,
  signAmlQuery,
  TalerProtocolTimestamp,
  TransactionIdStr,
  TransactionMajorState,
  TransactionMinorState,
} from "@gnu-taler/taler-util";
import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
  configureCommonKyc,
  createKycTestkudosEnvironment,
  postAmlDecision,
  withdrawViaBankV3,
} from "../harness/environments.js";
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";

/**
 * Test setting a `new_measure` as the AML officer.
 */
export async function runKycNewMeasureTest(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", "withdraw");
        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 M2");

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

        config.setString("KYC-MEASURE-M2", "check_name", "C2");
        config.setString("KYC-MEASURE-M2", "context", "{}");
        config.setString("KYC-MEASURE-M2", "program", "P2");

        config.setString("KYC-MEASURE-M3", "check_name", "C3");
        config.setString("KYC-MEASURE-M3", "context", "{}");
        config.setString("KYC-MEASURE-M3", "program", "P2");

        config.setString(
          "AML-PROGRAM-P1",
          "command",
          "taler-exchange-helper-measure-test-form",
        );
        config.setString("AML-PROGRAM-P1", "enabled", "true");
        config.setString(
          "AML-PROGRAM-P1",
          "description",
          "test for full_name and birthdate",
        );
        config.setString("AML-PROGRAM-P1", "description_i18n", "{}");
        config.setString("AML-PROGRAM-P1", "fallback", "FREEZE");

        config.setString("AML-PROGRAM-P2", "command", "/bin/true");
        config.setString("AML-PROGRAM-P2", "enabled", "true");
        config.setString("AML-PROGRAM-P2", "description", "does nothing");
        config.setString("AML-PROGRAM-P2", "description_i18n", "{}");
        config.setString("AML-PROGRAM-P2", "fallback", "FREEZE");

        config.setString("KYC-CHECK-C1", "type", "FORM");
        config.setString("KYC-CHECK-C1", "form_name", "myform");
        config.setString("KYC-CHECK-C1", "description", "my check!");
        config.setString("KYC-CHECK-C1", "description_i18n", "{}");
        config.setString("KYC-CHECK-C1", "outputs", "FULL_NAME DATE_OF_BIRTH");
        config.setString("KYC-CHECK-C1", "fallback", "FREEZE");

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

        config.setString("KYC-CHECK-C3", "type", "INFO");
        config.setString("KYC-CHECK-C3", "description", "this is info c3");
        config.setString("KYC-CHECK-C3", "description_i18n", "{}");
        config.setString("KYC-CHECK-C3", "fallback", "FREEZE");
      },
    });

  // Withdraw digital cash into the wallet.
  let kycPaytoHash: string | undefined;
  let accessToken: string | undefined;
  let firstTransaction: string | undefined;

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

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

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

    console.log(j2s(txDetails));

    accessToken = txDetails.kycAccessToken;
    kycPaytoHash = txDetails.kycPaytoHash;
    firstTransaction = wres.transactionId;
  }

  t.assertTrue(!!accessToken);
  const infoResp = await harnessHttpLib.fetch(
    new URL(`kyc-info/${accessToken}`, exchange.baseUrl).href,
  );

  const clientInfo = await readResponseJsonOrThrow(
    infoResp,
    codecForKycProcessClientInformation(),
  );

  console.log(j2s(clientInfo));

  const kycId = clientInfo.requirements.find((x) => x.id != null)?.id;
  t.assertTrue(!!kycId);

  const uploadResp = await harnessHttpLib.fetch(
    new URL(`kyc-upload/${kycId}`, exchange.baseUrl).href,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: {
        FULL_NAME: "Alice Abc",
        DATE_OF_BIRTH: "2020-01-01",
        FORM_ID: "test",
      },
    },
  );

  console.log("resp status", uploadResp.status);

  t.assertDeepEqual(uploadResp.status, 204);

  const sig = signAmlQuery(decodeCrock(amlKeypair.priv));
  {
    const decisionsResp = await harnessHttpLib.fetch(
      new URL(`aml/${amlKeypair.pub}/decisions`, exchange.baseUrl).href,
      {
        headers: {
          "Taler-AML-Officer-Signature": encodeCrock(sig),
        },
      },
    );

    const decisions = await readResponseJsonOrThrow(
      decisionsResp,
      codecForAny(),
    );
    console.log(j2s(decisions));

    t.assertDeepEqual(decisionsResp.status, 200);
  }
  // KYC should pass now
  await walletClient.call(WalletApiOperation.TestingWaitTransactionState, {
    transactionId: firstTransaction as TransactionIdStr,
    txState: {
      major: TransactionMajorState.Done,
    },
  });

  // Now, AML officer takes action and freezes the account, requiring a new_measure
  t.assertTrue(!!kycPaytoHash);

  await postAmlDecision(t, {
    amlPriv: amlKeypair.priv,
    amlPub: amlKeypair.pub,
    exchangeBaseUrl: exchange.baseUrl,
    paytoHash: kycPaytoHash,
    newMeasures: "m3",
    newRules: {
      expiration_time: TalerProtocolTimestamp.never(),
      custom_measures: {},
      rules: [
        // No rules!
      ],
    },
  });

  {
    const decisionsResp = await harnessHttpLib.fetch(
      new URL(`aml/${amlKeypair.pub}/decisions`, exchange.baseUrl).href,
      {
        headers: {
          "Taler-AML-Officer-Signature": encodeCrock(sig),
        },
      },
    );

    const decisions = await readResponseJsonOrThrow(
      decisionsResp,
      codecForAny(),
    );
    console.log(j2s(decisions));

    t.assertDeepEqual(decisionsResp.status, 200);
  }

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

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

    const txDetails = await walletClient.call(
      WalletApiOperation.GetTransactionById,
      {
        transactionId: wres.transactionId,
      },
    );
    console.log(j2s(txDetails));

    const accessToken = txDetails.kycAccessToken;
    t.assertTrue(!!accessToken);

    const infoResp = await harnessHttpLib.fetch(
      new URL(`kyc-info/${txDetails.kycAccessToken}`, exchange.baseUrl).href,
    );

    const clientInfo = await readResponseJsonOrThrow(
      infoResp,
      codecForKycProcessClientInformation(),
    );

    console.log("second withdrawal, clientInfo:");
    console.log(j2s(clientInfo));
  }
}

runKycNewMeasureTest.suites = ["wallet"];
