/*
 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,
  PaytoType,
  Paytos,
  TalerErrorCode,
  WithdrawUriResult,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import {
  Attention,
  ButtonBetter,
  LocalNotificationBanner,
  useBankCoreApiContext,
  useChallengeHandler,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, VNode, h } from "preact";
import { mutate } from "swr";
import { LoggedIn, useSessionState } from "../hooks/session.js";
import { LoginForm } from "./LoginForm.js";
import { RenderAmount } from "./PaytoWireTransferForm.js";
import { SolveMFAChallenges } from "./SolveMFA.js";

const TALER_SCREEN_ID = 114;

interface Props {
  withdrawUri: WithdrawUriResult;
  details: {
    account: Paytos.URI;
    reserve: string;
    username: string;
    amount?: AmountJson;
  };
}

function useComponentState(opid: string) {
  const { state: credentials } = useSessionState();
  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
  const [notification, safeFunctionHandler] = 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);

  const confirm = safeFunctionHandler(
    (creds: LoggedIn, challengeIds: string[]) =>
      api.confirmWithdrawalById(creds, {}, opid, {
        challengeIds,
      }),
    !creds ? undefined : [creds, []],
  );

  confirm.onSuccess = () => {
    mutate(() => true); // clean any info that we have
  };

  const repeat = confirm.lambda((ids: string[]) => {
    return [confirm.args![0], ids];
  });

  const abort = safeFunctionHandler(
    api.abortWithdrawalById.bind(api),
    !creds ? undefined : [creds, opid],
  );

  const spec = config.currency_specification;

  return {
    notification,
    mfa,
    wireFee,
    spec,
    abort,
    confirm,
    repeat,
  };
}

/**
 * Additional authentication required to complete the operation.
 * Not providing a back button, only abort.
 */
export function WithdrawalConfirmationQuestion({
  details,
  withdrawUri,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  const { notification, mfa, wireFee, spec, abort, confirm, repeat } =
    useComponentState(withdrawUri.withdrawalOperationId);

  confirm.onFail = (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: {
        mfa.onChallengeRequired(fail.body);
        return i18n.str`A second factor authentication is required.`;
      }
    }
  };

  abort.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.BadRequest:
        return i18n.str`Bad request`;
      case HttpStatusCode.NotFound:
        return i18n.str`The withdrawal operation has been aborted.`;
      case HttpStatusCode.Conflict:
        return i18n.str`The withdrawal operation has been confirmed previously and can’t be aborted.`;
    }
  };

  if (mfa.pendingChallenge) {
    return (
      <SolveMFAChallenges
        currentChallenge={mfa.pendingChallenge}
        description={i18n.str`Complete withdrawal.`}
        onCancel={mfa.doCancelChallenge}
        onCompleted={repeat}
        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-4 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={spec}
                                />
                              ) : (
                                <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={spec}
                                  />
                                </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={abort}
                    >
                      <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={confirm}
                    >
                      <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`} ></Attention>
        <LoginForm currentUser={username} fixedUser />
      </Fragment>
    );
  }
  if (credentials.status === "expired") {
    return (
        <LoginForm currentUser={username} fixedUser />
    );
  }
  if (credentials.username !== username) {
    return (
      <Fragment>
        <Attention
          type="warning"
          title={i18n.str`This operation was created with another username`}
        >
          <p>
            <i18n.Translate>
              You are currently logged in with user "{credentials.username}" and
              the operation was made with user "{username}"
            </i18n.Translate>
          </p>
        </Attention>
        <LoginForm currentUser={username} fixedUser />
      </Fragment>
    );
  }
  return <Fragment>{children}</Fragment>;
}
