/*
 This file is part of GNU Taler
 (C) 2025 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 {
  AccessToken,
  decodeCrock,
  encodeCrock,
  hashNormalizedPaytoUri,
  j2s,
  KycStatusLongPollingReason,
  Logger,
  OfficerAccount,
  OfficerId,
  parsePaytoUriOrThrow,
  succeedOrThrow,
  TalerExchangeHttpClient,
  TalerMerchantInstanceHttpClient,
  TalerProtocolTimestamp,
} from "@gnu-taler/taler-util";
import { startFakeChallenger } from "../harness/fake-challenger.js";
import { GlobalTestState, harnessHttpLib } from "../harness/harness.js";
import { createTopsEnvironment, doFakeChallenger } from "../harness/tops.js";

const logger = new Logger("test-tops-aml.ts");

/**
 * Test for the custom address validation measures.
 */
export async function runTopsAmlCustomAddrPostalTest(t: GlobalTestState) {
  // Set up test environment

  const {
    exchange,
    bank,
    officerAcc,
    merchant,
    merchantAdminAccessToken,
    wireGatewayApi,
  } = await createTopsEnvironment(t);

  const challenger = await startFakeChallenger({
    port: 6001,
    addressType: "postal-ch",
  });

  const merchantClient = new TalerMerchantInstanceHttpClient(
    merchant.makeInstanceBaseUrl(),
  );
  // Do KYC auth transfer
  {
    const kycStatus = await merchantClient.getCurrentInstanceKycStatus(
      merchantAdminAccessToken,
      {},
    );

    console.log(`kyc status: ${j2s(kycStatus)}`);

    t.assertDeepEqual(kycStatus.case, "ok");

    t.assertTrue(kycStatus.body != null);

    t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-wire-required");

    const depositPaytoUri = kycStatus.body.kyc_data[0].payto_uri;
    t.assertTrue(kycStatus.body.kyc_data[0].payto_kycauths != null);
    const authTxPayto = parsePaytoUriOrThrow(
      kycStatus.body.kyc_data[0]?.payto_kycauths[0],
    );
    const authTxMessage = authTxPayto?.params["message"];
    t.assertTrue(typeof authTxMessage === "string");
    t.assertTrue(authTxMessage.startsWith("KYC:"));
    const accountPub = authTxMessage.substring(4);
    logger.info(`merchant account pub: ${accountPub}`);
    await wireGatewayApi.addKycAuth({
      auth: bank.getAdminAuth(),
      body: {
        amount: "CHF:0.1",
        debit_account: depositPaytoUri,
        account_pub: accountPub,
      },
    });
  }

  let accessToken: AccessToken;
  let merchantPaytoHash: string;

  // Wait for auth transfer to be registered by the exchange
  {
    const kycStatus = await merchantClient.getCurrentInstanceKycStatus(
      merchantAdminAccessToken,
      {
        reason: KycStatusLongPollingReason.AUTH_TRANSFER,
        timeout: 30000,
      },
    );
    logger.info(`kyc status after transfer: ${j2s(kycStatus)}`);
    t.assertDeepEqual(kycStatus.case, "ok");
    t.assertTrue(kycStatus.body != null);
    t.assertDeepEqual(kycStatus.body.kyc_data[0].status, "kyc-required");
    t.assertTrue(typeof kycStatus.body.kyc_data[0].access_token === "string");
    accessToken = kycStatus.body.kyc_data[0].access_token as AccessToken;
    merchantPaytoHash = encodeCrock(
      hashNormalizedPaytoUri(kycStatus.body.kyc_data[0].payto_uri),
    );
  }

  const exchangeClient = new TalerExchangeHttpClient(exchange.baseUrl, {
    httpClient: harnessHttpLib,
  });

  // Accept ToS
  {
    const kycInfo = await exchangeClient.checkKycInfo(
      accessToken,
      undefined,
      undefined,
    );
    console.log(j2s(kycInfo));

    t.assertDeepEqual(kycInfo.case, "ok");
    t.assertDeepEqual(kycInfo.body.requirements.length, 1);
    t.assertDeepEqual(kycInfo.body.requirements[0].form, "accept-tos");
    const requirementId = kycInfo.body.requirements[0].id;
    t.assertTrue(typeof requirementId === "string");

    const uploadRes = await exchangeClient.uploadKycForm(requirementId, {
      FORM_ID: "accept-tos",
      FORM_VERSION: 1,
      ACCEPTED_TERMS_OF_SERVICE: "v1",
      DOWNLOADED_TERMS_OF_SERVICE: true,
    });
    console.log("upload res", uploadRes);
    t.assertDeepEqual(uploadRes.case, "ok");
  }

  {
    const kycInfo = await exchangeClient.checkKycInfo(
      accessToken,
      undefined,
      undefined,
    );
    console.log(j2s(kycInfo));

    // FIXME: Do we expect volunary measures here?
    // => not yet, see https://bugs.gnunet.org/view.php?id=9879
  }

  await merchant.runKyccheckOnce();

  {
    const kycStatus = await merchantClient.getCurrentInstanceKycStatus(
      merchantAdminAccessToken,
      {
        reason: KycStatusLongPollingReason.AUTH_TRANSFER,
        timeout: 30000,
      },
    );
    logger.info(`kyc status after accept-tos: ${j2s(kycStatus)}`);
  }

  // Trigger postal registration check
  // via AML officer.
  {
    const decisionsResp = succeedOrThrow(
      await exchangeClient.getAmlDecisions(officerAcc, {
        active: true,
      }),
    );
    console.log(j2s(decisionsResp));

    t.assertDeepEqual(decisionsResp.records.length, 1);
    const rec = decisionsResp.records[0];

    t.assertDeepEqual(merchantPaytoHash, rec.h_payto);

    succeedOrThrow(
      await exchangeClient.makeAmlDesicion(officerAcc, {
        decision_time: TalerProtocolTimestamp.now(),
        h_payto: rec.h_payto,
        justification: "bla",
        properties: rec.properties ?? {},
        keep_investigating: rec.to_investigate,
        new_measures: "my-postal-registration",
        new_rules: {
          custom_measures: {
            "my-postal-registration": {
              prog_name: "challenger-postal-from-context",
              context: {
                FULL_NAME: "Richard Stallman",
                ADDRESS_LINES: "Bundesgasse 1\n1234 Bern",
                ADDRESS_COUNTRY: "CH",
              },
              check_name: "SKIP",
            },
          },
          expiration_time: TalerProtocolTimestamp.never(),
          rules: rec.limits.rules,
        },
      }),
    );
  }

  {
    const kycInfoResp = await exchangeClient.checkKycInfo(
      accessToken,
      undefined,
      undefined,
    );
    console.log(
      `kyc info after my-postal-registration measure`,
      j2s(kycInfoResp),
    );
    t.assertDeepEqual(kycInfoResp.case, "ok");
    const kycInfo = kycInfoResp.body;
    t.assertDeepEqual(kycInfo.requirements[0].form, "LINK");
    const requirementId = kycInfo.requirements[0].id;
    t.assertTrue(typeof requirementId === "string");

    const chRes = await doFakeChallenger(t, {
      exchangeClient,
      requirementId,
      challenger,
      address: {
        CONTACT_NAME: "Richard Stallman",
        ADDRESS_LINES: "Bundesgasse 1\n1234 Bern",
      },
    });

    const setupReq = chRes.setupRequest;
    console.log(`setup request: ${j2s(setupReq)}`);

    t.assertDeepEqual(setupReq.CONTACT_NAME, "Richard Stallman");
    t.assertDeepEqual(setupReq.read_only, true);
  }
}

runTopsAmlCustomAddrPostalTest.suites = ["wallet"];
