/*
 This file is part of GNU Taler
 (C) 2019 GNUnet e.V.

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

import test from "ava";
import { TalerUriAction, TalerUriParseError, TalerUris } from "./taleruri.js";
import { HostPortPath } from "./payto.js";
import { AmountString } from "./types-taler-common.js";
import { failOrThrow, succeedOrThrow } from "./operation.js";

/**
 * 5.1 action: withdraw https://lsd.gnunet.org/lsd0006/#name-action-withdraw
 */

test("taler-new withdraw uri parsing", (t) => {
  const url1 = "taler://withdraw/bank.example.com/12345";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Withdraw) {
    t.fail();
    return;
  }
  t.is(r1.withdrawalOperationId, "12345");
  t.is(
    r1.bankIntegrationApiBaseUrl,
    "https://bank.example.com/" as HostPortPath,
  );
});

test("taler-new withdraw uri parsing with external confirmation", (t) => {
  const url1 =
    "taler://withdraw/bank.example.com/12345?external-confirmation=1";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Withdraw) {
    t.fail();
    return;
  }
  t.is(r1.externalConfirmation, true);
  t.is(r1.withdrawalOperationId, "12345");
  t.is(
    r1.bankIntegrationApiBaseUrl,
    "https://bank.example.com/" as HostPortPath,
  );
});

test("taler-new withdraw uri parsing (http)", (t) => {
  const url1 = "taler+http://withdraw/bank.example.com/12345";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Withdraw) {
    t.fail();
    return;
  }
  t.is(r1.withdrawalOperationId, "12345");
  t.is(
    r1.bankIntegrationApiBaseUrl,
    "http://bank.example.com/" as HostPortPath,
  );
});

test("taler-new withdraw URI (stringify)", (t) => {
  const url = TalerUris.toString({
    type: TalerUriAction.Withdraw,
    bankIntegrationApiBaseUrl:
      "https://bank.taler.test/integration-api/" as HostPortPath,
    withdrawalOperationId: "123",
  });
  t.deepEqual(url, "taler://withdraw/bank.taler.test/integration-api/123");
});

/**
 * 5.2 action: pay  https://lsd.gnunet.org/lsd0006/#name-action-pay
 */
test("taler-new pay url parsing: defaults", (t) => {
  const url1 = "taler://pay/example.com/myorder/";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Pay) {
    t.fail();
    return;
  }
  t.is(r1.merchantBaseUrl, "https://example.com/" as HostPortPath);
  t.is(r1.sessionId, "");

  const url2 = "taler://pay/example.com/myorder/mysession";
  const r2 = succeedOrThrow(TalerUris.fromString(url2));
  if (r2.type !== TalerUriAction.Pay) {
    t.fail();
    return;
  }
  t.is(r2.merchantBaseUrl, "https://example.com/" as HostPortPath);
  t.is(r2.sessionId, "mysession");
});

test("taler-new pay url parsing: instance", (t) => {
  const url1 = "taler://pay/example.com/instances/myinst/myorder/";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Pay) {
    t.fail();
    return;
  }
  t.is(
    r1.merchantBaseUrl,
    "https://example.com/instances/myinst/" as HostPortPath,
  );
  t.is(r1.orderId, "myorder");
});

test("taler-new pay url parsing (claim token)", (t) => {
  const url1 = "taler://pay/example.com/instances/myinst/myorder/?c=ASDF";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Pay) {
    t.fail();
    return;
  }
  t.is(
    r1.merchantBaseUrl,
    "https://example.com/instances/myinst/" as HostPortPath,
  );
  t.is(r1.orderId, "myorder");
  t.is(r1.claimToken, "ASDF");
});

test("taler-new pay uri parsing: non-https", (t) => {
  const url1 = "taler+http://pay/example.com/myorder/";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Pay) {
    t.fail();
    return;
  }
  t.is(r1.merchantBaseUrl, "http://example.com/" as HostPortPath);
  t.is(r1.orderId, "myorder");
});

