/*
 This file is part of GNU Taler
 (C) 2021-2024 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 <http://www.gnu.org/licenses/>
 */

/**
 *
 * @author Sebastian Javier Marchano (sebasjm)
 */
import { parsePaytoUri, PaytoUriGeneric, stringifyPaytoUri } from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { COUNTRY_TABLE } from "../../utils/constants.js";
import { undefinedIfEmpty } from "../../utils/table.js";
import { FormErrors, FormProvider } from "./FormProvider.js";
import { Input } from "./Input.js";
import { InputGroup } from "./InputGroup.js";
import { InputSelector } from "./InputSelector.js";
import { InputProps, useField } from "./useField.js";
import { useEffect, useState } from "preact/hooks";

export interface Props<T> extends InputProps<T> {
  isValid?: (e: any) => boolean;
}

// type Entity = PaytoUriGeneric
// https://datatracker.ietf.org/doc/html/rfc8905
type Entity = {
  // iban, bitcoin, x-taler-bank. it defined the format
  target: string;
  // path1 if the first field to be used
  path1?: string;
  // path2 if the second field to be used, optional
  path2?: string;
  // params of the payto uri
  params: {
    "receiver-name"?: string;
    sender?: string;
    message?: string;
    amount?: string;
    instruction?: string;
    [name: string]: string | undefined;
  };
};

function isEthereumAddress(address: string) {
  if (!/^(0x)?[0-9a-f]{40}$/i.test(address)) {
    return false;
  } else if (
    /^(0x|0X)?[0-9a-f]{40}$/.test(address) ||
    /^(0x|0X)?[0-9A-F]{40}$/.test(address)
  ) {
    return true;
  }
  return checkAddressChecksum(address);
}

function checkAddressChecksum(address: string) {
  //TODO implement ethereum checksum
  return true;
}

function validateBitcoin(
  addr: string,
  i18n: ReturnType<typeof useTranslationContext>["i18n"],
): string | undefined {
  try {
    const valid = /^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/.test(addr);
    if (valid) return undefined;
  } catch (e) {
    console.log(e);
  }
  return i18n.str`This is not a valid bitcoin address.`;
}

function validateEthereum(
  addr: string,
  i18n: ReturnType<typeof useTranslationContext>["i18n"],
): string | undefined {
  try {
    const valid = isEthereumAddress(addr);
    if (valid) return undefined;
  } catch (e) {
    console.log(e);
  }
  return i18n.str`This is not a valid Ethereum address.`;
}

/**
 * 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.
 *
 */
