/*
 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 } from "../harness/tops.js";

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

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

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

  const challengerSms = await startFakeChallenger({
    port: 6002,
    addressType: "phone",
  });

  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)}`);
  }

  const officerAcc: OfficerAccount = {
    id: amlKeypair.pub as OfficerId,
    signingKey: decodeCrock(amlKeypair.priv),
  };

  // 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-sms-registration",
        new_rules: {
          custom_measures: {
            "my-sms-registration": {
              prog_name: "challenger-sms-from-context",
              context: {
                CONTACT_PHONE: "+4123456789",
              },
              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");
    t.assertTrue(typeof kycInfo.requirements[0].id === "string");

    const startResp = succeedOrThrow(
      await exchangeClient.startExternalKycProcess(
        kycInfo.requirements[0].id,
        {},
      ),
    );
    console.log(`start resp`, j2s(startResp));

    let challengerRedirectUrl = startResp.redirect_url;

    const resp = await harnessHttpLib.fetch(challengerRedirectUrl);
    const respJson = await resp.json();
    console.log(`challenger resp: ${j2s(respJson)}`);

    const nonce = respJson.nonce;
    t.assertTrue(typeof nonce === "string");
    const proofRedirectUrl = respJson.redirect_url;

    challengerSms.fakeVerification(nonce, {
      CONTACT_PHONE: "+4123456789",
    });

    console.log("nonce", nonce);
    console.log("proof redirect URL", proofRedirectUrl);

    const proofResp = await harnessHttpLib.fetch(proofRedirectUrl, {
      redirect: "manual",
    });
    console.log("proof status:", proofResp.status);
    t.assertDeepEqual(proofResp.status, 303);

    const setupReq = challengerSms.getSetupRequest(nonce);
    console.log(`setup request: ${j2s(setupReq)}`);

    t.assertDeepEqual(setupReq.CONTACT_PHONE, "+4123456789");
    t.assertDeepEqual(setupReq.read_only, true);
  }
}

runTopsAmlCustomAddrSmsTest.suites = ["wallet"];
