/*
 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 <http://www.gnu.org/licenses/>
 */

import {
  AmountJson,
  AmountLike,
  AmountString,
  Amounts,
  CurrencySpecification,
  FRAC_SEPARATOR,
  HttpStatusCode,
  Logger,
  PaytoString,
  TalerErrorCode,
  TranslatedString,
  buildPayto,
  parsePaytoUri,
  stringifyPaytoUri
} from "@gnu-taler/taler-util";
import {
  useLocalNotification,
  useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, Ref, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { mutate } from "swr";
import { ShowInputErrorLabel } from "@gnu-taler/web-util/browser";
import { useBankCoreApiContext } from "../context/config.js";
import { useBackendState } from "../hooks/backend.js";
import {
  undefinedIfEmpty,
  validateIBAN,
  withRuntimeErrorHandling
} from "../utils.js";
import { assertUnreachable } from "./WithdrawalOperationPage.js";
import { LocalNotificationBanner } from "@gnu-taler/web-util/browser";

const logger = new Logger("PaytoWireTransferForm");

export function PaytoWireTransferForm({
  focus,
  title,
  toAccount,
  onSuccess,
  onCancel,
  limit,
}: {
  title: TranslatedString,
  focus?: boolean;
  toAccount?: string,
  onSuccess: () => void;
  onCancel: (() => void) | undefined;
  limit: AmountJson;
}): VNode {
  const [isRawPayto, setIsRawPayto] = useState(false);
  const { state: credentials } = useBackendState()
  const { api } = useBankCoreApiContext();

  const sendingToFixedAccount = toAccount !== undefined
  //FIXME: support other destination that just IBAN
  const [iban, setIban] = useState<string | undefined>(toAccount);
  const [subject, setSubject] = useState<string | undefined>();
  const [amount, setAmount] = useState<string | undefined>();

  const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
    undefined,
  );
  const { i18n } = useTranslationContext();
  const ibanRegex = "^[A-Z][A-Z][0-9]+$";

  const trimmedAmountStr = amount?.trim();
  const parsedAmount = Amounts.parse(`${limit.currency}:${trimmedAmountStr}`);
  const IBAN_REGEX = /^[A-Z][A-Z0-9]*$/;
  const [notification, notify, handleError] = useLocalNotification()

  const errorsWire = undefinedIfEmpty({
    iban: !iban
      ? i18n.str`required`
      : !IBAN_REGEX.test(iban)
        ? i18n.str`IBAN should have just uppercased letters and numbers`
        : validateIBAN(iban, i18n),
    subject: !subject ? i18n.str`required` : undefined,
    amount: !trimmedAmountStr
      ? i18n.str`required`
      : !parsedAmount
        ? i18n.str`not valid`
        : Amounts.isZero(parsedAmount)
          ? i18n.str`should be greater than 0`
          : Amounts.cmp(limit, parsedAmount) === -1
            ? i18n.str`balance is not enough`
            : undefined,
  });

  const parsed = !rawPaytoInput ? undefined : parsePaytoUri(rawPaytoInput);

  const errorsPayto = undefinedIfEmpty({
    rawPaytoInput: !rawPaytoInput
      ? i18n.str`required`
      : !parsed
        ? i18n.str`does not follow the pattern`
        : !parsed.isKnown || parsed.targetType !== "iban"
          ? i18n.str`only "IBAN" target are supported`
          : !parsed.params.amount
            ? i18n.str`use the "amount" parameter to specify the amount to be transferred`
            : Amounts.parse(parsed.params.amount) === undefined
              ? i18n.str`the amount is not valid`
              : !parsed.params.message
                ? i18n.str`use the "message" parameter to specify a reference text for the transfer`
                : !IBAN_REGEX.test(parsed.iban)
                  ? i18n.str`IBAN should have just uppercased letters and numbers`
                  : validateIBAN(parsed.iban, i18n),
  });

  async function doSend() {
    let payto_uri: PaytoString | undefined;
    let sendingAmount: AmountString | undefined;

    if (credentials.status !== "loggedIn") return;
    if (rawPaytoInput) {
      const p = parsePaytoUri(rawPaytoInput)
      if (!p) return;
      sendingAmount = p.params.amount as AmountString
      delete p.params.amount
      //if this payto is valid then it already have message
      payto_uri = stringifyPaytoUri(p)
    } else {
      if (!iban || !subject) return;
      const ibanPayto = buildPayto("iban", iban, undefined);
      ibanPayto.params.message = encodeURIComponent(subject);
      payto_uri = stringifyPaytoUri(ibanPayto);
      sendingAmount = `${limit.currency}:${trimmedAmountStr}` as AmountString
    }
    const puri = payto_uri;

    await handleError(async () => {
      const res = await api.createTransaction(credentials, {
        payto_uri: puri,
        amount: sendingAmount,
      });
      mutate(() => true)
      if (res.type === "fail") {
        switch (res.case) {
          case HttpStatusCode.BadRequest: return notify({
            type: "error",
            title: i18n.str`The request was invalid or the payto://-URI used unacceptable features.`,
            description: res.detail.hint as TranslatedString,
            debug: res.detail,
          })
          case HttpStatusCode.Unauthorized: return notify({
            type: "error",
            title: i18n.str`Not enough permission to complete the operation.`,
            description: res.detail.hint as TranslatedString,
            debug: res.detail,
          })
          case TalerErrorCode.BANK_UNKNOWN_CREDITOR: return notify({
            type: "error",
            title: i18n.str`The destination account "${puri}" was not found.`,
            description: res.detail.hint as TranslatedString,
            debug: res.detail,
          })
          case TalerErrorCode.BANK_SAME_ACCOUNT: return notify({
            type: "error",
            title: i18n.str`The origin and the destination of the transfer can't be the same.`,
            description: res.detail.hint as TranslatedString,
            debug: res.detail,
          })
          case TalerErrorCode.BANK_UNALLOWED_DEBIT: return notify({
            type: "error",
            title: i18n.str`Your balance is not enough.`,
            description: res.detail.hint as TranslatedString,
            debug: res.detail,
          })
          case HttpStatusCode.NotFound: return notify({
            type: "error",
            title: i18n.str`The origin account "${puri}" was not found.`,
            description: res.detail.hint as TranslatedString,
            debug: res.detail,
          })
          default: assertUnreachable(res)
        }
      }
      onSuccess();
      setAmount(undefined);
      setIban(undefined);
      setSubject(undefined);
      rawPaytoInputSetter(undefined)
    })
  }

  return (<div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
    {/**
     * FIXME: Scan a qr code
     */}
    <div class="">
      <h2 class="text-base font-semibold leading-7 text-gray-900">
        {title}
      </h2>
      <div>
        <div class="px-2 mt-2 grid grid-cols-1 gap-y-4 sm:gap-x-4">
          <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (!isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
            <input type="radio" name="project-type" value="Newsletter" class="sr-only" aria-labelledby="project-type-0-label" aria-describedby="project-type-0-description-0 project-type-0-description-1" onChange={() => {
              if (parsed && parsed.isKnown && parsed.targetType === "iban") {
                setIban(parsed.iban)
                const amountStr = parsed.params["amount"]
                if (amountStr) {
                  const amount = Amounts.parse(parsed.params["amount"])
                  if (amount) {
                    setAmount(Amounts.stringifyValue(amount))
                  }
                }
                const subject = parsed.params["message"]
                if (subject) {
                  setSubject(subject)
                }
              }
              setIsRawPayto(false)
            }} />
            <span class="flex flex-1">
              <span class="flex flex-col">
                <span class="block text-sm  font-medium text-gray-900">
                  <i18n.Translate>Using a form</i18n.Translate>
                </span>
              </span>
            </span>
          </label>

          {sendingToFixedAccount ? undefined :
            <label class={"relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none" + (isRawPayto ? "border-indigo-600 ring-2 ring-indigo-600" : "border-gray-300")}>
              <input type="radio" name="project-type" value="Existing Customers" class="sr-only" aria-labelledby="project-type-1-label" aria-describedby="project-type-1-description-0 project-type-1-description-1" onChange={() => {
                if (iban) {
                  const payto = buildPayto("iban", iban, undefined)
                  if (parsedAmount) {
                    payto.params["amount"] = Amounts.stringify(parsedAmount)
                  }
                  if (subject) {
                    payto.params["message"] = subject
                  }
                  rawPaytoInputSetter(stringifyPaytoUri(payto))
                }
                setIsRawPayto(true)
              }} />
              <span class="flex flex-1">
                <span class="flex flex-col">
                  <span class="block text-sm font-medium text-gray-900">
                    <i18n.Translate>Import payto:// URI</i18n.Translate>
                  </span>
                </span>
              </span>
            </label>
          }
        </div>
      </div>
    </div>

    <form
      class="bg-white shadow-sm ring-1 ring-gray-900/5 rounded-md sm:rounded-xl md:col-span-2 w-fit mx-auto"
      autoCapitalize="none"
      autoCorrect="off"
      onSubmit={e => {
        e.preventDefault()
      }}
    >
      <div class="p-4 sm:p-8">
        {!isRawPayto ?
          <div class="grid max-w-xs grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
            <div class="sm:col-span-5">
              <label for="iban" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Recipient`}</label>
              <div class="mt-2">
                <input
                  ref={focus ? doAutoFocus : undefined}
                  type="text"
                  class="block w-full disabled:bg-gray-200 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  name="iban"
                  id="iban"
                  disabled={sendingToFixedAccount}
                  value={iban ?? ""}
                  placeholder="CC0123456789"
                  autocomplete="off"
                  required
                  pattern={ibanRegex}
                  onInput={(e): void => {
                    setIban(e.currentTarget.value.toUpperCase());
                  }}
                />
                <ShowInputErrorLabel
                  message={errorsWire?.iban}
                  isDirty={iban !== undefined}
                />
              </div>
              <p class="mt-2 text-sm text-gray-500" >
                <i18n.Translate>IBAN of the recipient's account</i18n.Translate>
              </p>
            </div>

            <div class="sm:col-span-5">
              <label for="subject" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Transfer subject`}</label>
              <div class="mt-2">
                <input
                  type="text"
                  class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  name="subject"
                  id="subject"
                  autocomplete="off"
                  placeholder="subject"
                  value={subject ?? ""}
                  required
                  onInput={(e): void => {
                    setSubject(e.currentTarget.value);
                  }}
                />
                <ShowInputErrorLabel
                  message={errorsWire?.subject}
                  isDirty={subject !== undefined}
                />
              </div>
              <p class="mt-2 text-sm text-gray-500" >some text to identify the transfer</p>
            </div>

            <div class="sm:col-span-5">
              <label for="amount" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`Amount`}</label>
              <InputAmount
                name="amount"
                left
                currency={limit.currency}
                value={trimmedAmountStr}
                onChange={(d) => {
                  setAmount(d)
                }}
              />
              <ShowInputErrorLabel
                message={errorsWire?.amount}
                isDirty={trimmedAmountStr !== undefined}
              />
              <p class="mt-2 text-sm text-gray-500" >amount to transfer</p>
            </div>

          </div> :
          <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6 w-full">
            <div class="sm:col-span-6">
              <label for="address" class="block text-sm font-medium leading-6 text-gray-900">{i18n.str`payto URI:`}</label>
              <div class="mt-2">
                <textarea
                  ref={focus ? doAutoFocus : undefined}
                  name="address"
                  id="address"
                  type="textarea"
                  rows={5}
                  class="block overflow-hidden w-44 sm:w-96 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                  value={rawPaytoInput ?? ""}
                  required
                  placeholder={i18n.str`payto://iban/[receiver-iban]?message=[subject]&amount=[${limit.currency}:X.Y]`}
                  onInput={(e): void => {
                    rawPaytoInputSetter(e.currentTarget.value);
                  }}
                />
                <ShowInputErrorLabel
                  message={errorsPayto?.rawPaytoInput}
                  isDirty={rawPaytoInput !== undefined}
                />
              </div>
            </div>
          </div>
        }
      </div>
      <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
        {onCancel ?
          <button type="button" class="text-sm font-semibold leading-6 text-gray-900"
            onClick={onCancel}
          >
            <i18n.Translate>Cancel</i18n.Translate>
          </button>
          : <div />
        }
        <button type="submit"
          class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
          disabled={isRawPayto ? !!errorsPayto : !!errorsWire}
          onClick={(e) => {
            e.preventDefault()
            doSend()
          }}
        >
          <i18n.Translate>Send</i18n.Translate>
        </button>
      </div>
      <LocalNotificationBanner notification={notification} />
    </form>
  </div >
  )

}

