/*
 This file is part of GNU Taler
 (C) 2022 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 
 */
import { HttpStatusCode, TranslatedString } from "@gnu-taler/taler-util";
import {
  ErrorNotification,
  ErrorType,
  HttpError,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
/**
 * Validate (the number part of) an amount.  If needed,
 * replace comma with a dot.  Returns 'false' whenever
 * the input is invalid, the valid amount otherwise.
 */
const amountRegex = /^[0-9]+(.[0-9]+)?$/;
export function validateAmount(
  maybeAmount: string | undefined,
): string | undefined {
  if (!maybeAmount || !amountRegex.test(maybeAmount)) {
    return;
  }
  return maybeAmount;
}
/**
 * Extract IBAN from a Payto URI.
 */
export function getIbanFromPayto(url: string): string {
  const pathSplit = new URL(url).pathname.split("/");
  let lastIndex = pathSplit.length - 1;
  // Happens if the path ends with "/".
  if (pathSplit[lastIndex] === "") lastIndex--;
  const iban = pathSplit[lastIndex];
  return iban;
}
export function undefinedIfEmpty(obj: T): T | undefined {
  return Object.keys(obj).some((k) => (obj as any)[k] !== undefined)
    ? obj
    : undefined;
}
export type PartialButDefined = {
  [P in keyof T]: T[P] | undefined;
};
export type WithIntermediate = {
  [prop in keyof Type]: Type[prop] extends object
    ? WithIntermediate
    : Type[prop] | undefined;
};
export type RecursivePartial = {
  [P in keyof T]?: T[P] extends (infer U)[]
    ? RecursivePartial[]
    : T[P] extends object
    ? RecursivePartial
    : T[P];
};
export enum TanChannel {
  SMS = "sms",
  EMAIL = "email",
  FILE = "file",
}
export enum CashoutStatus {
  // The payment was initiated after a valid
  // TAN was received by the bank.
  CONFIRMED = "confirmed",
  // The cashout was created and now waits
  // for the TAN by the author.
  PENDING = "pending",
}
// export function partialWithObjects(obj: T | undefined, () => complete): WithIntermediate {
//   const root = obj === undefined ? {} : obj;
//   return Object.entries(root).([key, value]) => {
//   })
//   return undefined as any
// }
/**
 * Craft headers with Authorization and Content-Type.
 */
// export function prepareHeaders(username?: string, password?: string): Headers {
//   const headers = new Headers();
//   if (username && password) {
//     headers.append(
//       "Authorization",
//       `Basic ${window.btoa(`${username}:${password}`)}`,
//     );
//   }
//   headers.append("Content-Type", "application/json");
//   return headers;
// }
export const PAGE_SIZE = 20;
export const MAX_RESULT_SIZE = PAGE_SIZE * 2 - 1;
export function buildRequestErrorMessage(
  i18n: ReturnType["i18n"],
  cause: HttpError,
  specialCases: {
    onClientError?: (status: HttpStatusCode) => TranslatedString | undefined;
    onServerError?: (status: HttpStatusCode) => TranslatedString | undefined;
  } = {},
): ErrorNotification {
  let result: ErrorNotification;
  switch (cause.type) {
    case ErrorType.TIMEOUT: {
      result = {
        type: "error",
        title: i18n.str`Request timeout`,
      };
      break;
    }
    case ErrorType.CLIENT: {
      const title =
        specialCases.onClientError && specialCases.onClientError(cause.status);
      result = {
        type: "error",
        title: title ? title : i18n.str`The server didn't accept the request`,
        description: cause?.payload?.error?.description as TranslatedString,
        debug: JSON.stringify(cause),
      };
      break;
    }
    case ErrorType.SERVER: {
      const title =
        specialCases.onServerError && specialCases.onServerError(cause.status);
      result = {
        type: "error",
        title: title
          ? title
          : i18n.str`The server had problems processing the request`,
        description: cause?.payload?.error?.description as TranslatedString,
        debug: JSON.stringify(cause),
      };
      break;
    }
    case ErrorType.UNREADABLE: {
      result = {
        type: "error",
        title: i18n.str`Unexpected error`,
        description: `Response from ${cause?.info?.url} is unreadable, status: ${cause?.status}` as TranslatedString,
        debug: JSON.stringify(cause),
      };
      break;
    }
    case ErrorType.UNEXPECTED: {
      result = {
        type: "error",
        title: i18n.str`Unexpected error`,
        debug: JSON.stringify(cause),
      };
      break;
    }
  }
  return result;
}
export const COUNTRY_TABLE = {
  AE: "U.A.E.",
  AF: "Afghanistan",
  AL: "Albania",
  AM: "Armenia",
  AN: "Netherlands Antilles",
  AR: "Argentina",
  AT: "Austria",
  AU: "Australia",
  AZ: "Azerbaijan",
  BA: "Bosnia and Herzegovina",
  BD: "Bangladesh",
  BE: "Belgium",
  BG: "Bulgaria",
  BH: "Bahrain",
  BN: "Brunei Darussalam",
  BO: "Bolivia",
  BR: "Brazil",
  BT: "Bhutan",
  BY: "Belarus",
  BZ: "Belize",
  CA: "Canada",
  CG: "Congo",
  CH: "Switzerland",
  CI: "Cote d'Ivoire",
  CL: "Chile",
  CM: "Cameroon",
  CN: "People's Republic of China",
  CO: "Colombia",
  CR: "Costa Rica",
  CS: "Serbia and Montenegro",
  CZ: "Czech Republic",
  DE: "Germany",
  DK: "Denmark",
  DO: "Dominican Republic",
  DZ: "Algeria",
  EC: "Ecuador",
  EE: "Estonia",
  EG: "Egypt",
  ER: "Eritrea",
  ES: "Spain",
  ET: "Ethiopia",
  FI: "Finland",
  FO: "Faroe Islands",
  FR: "France",
  GB: "United Kingdom",
  GD: "Caribbean",
  GE: "Georgia",
  GL: "Greenland",
  GR: "Greece",
  GT: "Guatemala",
  HK: "Hong Kong",
  // HK: "Hong Kong S.A.R.",
  HN: "Honduras",
  HR: "Croatia",
  HT: "Haiti",
  HU: "Hungary",
  ID: "Indonesia",
  IE: "Ireland",
  IL: "Israel",
  IN: "India",
  IQ: "Iraq",
  IR: "Iran",
  IS: "Iceland",
  IT: "Italy",
  JM: "Jamaica",
  JO: "Jordan",
  JP: "Japan",
  KE: "Kenya",
  KG: "Kyrgyzstan",
  KH: "Cambodia",
  KR: "South Korea",
  KW: "Kuwait",
  KZ: "Kazakhstan",
  LA: "Laos",
  LB: "Lebanon",
  LI: "Liechtenstein",
  LK: "Sri Lanka",
  LT: "Lithuania",
  LU: "Luxembourg",
  LV: "Latvia",
  LY: "Libya",
  MA: "Morocco",
  MC: "Principality of Monaco",
  MD: "Moldava",
  // MD: "Moldova",
  ME: "Montenegro",
  MK: "Former Yugoslav Republic of Macedonia",
  ML: "Mali",
  MM: "Myanmar",
  MN: "Mongolia",
  MO: "Macau S.A.R.",
  MT: "Malta",
  MV: "Maldives",
  MX: "Mexico",
  MY: "Malaysia",
  NG: "Nigeria",
  NI: "Nicaragua",
  NL: "Netherlands",
  NO: "Norway",
  NP: "Nepal",
  NZ: "New Zealand",
  OM: "Oman",
  PA: "Panama",
  PE: "Peru",
  PH: "Philippines",
  PK: "Islamic Republic of Pakistan",
  PL: "Poland",
  PR: "Puerto Rico",
  PT: "Portugal",
  PY: "Paraguay",
  QA: "Qatar",
  RE: "Reunion",
  RO: "Romania",
  RS: "Serbia",
  RU: "Russia",
  RW: "Rwanda",
  SA: "Saudi Arabia",
  SE: "Sweden",
  SG: "Singapore",
  SI: "Slovenia",
  SK: "Slovak",
  SN: "Senegal",
  SO: "Somalia",
  SR: "Suriname",
  SV: "El Salvador",
  SY: "Syria",
  TH: "Thailand",
  TJ: "Tajikistan",
  TM: "Turkmenistan",
  TN: "Tunisia",
  TR: "Turkey",
  TT: "Trinidad and Tobago",
  TW: "Taiwan",
  TZ: "Tanzania",
  UA: "Ukraine",
  US: "United States",
  UY: "Uruguay",
  VA: "Vatican",
  VE: "Venezuela",
  VN: "Viet Nam",
  YE: "Yemen",
  ZA: "South Africa",
  ZW: "Zimbabwe",
};
/**
 * An IBAN is validated by converting it into an integer and performing a
 * basic mod-97 operation (as described in ISO 7064) on it.
 * If the IBAN is valid, the remainder equals 1.
 *
 * The algorithm of IBAN validation is as follows:
 * 1.- Check that the total IBAN length is correct as per the country. If not, the IBAN is invalid
 * 2.- Move the four initial characters to the end of the string
 * 3.- Replace each letter in the string with two digits, thereby expanding the string, where A = 10, B = 11, ..., Z = 35
 * 4.- Interpret the string as a decimal integer and compute the remainder of that number on division by 97
 *
 * If the remainder is 1, the check digit test is passed and the IBAN might be valid.
 *
 */
export function validateIBAN(
  iban: string,
  i18n: ReturnType["i18n"],
): string | undefined {
  // Check total length
  if (iban.length < 4)
    return i18n.str`IBAN numbers usually have more that 4 digits`;
  if (iban.length > 34)
    return i18n.str`IBAN numbers usually have less that 34 digits`;
  const A_code = "A".charCodeAt(0);
  const Z_code = "Z".charCodeAt(0);
  const IBAN = iban.toUpperCase();
  // check supported country
  const code = IBAN.substring(0, 2);
  const found = code in COUNTRY_TABLE;
  if (!found) return i18n.str`IBAN country code not found`;
  // 2.- Move the four initial characters to the end of the string
  const step2 = IBAN.substring(4) + iban.substring(0, 4);
  const step3 = Array.from(step2)
    .map((letter) => {
      const code = letter.charCodeAt(0);
      if (code < A_code || code > Z_code) return letter;
      return `${letter.charCodeAt(0) - "A".charCodeAt(0) + 10}`;
    })
    .join("");
  const checksum = calculate_iban_checksum(step3);
  if (checksum !== 1)
    return i18n.str`IBAN number is not valid, checksum is wrong`;
  return undefined;
}
function calculate_iban_checksum(str: string): number {
  const numberStr = str.substring(0, 5);
  const rest = str.substring(5);
  const number = parseInt(numberStr, 10);
  const result = number % 97;
  if (rest.length > 0) {
    return calculate_iban_checksum(`${result}${rest}`);
  }
  return result;
}