function validateIBAN(
  iban: string,
  i18n: ReturnType<typeof useTranslationContext>["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.substr(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.substr(4) + iban.substr(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("");

  function calculate_iban_checksum(str: string): number {
    const numberStr = str.substr(0, 5);
    const rest = str.substr(5);
    const number = parseInt(numberStr, 10);
    const result = number % 97;
    if (rest.length > 0) {
      return calculate_iban_checksum(`${result}${rest}`);
    }
    return result;
  }

  const checksum = calculate_iban_checksum(step3);
  if (checksum !== 1)
    return i18n.str`IBAN number is not valid, checksum is wrong`;
  return undefined;
}

// const targets = ['ach', 'bic', 'iban', 'upi', 'bitcoin', 'ilp', 'void', 'x-taler-bank']
const targets = [
  "Choose one...",
  "iban",
  "x-taler-bank",
  "bitcoin",
  "ethereum",
];
const noTargetValue = targets[0];
const defaultTarget: Entity = {
  target: noTargetValue,
  params: {},
};

export function InputPaytoForm<T>({
  name,
  readonly,
  label,
  tooltip,
}: Props<keyof T>): VNode {
  const { value: initialValueStr, onChange } = useField<T>(name);

  const initialPayto = parsePaytoUri(initialValueStr ?? "")
  const paths = !initialPayto ? [] : initialPayto.targetPath.split("/")
  const initialPath1 = paths.length >= 1 ? paths[0] : undefined;
  const initialPath2 = paths.length >= 2 ? paths[1] : undefined;
  const initial: Entity = initialPayto === undefined ? defaultTarget : {
    target: initialPayto.targetType,
    params: initialPayto.params,
    path1: initialPath1,
    path2: initialPath2,
  }
  const [value, setValue] = useState<Partial<Entity>>(initial)

  const { i18n } = useTranslationContext();

  const errors: FormErrors<Entity> = {
    target:
      value.target === noTargetValue
        ? i18n.str`required`
        : undefined,
    path1: !value.path1
      ? i18n.str`required`
      : value.target === "iban"
        ? validateIBAN(value.path1, i18n)
        : value.target === "bitcoin"
          ? validateBitcoin(value.path1, i18n)
          : value.target === "ethereum"
            ? validateEthereum(value.path1, i18n)
            : undefined,
    path2:
      value.target === "x-taler-bank"
        ? !value.path2
          ? i18n.str`required`
          : undefined
        : undefined,
    params: undefinedIfEmpty({
      "receiver-name": !value.params?.["receiver-name"]
        ? i18n.str`required`
        : undefined,
    }),
  };

  const hasErrors = Object.keys(errors).some(
    (k) => (errors as any)[k] !== undefined,
  );
  const str = hasErrors || !value.target ? undefined : stringifyPaytoUri({
    targetType: value.target,
    targetPath: value.path2 ? `${value.path1}/${value.path2}` : (value.path1 ?? ""),
    params: value.params ?? {} as any,
    isKnown: false,
  })
  useEffect(() => {
    onChange(str as any)
  }, [str])

  // const submit = useCallback((): void => {
  //   // const accounts: TalerMerchantApi.AccountAddDetails[] = paytos;
  //   // const alreadyExists =
  //   //   accounts.findIndex((x) => x.payto_uri === paytoURL) !== -1;
  //   // if (!alreadyExists) {
  //   const newValue: TalerMerchantApi.AccountAddDetails = {
  //     payto_uri: paytoURL,
  //   };
  //   if (value.auth) {
  //     if (value.auth.url) {
  //       newValue.credit_facade_url = value.auth.url;
  //     }
  //     if (value.auth.type === "none") {
  //       newValue.credit_facade_credentials = {
  //         type: "none",
  //       };
  //     }
  //     if (value.auth.type === "basic") {
  //       newValue.credit_facade_credentials = {
  //         type: "basic",
  //         username: value.auth.username ?? "",
  //         password: value.auth.password ?? "",
  //       };
  //     }
  //   }
  //   onChange(newValue as any);
  //   // }
  //   // valueHandler(defaultTarget);
  // }, [value]);

  //FIXME: translating plural singular
  return (
    <InputGroup name="payto" label={label} fixed tooltip={tooltip}>
      <FormProvider<Entity>
        name="tax"
        errors={errors}
        object={value}
        valueHandler={setValue}
      >
        <InputSelector<Entity>
          name="target"
          label={i18n.str`Account type`}
          tooltip={i18n.str`Method to use for wire transfer`}
          values={targets}
          readonly={readonly}
          toStr={(v) => (v === noTargetValue ? i18n.str`Choose one...` : v)}
        />

        {value.target === "ach" && (
          <Fragment>
            <Input<Entity>
              name="path1"
              label={i18n.str`Routing`}
              readonly={readonly}
              tooltip={i18n.str`Routing number.`}
            />
            <Input<Entity>
              name="path2"
              label={i18n.str`Account`}
              readonly={readonly}
              tooltip={i18n.str`Account number.`}
            />
          </Fragment>
        )}
        {value.target === "bic" && (
          <Fragment>
            <Input<Entity>
              name="path1"
              label={i18n.str`Code`}
              readonly={readonly}
              tooltip={i18n.str`Business Identifier Code.`}
            />
          </Fragment>
        )}
        {value.target === "iban" && (
          <Fragment>
            <Input<Entity>
              name="path1"
              label={i18n.str`IBAN`}
              tooltip={i18n.str`International Bank Account Number.`}
              readonly={readonly}
              placeholder="DE1231231231"
              inputExtra={{ style: { textTransform: "uppercase" } }}
            />
          </Fragment>
        )}
        {value.target === "upi" && (
          <Fragment>
            <Input<Entity>
              name="path1"
              readonly={readonly}
              label={i18n.str`Account`}
              tooltip={i18n.str`Unified Payment Interface.`}
            />
          </Fragment>
        )}
        {value.target === "bitcoin" && (
          <Fragment>
            <Input<Entity>
              name="path1"
              readonly={readonly}
              label={i18n.str`Address`}
              tooltip={i18n.str`Bitcoin protocol.`}
            />
          </Fragment>
        )}
        {value.target === "ethereum" && (
          <Fragment>
            <Input<Entity>
              name="path1"
              readonly={readonly}
              label={i18n.str`Address`}
              tooltip={i18n.str`Ethereum protocol.`}
            />
          </Fragment>
        )}
        {value.target === "ilp" && (
          <Fragment>
            <Input<Entity>
              name="path1"
              readonly={readonly}
              label={i18n.str`Address`}
              tooltip={i18n.str`Interledger protocol.`}
            />
          </Fragment>
        )}
        {value.target === "void" && <Fragment />}
        {value.target === "x-taler-bank" && (
          <Fragment>
            <Input<Entity>
              name="path1"
              readonly={readonly}
              label={i18n.str`Host`}
              tooltip={i18n.str`Bank host.`}
            />
            <Input<Entity>
              name="path2"
              readonly={readonly}
              label={i18n.str`Account`}
              tooltip={i18n.str`Bank account.`}
            />
          </Fragment>
        )}

        {/**
         * Show additional fields apart from the payto
         */}
        {value.target !== noTargetValue && (
          <Fragment>
            <Input
              name="params.receiver-name"
              readonly={readonly}
              label={i18n.str`Owner's name`}
              tooltip={i18n.str`Legal name of the person holding the account.`}
            />
          </Fragment>
        )}

      </FormProvider>
    </InputGroup>
  );
}