/**
 * Show the element when the load ended
 * @param element 
 */
export function doAutoFocus(element: HTMLElement | null) {
  if (element) {
    setTimeout(() => {
      element.focus({ preventScroll: true })
      element.scrollIntoView({
        behavior: "smooth",
        block: "center",
        inline: "center",
      })
    }, 100)
  }
}

export function InputAmount(
  {
    currency,
    name,
    value,
    error,
    left,
    onChange,
  }: {
    error?: string;
    currency: string;
    name: string;
    left?: boolean | undefined,
    value: string | undefined;
    onChange?: (s: string) => void;
  },
  ref: Ref<HTMLInputElement>,
): VNode {
  const { config } = useBankCoreApiContext()
  return (
    <div class="mt-2">
      <div class="flex rounded-md shadow-sm border-0 ring-1 ring-inset ring-gray-300 focus:ring-2 focus:ring-inset focus:ring-indigo-600">
        <div
          class="pointer-events-none inset-y-0 flex items-center px-3"
        >
          <span class="text-gray-500 sm:text-sm">{currency}</span>
        </div>
        <input
          type="number"
          data-left={left}
          class="disabled:bg-gray-200 text-right rounded-md rounded-l-none data-[left=true]:text-left w-full py-1.5 pl-3 text-gray-900  placeholder:text-gray-400  sm:text-sm sm:leading-6"
          placeholder="0.00" aria-describedby="price-currency"
          ref={ref}
          name={name}
          id={name}
          autocomplete="off"
          value={value ?? ""}
          disabled={!onChange}
          onInput={(e) => {
            if (!onChange) return;
            const l = e.currentTarget.value.length
            const sep_pos = e.currentTarget.value.indexOf(FRAC_SEPARATOR)
            if (sep_pos !== -1 && l - sep_pos - 1 > config.currency_specification.num_fractional_input_digits) {
              e.currentTarget.value = e.currentTarget.value.substring(0, sep_pos + config.currency_specification.num_fractional_input_digits + 1)
            }
            onChange(e.currentTarget.value);
          }}
        />
      </div>
      <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
    </div>
  );
}

export function RenderAmount({ value, spec, negative, withColor, hideSmall }: { spec: CurrencySpecification; value: AmountJson, hideSmall?: boolean, negative?: boolean, withColor?: boolean }): VNode {
  const neg = !!negative //convert to true or false

  const { currency, normal, small } = Amounts.stringifyValueWithSpec(value, spec)

  return <span data-negative={withColor ? neg : undefined} class="whitespace-nowrap data-[negative=false]:text-green-600 data-[negative=true]:text-red-600">
    {negative ? "- " : undefined}
    {currency} {normal} {!hideSmall && small && <sup class="-ml-1">{small}</sup>}
  </span>
}