/*
 This file is part of GNU Taler
 (C) 2024 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 {
  codecForAccountKycRedirects,
  codecForKycProcessClientInformation,
  codecForQueryInstancesResponse,
  Configuration,
  encodeCrock,
  hashNormalizedPaytoUri,
  j2s,
  Logger,
  MerchantAccountKycRedirectsResponse,
  MerchantAccountKycStatus,
  succeedOrThrow,
  TalerMerchantApi,
} from "@gnu-taler/taler-util";
import {
  readResponseJsonOrThrow,
  readSuccessResponseJsonOrThrow,
} from "@gnu-taler/taler-util/http";
import {
  configureCommonKyc,
  createKycTestkudosEnvironment,
  postAmlDecisionNoRules,
} from "../harness/environments.js";
import {
  delayMs,
  GlobalTestState,
  harnessHttpLib,
} from "../harness/harness.js";

const logger = new Logger(`test-kyc-merchant-deposit.ts`);

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

  config.setString("KYC-RULE-R1", "operation_type", "deposit");
  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:0");
  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", "P1");

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

  config.setString("AML-PROGRAM-P1", "command", "/bin/true");
  config.setString("AML-PROGRAM-P1", "enabled", "true");
  config.setString("AML-PROGRAM-P1", "description", "this does nothing");
  config.setString("AML-PROGRAM-P1", "fallback", "FREEZE");

  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 runKycMerchantDepositTest(t: GlobalTestState) {
  // Set up test environment

  const {
    merchant,
    merchantAdminAccessToken,
    bankClient,
    exchange,
    bank,
    amlKeypair,
    wireGatewayApi,
  } = await createKycTestkudosEnvironment(t, {
    adjustExchangeConfig,
  });

  let accountPub: string;

  const headers = {
    Authorization: `Bearer ${merchantAdminAccessToken}`,
  };
  {
    const instanceUrl = new URL("private", merchant.makeInstanceBaseUrl());
    const resp = await harnessHttpLib.fetch(instanceUrl.href, { headers });
    const parsedResp = await readSuccessResponseJsonOrThrow(
      resp,
      codecForQueryInstancesResponse(),
    );
    accountPub = parsedResp.merchant_pub;
  }

  // Withdraw digital cash into the wallet.

  let kycRespOne: MerchantAccountKycRedirectsResponse | undefined = undefined;

  while (1) {
    const kycStatusUrl = new URL("private/kyc", merchant.makeInstanceBaseUrl())
      .href;
    logger.info(`requesting GET ${kycStatusUrl}`);
    const resp = await harnessHttpLib.fetch(kycStatusUrl, { headers });
    if (resp.status === 200) {
      const pr = await readSuccessResponseJsonOrThrow(
        resp,
        codecForAccountKycRedirects(),
      );
      if (
        pr.kyc_data[0].status === MerchantAccountKycStatus.EXCHANGE_UNREACHABLE
      ) {
        logger.info(`exchange still unreachable according to merchant`);
      } else {
        kycRespOne = pr;
        break;
      }
    }
    // Wait 500ms
    await delayMs(500);
  }

  t.assertTrue(!!kycRespOne);

  logger.info(`mechant kyc status: ${j2s(kycRespOne)}`);

  t.assertDeepEqual(
    kycRespOne.kyc_data[0].status,
    MerchantAccountKycStatus.KYC_WIRE_REQUIRED,
  );

  t.assertDeepEqual(kycRespOne.kyc_data[0].exchange_http_status, 404);

  t.assertTrue(
    (kycRespOne.kyc_data[0].limits?.length ?? 0) > 0,
    "kyc status should contain non-empty limits",
  );

  // Order creation should fail!
  await t.runSpanAsync("order creation should be rejected", async () => {
    let url = new URL("private/orders", merchant.makeInstanceBaseUrl());
    const order = {
      summary: "Test",
      amount: "TESTKUDOS:5",
      fulfillment_url: "taler://fulfillment-success/thx",
    } satisfies TalerMerchantApi.Order;
    const resp = await harnessHttpLib.fetch(url.href, {
      method: "POST",
      body: {
        order,
      },
      headers,
    });

    logger.info(`order creation status: ${resp.status}`);
    t.assertTrue(resp.status !== 200);
  });

  await bankClient.registerAccountExtended({
    name: "merchant-default",
    password: "merchant-default",
    username: "merchant-default",
    payto_uri: kycRespOne.kyc_data[0].payto_uri, //this bank user needs to have the same payto that the exchange is asking from
  });
  succeedOrThrow(
    await wireGatewayApi.addKycAuth({
      auth: bank.getAdminAuth(),
      body: {
        amount: "TESTKUDOS:0.1",
        account_pub: accountPub,
        debit_account: kycRespOne.kyc_data[0].payto_uri,
      },
    }),
  );

  let kycRespTwo: MerchantAccountKycRedirectsResponse | undefined = undefined;

  // We do this in a loop as a work-around.
  // Not exactly the correct behavior from the merchant right now.
  while (true) {
    const kycStatusLongpollUrl = new URL(
      "private/kyc",
      merchant.makeInstanceBaseUrl(),
    );
    kycStatusLongpollUrl.searchParams.set("lpt", "1");
    const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href, {
      headers,
    });
    t.assertDeepEqual(resp.status, 200);
    const parsedResp = await readSuccessResponseJsonOrThrow(
      resp,
      codecForAccountKycRedirects(),
    );
    logger.info(`kyc resp 2: ${j2s(parsedResp)}`);
    if (parsedResp.kyc_data[0].payto_kycauths == null) {
      kycRespTwo = parsedResp;
      break;
    }
    // Wait 500ms
    await delayMs(500);
  }

  t.assertTrue(!!kycRespTwo);

  await postAmlDecisionNoRules(t, {
    amlPriv: amlKeypair.priv,
    amlPub: amlKeypair.pub,
    exchangeBaseUrl: exchange.baseUrl,
    paytoHash: encodeCrock(
      hashNormalizedPaytoUri(kycRespTwo.kyc_data[0].payto_uri),
    ),
  });

  while (true) {
    // We first POST an order to trigger checking of the KYC status,
    // as no requirement is active.
    let url = new URL("private/orders", merchant.makeInstanceBaseUrl());
    const order = {
      summary: "Test",
      amount: "TESTKUDOS:5",
      fulfillment_url: "taler://fulfillment-success/thx",
    } satisfies TalerMerchantApi.Order;
    const postOrderResp = await harnessHttpLib.fetch(url.href, {
      method: "POST",
      body: {
        order,
      },
      headers,
    });

    logger.info(`POST /private/orders status: ${postOrderResp.status}`);
    if (postOrderResp.status === 409) {
      const respJson = await postOrderResp.json();
      console.log(`${j2s(respJson)}`);
    }

    const kycStatusLongpollUrl = new URL(
      "private/kyc",
      merchant.makeInstanceBaseUrl(),
    );
    kycStatusLongpollUrl.searchParams.set("lpt", "3");
    const resp = await harnessHttpLib.fetch(kycStatusLongpollUrl.href, {
      headers,
    });
    t.assertDeepEqual(resp.status, 200);
    const parsedResp = await readSuccessResponseJsonOrThrow(
      resp,
      codecForAccountKycRedirects(),
    );
    logger.info(`kyc resp 3: ${j2s(parsedResp)}`);
    if ((parsedResp.kyc_data[0].limits?.length ?? 0) == 0) {
      break;
    }

    const accessToken = parsedResp.kyc_data[0].access_token;

    t.assertTrue(!!accessToken);

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

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

    logger.info(`kyc-info: ${j2s(clientInfo)}`);

    // Wait 500ms
    await delayMs(500);

    // FIXME: Remove this once we can configure timeouts
    // https://bugs.gnunet.org/view.php?id=9892
    await merchant.runKyccheckOnce();
  }
}

runKycMerchantDepositTest.suites = ["wallet", "merchant", "kyc"];
