/*
 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 {
  AbsoluteTime,
  Amounts,
  assertUnreachable,
  ChoiceSelectionDetailType,
  ConfirmPayResultType,
  GetChoicesForPaymentResult,
  NotificationType,
  PreparePayResultType
} from "@gnu-taler/taler-util";
import { WalletApiOperation } from "@gnu-taler/taler-wallet-core";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { useEffect, useState } from "preact/hooks";
import { alertFromError, useAlertContext } from "../../context/alert.js";
import { useBackendContext } from "../../context/backend.js";
import { useAsyncAsHook } from "../../hooks/useAsyncAsHook.js";
import { ButtonHandler } from "../../mui/handlers.js";
import { Props, State } from "./index.js";

export function useComponentState({
  talerPayUri,
  cancel,
  goToWalletManualWithdraw,
  onSuccess,
}: Props): State {
  const { pushAlertOnError } = useAlertContext();
  const api = useBackendContext();
  const { i18n } = useTranslationContext();
  const [selectedChoice, onSelectChoice] = useState<number>(0)

  const hook = useAsyncAsHook(async () => {
    if (!talerPayUri) throw Error("ERROR_NO-URI-FOR-PAYMENT");
    const payStatus = await api.wallet.call(
      WalletApiOperation.PreparePayForUri,
      {
        talerPayUri: talerPayUri,
      },
    );

    let choicesForPayment: GetChoicesForPaymentResult | undefined;
    if (payStatus.status === PreparePayResultType.ChoiceSelection) {

      choicesForPayment = await api.wallet.call(WalletApiOperation.GetChoicesForPayment, {
        transactionId: payStatus.transactionId
      });

    }
    return { payStatus, uri: talerPayUri, choicesForPayment };
  }, []);

  useEffect(
    () =>
      api.listener.onUpdateNotification(
        [NotificationType.TransactionStateTransition],
        hook?.retry,
      ),
    [hook],
  );

  const hookResponse = !hook || hook.hasError ? undefined : hook.response;

  useEffect(() => {
    if (!hookResponse) return;
    const { payStatus } = hookResponse;
    if (
      payStatus &&
      payStatus.status === PreparePayResultType.AlreadyConfirmed &&
      payStatus.paid
    ) {
      const fu = payStatus.contractTerms.fulfillment_url;
      if (fu) {
        setTimeout(() => {
          document.location.href = fu;
        }, 3000);
      }
    }
  }, [hookResponse]);

  if (!hook) return { status: "loading", error: undefined };
  if (hook.hasError) {
    return {
      status: "error",
      error: alertFromError(
        i18n,
        i18n.str`Could not load the payment and balance status`,
        hook,
      ),
    };
  }

  const { payStatus, choicesForPayment } = hook.response;

  async function doPayment(): Promise<void> {
    const res = await api.wallet.call(WalletApiOperation.ConfirmPay, {
      transactionId: payStatus.transactionId,
      choiceIndex: selectedChoice
    });
    // handle confirm pay
    if (res.type !== ConfirmPayResultType.Done) {
      onSuccess(res.transactionId);
      return;
    }
    const fu = res.contractTerms.fulfillment_url;
    if (fu) {
      if (typeof window !== "undefined") {
        document.location.href = fu;
      }
    }
    onSuccess(res.transactionId);
  }

  const payHandler: ButtonHandler = {
    onClick: pushAlertOnError(doPayment),
  };

  const expiration: AbsoluteTime =
    AbsoluteTime.fromProtocolTimestamp(payStatus.contractTerms.pay_deadline);

  const baseResult = {
    shareUri: hook.response.uri,
    transactionId: payStatus.transactionId,
    receiver: payStatus.contractTerms.merchant,    
    summary: payStatus.contractTerms.summary,
    merchant: payStatus.contractTerms.merchant,
    error: undefined,
    expiration,
    minimum_age: payStatus.contractTerms.minimum_age,
    cancel,
    goToWalletManualWithdraw,
  };

  if (payStatus.status === PreparePayResultType.ChoiceSelection) {
    // if status is choiceSelection we expect choicesForPayment to be present
    const { choices: choicesDetails } = choicesForPayment!;
    const selectedChoiceData = choicesDetails[selectedChoice];
    const amount = Amounts.parseOrThrow(selectedChoiceData.amountRaw);
    
    const choices = {
            list: payStatus.contractTerms.version === 1 ? payStatus.contractTerms.choices : [],
            index: selectedChoice,
            select: onSelectChoice,
          }
    switch (selectedChoiceData.status) {
      case ChoiceSelectionDetailType.PaymentPossible: {
        const effective = Amounts.parseOrThrow(selectedChoiceData.amountEffective)
        return {
          status: "ready",
          payHandler,
          choices,
          effective,
          amount,
          ...baseResult,
        };
      }
      case ChoiceSelectionDetailType.InsufficientBalance: {
        return {
          status: "no-enough-balance",
          amount,
          choices,
          effective: undefined,
          balanceDetails: selectedChoiceData.balanceDetails!,
          ...baseResult,
        };
      }
      default: {
        assertUnreachable(selectedChoiceData)
      }
    }

  }

  const amount = Amounts.parseOrThrow(payStatus.amountRaw);
  const effective =
    "amountEffective" in payStatus
      ? payStatus.amountEffective
        ? Amounts.parseOrThrow(payStatus.amountEffective)
        : Amounts.zeroOfCurrency(amount.currency)
      : amount;


  if (payStatus.status === PreparePayResultType.InsufficientBalance) {
    return {
      status: "no-enough-balance",
      amount,
      choices: undefined,
      effective: undefined,
      balanceDetails: payStatus.balanceDetails,
      ...baseResult,
    };
  }

  if (payStatus.status === PreparePayResultType.AlreadyConfirmed) {
    return {
      status: "confirmed",
      amount,
      effective: undefined,
      paid: payStatus.paid,
      message: payStatus.paid && payStatus.contractTerms.fulfillment_message ? payStatus.contractTerms.fulfillment_message : undefined,
      ...baseResult,
    };
  }

  return {
    status: "ready",
    payHandler,
    amount,
    effective,
    ...baseResult,
    choices: undefined,
  };
}
