/*
 This file is part of GNU Taler
 (C) 2022-2024 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,
  HttpStatusCode,
  PaytoUri,
  TalerErrorCode,
  WithdrawUriResult,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import {
  Attention,
  ButtonBetter,
  LocalNotificationBanner,
  makeSafeCall,
  notifyInfo,
  MfaHandler,
  safeFunctionCall,
  useBankCoreApiContext,
  useChallengeHandler,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, VNode, h } from "preact";
import { mutate } from "swr";
import { usePreferences } from "../hooks/preferences.js";
import { LoggedIn, useSessionState } from "../hooks/session.js";
import { LoginForm } from "./LoginForm.js";
import { RenderAmount } from "./PaytoWireTransferForm.js";
import { SolveMFAChallenges } from "./SolveMFA.js";
import { Paytos } from "@gnu-taler/taler-util";
import { PaytoType } from "@gnu-taler/taler-util";

const TALER_SCREEN_ID = 114;

interface Props {
  onAborted: () => void;
  withdrawUri: WithdrawUriResult;
  details: {
    account: Paytos.URI;
    reserve: string;
    username: string;
    amount?: AmountJson;
  };
}
/**
 * Additional authentication required to complete the operation.
 * Not providing a back button, only abort.
 */
export function WithdrawalConfirmationQuestion({
  onAborted,
  details,
  withdrawUri,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  const [settings] = usePreferences();
  const { state: credentials } = useSessionState();
  const creds = credentials.status !== "loggedIn" ? undefined : credentials;

  const [notification, notifyOnError] = useLocalNotificationBetter();
  const mfa = useChallengeHandler();

  const {
    config,
    lib: { bank: api },
  } = useBankCoreApiContext();

  const wireFee =
    config.wire_transfer_fees === undefined
      ? Amounts.zeroOfCurrency(config.currency)
      : Amounts.parseOrThrow(config.wire_transfer_fees);

      /**
       * Continue here:
       * 
       * Make the useNotification hook take "handler|undefine"
       * to cover the most common use case.
       * 
       * Usenotification hook also should take the new SafeHandler interface
       * 
       * safeFunctionCall should also include the default error handler that shows
       * the generic erros (talererror and http errors)
       */
  // const fn = safeFunctionCall(
  //   (mfa: MfaHandler, creds: LoggedIn, opId: string) =>
  //     api.confirmWithdrawalById(creds, {}, opId, mfa),
  // );

  // fn.onSuccess = () => {
  //   mutate(() => true); // clean any info that we have
  //   if (!settings.showWithdrawalSuccess) {
  //     notifyInfo(i18n.str`Wire transfer completed!`);
  //   }
  // };

  // fn.onFail = (fail, mfa, a, b) => {
  //   switch (fail.case) {
  //     case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT:
  //       return i18n.str`The withdrawal has been aborted previously and can't be confirmed`;
  //     case TalerErrorCode.BANK_CONFIRM_INCOMPLETE:
  //       return i18n.str`The withdrawal operation can't be confirmed before a wallet accepted the transaction.`;
  //     case HttpStatusCode.BadRequest:
  //       return i18n.str`The operation ID is invalid.`;
  //     case HttpStatusCode.NotFound:
  //       return i18n.str`The operation was not found.`;
  //     case TalerErrorCode.BANK_UNALLOWED_DEBIT:
  //       return i18n.str`Your balance is not sufficient for the operation.`;
  //     case TalerErrorCode.BANK_AMOUNT_DIFFERS:
  //       return i18n.str`The starting withdrawal amount and the confirmation amount differs.`;
  //     case TalerErrorCode.BANK_AMOUNT_REQUIRED:
  //       return i18n.str`The bank requires a bank account which has not been specified yet.`;
  //     case HttpStatusCode.Accepted: {
  //       mfa.onChallengeRequired(fail.body);
  //       return i18n.str`A second factor authentication is required.`;
  //     }
  //   }
  // };

  //   const [doConfirm2, repeatConfirm2] = mfa.addMfaHandler( fn )

  const [doConfirm, repeatConfirm] = mfa.withMfaHandler(
    ({ challengeIds, onChallengeRequired }) =>
      makeSafeCall(
        i18n,
        (creds: LoggedIn, opId: string) =>
          api.confirmWithdrawalById(creds, {}, opId, { challengeIds }),
        (success) => {
          mutate(() => true); // clean any info that we have
          if (!settings.showWithdrawalSuccess) {
            notifyInfo(i18n.str`Wire transfer completed!`);
          }
        },
        (fail) => {
          switch (fail.case) {
            case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT:
              return i18n.str`The withdrawal has been aborted previously and can't be confirmed`;
            case TalerErrorCode.BANK_CONFIRM_INCOMPLETE:
              return i18n.str`The withdrawal operation can't be confirmed before a wallet accepted the transaction.`;
            case HttpStatusCode.BadRequest:
              return i18n.str`The operation ID is invalid.`;
            case HttpStatusCode.NotFound:
              return i18n.str`The operation was not found.`;
            case TalerErrorCode.BANK_UNALLOWED_DEBIT:
              return i18n.str`Your balance is not sufficient for the operation.`;
            case TalerErrorCode.BANK_AMOUNT_DIFFERS:
              return i18n.str`The starting withdrawal amount and the confirmation amount differs.`;
            case TalerErrorCode.BANK_AMOUNT_REQUIRED:
              return i18n.str`The bank requires a bank account which has not been specified yet.`;
            case HttpStatusCode.Accepted: {
              onChallengeRequired(fail.body);
              return i18n.str`A second factor authentication is required.`;
            }
          }
        },
      ),
  );

  const confirmHandler = !creds
    ? undefined
    : () => notifyOnError(doConfirm)(creds, withdrawUri.withdrawalOperationId);

  const abortHandler = !creds
    ? undefined
    : notifyOnError(() =>
        makeSafeCall(
          i18n,
          (creds, opId) => api.abortWithdrawalById(creds, opId),
          (success) => {
            onAborted();
          },
          (fail) => {
            switch (fail.case) {
              case HttpStatusCode.Conflict:
                return i18n.str`The reserve operation has been confirmed previously and can't be aborted`;
              case HttpStatusCode.BadRequest:
                return i18n.str`The operation ID is invalid.`;
              case HttpStatusCode.NotFound:
                return i18n.str`The operation was not found.`;
              default: {
                assertUnreachable(fail);
              }
            }
          },
        )(creds, withdrawUri.withdrawalOperationId),
      );

  if (mfa.pendingChallenge && repeatConfirm) {
    return (
      <SolveMFAChallenges
        currentChallenge={mfa.pendingChallenge}
        description={i18n.str`Complete withdrawal.`}
        onCancel={mfa.doCancelChallenge}
        onCompleted={repeatConfirm}
        username={details.username}
      />
    );
  }

  return (
    <Fragment>
      <LocalNotificationBanner notification={notification} />

      <div class="bg-white shadow sm:rounded-lg">
        <div class="px-4 py-5 sm:p-6">
          <h3 class="text-base font-semibold text-gray-900">
            <i18n.Translate>Confirm the withdrawal operation</i18n.Translate>
          </h3>
          <div class="mt-3 text-sm leading-6">
            <ShouldBeSameUser username={details.username}>
              <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 md:grid-cols-2 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
                <form
                  class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
                  autoCapitalize="none"
                  autoCorrect="off"
                  onSubmit={(e) => {
                    e.preventDefault();
                  }}
                >
                  <div class="px-4 mt-4">
                    <div class="w-full">
                      <div class="px-4 sm:px-0 text-sm">
                        <p>
                          <i18n.Translate>Wire transfer details</i18n.Translate>
                        </p>
                      </div>
                      <div class="mt-6 border-t border-gray-100">
                        <dl class="divide-y divide-gray-100">
                          {((): VNode => {
                            switch (details.account.targetType) {
                              case undefined:
                              case PaytoType.TalerReserveHttp:
                              case PaytoType.TalerReserve: {
                                // FIXME: support wire transfer to wallet
                                return <div>not yet supported</div>;
                              }
                              case PaytoType.IBAN: {
                                const name =
                                  details.account.params["receiver-name"];
                                return (
                                  <Fragment>
                                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                      <dt class="text-sm font-medium leading-6 text-gray-900">
                                        <i18n.Translate>
                                          Payment Service Provider's account
                                          number
                                        </i18n.Translate>
                                      </dt>
                                      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                        {details.account.iban}
                                      </dd>
                                    </div>
                                    {name && (
                                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                        <dt class="text-sm font-medium leading-6 text-gray-900">
                                          <i18n.Translate>
                                            Payment Service Provider's name
                                          </i18n.Translate>
                                        </dt>
                                        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                          {name}
                                        </dd>
                                      </div>
                                    )}
                                  </Fragment>
                                );
                              }
                              case PaytoType.TalerBank: {
                                const name =
                                  details.account.params["receiver-name"];
                                return (
                                  <Fragment>
                                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                      <dt class="text-sm font-medium leading-6 text-gray-900">
                                        <i18n.Translate>
                                          Payment Service Provider's account
                                          bank hostname
                                        </i18n.Translate>
                                      </dt>
                                      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                        {details.account.host}
                                      </dd>
                                    </div>
                                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                      <dt class="text-sm font-medium leading-6 text-gray-900">
                                        <i18n.Translate>
                                          Payment Service Provider's account id
                                        </i18n.Translate>
                                      </dt>
                                      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                        {details.account.account}
                                      </dd>
                                    </div>
                                    {name && (
                                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                        <dt class="text-sm font-medium leading-6 text-gray-900">
                                          <i18n.Translate>
                                            Payment Service Provider's name
                                          </i18n.Translate>
                                        </dt>
                                        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                          {name}
                                        </dd>
                                      </div>
                                    )}
                                  </Fragment>
                                );
                              }
                              case PaytoType.Bitcoin: {
                                const name =
                                  details.account.params["receiver-name"];
                                return (
                                  <Fragment>
                                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                      <dt class="text-sm font-medium leading-6 text-gray-900">
                                        <i18n.Translate>
                                          Payment Service Provider's account
                                          address
                                        </i18n.Translate>
                                      </dt>
                                      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                        {details.account.address}
                                      </dd>
                                    </div>
                                    {name && (
                                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                        <dt class="text-sm font-medium leading-6 text-gray-900">
                                          <i18n.Translate>
                                            Payment Service Provider's name
                                          </i18n.Translate>
                                        </dt>
                                        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                          {name}
                                        </dd>
                                      </div>
                                    )}
                                  </Fragment>
                                );
                              }
                              case PaytoType.Ethereum: {
                                const name =
                                  details.account.params["receiver-name"];
                                return (
                                  <Fragment>
                                    <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                      <dt class="text-sm font-medium leading-6 text-gray-900">
                                        <i18n.Translate>
                                          Payment Service Provider's account
                                          address
                                        </i18n.Translate>
                                      </dt>
                                      <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                        {details.account.address}
                                      </dd>
                                    </div>
                                    {name && (
                                      <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                        <dt class="text-sm font-medium leading-6 text-gray-900">
                                          <i18n.Translate>
                                            Payment Service Provider's name
                                          </i18n.Translate>
                                        </dt>
                                        <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                          {name}
                                        </dd>
                                      </div>
                                    )}
                                  </Fragment>
                                );
                              }
                              default: {
                                assertUnreachable(details.account);
                              }
                            }
                          })()}
                          <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                            <dt class="text-sm font-medium leading-6 text-gray-900">
                              <i18n.Translate>Amount</i18n.Translate>
                            </dt>
                            <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                              {details.amount !== undefined ? (
                                <RenderAmount
                                  value={details.amount}
                                  spec={config.currency_specification}
                                />
                              ) : (
                                <i18n.Translate>
                                  No amount has yet been determined.
                                </i18n.Translate>
                              )}
                            </dd>
                          </div>
                          {Amounts.isZero(wireFee) ? undefined : (
                            <Fragment>
                              <div class="px-4 py-2 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0">
                                <dt class="text-sm font-medium leading-6 text-gray-900">
                                  <i18n.Translate>Cost</i18n.Translate>
                                </dt>
                                <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                  <RenderAmount
                                    value={wireFee}
                                    negative
                                    withColor
                                    spec={config.currency_specification}
                                  />
                                </dd>
                              </div>
                            </Fragment>
                          )}
                        </dl>
                      </div>
                    </div>
                  </div>

                  <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
                    <ButtonBetter
                      type="button"
                      name="cancel"
                      class="text-sm font-semibold leading-6 text-gray-900"
                      onClick={abortHandler}
                    >
                      <i18n.Translate>Cancel</i18n.Translate>
                    </ButtonBetter>
                    <ButtonBetter
                      type="submit"
                      name="transfer"
                      class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
                      onClick={confirmHandler}
                    >
                      <i18n.Translate>Transfer</i18n.Translate>
                    </ButtonBetter>
                  </div>
                </form>
              </div>
            </ShouldBeSameUser>
          </div>
        </div>
      </div>
    </Fragment>
  );
}

export function ShouldBeSameUser({
  username,
  children,
}: {
  username: string;
  children: ComponentChildren;
}): VNode {
  const { state: credentials } = useSessionState();
  const { i18n } = useTranslationContext();
  if (credentials.status === "loggedOut") {
    return (
      <Fragment>
        <Attention type="info" title={i18n.str`Authentication required`} />
        <LoginForm currentUser={username} fixedUser />
      </Fragment>
    );
  }
  if (credentials.username !== username) {
    return (
      <Fragment>
        <Attention
          type="warning"
          title={i18n.str`This operation was created with another username`}
        />
        <LoginForm currentUser={username} fixedUser />
      </Fragment>
    );
  }
  return <Fragment>{children}</Fragment>;
}
