/*
 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 
 */
/* eslint-disable react-hooks/rules-of-hooks */
import {
  AmountJson,
  Amounts,
  ExchangeFullDetails,
  ExchangeListItem,
  ExchangeTosStatus,
  TalerError,
  parseWithdrawExchangeUri,
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useEffect, useState } from "preact/hooks";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { useSelectedExchange } from "../../hooks/useSelectedExchange.js";
import { RecursiveState } from "../../utils/index.js";
import { PropsFromParams, PropsFromURI, State } from "./index.js";
export function useComponentStateFromParams({
  talerExchangeWithdrawUri: maybeTalerUri,
  amount,
  cancel,
  onAmountChanged,
  onSuccess,
}: PropsFromParams): RecursiveState {
  const api = useBackendContext();
  const { i18n } = useTranslationContext();
  const paramsAmount = amount ? Amounts.parse(amount) : undefined;
  const uriInfoHook = useAsyncAsHook(async () => {
    const exchanges = await api.wallet.call(
      WalletApiOperation.ListExchanges,
      {},
    );
    const uri = maybeTalerUri
      ? parseWithdrawExchangeUri(maybeTalerUri)
      : undefined;
    const exchangeByTalerUri = uri?.exchangeBaseUrl;
    let ex: ExchangeFullDetails | undefined;
    if (exchangeByTalerUri && uri.exchangePub) {
      await api.wallet.call(WalletApiOperation.AddExchange, {
        exchangeBaseUrl: exchangeByTalerUri,
        masterPub: uri.exchangePub,
      });
      const info = await api.wallet.call(
        WalletApiOperation.GetExchangeDetailedInfo,
        {
          exchangeBaseUrl: exchangeByTalerUri,
        },
      );
      ex = info.exchange;
    }
    const chosenAmount =
      !uri || !uri.amount ? undefined : Amounts.parse(uri.amount);
    return { amount: chosenAmount, exchanges, exchange: ex };
  });
  if (!uriInfoHook) return { status: "loading", error: undefined };
  if (uriInfoHook.hasError) {
    return {
      status: "error",
      error: alertFromError(
        i18n.str`Could not load the list of exchanges`,
        uriInfoHook,
      ),
    };
  }
  useEffect(() => {
    uriInfoHook?.retry();
  }, [amount]);
  const exchangeByTalerUri = uriInfoHook.response.exchange?.exchangeBaseUrl;
  const exchangeList = uriInfoHook.response.exchanges.exchanges;
  const maybeAmount = uriInfoHook.response.amount ?? paramsAmount;
  if (!maybeAmount) {
    const exchangeBaseUrl =
      uriInfoHook.response.exchange?.exchangeBaseUrl ??
      (exchangeList.length > 0 ? exchangeList[0].exchangeBaseUrl : undefined);
    const currency =
      uriInfoHook.response.exchange?.currency ??
      (exchangeList.length > 0 ? exchangeList[0].currency : undefined);
    if (!exchangeBaseUrl) {
      return {
        status: "error",
        error: {
          message: i18n.str`Can't withdraw from exchange`,
          description: i18n.str`Missing base URL`,
          cause: undefined,
          context: {},
          type: "error",
        },
      };
    }
    if (!currency) {
      return {
        status: "error",
        error: {
          message: i18n.str`Can't withdraw from exchange`,
          description: i18n.str`Missing unknown currency`,
          cause: undefined,
          context: {},
          type: "error",
        },
      };
    }
    return () => {
      const { pushAlertOnError } = useAlertContext();
      const [amount, setAmount] = useState(
        Amounts.zeroOfCurrency(currency),
      );
      const isValid = Amounts.isNonZero(amount);
      return {
        status: "select-amount",
        currency,
        exchangeBaseUrl,
        error: undefined,
        confirm: {
          onClick: isValid
            ? pushAlertOnError(async () => {
              onAmountChanged(Amounts.stringify(amount));
            })
            : undefined,
        },
        amount: {
          value: amount,
          onInput: pushAlertOnError(async (e) => {
            setAmount(e);
          }),
        },
      };
    };
  }
  const chosenAmount = maybeAmount;
  async function doManualWithdraw(
    exchange: string,
    ageRestricted: number | undefined,
  ): Promise<{
    transactionId: string;
    confirmTransferUrl: string | undefined;
  }> {
    const res = await api.wallet.call(
      WalletApiOperation.AcceptManualWithdrawal,
      {
        exchangeBaseUrl: exchange,
        amount: Amounts.stringify(chosenAmount),
        restrictAge: ageRestricted,
      },
    );
    return {
      confirmTransferUrl: undefined,
      transactionId: res.transactionId,
    };
  }
  return () =>
    exchangeSelectionState(
      uriInfoHook.retry,
      doManualWithdraw,
      cancel,
      onSuccess,
      undefined,
      chosenAmount,
      exchangeList,
      exchangeByTalerUri,
    );
}
export function useComponentStateFromURI({
  talerWithdrawUri: maybeTalerUri,
  cancel,
  onSuccess,
}: PropsFromURI): RecursiveState {
  const api = useBackendContext();
  const { i18n } = useTranslationContext();
  /**
   * Ask the wallet about the withdraw URI
   */
  const uriInfoHook = useAsyncAsHook(async () => {
    if (!maybeTalerUri) throw Error("ERROR_NO-URI-FOR-WITHDRAWAL");
    const talerWithdrawUri = maybeTalerUri.startsWith("ext+")
      ? maybeTalerUri.substring(4)
      : maybeTalerUri;
    const uriInfo = await api.wallet.call(
      WalletApiOperation.GetWithdrawalDetailsForUri,
      {
        talerWithdrawUri,
      },
    );
    const { amount, defaultExchangeBaseUrl, possibleExchanges } = uriInfo;
    return {
      talerWithdrawUri,
      amount: Amounts.parseOrThrow(amount),
      thisExchange: defaultExchangeBaseUrl,
      exchanges: possibleExchanges,
    };
  });
  if (!uriInfoHook) return { status: "loading", error: undefined };
  if (uriInfoHook.hasError) {
    return {
      status: "error",
      error: alertFromError(
        i18n.str`Could not load info from URI`,
        uriInfoHook,
      ),
    };
  }
  const uri = uriInfoHook.response.talerWithdrawUri;
  const chosenAmount = uriInfoHook.response.amount;
  const defaultExchange = uriInfoHook.response.thisExchange;
  const exchangeList = uriInfoHook.response.exchanges;
  async function doManagedWithdraw(
    exchange: string,
    ageRestricted: number | undefined,
  ): Promise<{
    transactionId: string;
    confirmTransferUrl: string | undefined;
  }> {
    const res = await api.wallet.call(
      WalletApiOperation.AcceptBankIntegratedWithdrawal,
      {
        exchangeBaseUrl: exchange,
        talerWithdrawUri: uri,
        restrictAge: ageRestricted,
      },
    );
    return {
      confirmTransferUrl: res.confirmTransferUrl,
      transactionId: res.transactionId,
    };
  }
  return () =>
    exchangeSelectionState(
      uriInfoHook.retry,
      doManagedWithdraw,
      cancel,
      onSuccess,
      uri,
      chosenAmount,
      exchangeList,
      defaultExchange,
    );
}
type ManualOrManagedWithdrawFunction = (
  exchange: string,
  ageRestricted: number | undefined,
) => Promise<{ transactionId: string; confirmTransferUrl: string | undefined }>;
function exchangeSelectionState(
  onTosUpdate: () => void,
  doWithdraw: ManualOrManagedWithdrawFunction,
  cancel: () => Promise,
  onSuccess: (txid: string) => Promise,
  talerWithdrawUri: string | undefined,
  chosenAmount: AmountJson,
  exchangeList: ExchangeListItem[],
  exchangeSuggestedByTheBank: string | undefined,
): RecursiveState {
  const api = useBackendContext();
  const selectedExchange = useSelectedExchange({
    currency: chosenAmount.currency,
    defaultExchange: exchangeSuggestedByTheBank,
    list: exchangeList,
  });
  if (selectedExchange.status !== "ready") {
    return selectedExchange;
  }
  return () => {
    const { i18n } = useTranslationContext();
    const { pushAlertOnError } = useAlertContext();
    const [ageRestricted, setAgeRestricted] = useState(0);
    const currentExchange = selectedExchange.selected;
    const tosNeedToBeAccepted =
      currentExchange.tosStatus == ExchangeTosStatus.Pending ||
      currentExchange.tosStatus == ExchangeTosStatus.Proposed;
    /**
     * With the exchange and amount, ask the wallet the information
     * about the withdrawal
     */
    const amountHook = useAsyncAsHook(async () => {
      const info = await api.wallet.call(
        WalletApiOperation.GetWithdrawalDetailsForAmount,
        {
          exchangeBaseUrl: currentExchange.exchangeBaseUrl,
          amount: Amounts.stringify(chosenAmount),
          restrictAge: ageRestricted,
        },
      );
      const withdrawAmount = {
        raw: Amounts.parseOrThrow(info.amountRaw),
        effective: Amounts.parseOrThrow(info.amountEffective),
      };
      return {
        amount: withdrawAmount,
        ageRestrictionOptions: info.ageRestrictionOptions,
      };
    }, []);
    const [withdrawError, setWithdrawError] = useState(
      undefined,
    );
    const [doingWithdraw, setDoingWithdraw] = useState(false);
    async function doWithdrawAndCheckError(): Promise {
      try {
        setDoingWithdraw(true);
        const res = await doWithdraw(
          currentExchange.exchangeBaseUrl,
          !ageRestricted ? undefined : ageRestricted,
        );
        if (res.confirmTransferUrl) {
          document.location.href = res.confirmTransferUrl;
        } else {
          onSuccess(res.transactionId);
        }
      } catch (e) {
        if (e instanceof TalerError) {
          setWithdrawError(e);
        }
      }
      setDoingWithdraw(false);
    }
    if (!amountHook) {
      return { status: "loading", error: undefined };
    }
    if (amountHook.hasError) {
      return {
        status: "error",
        error: alertFromError(
          i18n.str`Could not load the withdrawal details`,
          amountHook,
        ),
      };
    }
    if (!amountHook.response) {
      return { status: "loading", error: undefined };
    }
    const withdrawalFee = Amounts.sub(
      amountHook.response.amount.raw,
      amountHook.response.amount.effective,
    ).amount;
    const toBeReceived = amountHook.response.amount.effective;
    const ageRestrictionOptions =
      amountHook.response.ageRestrictionOptions?.reduce(
        (p, c) => ({ ...p, [c]: `under ${c}` }),
        {} as Record,
      );
    const ageRestrictionEnabled = ageRestrictionOptions !== undefined;
    if (ageRestrictionEnabled) {
      ageRestrictionOptions["0"] = "Not restricted";
    }
    //TODO: calculate based on exchange info
    const ageRestriction = ageRestrictionEnabled
      ? {
        list: ageRestrictionOptions,
        value: String(ageRestricted),
        onChange: pushAlertOnError(async (v: string) =>
          setAgeRestricted(parseInt(v, 10)),
        ),
      }
      : undefined;
    return {
      status: "success",
      error: undefined,
      doSelectExchange: selectedExchange.doSelect,
      currentExchange,
      toBeReceived,
      withdrawalFee,
      chosenAmount,
      talerWithdrawUri,
      ageRestriction,
      doWithdrawal: {
        onClick:
          doingWithdraw || tosNeedToBeAccepted
            ? undefined
            : pushAlertOnError(doWithdrawAndCheckError),
        error: withdrawError,
      },
      onTosUpdate,
      cancel,
    };
  };
}