/*
 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,
  PaymentInsufficientBalanceDetails,
  PreparePayResult,
  PreparePayResultType,
  TranslatedString,
  parsePayUri,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { useBackendContext } from "../context/backend.js";
import { Button } from "../mui/Button.js";
import { ButtonHandler } from "../mui/handlers.js";
import { assertUnreachable } from "../utils/index.js";
import { Amount } from "./Amount.js";
import { Part } from "./Part.js";
import { QR } from "./QR.js";
import { LinkSuccess, WarningBox } from "./styled/index.js";

interface Props {
  payStatus: PreparePayResult;
  payHandler: ButtonHandler | undefined;
  uri: string;
  amount: AmountJson;
  goToWalletManualWithdraw: (currency: string) => Promise<void>;
}

export function PaymentButtons({
  payStatus,
  uri,
  payHandler,
  amount,
  goToWalletManualWithdraw,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  if (payStatus.status === PreparePayResultType.PaymentPossible) {
    return (
      <Fragment>
        <section>
          <Button
            variant="contained"
            color="success"
            onClick={payHandler?.onClick}
          >
            <i18n.Translate>
              Pay &nbsp;
              {<Amount value={amount} />}
            </i18n.Translate>
          </Button>
        </section>
        <PayWithMobile uri={uri} />
      </Fragment>
    );
  }

  if (payStatus.status === PreparePayResultType.InsufficientBalance) {
    const reason = getReason(payStatus.balanceDetails);

    let BalanceMessage = "";
    switch (reason) {
      case "age-acceptable": {
        BalanceMessage = i18n.str`Balance is not enough because you have ${Amounts.stringifyValue(
          payStatus.balanceDetails.balanceAgeAcceptable,
        )} ${amount.currency} to pay for this contract which is restricted.`;
        break;
      }
      case "available": {
        BalanceMessage = i18n.str`Balance is not enough because you have ${Amounts.stringifyValue(
          payStatus.balanceDetails.balanceAvailable,
        )} ${amount.currency} available.`;
        break;
      }
      case "merchant-acceptable": {
        BalanceMessage = i18n.str`Balance is not enough because merchant will just accept ${Amounts.stringifyValue(
          payStatus.balanceDetails.balanceReceiverAcceptable,
        )} ${amount.currency
          } . To know more you can check which exchange and auditors the merchant trust.`;
        break;
      }
      case "merchant-depositable": {
        BalanceMessage = i18n.str`Balance is not enough because merchant will just accept ${Amounts.stringifyValue(
          payStatus.balanceDetails.balanceReceiverDepositable,
        )} ${amount.currency
          } . To know more you can check which wire methods the merchant accepts.`;
        break;
      }
      case "material": {
        BalanceMessage = i18n.str`Balance is not enough because you have ${Amounts.stringifyValue(
          payStatus.balanceDetails.balanceMaterial,
        )} ${amount.currency
          } to spend right know. There are some coins that need to be refreshed.`;
        break;
      }
      case "fee-gap": {
        BalanceMessage = i18n.str`Balance looks like it should be enough, but doesn't cover all fees requested by the merchant and payment processor. Please ensure there is at least ${Amounts.stringifyValue(
          Amounts.stringify(
            Amounts.sub(
              amount,
              payStatus.balanceDetails.maxEffectiveSpendAmount,
            ).amount,
          ),
        )} ${amount.currency
          } more balance in your wallet or ask your merchant to cover more of the fees.`;
        break;
      }
      default:
        assertUnreachable(reason);
    }

    return (
      <Fragment>
        <section>
          <WarningBox>{BalanceMessage}</WarningBox>
        </section>
        <section>
          <Button
            variant="contained"
            color="success"
            onClick={() => goToWalletManualWithdraw(Amounts.stringify(amount))}
          >
            <i18n.Translate>Get digital cash</i18n.Translate>
          </Button>
        </section>
        <PayWithMobile uri={uri} />
      </Fragment>
    );
  }
  if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
    return (
      <Fragment>
        <section>
          {payStatus.paid && payStatus.contractTerms.fulfillment_message && (
            <Part
              title={i18n.str`Merchant message`}
              text={
                payStatus.contractTerms.fulfillment_message as TranslatedString
              }
              kind="neutral"
            />
          )}
        </section>
      </Fragment>
    );
  }

  assertUnreachable(payStatus);
}

function PayWithMobile({ uri }: { uri: string }): VNode {
  const { i18n } = useTranslationContext();
  const api = useBackendContext();

  const payUri = parsePayUri(uri);

  const [showQR, setShowQR] = useState<string | undefined>(undefined);
  async function sharePrivatePaymentURI() {
    if (!payUri) {
      return;
    }
    if (!showQR) {
      const result = await api.wallet.call(WalletApiOperation.SharePayment, {
        merchantBaseUrl: payUri.merchantBaseUrl,
        orderId: payUri.orderId,
      });
      setShowQR(result.privatePayUri);
    } else {
      setShowQR(undefined);
    }
  }
  if (!payUri) {
    return <Fragment />
  }
  return (
    <section>
      <LinkSuccess upperCased onClick={sharePrivatePaymentURI}>
        {!showQR ? i18n.str`Pay with a mobile phone` : i18n.str`Hide QR`}
      </LinkSuccess>
      {showQR && (
        <div>
          <QR text={showQR} />
          <i18n.Translate>
            Scan the QR code or &nbsp;
            <a href={showQR}>
              <i18n.Translate>click here</i18n.Translate>
            </a>
          </i18n.Translate>
        </div>
      )}
    </section>
  );
}

type NoEnoughBalanceReason =
  | "available"
  | "material"
  | "age-acceptable"
  | "merchant-acceptable"
  | "merchant-depositable"
  | "fee-gap";

function getReason(
  info: PaymentInsufficientBalanceDetails,
): NoEnoughBalanceReason {
  if (Amounts.cmp(info.amountRequested, info.balanceAvailable) > 0) {
    return "available";
  }
  if (Amounts.cmp(info.amountRequested, info.balanceMaterial) > 0) {
    return "material";
  }
  if (Amounts.cmp(info.amountRequested, info.balanceAgeAcceptable) > 0) {
    return "age-acceptable";
  }
  if (Amounts.cmp(info.amountRequested, info.balanceReceiverAcceptable) > 0) {
    return "merchant-acceptable";
  }
  if (Amounts.cmp(info.amountRequested, info.balanceReceiverDepositable) > 0) {
    return "merchant-depositable";
  }
  return "fee-gap";
}
