/*
 This file is part of GNU Taler
 (C) 2021 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 {
  alternativeOrThrow,
  ChallengeResponse,
  Duration,
  HttpStatusCode,
  LoginTokenScope,
  MerchantAuthMethod,
  succeedOrThrow,
  TalerMerchantInstanceHttpClient,
  TalerMerchantManagementHttpClient,
  TanChannel,
} from "@gnu-taler/taler-util";
import { createSimpleTestkudosEnvironmentV3 } from "harness/environments.js";
import { startTanHelper, TestTanHelperService } from "harness/tan-helper.js";
import { randomBytes } from "node:crypto";
import { chmodSync, writeFileSync } from "node:fs";
import { GlobalTestState } from "../harness/harness.js";

/**
 * Test that the merchant can change name and emails address but can't start kyc process
 * before activating the account
 */
export async function runMerchantSelfProvisionInactiveAccountPermissionsTest(
  t: GlobalTestState,
) {
  // Set up test environment

  // FIXME: maybe merchant can use commands?
  const RND = randomBytes(10).toString("hex");
  const socketFile = `${t.testDir}/tan-helper-${RND}.socket`;
  const helperScript = `${t.testDir}/harness-helper-${RND}.sh`;
  writeFileSync(
    helperScript,
    `#!/bin/bash
  taler-harness run-helper --socket ${socketFile} -- $@
  `,
  );
  chmodSync(helperScript, "777");

  const {
    walletClient,
    bankClient,
    exchange,
    merchant,
    bank,
    merchantAdminAccessToken,
  } = await createSimpleTestkudosEnvironmentV3(t, undefined, {
    additionalMerchantConfig(m) {
      m.modifyConfig(async (cfg) => {
        cfg.setString("merchant", "ENABLE_SELF_PROVISIONING", "yes");
        cfg.setString("merchant", "HELPER_SMS", helperScript);
        cfg.setString("merchant", "HELPER_EMAIL", helperScript);
        cfg.setString("merchant", "MANDATORY_TAN_CHANNELS", "email");
      });
    },
  });
  const helper = await startTanHelper({ socketFile });

  const merchantClient = new TalerMerchantManagementHttpClient(
    merchant.makeInstanceBaseUrl(),
  );

  {
    const r = succeedOrThrow(
      await merchantClient.listInstances(merchantAdminAccessToken),
    );
    t.assertDeepEqual(r.instances.length, 2);
  }
  const instanceInfo = {
    id: "self-instance",
    name: "My instance",
    auth: {
      method: MerchantAuthMethod.TOKEN,
      password: "123",
    },
    default_pay_delay: Duration.toTalerProtocolDuration(
      Duration.fromSpec({ days: 14 }),
    ),
    default_wire_transfer_delay: Duration.toTalerProtocolDuration(
      Duration.fromSpec({ days: 14 }),
    ),
    jurisdiction: {},
    address: {},
    email: "some@taler.net",
    use_stefan: false,
  };

  const signupStart = alternativeOrThrow(
    await merchantClient.createInstanceSelfProvision(instanceInfo),
    HttpStatusCode.Accepted,
  );

  // creation requires 2fa
  t.assertDeepEqual(signupStart.challenges.length, 1);
  t.assertDeepEqual(signupStart.combi_and, true);

  const { challenge_id: firstChallenge } = signupStart.challenges[0];

  {
    // new instance is pending, then is not listed
    const r = succeedOrThrow(
      await merchantClient.listInstances(merchantAdminAccessToken),
    );
    t.assertDeepEqual(r.instances.length, 2);
  }

  const instanceApi = new TalerMerchantInstanceHttpClient(
    merchantClient.getSubInstanceAPI(instanceInfo.id),
    merchantClient.httpLib,
  );

  succeedOrThrow(await merchantClient.sendChallenge(firstChallenge));

  const [tan] = helper.getLastCodeForAddress(instanceInfo.email).split("\n");

  t.assertTrue(tan.length > 0, "the mechant didn't send the code to the email");

  // in this case the user used the wrong email and
  // needs to change it before confirm

  instanceInfo.email = "another@email.com";

  const emailChange = alternativeOrThrow(
    await merchantClient.createInstanceSelfProvision(instanceInfo),
    HttpStatusCode.Accepted,
  );

  // creation requires 2fa
  t.assertDeepEqual(emailChange.challenges.length, 1);
  t.assertDeepEqual(emailChange.combi_and, true);

  await solveMFA(merchantClient, helper, emailChange, {
    [TanChannel.EMAIL]: instanceInfo.email,
  });

  const wrongInfo = {
    ...instanceInfo,
    email: "just_wrong_email",
  };

  // const bad = failOrThrow(
  await merchantClient.createInstanceSelfProvision(wrongInfo, {
    challengeIds: emailChange.challenges.map((c) => c.challenge_id),
  });
  // ,
  // HttpStatusCode.Conflict,
  // );

  // console.log("ERRORORORORORO", bad)

  const completeSignup = succeedOrThrow(
    await merchantClient.createInstanceSelfProvision(instanceInfo, {
      challengeIds: emailChange.challenges.map((c) => c.challenge_id),
    }),
  );

  await merchantClient.createInstanceSelfProvision(wrongInfo, {
    challengeIds: emailChange.challenges.map((c) => c.challenge_id),
  });

  const loginChallenge = alternativeOrThrow(
    await instanceApi.createAccessToken(
      instanceInfo.id,
      instanceInfo.auth.password,
      {
        scope: LoginTokenScope.All,
      },
    ),
    HttpStatusCode.Accepted,
  );

  await solveMFA(merchantClient, helper, loginChallenge, {
    [TanChannel.EMAIL]: instanceInfo.email,
  });

  const { access_token: token } = succeedOrThrow(
    await instanceApi.createAccessToken(
      instanceInfo.id,
      instanceInfo.auth.password,
      {
        scope: LoginTokenScope.All,
      },
      {
        challengeIds: loginChallenge.challenges.map((c) => c.challenge_id),
      },
    ),
  );

  const det = succeedOrThrow(
    await instanceApi.getCurrentInstanceDetails(token),
  );

  // check that the instance has the new email
  t.assertDeepEqual(det.email, instanceInfo.email);
  t.assertDeepEqual(det.email_validated, true);
  helper.stop();
}

runMerchantSelfProvisionInactiveAccountPermissionsTest.suites = [
  "merchant",
  "self-provision",
];

export async function solveMFA(
  api: TalerMerchantInstanceHttpClient,
  helper: TestTanHelperService,
  resp: ChallengeResponse,
  addrs: Partial<Record<TanChannel, string>>,
) {
  for (const {
    challenge_id: challenge,
    tan_channel: type,
  } of resp.challenges) {
    const addrUsed = addrs[type];
    if (!addrUsed) {
      throw Error(
        `The MFA is asking unexpected channel: ${type} id ${challenge}`,
      );
    }
    succeedOrThrow(await api.sendChallenge(challenge));

    const [tan] = helper.getLastCodeForAddress(addrUsed).split("\n");

    succeedOrThrow(await api.confirmChallenge(challenge, { tan }));
  }
}
