/*
 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 {
  LocalNotificationBanner,
  makeSafeCall,
  urlPattern,
  useBankCoreApiContext,
  useChallengeHandler,
  useCurrentLocation,
  useLocalNotificationBetter,
  useNavigationContext,
  useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";

import {
  AbsoluteTime,
  AccessToken,
  HttpStatusCode,
  TalerErrorCode,
  TokenRequest,
  assertUnreachable,
  createRFC8959AccessTokenEncoded
} from "@gnu-taler/taler-util";
import { useEffect } from "preact/hooks";
import {
  useRefreshSessionBeforeExpires,
  useSessionState,
} from "./hooks/session.js";
import { AccountPage } from "./pages/AccountPage/index.js";
import { BankFrame } from "./pages/BankFrame.js";
import { ConversionRateClassDetails } from "./pages/ConversionRateClassDetails.js";
import { LoginForm, SESSION_DURATION } from "./pages/LoginForm.js";
import { NewConversionRateClass } from "./pages/NewConversionRateClass.js";
import { PublicHistoriesPage } from "./pages/PublicHistoriesPage.js";
import { RegistrationPage } from "./pages/RegistrationPage.js";
import { ShowNotifications } from "./pages/ShowNotifications.js";
import { SolveMFAChallenges } from "./pages/SolveMFA.js";
import { WireTransfer } from "./pages/WireTransfer.js";
import { WithdrawalOperationPage } from "./pages/WithdrawalOperationPage.js";
import { CashoutListForAccount } from "./pages/account/CashoutListForAccount.js";
import { ShowAccountDetails } from "./pages/account/ShowAccountDetails.js";
import { UpdateAccountPassword } from "./pages/account/UpdateAccountPassword.js";
import { AdminHome } from "./pages/admin/AdminHome.js";
import { CreateNewAccount } from "./pages/admin/CreateNewAccount.js";
import { DownloadStats } from "./pages/admin/DownloadStats.js";
import { RemoveAccount } from "./pages/admin/RemoveAccount.js";
import { ConversionConfig } from "./pages/regional/ConversionConfig.js";
import { CreateCashout } from "./pages/regional/CreateCashout.js";
import { ShowCashoutDetails } from "./pages/regional/ShowCashoutDetails.js";

const TALER_SCREEN_ID = 100;

Routing.SCREEN_ID = TALER_SCREEN_ID;
export function Routing(): VNode {
  const session = useSessionState();

  useRefreshSessionBeforeExpires();

  if (session.state.status === "loggedIn") {
    const { isUserAdministrator, username } = session.state;
    return (
      <BankFrame
        account={username}
        routeNotifications={privatePages.notifications}
        routeAccountDetails={privatePages.myAccountDetails}
      >
        <PrivateRouting username={username} isAdmin={isUserAdministrator} />
      </BankFrame>
    );
  }
  return (
    <BankFrame routeNotifications={privatePages.notifications}>
      <PublicRounting
        onLoggedUser={(username, token, expiration) => {
          session.logIn({ username, token, expiration });
        }}
      />
    </BankFrame>
  );
}

const publicPages = {
  login: urlPattern(/\/login/, () => "#/login"),
  register: urlPattern(/\/register/, () => "#/register"),
  publicAccounts: urlPattern(/\/public-accounts/, () => "#/public-accounts"),
  operationDetails: urlPattern<{ wopid: string }>(
    /\/operation\/(?<wopid>[a-zA-Z0-9-]+)/,
    ({ wopid }) => `#/operation/${wopid}`,
  ),
};

function PublicRounting({
  onLoggedUser,
}: {
  onLoggedUser: (
    username: string,
    token: AccessToken,
    expiration: AbsoluteTime,
  ) => void;
}): VNode {
  const { i18n } = useTranslationContext();
  const location = useCurrentLocation(publicPages);
  const { navigateTo } = useNavigationContext();

  const { config, lib } = useBankCoreApiContext();
  const [notification, notifyOnError] = useLocalNotificationBetter();
  const mfa = useChallengeHandler();

  useEffect(() => {
    if (location === undefined) {
      navigateTo(publicPages.login.url({}));
    }
  }, [location]);

  const tokenRequest = {
    scope: "readwrite",
    duration: SESSION_DURATION,
    refreshable: true,
  } as TokenRequest;

  const [doAutomaticLogin, repeatLogin, lastCallingArgs] = mfa.withMfaHandler(
    ({ challengeIds, onChallengeRequired }) =>
      makeSafeCall(
        i18n,
        (username: string, password: string) =>
          lib.bank.createAccessToken(
            username,
            { type: "basic", password },
            tokenRequest,
            { challengeIds },
          ),
        (success, username) => {
          onLoggedUser(
            username,
            createRFC8959AccessTokenEncoded(success.body.access_token),
            AbsoluteTime.fromProtocolTimestamp(success.body.expiration),
          );
        },
        (fail, username) => {
          switch (fail.case) {
            case HttpStatusCode.Accepted: {
              onChallengeRequired(fail.body);
              return i18n.str`A second factor authentication is required.`;
            }
            case HttpStatusCode.Unauthorized:
              return i18n.str`Wrong credentials for "${username}"`;
            case TalerErrorCode.GENERIC_FORBIDDEN:
              return i18n.str`You have no permission to this account.`;
            case TalerErrorCode.BANK_ACCOUNT_LOCKED:
              return i18n.str`This account is locked. If you have a active session you can change the password or contact the administrator.`;
            case HttpStatusCode.NotFound:
              return i18n.str`Account not found`;
          }
        },
      ),
  );

  if (mfa.pendingChallenge && repeatLogin) {
    return (
      <SolveMFAChallenges
        currentChallenge={mfa.pendingChallenge}
        description={i18n.str`New web session`}
        onCancel={mfa.doCancelChallenge}
        username={lastCallingArgs[0]}
        onCompleted={repeatLogin}
      />
    );
  }

  switch (location.name) {
    case undefined:
    case "login": {
      return (
        <Fragment>
          <div class="sm:mx-auto sm:w-full sm:max-w-sm">
            <h2 class="text-center text-2xl font-bold leading-9 tracking-tight text-gray-900">{i18n.str`Welcome to ${config.bank_name}!`}</h2>
          </div>
          <LoginForm routeRegister={publicPages.register} />
        </Fragment>
      );
    }
    case "publicAccounts": {
      return <PublicHistoriesPage />;
    }
    case "operationDetails": {
      return (
        <WithdrawalOperationPage
          operationId={location.values.wopid}
          origin="from-wallet-ui"
          onOperationAborted={() => navigateTo(publicPages.login.url({}))}
          routeClose={publicPages.login}
        />
      );
    }
    case "register": {
      return (
        <Fragment>
          <LocalNotificationBanner notification={notification} />
          <RegistrationPage
            onRegistrationSuccesful={notifyOnError(doAutomaticLogin)}
            routeCancel={publicPages.login}
          />
        </Fragment>
      );
    }
    default:
      assertUnreachable(location);
  }
}

const privatePages = {
  homeChargeWallet: urlPattern(
    /\/account\/charge-wallet/,
    () => "#/account/charge-wallet",
  ),
  homeWireTransfer: urlPattern<{
    account?: string;
    subject?: string;
    amount?: string;
  }>(/\/account\/wire-transfer/, () => "#/account/wire-transfer"),
  home: urlPattern(/\/account/, () => "#/account"),
  notifications: urlPattern(/\/notifications/, () => "#/notifications"),
  cashoutCreate: urlPattern(/\/new-cashout/, () => "#/new-cashout"),
  cashoutDetails: urlPattern<{ cid: string }>(
    /\/cashout\/(?<cid>[a-zA-Z0-9]+)/,
    ({ cid }) => `#/cashout/${cid}`,
  ),
  wireTranserCreate: urlPattern<{
    account?: string;
    subject?: string;
    amount?: string;
  }>(
    /\/wire-transfer\/(?<account>[a-zA-Z0-9]+)/,
    ({ account }) => `#/wire-transfer/${account}`,
  ),
  publicAccountList: urlPattern(/\/public-accounts/, () => "#/public-accounts"),
  statsDownload: urlPattern(/\/download-stats/, () => "#/download-stats"),
  accountCreate: urlPattern(/\/new-account/, () => "#/new-account"),
  myAccountDelete: urlPattern(
    /\/delete-my-account/,
    () => "#/delete-my-account",
  ),
  myAccountDetails: urlPattern(/\/my-profile/, () => "#/my-profile"),
  myAccountPassword: urlPattern(/\/my-password/, () => "#/my-password"),
  myAccountCashouts: urlPattern(/\/my-cashouts/, () => "#/my-cashouts"),
  conversionConfig: urlPattern(/\/conversion$/, () => "#/conversion"),
  accountDetails: urlPattern<{ account: string }>(
    /\/profile\/(?<account>[a-zA-Z0-9_-]+)\/details/,
    ({ account }) => `#/profile/${account}/details`,
  ),
  accountChangePassword: urlPattern<{ account: string }>(
    /\/profile\/(?<account>[a-zA-Z0-9_-]+)\/change-password/,
    ({ account }) => `#/profile/${account}/change-password`,
  ),
  accountDelete: urlPattern<{ account: string }>(
    /\/profile\/(?<account>[a-zA-Z0-9_-]+)\/delete/,
    ({ account }) => `#/profile/${account}/delete`,
  ),
  accountCashouts: urlPattern<{ account: string }>(
    /\/profile\/(?<account>[a-zA-Z0-9_-]+)\/cashouts/,
    ({ account }) => `#/profile/${account}/cashouts`,
  ),
  startOperation: urlPattern<{ wopid: string }>(
    /\/start-operation\/(?<wopid>[a-zA-Z0-9-]+)/,
    ({ wopid }) => `#/start-operation/${wopid}`,
  ),
  operationDetails: urlPattern<{ wopid: string }>(
    /\/operation\/(?<wopid>[a-zA-Z0-9-]+)/,
    ({ wopid }) => `#/operation/${wopid}`,
  ),
  conversionRateClassCreate: urlPattern(
    /\/new-conversion-rate-class/,
    () => "#/new-conversion-rate-class",
  ),
  conversionRateClassDetails: urlPattern<{ classId: string }>(
    /\/conversion-rate-class\/(?<classId>[0-9]+)\/details/,
    ({ classId }) => `#/conversion-rate-class/${classId}/details`,
  ),
};

function PrivateRouting({
  username,
  isAdmin,
}: {
  username: string;
  isAdmin: boolean;
}): VNode {
  const { navigateTo } = useNavigationContext();
  const location = useCurrentLocation(privatePages);
  useEffect(() => {
    if (location === undefined) {
      navigateTo(privatePages.home.url({}));
    }
  }, [location]);

  switch (location.name) {
    case "operationDetails": {
      return (
        <WithdrawalOperationPage
          operationId={location.values.wopid}
          origin="from-wallet-ui"
          onOperationAborted={() => navigateTo(privatePages.home.url({}))}
          routeClose={privatePages.home}
        />
      );
    }
    case "startOperation": {
      return (
        <WithdrawalOperationPage
          operationId={location.values.wopid}
          origin="from-bank-ui"
          onOperationAborted={() => navigateTo(privatePages.home.url({}))}
          routeClose={privatePages.home}
        />
      );
    }
    case "publicAccountList": {
      return <PublicHistoriesPage />;
    }
    case "statsDownload": {
      return <DownloadStats routeCancel={privatePages.home} />;
    }
    case "accountCreate": {
      return (
        <CreateNewAccount
          routeCancel={privatePages.home}
          onCreateSuccess={() => navigateTo(privatePages.home.url({}))}
        />
      );
    }
    case "accountDetails": {
      return (
        <ShowAccountDetails
          account={location.values.account}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          routeClose={privatePages.home}
        />
      );
    }
    case "accountChangePassword": {
      return (
        <UpdateAccountPassword
          focus
          account={location.values.account}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          routeClose={privatePages.home}
        />
      );
    }
    case "accountDelete": {
      return (
        <RemoveAccount
          account={location.values.account}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          routeCancel={privatePages.home}
        />
      );
    }
    case "accountCashouts": {
      return (
        <CashoutListForAccount
          account={location.values.account}
          routeCashoutDetails={privatePages.cashoutDetails}
          routeClose={privatePages.home}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          onCashout={() => navigateTo(privatePages.home.url({}))}
        />
      );
    }
    case "myAccountDelete": {
      return (
        <RemoveAccount
          account={username}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          routeCancel={privatePages.home}
        />
      );
    }
    case "myAccountDetails": {
      return (
        <ShowAccountDetails
          account={username}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeConversionConfig={privatePages.conversionConfig}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeClose={privatePages.home}
        />
      );
    }
    case "myAccountPassword": {
      return (
        <UpdateAccountPassword
          focus
          account={username}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          routeClose={privatePages.home}
        />
      );
    }
    case "myAccountCashouts": {
      return (
        <CashoutListForAccount
          account={username}
          routeCashoutDetails={privatePages.cashoutDetails}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          onCashout={() => navigateTo(privatePages.home.url({}))}
          routeClose={privatePages.home}
        />
      );
    }
    case undefined:
    case "home": {
      if (isAdmin) {
        return (
          <AdminHome
            routeCreateAccount={privatePages.accountCreate}
            routeRemoveAccount={privatePages.accountDelete}
            routeShowAccount={privatePages.accountDetails}
            routeShowCashoutsAccount={privatePages.accountCashouts}
            routeUpdatePasswordAccount={privatePages.accountChangePassword}
            routeCreateWireTransfer={privatePages.wireTranserCreate}
            routeDownloadStats={privatePages.statsDownload}
            routeCreateConversionRateClass={
              privatePages.conversionRateClassCreate
            }
            routeShowConversionRateClass={
              privatePages.conversionRateClassDetails
            }
          />
        );
      }
      return (
        <AccountPage
          account={username}
          tab={undefined}
          routeCreateWireTransfer={privatePages.wireTranserCreate}
          routePublicAccounts={privatePages.publicAccountList}
          routeOperationDetails={privatePages.startOperation}
          routeChargeWallet={privatePages.homeChargeWallet}
          routeWireTransfer={privatePages.homeWireTransfer}
          routeCashout={privatePages.myAccountCashouts}
          routeClose={privatePages.home}
          onClose={() => navigateTo(privatePages.home.url({}))}
          onOperationCreated={(wopid) =>
            navigateTo(privatePages.startOperation.url({ wopid }))
          }
        />
      );
    }
    case "cashoutCreate": {
      return (
        <CreateCashout
          account={username}
          onCashout={() => navigateTo(privatePages.home.url({}))}
          routeClose={privatePages.home}
        />
      );
    }
    case "cashoutDetails": {
      return (
        <ShowCashoutDetails
          id={location.values.cid}
          routeClose={privatePages.myAccountCashouts}
        />
      );
    }
    case "wireTranserCreate": {
      return (
        <WireTransfer
          toAccount={location.values.account}
          withAmount={location.values.amount}
          withSubject={location.values.subject}
          routeCancel={privatePages.home}
          onSuccess={() => navigateTo(privatePages.home.url({}))}
        />
      );
    }
    case "homeChargeWallet": {
      return (
        <AccountPage
          account={username}
          tab="charge-wallet"
          routeChargeWallet={privatePages.homeChargeWallet}
          routeWireTransfer={privatePages.homeWireTransfer}
          routeCreateWireTransfer={privatePages.wireTranserCreate}
          routePublicAccounts={privatePages.publicAccountList}
          routeOperationDetails={privatePages.startOperation}
          routeCashout={privatePages.myAccountCashouts}
          routeClose={privatePages.home}
          onClose={() => navigateTo(privatePages.home.url({}))}
          onOperationCreated={(wopid) =>
            navigateTo(privatePages.startOperation.url({ wopid }))
          }
        />
      );
    }
    case "conversionConfig": {
      return (
        <ConversionConfig
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          routeCancel={privatePages.home}
          onUpdateSuccess={() => {
            navigateTo(privatePages.home.url({}));
          }}
        />
      );
    }
    case "homeWireTransfer": {
      return (
        <AccountPage
          account={username}
          tab="wire-transfer"
          routeChargeWallet={privatePages.homeChargeWallet}
          routeWireTransfer={privatePages.homeWireTransfer}
          routeCreateWireTransfer={privatePages.wireTranserCreate}
          routePublicAccounts={privatePages.publicAccountList}
          routeOperationDetails={privatePages.startOperation}
          routeCashout={privatePages.myAccountCashouts}
          routeClose={privatePages.home}
          onClose={() => navigateTo(privatePages.home.url({}))}
          onOperationCreated={(wopid) =>
            navigateTo(privatePages.startOperation.url({ wopid }))
          }
        />
      );
    }
    case "conversionRateClassCreate": {
      return (
        <NewConversionRateClass
          onCreated={(id) =>
            navigateTo(
              privatePages.conversionRateClassDetails.url({
                classId: String(id),
              }),
            )
          }
          routeCancel={privatePages.home}
        />
      );
    }
    case "conversionRateClassDetails": {
      const id = Number.parseInt(location.values.classId, 10);
      if (Number.isNaN(id)) {
        return <div>class id is not a number "{location.values.classId}"</div>;
      }
      return (
        <ConversionRateClassDetails
          classId={id}
          routeCancel={privatePages.home}
          onClassDeleted={() => {
            navigateTo(privatePages.home.url({}));
          }}
        />
      );
    }
    case "notifications": {
      return <ShowNotifications />;
    }
    default:
      assertUnreachable(location);
  }
}
