/*
 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 { AmountString } from "./types-taler-common.js";
import { HostPortPath } from "./payto.js";
import {
  // TalerUris,
  parseAddExchangeUri,
  parseDevExperimentUri,
  parsePayPullUri,
  parsePayPushUri,
  parsePayTemplateUri,
  parsePayUri,
  parseRefundUri,
  parseRestoreUri,
  parseWithdrawExchangeUri,
  parseWithdrawUri,
  stringifyAddContact,
  stringifyAddExchange,
  stringifyDevExperimentUri,
  stringifyPayPullUri,
  stringifyPayPushUri,
  stringifyPayTemplateUri,
  stringifyPayUri,
  stringifyRefundUri,
  stringifyRestoreUri,
  stringifyWithdrawExchange,
  stringifyWithdrawUri
} from "./taleruri.js";
import { AmountString } from "./types-taler-common.js";

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

  test("taler withdraw uri parsing", (t) => {
    const url1 = "taler://withdraw/bank.example.com/12345";
    const r1 = parseWithdrawUri(url1);
    if (!r1) {
      t.fail();
      return;
    }
    t.is(r1.withdrawalOperationId, "12345");
    t.is(
      r1.bankIntegrationApiBaseUrl,
      "https://bank.example.com/" as HostPortPath,
    );
  });

  test("taler withdraw uri parsing with external confirmation", (t) => {
    const url1 =
      "taler://withdraw/bank.example.com/12345?external-confirmation=1";
    const r1 = parseWithdrawUri(url1);
    if (!r1) {
      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 withdraw uri parsing (http)", (t) => {
    const url1 = "taler+http://withdraw/bank.example.com/12345";
    const r1 = parseWithdrawUri(url1);
    if (!r1) {
      t.fail();
      return;
    }
    t.is(r1.withdrawalOperationId, "12345");
    t.is(
      r1.bankIntegrationApiBaseUrl,
      "http://bank.example.com/" as HostPortPath,
    );
  });

  test("taler withdraw URI (stringify)", (t) => {
    const url = stringifyWithdrawUri({
      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 pay url parsing: defaults", (t) => {
    const url1 = "taler://pay/example.com/myorder/";
    const r1 = parsePayUri(url1);
    if (!r1) {
      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 = parsePayUri(url2);
    if (!r2) {
      t.fail();
      return;
    }
    t.is(r2.merchantBaseUrl, "https://example.com/" as HostPortPath);
    t.is(r2.sessionId, "mysession");
  });

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

  test("taler pay url parsing (claim token)", (t) => {
    const url1 = "taler://pay/example.com/instances/myinst/myorder/?c=ASDF";
    const r1 = parsePayUri(url1);
    if (!r1) {
      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 pay uri parsing: non-https", (t) => {
    const url1 = "taler+http://pay/example.com/myorder/";
    const r1 = parsePayUri(url1);
    if (!r1) {
      t.fail();
      return;
    }
    t.is(r1.merchantBaseUrl, "http://example.com/" as HostPortPath);
    t.is(r1.orderId, "myorder");
  });

  test("taler pay uri parsing: missing session component", (t) => {
    const url1 = "taler+http://pay/example.com/myorder";
    const r1 = parsePayUri(url1);
    if (r1) {
      t.fail();
      return;
    }
    t.pass();
  });

  test("taler pay URI (stringify)", (t) => {
    const url1 = stringifyPayUri({
      merchantBaseUrl: "http://localhost:123/" as HostPortPath,
      orderId: "foo",
      sessionId: "",
    });
    t.deepEqual(url1, "taler+http://pay/localhost:123/foo/");

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

  test("taler pay URI (stringify with https)", (t) => {
    const url1 = stringifyPayUri({
      merchantBaseUrl: "https://localhost:123/" as HostPortPath,
      orderId: "foo",
      sessionId: "",
    });
    t.deepEqual(url1, "taler://pay/localhost:123/foo/");

    const url2 = stringifyPayUri({
      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 refund uri parsing: non-https #1", (t) => {
    const url1 = "taler+http://refund/example.com/myorder/";
    const r1 = parseRefundUri(url1);
    if (!r1) {
      t.fail();
      return;
    }
    t.is(r1.merchantBaseUrl, "http://example.com/" as HostPortPath);
    t.is(r1.orderId, "myorder");
  });

  test("taler refund uri parsing", (t) => {
    const url1 = "taler://refund/merchant.example.com/1234/";
    const r1 = parseRefundUri(url1);
    if (!r1) {
      t.fail();
      return;
    }
    t.is(r1.merchantBaseUrl, "https://merchant.example.com/" as HostPortPath);
    t.is(r1.orderId, "1234");
  });

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

  test("taler refund URI (stringify)", (t) => {
    const url = stringifyRefundUri({
      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 peer to peer push URI", (t) => {
    const url1 = "taler://pay-push/exch.example.com/foo";
    const r1 = parsePayPushUri(url1);
    if (!r1) {
      t.fail();
      return;
    }
    t.is(r1.exchangeBaseUrl, "https://exch.example.com/" as HostPortPath);
    t.is(r1.contractPriv, "foo");
  });

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

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

  test("taler peer to peer push URI (stringify)", (t) => {
    const url = stringifyPayPushUri({
      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 peer to peer pull URI", (t) => {
    const url1 = "taler://pay-pull/exch.example.com/foo";
    const r1 = parsePayPullUri(url1);
    if (!r1) {
      t.fail();
      return;
    }
    t.is(r1.exchangeBaseUrl, "https://exch.example.com/" as HostPortPath);
    t.is(r1.contractPriv, "foo");
  });

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

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

  test("taler peer to peer pull URI (stringify)", (t) => {
    const url = stringifyPayPullUri({
      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 pay template URI (parsing)", (t) => {
    const url1 =
      "taler://pay-template/merchant.example.com/FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY";
    const r1 = parsePayTemplateUri(url1);
    if (!r1) {
      t.fail();
      return;
    }
    t.deepEqual(
      r1.merchantBaseUrl,
      "https://merchant.example.com/" as HostPortPath,
    );
    t.deepEqual(r1.templateId, "FEGHYJY48FEGU6WETYIOIDEDE2QW3OCZVY");
  });

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

  test("taler pay template URI (stringify)", (t) => {
    const url1 = stringifyPayTemplateUri({
      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 restore URI (parsing, http with port)", (t) => {
    const r1 = parseRestoreUri(
      "taler+http://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:123",
    );
    if (!r1) {
      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 restore URI (parsing, https with port)", (t) => {
    const r1 = parseRestoreUri(
      "taler://restore/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0/prov1.example.com,prov2.example.com:234,https%3A%2F%2Fprov1.example.com%2F",
    );
    if (!r1) {
      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 restore URI (stringify)", (t) => {
    const url = stringifyRestoreUri({
      walletRootPriv: "GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0",
      providers: [
        // FIXME: why here the stringify version add a slash in this provider?
        "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 dev exp URI (parsing)", (t) => {
    const url1 = "taler://dev-experiment/123";
    const r1 = parseDevExperimentUri(url1);
    if (!r1) {
      t.fail();
      return;
    }
    t.deepEqual(r1.devExperimentId, "123");
  });

  test("taler dev exp URI (stringify)", (t) => {
    const url1 = stringifyDevExperimentUri({
      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 withdraw exchange URI (parse)", (t) => {
    // Pubkey has been phased out, may no longer be specified.
    {
      const rx1 = parseWithdrawExchangeUri(
        "taler://withdraw-exchange/exchange.demo.taler.net/someroot/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0?a=KUDOS%3A2",
      );
      if (rx1) {
        t.fail();
        return;
      }
    }
    {
      const rx2 = parseWithdrawExchangeUri(
        "taler://withdraw-exchange/exchange.demo.taler.net/GJKG23V4ZBHEH45YRK7TWQE8ZTY7JWTY5094TQJSRZN5DSDBX8E0",
      );
      if (rx2) {
        t.fail();
        return;
      }
    }

    // Now test well-formed URIs
    {
      const r2 = parseWithdrawExchangeUri(
        "taler://withdraw-exchange/exchange.demo.taler.net/someroot/",
      );
      if (!r2) {
        t.fail();
        return;
      }
      t.deepEqual(r2.amount, undefined);
      t.deepEqual(
        r2.exchangeBaseUrl,
        "https://exchange.demo.taler.net/someroot/",
      );
    }

    {
      const r3 = parseWithdrawExchangeUri(
        "taler://withdraw-exchange/exchange.demo.taler.net/",
      );
      if (!r3) {
        t.fail();
        return;
      }
      t.deepEqual(r3.amount, undefined);
      t.deepEqual(
        r3.exchangeBaseUrl,
        "https://exchange.demo.taler.net/" as HostPortPath,
      );
    }

    {
      // FIXME: why here the parser allows no ending slash
      // No trailing slash, no path component
      const r4 = parseWithdrawExchangeUri(
        "taler://withdraw-exchange/exchange.demo.taler.net",
      );
      if (!r4) {
        t.fail();
        return;
      }
      t.deepEqual(r4.amount, undefined);
      t.deepEqual(
        r4.exchangeBaseUrl,
        "https://exchange.demo.taler.net/" as HostPortPath,
      );
    }
  });

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

  test("taler withdraw exchange URI with amount (stringify)", (t) => {
    const url = stringifyWithdrawExchange({
      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 add exchange URI (parse)", (t) => {
    {
      const r1 = parseAddExchangeUri(
        "taler://add-exchange/exchange.example.com/",
      );
      if (!r1) {
        t.fail();
        return;
      }
      t.deepEqual(
        r1.exchangeBaseUrl,
        "https://exchange.example.com/" as HostPortPath,
      );
    }
    {
      const r2 = parseAddExchangeUri(
        "taler://add-exchange/exchanges.example.com/api/",
      );
      if (!r2) {
        t.fail();
        return;
      }
      t.deepEqual(
        r2.exchangeBaseUrl,
        "https://exchanges.example.com/api/" as HostPortPath,
      );
    }
  });

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

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

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

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