test("taler-new pay uri parsing: missing session component", (t) => {
  const url1 = "taler+http://pay/example.com/myorder";
  failOrThrow(TalerUris.fromString(url1), TalerUriParseError.COMPONENTS_LENGTH);
  t.pass();
});

test("taler-new pay URI (stringify)", (t) => {
  const url1 = TalerUris.toString({
    type: TalerUriAction.Pay,
    merchantBaseUrl: "http://localhost:123/" as HostPortPath,
    orderId: "foo",
    sessionId: "",
  });
  t.deepEqual(url1, "taler+http://pay/localhost:123/foo/");

  const url2 = TalerUris.toString({
    type: TalerUriAction.Pay,
    merchantBaseUrl: "http://localhost:123/" as HostPortPath,
    orderId: "foo",
    sessionId: "bla",
  });
  t.deepEqual(url2, "taler+http://pay/localhost:123/foo/bla");
});

test("taler-new pay URI (stringify with https)", (t) => {
  const url1 = TalerUris.toString({
    type: TalerUriAction.Pay,
    merchantBaseUrl: "https://localhost:123/" as HostPortPath,
    orderId: "foo",
    sessionId: "",
  });
  t.deepEqual(url1, "taler://pay/localhost:123/foo/");

  const url2 = TalerUris.toString({
    type: TalerUriAction.Pay,
    merchantBaseUrl: "https://localhost/" as HostPortPath,
    orderId: "foo",
    sessionId: "bla",
    noncePriv: "123",
  });
  t.deepEqual(url2, "taler://pay/localhost/foo/bla?n=123");
});

/**
 * 5.3 action: refund https://lsd.gnunet.org/lsd0006/#name-action-refund
 */

test("taler-new refund uri parsing: non-https #1", (t) => {
  const url1 = "taler+http://refund/example.com/myorder/";
  // const r1 = parseRefundUri(url1);
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Refund) {
    t.fail();
    return;
  }
  t.is(r1.merchantBaseUrl, "http://example.com/" as HostPortPath);
  t.is(r1.orderId, "myorder");
});

test("taler-new refund uri parsing", (t) => {
  const url1 = "taler://refund/merchant.example.com/1234/";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Refund) {
    t.fail();
    return;
  }
  t.is(r1.merchantBaseUrl, "https://merchant.example.com/" as HostPortPath);
  t.is(r1.orderId, "1234");
});

test("taler-new refund uri parsing with instance", (t) => {
  const url1 = "taler://refund/merchant.example.com/instances/myinst/1234/";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.Refund) {
    t.fail();
    return;
  }
  t.is(r1.orderId, "1234");
  t.is(
    r1.merchantBaseUrl,
    "https://merchant.example.com/instances/myinst/" as HostPortPath,
  );
});

test("taler-new refund URI (stringify)", (t) => {
  const url = TalerUris.toString({
    type: TalerUriAction.Refund,
    merchantBaseUrl: "https://merchant.test/instance/pepe/" as HostPortPath,
    orderId: "123",
  });
  t.deepEqual(url, "taler://refund/merchant.test/instance/pepe/123/");
});

/**
 * 5.5 action: pay-push  https://lsd.gnunet.org/lsd0006/#name-action-pay-push
 */

test("taler-new peer to peer push URI", (t) => {
  const url1 = "taler://pay-push/exch.example.com/foo";
  // const r1 = parsePayPushUri(url1);
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.PayPush) {
    t.fail();
    return;
  }
  t.is(r1.exchangeBaseUrl, "https://exch.example.com/" as HostPortPath);
  t.is(r1.contractPriv, "foo");
});

test("taler-new peer to peer push URI (path)", (t) => {
  const url1 = "taler://pay-push/exch.example.com:123/bla/foo";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.PayPush) {
    t.fail();
    return;
  }
  t.is(r1.exchangeBaseUrl, "https://exch.example.com:123/bla/" as HostPortPath);
  t.is(r1.contractPriv, "foo");
});

