/*
 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 
 */
import { generateFakeSegwitAddress } from "./bitcoin.js";
import { URLSearchParams } from "./url.js";
export type PaytoUri =
  | PaytoUriUnknown
  | PaytoUriIBAN
  | PaytoUriTalerBank
  | PaytoUriBitcoin;
export interface PaytoUriGeneric {
  targetType: PaytoType | string;
  targetPath: string;
  params: { [name: string]: string };
}
export interface PaytoUriUnknown extends PaytoUriGeneric {
  isKnown: false;
}
export interface PaytoUriIBAN extends PaytoUriGeneric {
  isKnown: true;
  targetType: "iban";
  iban: string;
  bic?: string;
}
export interface PaytoUriTalerBank extends PaytoUriGeneric {
  isKnown: true;
  targetType: "x-taler-bank";
  host: string;
  account: string;
}
export interface PaytoUriBitcoin extends PaytoUriGeneric {
  isKnown: true;
  targetType: "bitcoin";
  segwitAddrs: Array;
}
const paytoPfx = "payto://";
export type PaytoType = "iban" | "bitcoin" | "x-taler-bank"
export function buildPayto(
  type: "iban",
  iban: string,
  bic: string | undefined,
): PaytoUriIBAN;
export function buildPayto(
  type: "bitcoin",
  address: string,
  reserve: string | undefined,
): PaytoUriBitcoin;
export function buildPayto(
  type: "x-taler-bank",
  host: string,
  account: string,
): PaytoUriTalerBank;
export function buildPayto(
  type: PaytoType,
  first: string,
  second?: string,
): PaytoUriGeneric {
  switch (type) {
    case "bitcoin": {
      const result: PaytoUriBitcoin = {
        isKnown: true,
        targetType: "bitcoin",
        targetPath: first,
        params: {},
        segwitAddrs: !second ? [] : generateFakeSegwitAddress(second, first),
      };
      return result;
    }
    case "iban": {
      const result: PaytoUriIBAN = {
        isKnown: true,
        targetType: "iban",
        iban: first,
        params: {},
        targetPath: !second ? first : `${second}/${first}`,
      };
      return result;
    }
    case "x-taler-bank": {
      if (!second) throw Error("missing account for payto://x-taler-bank");
      const result: PaytoUriTalerBank = {
        isKnown: true,
        targetType: "x-taler-bank",
        host: first,
        account: second,
        params: {},
        targetPath: `${first}/${second}`,
      };
      return result;
    }
    default: {
      const unknownType: never = type;
      throw Error(`unknown payto:// type ${unknownType}`);
    }
  }
}
/**
 * Add query parameters to a payto URI
 */
export function addPaytoQueryParams(
  s: string,
  params: { [name: string]: string },
): string {
  const [acct, search] = s.slice(paytoPfx.length).split("?");
  const searchParams = new URLSearchParams(search || "");
  const keys = Object.keys(params);
  if (keys.length === 0) {
    return paytoPfx + acct;
  }
  for (const k of keys) {
    searchParams.set(k, params[k]);
  }
  return paytoPfx + acct + "?" + searchParams.toString();
}
/**
 * Serialize a PaytoURI into a valid payto:// string
 *
 * @param p
 * @returns
 */
export function stringifyPaytoUri(p: PaytoUri): string {
  const url = new URL(`${paytoPfx}${p.targetType}/${p.targetPath}`);
  const paramList = !p.params ? [] : Object.entries(p.params);
  paramList.forEach(([key, value]) => {
    url.searchParams.set(key, value);
  });
  return url.href;
}
/**
 * Parse a valid payto:// uri into a PaytoUri object
 * RFC 8905
 *
 * @param s
 * @returns
 */
export function parsePaytoUri(s: string): PaytoUri | undefined {
  if (!s.startsWith(paytoPfx)) {
    return undefined;
  }
  const [acct, search] = s.slice(paytoPfx.length).split("?");
  const firstSlashPos = acct.indexOf("/");
  if (firstSlashPos === -1) {
    return undefined;
  }
  const targetType = acct.slice(0, firstSlashPos);
  const targetPath = acct.slice(firstSlashPos + 1);
  const params: { [k: string]: string } = {};
  const searchParams = new URLSearchParams(search || "");
  searchParams.forEach((v, k) => {
    params[k] = v;
  });
  if (targetType === "x-taler-bank") {
    const parts = targetPath.split("/");
    const host = parts[0];
    const account = parts[1];
    return {
      targetPath,
      targetType,
      params,
      isKnown: true,
      host,
      account,
    };
  }
  if (targetType === "iban") {
    const parts = targetPath.split("/");
    let iban: string | undefined = undefined;
    let bic: string | undefined = undefined;
    if (parts.length === 1) {
      iban = parts[0];
    }
    if (parts.length === 2) {
      bic = parts[0];
      iban = parts[1];
    } else {
      iban = targetPath;
    }
    return {
      isKnown: true,
      targetPath,
      targetType,
      params,
      iban,
      bic,
    };
  }
  if (targetType === "bitcoin") {
    const msg = /\b([A-Z0-9]{52})\b/.exec(params["message"]);
    const reserve = !msg ? params["subject"] : msg[0];
    const segwitAddrs = !reserve
      ? []
      : generateFakeSegwitAddress(reserve, targetPath);
    const result: PaytoUriBitcoin = {
      isKnown: true,
      targetPath,
      targetType,
      params,
      segwitAddrs,
    };
    return result;
  }
  return {
    targetPath,
    targetType,
    params,
    isKnown: false,
  };
}
export function talerPaytoFromExchangeReserve(
  exchangeBaseUrl: string,
  reservePub: string,
): string {
  const url = new URL(exchangeBaseUrl);
  let proto: string;
  if (url.protocol === "http:") {
    proto = "taler-reserve-http";
  } else if (url.protocol === "https:") {
    proto = "taler-reserve";
  } else {
    throw Error(`unsupported exchange base URL protocol (${url.protocol})`);
  }
  let path = url.pathname;
  if (!path.endsWith("/")) {
    path = path + "/";
  }
  return `payto://${proto}/${url.host}${url.pathname}${reservePub}`;
}