/*
 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.

NU General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
 */

/**
 * Imports.
 */
import {
  AbsoluteTime,
  AccessToken,
  DiscountListDetail,
  Duration,
  Order,
  OrderInputType,
  OrderOutputType,
  PreparePayResultType,
  succeedOrThrow,
  TalerMerchantInstanceHttpClient,
  TalerProtocolTimestamp,
  TokenFamilyDetails,
  TokenFamilyKind,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import {
  createSimpleTestkudosEnvironmentV3,
  withdrawViaBankV3,
} from "harness/environments.js";
import { defaultCoinConfig } from "../harness/denomStructures.js";
import { GlobalTestState, WalletClient } from "../harness/harness.js";
import { logger } from "./test-tops-challenger-twice.js";

export async function runWalletTokensDiscountTest(t: GlobalTestState) {
  let {
    bankClient,
    exchange,
    merchant,
    walletClient,
    merchantAdminAccessToken,
  } = await createSimpleTestkudosEnvironmentV3(
    t,
    defaultCoinConfig.map((x) => x("TESTKUDOS")),
    {
      walletConfig: {
        features: {
          enableV1Contracts: true,
        },
      },
    },
  );

  const merchantApi = new TalerMerchantInstanceHttpClient(
    merchant.makeInstanceBaseUrl(),
  );

  // withdraw some test money
  const wres = await withdrawViaBankV3(t, {
    walletClient,
    bankClient,
    exchange,
    amount: "TESTKUDOS:40",
  });
  await wres.withdrawalFinishedCond;

  let tokenFamilyJson = {
    kind: TokenFamilyKind.Discount,
    slug: "test_discount",
    name: "Test discount",
    description: "This is a test discount",
    description_i18n: {},
    valid_after: TalerProtocolTimestamp.now(),
    valid_before: AbsoluteTime.toProtocolTimestamp(
      AbsoluteTime.addDuration(
        AbsoluteTime.now(),
        Duration.fromSpec({ years: 1 }),
      ),
    ),
    duration: Duration.toTalerProtocolDuration(
      Duration.fromSpec({ days: 90 }),
    ),
    validity_granularity: Duration.toTalerProtocolDuration(
      Duration.fromSpec({ days: 1 }),
    ),
  };

  // setup discount token family
  succeedOrThrow(
    await merchantApi.createTokenFamily(
      merchantAdminAccessToken,
      tokenFamilyJson,
    ),
  );

  let orderJsonDiscount: Order = {
    version: 1,
    summary: "Test order",
    timestamp: TalerProtocolTimestamp.now(),
    pay_deadline: AbsoluteTime.toProtocolTimestamp(
      AbsoluteTime.addDuration(
        AbsoluteTime.now(),
        Duration.fromSpec({ days: 1 }),
      ),
    ),
    choices: [
      {
        amount: "TESTKUDOS:2",
        inputs: [],
        outputs: [
          {
            type: OrderOutputType.Token,
            token_family_slug: "test_discount",
          },
        ],
      },
      {
        amount: "TESTKUDOS:1",
        inputs: [
          {
            type: OrderInputType.Token,
            token_family_slug: "test_discount",
          },
        ],
        outputs: [],
      },
    ],
  };

  {
    logger.info("Payment with discount token output...");

    {
      await createAndPayOrder({
        orderJson: orderJsonDiscount,
        choiceIndex: 0,
        t,
        walletClient,
        merchantApi,
        merchantAdminAccessToken,
      });

      const {discounts} = await walletClient.call(
        WalletApiOperation.ListDiscounts,
        {}
      );

      t.assertTrue(discounts.length === 1);
      t.assertTrue(discounts[0].tokensAvailable === 1);
    }

    {
      await createAndPayOrder({
        orderJson: orderJsonDiscount,
        choiceIndex: 0,
        t,
        walletClient,
        merchantApi,
        merchantAdminAccessToken,
      });

      let {discounts} = await walletClient.call(
        WalletApiOperation.ListDiscounts,
        {}
      );

      t.assertTrue(discounts.length === 1);
      t.assertTrue(discounts[0].tokensAvailable === 2);
    }
  }

  // change name of token family
  tokenFamilyJson.name = "Test discount, but different name";

  succeedOrThrow<TokenFamilyDetails | void>(
    await merchantApi.updateTokenFamily(
      merchantAdminAccessToken,
      tokenFamilyJson.slug,
      tokenFamilyJson,
    ),
  );

  let d2: DiscountListDetail | undefined;

  {
    logger.info("Payment with discount token output (different name)...");

    {
      await createAndPayOrder({
        orderJson: orderJsonDiscount,
        choiceIndex: 0,
        t,
        walletClient,
        merchantApi,
        merchantAdminAccessToken,
      });

      const {discounts} = await walletClient.call(
        WalletApiOperation.ListDiscounts,
        {}
      );

      const d1 = discounts.find(d => d.name === "Test discount");
      t.assertTrue(d1 !== undefined);
      t.assertTrue(d1.tokensAvailable === 2);

      d2 = discounts.find(d => d.name === "Test discount, but different name");
      t.assertTrue(d2 !== undefined);
      t.assertTrue(d2.tokensAvailable === 1);
    }
  }

  logger.info(`Deleting token family with hash ${d2.tokenFamilyHash}`);

  // delete token family with different name
  await walletClient.call(
    WalletApiOperation.DeleteDiscount,
    { tokenFamilyHash: d2.tokenFamilyHash },
  );

  const {discounts} = await walletClient.call(
    WalletApiOperation.ListDiscounts,
    {}
  );

  t.assertTrue(discounts.length === 1);
  t.assertTrue(discounts[0].tokensAvailable === 2);
}

async function createAndPayOrder(req: {
  orderJson: Order,
  choiceIndex: number,
  t: GlobalTestState,
  walletClient: WalletClient,
  merchantApi: TalerMerchantInstanceHttpClient,
  merchantAdminAccessToken: AccessToken,
}) {
  const orderResp = succeedOrThrow(
    await req.merchantApi.createOrder(req.merchantAdminAccessToken, {
      order: req.orderJson,
    }),
  );

  let orderStatus = succeedOrThrow(
    await req.merchantApi.getOrderDetails(
      req.merchantAdminAccessToken,
      orderResp.order_id,
    ),
  );

  req.t.assertTrue(orderStatus.order_status === "unpaid");

  const talerPayUri = orderStatus.taler_pay_uri;

  const preparePayResult = await req.walletClient.call(
    WalletApiOperation.PreparePayForUri,
    {
      talerPayUri,
    },
  );

  req.t.assertTrue(
    preparePayResult.status === PreparePayResultType.ChoiceSelection,
  );

  await req.walletClient.call(WalletApiOperation.ConfirmPay, {
    transactionId: preparePayResult.transactionId,
    choiceIndex: req.choiceIndex,
  });

  await req.walletClient.call(WalletApiOperation.TestingWaitTransactionsFinal, {});
}

runWalletTokensDiscountTest.suites = ["merchant", "wallet"];
