/*
 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,
  TalerCorebankApiClient,
  TransactionIdStr,
  TransactionMajorState,
  TransactionMinorState,
} from "@gnu-taler/taler-util";
import { readResponseJsonOrThrow } from "@gnu-taler/taler-util/http";
import {
  createSyncCryptoApi,
  EddsaKeypair,
  WalletApiOperation,
} from "@gnu-taler/taler-wallet-core";
import { CoinConfig, defaultCoinConfig } from "../harness/denomStructures.js";
import {
  BankService,
  DbInfo,
  ExchangeService,
  generateRandomPayto,
  GlobalTestState,
  HarnessExchangeBankAccount,
  harnessHttpLib,
  setupDb,
  WalletClient,
  WalletService,
} from "../harness/harness.js";
import { EnvOptions, withdrawViaBankV3 } from "../harness/helpers.js";

interface KycTestEnv {
  commonDb: DbInfo;
  bankClient: TalerCorebankApiClient;
  exchange: ExchangeService;
  exchangeBankAccount: HarnessExchangeBankAccount;
  walletClient: WalletClient;
  walletService: WalletService;
  amlKeypair: EddsaKeypair;
}

async function createKycTestkudosEnvironment(
  t: GlobalTestState,
  coinConfig: CoinConfig[] = defaultCoinConfig.map((x) => x("TESTKUDOS")),
  opts: EnvOptions = {},
): Promise<KycTestEnv> {
  const db = await setupDb(t);

  const bank = await BankService.create(t, {
    allowRegistrations: true,
    currency: "TESTKUDOS",
    database: db.connStr,
    httpPort: 8082,
  });

  const exchange = ExchangeService.create(t, {
    name: "testexchange-1",
    currency: "TESTKUDOS",
    httpPort: 8081,
    database: db.connStr,
  });

  let receiverName = "Exchange";
  let exchangeBankUsername = "exchange";
  let exchangeBankPassword = "mypw";
  let exchangePaytoUri = generateRandomPayto(exchangeBankUsername);

  await exchange.addBankAccount("1", {
    accountName: exchangeBankUsername,
    accountPassword: exchangeBankPassword,
    wireGatewayApiBaseUrl: new URL(
      "accounts/exchange/taler-wire-gateway/",
      bank.baseUrl,
    ).href,
    accountPaytoUri: exchangePaytoUri,
  });

  bank.setSuggestedExchange(exchange, exchangePaytoUri);

  await bank.start();

  await bank.pingUntilAvailable();

  const bankClient = new TalerCorebankApiClient(bank.corebankApiBaseUrl, {
    auth: {
      username: "admin",
      password: "adminpw",
    },
  });

  await bankClient.registerAccountExtended({
    name: receiverName,
    password: exchangeBankPassword,
    username: exchangeBankUsername,
    is_taler_exchange: true,
    payto_uri: exchangePaytoUri,
  });

  exchange.addCoinConfigList(coinConfig);

  await exchange.modifyConfig(async (config) => {
    config.setString("exchange", "enable_kyc", "yes");

    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(
      "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", "M1");

    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", "M1");

    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 birthdate");
    config.setString("KYC-CHECK-C1", "fallback", "M1");

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

  await exchange.start();

  const cryptoApi = createSyncCryptoApi();
  const amlKeypair = await cryptoApi.createEddsaKeypair({});

  await exchange.enableAmlAccount(amlKeypair.pub, "Alice");

  const walletService = new WalletService(t, {
    name: "wallet",
    useInMemoryDb: true,
  });
  await walletService.start();
  await walletService.pingUntilAvailable();

  const walletClient = new WalletClient({
    name: "wallet",
    unixPath: walletService.socketPath,
    onNotification(n) {
      console.log("got notification", n);
    },
  });
  await walletClient.connect();
  await walletClient.client.call(WalletApiOperation.InitWallet, {
    config: {
      testing: {
        skipDefaults: true,
      },
    },
  });

  console.log("setup done!");

  return {
    commonDb: db,
    exchange,
    amlKeypair,
    walletClient,
    walletService,
    bankClient,
    exchangeBankAccount: {
      accountName: "",
      accountPassword: "",
      accountPaytoUri: "",
      wireGatewayApiBaseUrl: "",
    },
  };
}

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

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

  // Withdraw digital cash into the wallet.

  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));
  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(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/x-www-form-urlencoded",
      },
      body: "full_name=Alice+Abc&birthdate=2000-01-01",
    },
  );

  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: wres.transactionId as TransactionIdStr,
    txState: {
      major: TransactionMajorState.Done,
    },
  });
}

runKycFormWithdrawalTest.suites = ["wallet"];