test("taler-new peer to peer push URI (http)", (t) => {
  const url1 = "taler+http://pay-push/exch.example.com:123/bla/foo";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.PayPush) {
    t.fail();
    return;
  }
  t.is(r1.exchangeBaseUrl, "http://exch.example.com:123/bla/" as HostPortPath);
  t.is(r1.contractPriv, "foo");
});

test("taler-new peer to peer push URI (stringify)", (t) => {
  const url = TalerUris.toString({
    type: TalerUriAction.PayPush,
    exchangeBaseUrl: "https://foo.example.com/bla/" as HostPortPath,
    contractPriv: "123",
  });
  t.deepEqual(url, "taler://pay-push/foo.example.com/bla/123");
});

/**
 * 5.6 action: pay-pull https://lsd.gnunet.org/lsd0006/#name-action-pay-pull
 */

test("taler-new peer to peer pull URI", (t) => {
  const url1 = "taler://pay-pull/exch.example.com/foo";
  // const r1 = parsePayPullUri(url1);
  const r1 = succeedOrThrow(TalerUris.fromString(url1));

  if (r1.type !== TalerUriAction.PayPull) {
    t.fail();
    return;
  }
  t.is(r1.exchangeBaseUrl, "https://exch.example.com/" as HostPortPath);
  t.is(r1.contractPriv, "foo");
});

test("taler-new peer to peer pull URI (path)", (t) => {
  const url1 = "taler://pay-pull/exch.example.com:123/bla/foo";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.PayPull) {
    t.fail();
    return;
  }
  t.is(r1.exchangeBaseUrl, "https://exch.example.com:123/bla/" as HostPortPath);
  t.is(r1.contractPriv, "foo");
});

test("taler-new peer to peer pull URI (http)", (t) => {
  const url1 = "taler+http://pay-pull/exch.example.com:123/bla/foo";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.PayPull) {
    t.fail();
    return;
  }
  t.is(r1.exchangeBaseUrl, "http://exch.example.com:123/bla/" as HostPortPath);
  t.is(r1.contractPriv, "foo");
});

test("taler-new peer to peer pull URI (stringify)", (t) => {
  const url = TalerUris.toString({
    type: TalerUriAction.PayPull,
    exchangeBaseUrl: "https://foo.example.com/bla/" as HostPortPath,
    contractPriv: "123",
  });
  t.deepEqual(url, "taler://pay-pull/foo.example.com/bla/123");
});

/**
 * 5.7 action: pay-template https://lsd.gnunet.org/lsd0006/#name-action-pay-template
 */

test("taler-new pay template URI (parsing)", (t) => {
  const url1 =
    "taler://pay-template/merchant.example.com/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY";
  // const r1 = parsePayTemplateUri(url1);
  const r1 = succeedOrThrow(TalerUris.fromString(url1));

  if (r1.type !== TalerUriAction.PayTemplate) {
    t.fail();
    return;
  }
  t.deepEqual(
    r1.merchantBaseUrl,
    "https://merchant.example.com/" as HostPortPath,
  );
  t.deepEqual(r1.templateId, "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY");
});

test("taler-new pay template URI (parsing, http with port)", (t) => {
  const url1 =
    "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  if (r1.type !== TalerUriAction.PayTemplate) {
    t.fail();
    return;
  }
  t.deepEqual(
    r1.merchantBaseUrl,
    "http://merchant.example.com:1234/" as HostPortPath,
  );
  t.deepEqual(r1.templateId, "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY");
});

test("taler-new pay template URI (stringify)", (t) => {
  const url1 = TalerUris.toString({
    type: TalerUriAction.PayTemplate,
    merchantBaseUrl: "http://merchant.example.com:1234/" as HostPortPath,
    templateId: "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY",
  });
  t.deepEqual(
    url1,
    "taler+http://pay-template/merchant.example.com:1234/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY",
  );
});

/**
 * 5.10 action: restore https://lsd.gnunet.org/lsd0006/#name-action-restore
 */
