/*
 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,
  Amounts,
  HttpStatusCode,
  Logger,
  TranslatedString,
  buildPayto,
  parsePaytoUri,
  stringifyPaytoUri
} from "@gnu-taler/taler-util";
import {
  RequestError,
  notify,
  notifyError,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { h, VNode, Fragment, Ref } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { ShowInputErrorLabel } from "../components/ShowInputErrorLabel.js";
import { useAccessAPI } from "../hooks/access.js";
import {
  buildRequestErrorMessage,
  undefinedIfEmpty,
  validateIBAN,
} from "../utils.js";

const logger = new Logger("PaytoWireTransferForm");

export function PaytoWireTransferForm({
  focus,
  title,
  onSuccess,
  onCancel,
  limit,
}: {
  title: TranslatedString,
  focus?: boolean;
  onSuccess: () => void;
  onCancel: (() => void) | undefined;
  limit: AmountJson;
}): VNode {
  const [isRawPayto, setIsRawPayto] = useState(true);
  // FIXME: remove this
  const [iban, setIban] = useState<string | undefined>("DE4745461198061");
  const [subject, setSubject] = useState<string | undefined>("ASD");
  const [amount, setAmount] = useState<string | undefined>("1.00001");

  const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
    undefined,
  );
  const { i18n } = useTranslationContext();
  const ibanRegex = "^[A-Z][A-Z][0-9]+$";
  const ref = useRef<HTMLInputElement>(null);
  useEffect(() => {
    if (focus) ref.current?.focus();
  }, [focus, isRawPayto]);

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

  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 { createTransaction } = useAccessAPI();

  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: string | undefined;

    if (rawPaytoInput) {
      payto_uri = rawPaytoInput
    } else {
      if (!iban || !subject) return;
      const ibanPayto = buildPayto("iban", iban, undefined);
      ibanPayto.params.message = encodeURIComponent(subject);
      payto_uri = stringifyPaytoUri(ibanPayto);
    }

    try {
      await createTransaction({
        payto_uri,
        amount: `${limit.currency}:${amount}`,
      });
      onSuccess();
      setAmount(undefined);
      setIban(undefined);
      setSubject(undefined);
      rawPaytoInputSetter(undefined)
    } catch (error) {
      if (error instanceof RequestError) {
        notify(
          buildRequestErrorMessage(i18n, error.cause, {
            onClientError: (status) =>
              status === HttpStatusCode.BadRequest
                ? i18n.str`The request was invalid or the payto://-URI used unacceptable features.`
                : undefined,
          }),
        );
      } else {
        notifyError(
          i18n.str`Operation failed, please report`,
          (error instanceof Error
            ? error.message
            : JSON.stringify(error)) as TranslatedString
        )
      }
    }

  }

  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="px-4 sm:px-0">
      <h2 class="text-base font-semibold leading-7 text-gray-900">
        {title}
      </h2>
      <div>
        <div class="px-4 mt-4 grid grid-cols-1 gap-y-6 sm:grid-cols-1 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 amount = Amounts.parse(parsed.params["amount"])
                if (amount) {
                  setAmount(Amounts.stringifyValue(amount))
                }
                const subject = parsed.params["subject"]
                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>

          <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 sm:rounded-xl md:col-span-2 w-fit mx-auto"
      autoCapitalize="none"
      autoCorrect="off"
      onSubmit={e => {
        e.preventDefault()
      }}
    >
      <div class="px-4 py-6 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 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"
                  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>
              <Amount
                name="amount"
                left
                currency={limit.currency}
                value={trimmedAmountStr}
                onChange={(d) => {
                  setAmount(d)
                }}
              />
              <ShowInputErrorLabel
                message={errorsWire?.amount}
                isDirty={subject !== 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={3}
                  class="block overflow-hidden w-64 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>
    </form>
  </div >
  )

}

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

export function Amount(
  {
    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 {
  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="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): void => {
            if (onChange) {
              onChange(e.currentTarget.value);
            }
          }}
        />
      </div>
      <ShowInputErrorLabel message={error} isDirty={value !== undefined} />
    </div>
  );
}

