/*
 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 {
  AbsoluteTime,
  AmountJson,
  Amounts,
  HttpStatusCode,
  PaytoUri,
  PaytoUriIBAN,
  PaytoUriTalerBank,
  TalerErrorCode,
  TranslatedString,
  WithdrawUriResult,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import {
  Attention,
  LocalNotificationBanner,
  notifyInfo,
  useLocalNotification,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { ComponentChildren, Fragment, VNode, h } from "preact";
import { mutate } from "swr";
import { useBankCoreApiContext } from "@gnu-taler/web-util/browser";
import { useBankState } from "../hooks/bank-state.js";
import { usePreferences } from "../hooks/preferences.js";
import { useSessionState } from "../hooks/session.js";
import { RouteDefinition } from "@gnu-taler/web-util/browser";
import { LoginForm } from "./LoginForm.js";
import { RenderAmount } from "./PaytoWireTransferForm.js";

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

  const [notification, notify, handleError] = useLocalNotification();

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

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

  async function doTransfer() {
    await handleError(async () => {
      if (!creds) return;
      const resp = await api.confirmWithdrawalById(
        creds,
        withdrawUri.withdrawalOperationId,
      );
      if (resp.type === "ok") {
        mutate(() => true); // clean any info that we have
        if (!settings.showWithdrawalSuccess) {
          notifyInfo(i18n.str`Wire transfer completed!`);
        }
      } else {
        switch (resp.case) {
          case TalerErrorCode.BANK_CONFIRM_ABORT_CONFLICT:
            return notify({
              type: "error",
              title: i18n.str`The withdrawal has been aborted previously and can't be confirmed`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE:
            return notify({
              type: "error",
              title: i18n.str`The withdrawal operation can't be confirmed before a wallet accepted the transaction.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case HttpStatusCode.BadRequest:
            return notify({
              type: "error",
              title: i18n.str`The operation id is invalid.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case HttpStatusCode.NotFound:
            return notify({
              type: "error",
              title: i18n.str`The operation was not found.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
            return notify({
              type: "error",
              title: i18n.str`Your balance is not enough for the operation.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case HttpStatusCode.Accepted: {
            updateBankState("currentChallenge", {
              operation: "confirm-withdrawal",
              id: String(resp.body.challenge_id),
              location: routeHere.url({
                wopid: withdrawUri.withdrawalOperationId,
              }),
              sent: AbsoluteTime.never(),
              request: withdrawUri.withdrawalOperationId,
            });
            return onAuthorizationRequired();
          }
          default:
            assertUnreachable(resp);
        }
      }
    });
  }

  async function doCancel() {
    await handleError(async () => {
      if (!creds) return;
      const resp = await api.abortWithdrawalById(
        creds,
        withdrawUri.withdrawalOperationId,
      );
      if (resp.type === "ok") {
        onAborted();
      } else {
        switch (resp.case) {
          case HttpStatusCode.Conflict:
            return notify({
              type: "error",
              title: i18n.str`The reserve operation has been confirmed previously and can't be aborted`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case HttpStatusCode.BadRequest:
            return notify({
              type: "error",
              title: i18n.str`The operation id is invalid.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case HttpStatusCode.NotFound:
            return notify({
              type: "error",
              title: i18n.str`The operation was not found.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          default: {
            assertUnreachable(resp);
          }
        }
      }
    });
  }

  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 => {
                            if (!details.account.isKnown) {
                              return (
                                <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 provider's account
                                    </i18n.Translate>
                                  </dt>
                                  <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">
                                    {details.account.targetPath}
                                  </dd>
                                </div>
                              );
                            }
                            switch (details.account.targetType) {
                              case "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 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 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 "x-taler-bank": {
                                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 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 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 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 "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 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 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 specified yet.
                                </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">
                    <button
                      type="button"
                      name="cancel"
                      class="text-sm font-semibold leading-6 text-gray-900"
                      onClick={doCancel}
                    >
                      <i18n.Translate>Cancel</i18n.Translate>
                    </button>
                    <button
                      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={(e) => {
                        e.preventDefault();
                        doTransfer();
                      }}
                    >
                      <i18n.Translate>Transfer</i18n.Translate>
                    </button>
                  </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 other username`}
        />
        <LoginForm currentUser={username} fixedUser />
      </Fragment>
    );
  }
  return <Fragment>{children}</Fragment>;
}