test("taler-new restore URI (parsing, http with port)", (t) => {
  const url1 =
    "taler+http://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:123";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));
  // const r1 = parseRestoreUri(
  //   ,
  // );
  if (r1.type !== TalerUriAction.Restore) {
    t.fail();
    return;
  }
  t.deepEqual(
    r1.walletRootPriv,
    "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0",
  );
  t.deepEqual(r1.providers[0], "http://prov1.example.com/");
  t.deepEqual(r1.providers[1], "http://prov2.example.com:123/");
});
test("taler-new restore URI (parsing, https with port)", (t) => {
  const url1 =
    "taler://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:234,https%3A%2F%2Fprov1.example.com%2F";
  const r1 = succeedOrThrow(TalerUris.fromString(url1));

  if (r1.type !== TalerUriAction.Restore) {
    t.fail();
    return;
  }
  t.deepEqual(
    r1.walletRootPriv,
    "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0",
  );
  t.deepEqual(r1.providers[0], "https://prov1.example.com/");
  t.deepEqual(r1.providers[1], "https://prov2.example.com:234/");
});

test("taler-new restore URI (stringify)", (t) => {
  const url = TalerUris.toString({
    type: TalerUriAction.Restore,
    walletRootPriv: "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0",
    providers: [
      "http://prov1.example.com/" as HostPortPath,
      "https://prov2.example.com:234/" as HostPortPath,
    ],
  });
  t.deepEqual(
    url,
    "taler://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/http%3A%2F%2Fprov1.example.com%2F,https%3A%2F%2Fprov2.example.com%3A234%2F",
  );
});

/**
 * 5.11 action: dev-experiment https://lsd.gnunet.org/lsd0006/#name-action-dev-experiment
 */

test("taler-new dev exp URI (parsing)", (t) => {
  const url1 = "taler://dev-experiment/123";
  // const r1 = parseDevExperimentUri(url1);
  const r1 = succeedOrThrow(TalerUris.fromString(url1));

  if (r1.type !== TalerUriAction.DevExperiment) {
    t.fail();
    return;
  }
  t.deepEqual(r1.devExperimentId, "123");
});

test("taler-new dev exp URI (stringify)", (t) => {
  const url1 = TalerUris.toString({
    type: TalerUriAction.DevExperiment,
    devExperimentId: "123",
  });
  t.deepEqual(url1, "taler://dev-experiment/123");
});

/**
 * 5.12 action: withdraw-exchange https://lsd.gnunet.org/lsd0006/#name-action-withdraw-exchange
 */

test("taler-new withdraw exchange URI (parse)", (t) => {
  // Pubkey has been phased out, may no longer be specified.
  {
    const url1 =
      "taler://withdraw-exchange/exchange.demo.taler.net/someroot/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0?a=KUDOS%3A2";
    failOrThrow(
      TalerUris.fromString(url1),
      TalerUriParseError.INVALID_TARGET_PATH,
    );

    // if (r1.type !== TalerUriAction.WithdrawExchange) {
    //   t.fail();
    //   return;
    // }
  }
  {
    const url1 =
      "taler://withdraw-exchange/exchange.demo.taler.net/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0";
    failOrThrow(
      TalerUris.fromString(url1),
      TalerUriParseError.INVALID_TARGET_PATH,
    );
  }

  // Now test well-formed URIs
  {
    const url1 = "taler://withdraw-exchange/exchange.demo.taler.net/someroot/";
    const r1 = succeedOrThrow(TalerUris.fromString(url1));
    if (r1.type !== TalerUriAction.WithdrawExchange) {
      t.fail();
      return;
    }
    t.deepEqual(r1.amount, undefined);
    t.deepEqual(
      r1.exchangeBaseUrl,
      "https://exchange.demo.taler.net/someroot/",
    );
  }

  {
    const url1 = "taler://withdraw-exchange/exchange.demo.taler.net/";
    const r1 = succeedOrThrow(TalerUris.fromString(url1));
    if (r1.type !== TalerUriAction.WithdrawExchange) {
      t.fail();
      return;
    }
    t.deepEqual(r1.amount, undefined);
    t.deepEqual(
      r1.exchangeBaseUrl,
      "https://exchange.demo.taler.net/" as HostPortPath,
    );
  }

  {
    const url1 = "taler://withdraw-exchange/exchange.demo.taler.net";
    // No trailing slash, no path component
    const r1 = succeedOrThrow(TalerUris.fromString(url1));
    if (r1.type !== TalerUriAction.WithdrawExchange) {
      t.fail();
      return;
    }
    t.deepEqual(r1.amount, undefined);
    t.deepEqual(
      r1.exchangeBaseUrl,
      "https://exchange.demo.taler.net/" as HostPortPath,
    );
  }
});

