/*
 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,
  AmountString,
  parsePaytoUri,
  PaytoUriIBAN,
  PaytoUriTaler,
  PaytoUriTalerBank,
  PaytoUriTalerHttp,
  PaytoUriUnknown,
  segwitMinAmount,
  stringifyPaytoUri,
  TranslatedString,
  WithdrawalExchangeAccountDetails,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, h, VNode } from "preact";
import { useEffect, useRef, useState } from "preact/hooks";
import { useBackendContext } from "../context/backend.js";
import { useAsyncAsHook } from "../hooks/useAsyncAsHook.js";
import { Button } from "../mui/Button.js";
import { SafeHandler } from "../mui/handlers.js";
import { CopiedIcon, CopyIcon } from "../svg/index.js";
import { Amount } from "./Amount.js";
import { ShowBanksForPaytoPopup } from "./ShowBanksForPaytoPopup.js";
import { ShowQRsForPaytoPopup } from "./ShowQRsForPaytoPopup.js";
import { ButtonBox, TooltipLeft, WarningBox } from "./styled/index.js";

export interface BankDetailsProps {
  subject: string;
  amount: AmountJson;
  accounts: WithdrawalExchangeAccountDetails[];
}

export function BankDetailsByPaytoType({
  subject,
  amount,
  accounts: unsortedAccounts,
}: BankDetailsProps): VNode {
  const { i18n } = useTranslationContext();
  const [index, setIndex] = useState(0);

  if (!unsortedAccounts.length) {
    return <div>the exchange account list is empty</div>;
  }

  const accounts = unsortedAccounts.sort((a, b) => {
    return (b.priority ?? 0) - (a.priority ?? 0);
  });

  const selectedAccount = accounts[index];
  const altCurrency = selectedAccount.currencySpecification?.name;

  const payto = parsePaytoUri(selectedAccount.paytoUri);

  if (!payto) return <Fragment />;
  // make sure the payto has the right params
  payto.params["amount"] = altCurrency
    ? selectedAccount.transferAmount!
    : Amounts.stringify(amount);
  payto.params["message"] = subject;

  if (payto.isKnown && payto.targetType === "bitcoin") {
    const min = segwitMinAmount(amount.currency);
    const addrs = payto.segwitAddrs.map(
      (a) => `${a} ${Amounts.stringifyValue(min)}`,
    );
    addrs.unshift(`${payto.targetPath} ${Amounts.stringifyValue(amount)}`);
    const copyContent = addrs.join("\n");
    return (
      <Frame
        title={i18n.str`Bitcoin transfer details`}
        accounts={accounts}
        updateIndex={setIndex}
        currentIndex={index}
        defaultCurrency={amount.currency}
      >
        <p>
          <i18n.Translate>
            The exchange need a transaction with 3 output, one output is the
            exchange account and the other two are segwit fake address for
            metadata with an minimum amount.
          </i18n.Translate>
        </p>

        <p>
          <i18n.Translate>
            In bitcoincore wallet use &apos;Add Recipient&apos; button to add
            two additional recipient and copy addresses and amounts
          </i18n.Translate>
        </p>
        <table>
          <tr>
            <td>
              <div>
                {payto.targetPath} <Amount value={amount} hideCurrency /> BTC
              </div>
              {payto.segwitAddrs.map((addr, i) => (
                <div key={i}>
                  {addr} <Amount value={min} hideCurrency /> BTC
                </div>
              ))}
            </td>
            <td></td>
            <td>
              <CopyButton getContent={() => copyContent} />
            </td>
          </tr>
        </table>
        <p>
          <i18n.Translate>
            Make sure the amount show{" "}
            {Amounts.stringifyValue(Amounts.sum([amount, min, min]).amount)}{" "}
            BTC, else you have to change the base unit to BTC
          </i18n.Translate>
        </p>
      </Frame>
    );
  }

  if (payto.isKnown && payto.targetType === "ethereum") {
    const min = segwitMinAmount(amount.currency);
    const copyContent = `${payto.targetPath} ${Amounts.stringifyValue(amount)}`;
    return (
      <Frame
        title={i18n.str`Ethereum transfer details`}
        accounts={accounts}
        updateIndex={setIndex}
        currentIndex={index}
        defaultCurrency={amount.currency}
      >
        <p>
          <i18n.Translate>
            You need to wire to the service provider account.
          </i18n.Translate>
        </p>

        <table>
          <tr>
            <td>
              <div>
                {payto.targetPath} <Amount value={amount} hideCurrency /> ETH
              </div>
            </td>
            <td></td>
            <td>
              <CopyButton getContent={() => copyContent} />
            </td>
          </tr>
        </table>
        <p>
          <i18n.Translate>
            Make sure the amount show {Amounts.stringifyValue(amount)} ETH
          </i18n.Translate>
        </p>
      </Frame>
    );
  }

  return (
    <Frame
      title={i18n.str`Bank transfer details`}
      accounts={accounts}
      updateIndex={setIndex}
      currentIndex={index}
      defaultCurrency={amount.currency}
    >
      <IBANAccountInfoTable payto={payto} subject={subject} />
    </Frame>
  );
}

function IBANAccountInfoTable({
  payto,
  subject,
}: {
  subject: string;
  payto:
  | PaytoUriUnknown
  | PaytoUriIBAN
  | PaytoUriTalerBank
  | PaytoUriTalerHttp
  | PaytoUriTaler;
}) {
  const { i18n } = useTranslationContext();
  const api = useBackendContext();
  const [showBanks, setShowBanks] = useState(false);
  const [showQrs, setShowQrs] = useState(false);

  const hook = useAsyncAsHook(async () => {
    const qrs = await api.wallet.call(WalletApiOperation.GetQrCodesForPayto, {
      paytoUri: stringifyPaytoUri(payto),
    });
    const banks = await api.wallet.call(
      WalletApiOperation.GetBankingChoicesForPayto,
      {
        paytoUri: stringifyPaytoUri(payto),
      },
    );
    return { qrs, banks };
  }, []);

  const qrCodes = !hook || hook.hasError ? [] : hook.response.qrs.codes;
  const banksSites = !hook || hook.hasError ? [] : hook.response.banks.choices;

  const accountPart = !payto.isKnown ? (
    <Fragment>
      <Row name={i18n.str`Account`} value={payto.targetPath} />
    </Fragment>
  ) : payto.targetType === "x-taler-bank" ? (
    <Fragment>
      <Row name={i18n.str`Bank host`} value={payto.host} />
      <Row name={i18n.str`Bank account`} value={payto.account} />
    </Fragment>
  ) : payto.targetType === "iban" ? (
    <Fragment>
      {payto.bic !== undefined ? (
        <Row name={i18n.str`BIC`} value={payto.bic} />
      ) : undefined}
      <Row name={i18n.str`IBAN`} value={payto.iban} />
    </Fragment>
  ) : payto.targetType === "taler-reserve" ||
    payto.targetType === "taler-reserve-http" ? (
    <Fragment>
      <Row name={i18n.str`Exchange`} value={payto.exchange} />
      <Row name={i18n.str`Reserve Pub`} value={payto.reservePub} />
    </Fragment>
  ) : undefined;

  const receiverName =
    payto.params["receiver-name"] || payto.params["receiver"] || undefined;

  const receiverPostalCode =
    payto.params["receiver-postal-code"] || undefined;

  const receiverTown =
    payto.params["receiver-town"] || undefined;

  return (
    <table>
      <tbody>
        <tr>
          <td colSpan={3}>
            <i18n.Translate>Step 1:</i18n.Translate>
            &nbsp;
            <i18n.Translate>
              Copy this code and paste it into the subject/purpose field in your
              banking app or bank website
            </i18n.Translate>
          </td>
        </tr>
        <Row name={i18n.str`Subject`} value={`Taler ${subject}`} literal />

        <tr>
          <td colSpan={3}>
            <i18n.Translate>Step 2:</i18n.Translate>
            &nbsp;
            <i18n.Translate>
              If you don't already have it in your banking favourites list, then
              copy and paste this IBAN and the name into the receiver fields in
              your banking app or website
            </i18n.Translate>
          </td>
        </tr>
        {accountPart}
        {receiverName ? (
          <Row name={i18n.str`Receiver name`} value={receiverName} />
        ) : undefined}
        {receiverPostalCode ? (
          <Row name={i18n.str`Receiver postal code`} value={receiverPostalCode} />
        ) : undefined}
        {receiverTown ? (
          <Row name={i18n.str`Receiver town`} value={receiverTown} />
        ) : undefined}

        <tr>
          <td colSpan={3}>
            <i18n.Translate>Step 3:</i18n.Translate>
            &nbsp;
            <i18n.Translate>
              Finish the wire transfer setting the amount in your banking app or
              website, then this withdrawal will proceed automatically.
            </i18n.Translate>
          </td>
        </tr>
        <Row
          name={i18n.str`Amount`}
          value={
            <Amount
              value={payto.params["amount"] as AmountString}
              hideCurrency
            />
          }
        />

        <tr>
          <td colSpan={3}>
            <WarningBox style={{ margin: 0 }}>
              <span>
                <i18n.Translate>
                  Make sure ALL data is correct, including the subject;
                  otherwise, the money will not arrive in this wallet. You can
                  use the copy buttons (<CopyIcon />) to prevent typing errors
                  or the "payto://" URI below to copy just one value.
                </i18n.Translate>
              </span>
            </WarningBox>
          </td>
        </tr>

        <tr>
          <td colSpan={2} width="100%">
            <i18n.Translate>
              Alternative if your bank already supports PayTo URI, you can use
              this{" "}
              <a
                target="_bank"
                rel="noreferrer"
                title="RFC 8905 for designating targets for payments"
                href="https://tools.ietf.org/html/rfc8905"
              >
                PayTo URI
              </a>{" "}
              link instead
            </i18n.Translate>
          </td>
          <td>
            <CopyButton getContent={() => stringifyPaytoUri(payto)} />
          </td>
        </tr>

        {banksSites.length < 1 ? undefined : (
          <Fragment>
            <div>
              <a
                href="#"
                onClick={(e) => {
                  setShowBanks(true);
                  e.preventDefault();
                }}
              >
                <i18n.Translate>
                  Continue with banking app or website
                </i18n.Translate>
              </a>
            </div>

            {showBanks ? (
              <ShowBanksForPaytoPopup
                banks={banksSites}
                onClose={{
                  onClick: (async () =>
                    setShowBanks(false)) as SafeHandler<void>,
                }}
              />
            ) : undefined}
          </Fragment>
        )}

        {qrCodes.length < 1 ? undefined : (
          <Fragment>
            <div>
              <a
                href="#"
                onClick={(e) => {
                  setShowQrs(true);
                  e.preventDefault();
                }}
              >
                <i18n.Translate>Show QR code</i18n.Translate>
              </a>
            </div>
            {showQrs ? (
              <ShowQRsForPaytoPopup
                qrs={qrCodes}
                onClose={{
                  onClick: (async () => setShowQrs(false)) as SafeHandler<void>,
                }}
              />
            ) : undefined}
          </Fragment>
        )}
      </tbody>
    </table>
  );
}

function CopyButton({ getContent }: { getContent: () => string }): VNode {
  const [copied, setCopied] = useState(false);
  function copyText(): void {
    navigator.clipboard.writeText(getContent() || "");
    setCopied(true);
  }
  useEffect(() => {
    if (copied) {
      setTimeout(() => {
        setCopied(false);
      }, 1000);
    }
  }, [copied]);

  if (!copied) {
    return (
      <ButtonBox onClick={copyText}>
        <CopyIcon />
      </ButtonBox>
    );
  }
  return (
    <TooltipLeft content="Copied">
      <ButtonBox disabled>
        <CopiedIcon />
      </ButtonBox>
    </TooltipLeft>
  );
}

function Row({
  name,
  value,
  literal,
}: {
  name: TranslatedString;
  value: string | VNode;
  literal?: boolean;
}): VNode {
  const preRef = useRef<HTMLPreElement>(null);
  const tdRef = useRef<HTMLTableCellElement>(null);

  function getContent(): string {
    return preRef.current?.textContent || tdRef.current?.textContent || "";
  }

  return (
    <tr>
      <td style={{ paddingRight: 8 }}>
        <b>{name}</b>
      </td>
      {literal ? (
        <td>
          <pre
            ref={preRef}
            style={{ whiteSpace: "pre-wrap", wordBreak: "break-word" }}
          >
            {value}
          </pre>
        </td>
      ) : (
        <td ref={tdRef}>{value}</td>
      )}
      <td>
        <CopyButton getContent={getContent} />
      </td>
    </tr>
  );
}

function Frame({
  title,
  children,
  accounts,
  defaultCurrency,
  currentIndex,
  updateIndex,
}: {
  title: TranslatedString;
  children: ComponentChildren;
  currentIndex: number;
  updateIndex: (idx: number) => void;
  defaultCurrency: string;
  accounts: WithdrawalExchangeAccountDetails[];
}): VNode {
  return (
    <section
      style={{
        textAlign: "left",
        border: "solid 1px black",
        padding: 8,
        borderRadius: 4,
      }}
    >
      <div
        style={{
          display: "flex",
          width: "100%",
          justifyContent: "space-between",
        }}
      >
        <p style={{ marginTop: 0 }}>{title}</p>
        <div></div>
      </div>

      {children}

      {accounts.length > 1 ? (
        <Fragment>
          {accounts.map((ac, acIdx) => {
            const accountLabel = ac.bankLabel ?? `Account #${acIdx + 1}`;
            return (
              <Button
                key={acIdx}
                variant={acIdx === currentIndex ? "contained" : "outlined"}
                onClick={async () => {
                  updateIndex(acIdx);
                }}
              >
                {accountLabel} (
                {ac.currencySpecification?.name ?? defaultCurrency})
              </Button>
            );
          })}

          {/* <Button variant={currency === altCurrency ? "contained" : "outlined"}
          onClick={async () => {
            setCurrency(altCurrency)
          }}
        >
          <i18n.Translate>{altCurrency}</i18n.Translate>
        </Button> */}
        </Fragment>
      ) : undefined}
    </section>
  );
}
