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

const logger = new Logger("PaytoWireTransferForm");

export function PaytoWireTransferForm({
  focus,
  onSuccess,
  limit,
}: {
  focus?: boolean;
  onSuccess: () => void;
  limit: AmountJson;
}): VNode {
  const [isRawPayto, setIsRawPayto] = useState(false);
  const [iban, setIban] = useState<string | undefined>(undefined);
  const [subject, setSubject] = useState<string | undefined>(undefined);
  const [amount, setAmount] = useState<string | undefined>(undefined);

  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`Missing IBAN`
      : !IBAN_REGEX.test(iban)
      ? i18n.str`IBAN should have just uppercased letters and numbers`
      : validateIBAN(iban, i18n),
    subject: !subject ? i18n.str`Missing subject` : undefined,
    amount: !trimmedAmountStr
      ? i18n.str`Missing amount`
      : !parsedAmount
      ? i18n.str`Amount is 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();

  if (!isRawPayto)
    return (
      <div>
        <form
          class="pure-form"
          name="wire-transfer-form"
          onSubmit={(e) => {
            e.preventDefault();
          }}
          autoCapitalize="none"
          autoCorrect="off"
        >
          <label for="iban">{i18n.str`Receiver IBAN:`}</label>&nbsp;
          <input
            ref={ref}
            type="text"
            id="iban"
            name="iban"
            value={iban ?? ""}
            placeholder="CC0123456789"
            required
            pattern={ibanRegex}
            onInput={(e): void => {
              setIban(e.currentTarget.value);
            }}
          />
          <ShowInputErrorLabel
            message={errorsWire?.iban}
            isDirty={iban !== undefined}
          />
          <label for="subject">{i18n.str`Transfer subject:`}</label>&nbsp;
          <input
            type="text"
            name="subject"
            id="subject"
            placeholder="subject"
            value={subject ?? ""}
            required
            onInput={(e): void => {
              setSubject(e.currentTarget.value);
            }}
          />
          <ShowInputErrorLabel
            message={errorsWire?.subject}
            isDirty={subject !== undefined}
          />
          <label for="amount">{i18n.str`Amount:`}</label>&nbsp;
          <div style={{ width: "max-content", display: "flex" }}>
            <input
              type="text"
              readonly
              class="currency-indicator"
              size={limit.currency.length}
              maxLength={limit.currency.length}
              tabIndex={-1}
              style={{
                borderTopRightRadius: 0,
                borderBottomRightRadius: 0,
                borderRight: 0,
              }}
              value={limit.currency}
            />
            <input
              type="number"
              name="amount"
              id="amount"
              placeholder="amount"
              required
              style={{
                borderTopLeftRadius: 0,
                borderBottomLeftRadius: 0,
                borderLeft: 0,
                width: 150,
              }}
              value={amount ?? ""}
              onInput={(e): void => {
                setAmount(e.currentTarget.value);
              }}
            />
          </div>
          <ShowInputErrorLabel
            message={errorsWire?.amount}
            isDirty={amount !== undefined}
          />
          <p style={{ display: "flex", justifyContent: "space-between" }}>
            <input
              type="submit"
              class="pure-button pure-button-primary"
              disabled={!!errorsWire}
              value="Send"
              onClick={async (e) => {
                e.preventDefault();
                if (!(iban && subject && amount)) {
                  return;
                }
                const ibanPayto = buildPayto("iban", iban, undefined);
                ibanPayto.params.message = encodeURIComponent(subject);
                const paytoUri = stringifyPaytoUri(ibanPayto);

                try {
                  await createTransaction({
                    paytoUri,
                    amount: `${limit.currency}:${amount}`,
                  });
                  onSuccess();
                  setAmount(undefined);
                  setIban(undefined);
                  setSubject(undefined);
                } catch (error) {
                  if (error instanceof RequestError) {
                    notifyError(
                      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({
                      title: i18n.str`Operation failed, please report`,
                      description:
                        error instanceof Error
                          ? error.message
                          : JSON.stringify(error),
                    });
                  }
                }
              }}
            />
            <input
              type="button"
              class="pure-button"
              value="Clear"
              onClick={async (e) => {
                e.preventDefault();
                setAmount(undefined);
                setIban(undefined);
                setSubject(undefined);
              }}
            />
          </p>
        </form>
        <p>
          <a
            href="#"
            onClick={(e) => {
              setIsRawPayto(true);
              e.preventDefault();
            }}
          >
            {i18n.str`Want to try the raw payto://-format?`}
          </a>
        </p>
      </div>
    );

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

  const errorsPayto = undefinedIfEmpty({
    rawPaytoInput: !rawPaytoInput
      ? i18n.str`required`
      : !parsed
      ? i18n.str`does not follow the pattern`
      : !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`
      : !parsed.isKnown || parsed.targetType !== "iban"
      ? i18n.str`only "IBAN" target are supported`
      : !IBAN_REGEX.test(parsed.iban)
      ? i18n.str`IBAN should have just uppercased letters and numbers`
      : validateIBAN(parsed.iban, i18n),
  });

  return (
    <div>
      <p>{i18n.str`Transfer money to account identified by payto:// URI:`}</p>
      <form
        class="pure-form"
        name="payto-form"
        onSubmit={(e) => {
          e.preventDefault();
        }}
        autoCapitalize="none"
        autoCorrect="off"
      >
        <p>
          <label for="address">{i18n.str`payto URI:`}</label>&nbsp;
          <input
            name="address"
            type="text"
            size={50}
            ref={ref}
            id="address"
            value={rawPaytoInput ?? ""}
            required
            placeholder={i18n.str`payto address`}
            // pattern={`payto://iban/[A-Z][A-Z][0-9]+?message=[a-zA-Z0-9 ]+&amount=${currency}:[0-9]+(.[0-9]+)?`}
            onInput={(e): void => {
              rawPaytoInputSetter(e.currentTarget.value);
            }}
          />
          <ShowInputErrorLabel
            message={errorsPayto?.rawPaytoInput}
            isDirty={rawPaytoInput !== undefined}
          />
          <br />
          <div style={{ fontSize: "small", marginTop: 4 }}>
            Hint:
            <code>
              payto://iban/[receiver-iban]?message=[subject]&amount=[
              {limit.currency}
              :X.Y]
            </code>
          </div>
        </p>
        <p>
          <input
            class="pure-button pure-button-primary"
            type="button"
            disabled={!!errorsPayto}
            value={i18n.str`Send`}
            onClick={async () => {
              if (!rawPaytoInput) {
                logger.error("Didn't get any raw Payto string!");
                return;
              }

              try {
                await createTransaction({
                  paytoUri: rawPaytoInput,
                });
                onSuccess();
                rawPaytoInputSetter(undefined);
              } catch (error) {
                if (error instanceof RequestError) {
                  notifyError(
                    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({
                    title: i18n.str`Operation failed, please report`,
                    description:
                      error instanceof Error
                        ? error.message
                        : JSON.stringify(error),
                  });
                }
              }
            }}
          />
        </p>
        <p>
          <a
            href="/account"
            onClick={() => {
              setIsRawPayto(false);
            }}
          >
            {i18n.str`Use wire-transfer form?`}
          </a>
        </p>
      </form>
    </div>
  );
}