test("taler-new withdraw exchange URI (stringify)", (t) => {
  const url = TalerUris.toString({
    type: TalerUriAction.WithdrawExchange,
    exchangeBaseUrl: "https://exchange.demo.taler.net" as HostPortPath,
  });
  t.deepEqual(url, "taler://withdraw-exchange/exchange.demo.taler.net/");
});

test("taler-new withdraw exchange URI with amount (stringify)", (t) => {
  const url = TalerUris.toString({
    type: TalerUriAction.WithdrawExchange,
    exchangeBaseUrl: "https://exchange.demo.taler.net" as HostPortPath,
    amount: "KUDOS:19" as AmountString,
  });
  t.deepEqual(
    url,
    "taler://withdraw-exchange/exchange.demo.taler.net/?a=KUDOS%3A19",
  );
});

/**
 * 5.13 action: add-exchange https://lsd.gnunet.org/lsd0006/#name-action-add-exchange
 */

test("taler-new add exchange URI (parse)", (t) => {
  {
    const url1 = "taler://add-exchange/exchange.example.com/";
    const r1 = succeedOrThrow(TalerUris.fromString(url1));
    if (r1.type !== TalerUriAction.AddExchange) {
      t.fail();
      return;
    }
    t.deepEqual(
      r1.exchangeBaseUrl,
      "https://exchange.example.com/" as HostPortPath,
    );
  }
  {
    const url1 = "taler://add-exchange/exchanges.example.com/api/";
    const r1 = succeedOrThrow(TalerUris.fromString(url1));
    if (r1.type !== TalerUriAction.AddExchange) {
      t.fail();
      return;
    }
    t.deepEqual(
      r1.exchangeBaseUrl,
      "https://exchanges.example.com/api/" as HostPortPath,
    );
  }
});

test("taler-new add exchange URI (stringify)", (t) => {
  const url = TalerUris.toString({
    type: TalerUriAction.AddExchange,
    exchangeBaseUrl: "https://exchange.demo.taler.net" as HostPortPath,
  });
  t.deepEqual(url, "taler://add-exchange/exchange.demo.taler.net/");
});

test("taler-new add contact URI (stringify)", (t) => {
  const url = TalerUris.toString({
    type: TalerUriAction.AddContact,
    alias: "bob@example.com",
    aliasType: "email",
    mailboxBaseUri: "https://mailbox.example.com/mb",
    mailboxIdentity: "SOMEHADDR",
    sourceBaseUrl: "https://taldir.example.com",
  });
  t.deepEqual(url, "taler://add-contact/email/bob@example.com/mailbox.example.com/mb/SOMEHADDR?sourceBaseUrl=https%3A%2F%2Ftaldir.example.com");
});

/**
 * wrong uris
 */
test("taler-new pay url parsing: wrong scheme", (t) => {
  const url1 = "talerfoo://";
  const r1 = failOrThrow(
    TalerUris.fromString(url1),
    TalerUriParseError.WRONG_PREFIX,
  );
  // const r1 = parsePayUri(url1);
  t.is(r1, undefined);

  const url2 = "taler://refund/a/b/c/d/e/f";
  const r2 = failOrThrow(
    TalerUris.fromString(url2),
    TalerUriParseError.INVALID_TARGET_PATH,
  );
  // const r2 = parsePayUri(url2);
  t.is(r2, undefined);
});
