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

import {
  AbsoluteTime,
  AccessToken,
  HttpStatusCode,
  TranslatedString,
  assertUnreachable,
  createRFC8959AccessTokenEncoded,
} from "@gnu-taler/taler-util";
import { useEffect } from "preact/hooks";
import { useSessionState } from "./hooks/session.js";
import { AccountPage } from "./pages/AccountPage/index.js";
import { BankFrame } from "./pages/BankFrame.js";
import { LoginForm } from "./pages/LoginForm.js";
import { PublicHistoriesPage } from "./pages/PublicHistoriesPage.js";
import { RegistrationPage } from "./pages/RegistrationPage.js";
import { ShowNotifications } from "./pages/ShowNotifications.js";
import { SolveChallengePage } from "./pages/SolveChallengePage.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";

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

  if (session.state.status === "loggedIn") {
    const { isUserAdministrator, username } = session.state;
    return (
      <BankFrame
        account={username}
        routeAccountDetails={privatePages.myAccountDetails}
      >
        <PrivateRouting username={username} isAdmin={isUserAdministrator} />
      </BankFrame>
    );
  }
  return (
    <BankFrame>
      <PublicRounting
        onLoggedUser={(username, token) => {
          session.logIn({ username, token: token });
        }}
      />
    </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}`,
  ),
  solveSecondFactor: urlPattern(/\/2fa/, () => "#/2fa"),
};

function PublicRounting({
  onLoggedUser,
}: {
  onLoggedUser: (username: string, token: AccessToken) => void;
}): VNode {
  const { i18n } = useTranslationContext();
  const location = useCurrentLocation(publicPages);
  const { navigateTo } = useNavigationContext();
  const { config, lib } = useBankCoreApiContext();
  const [notification, notify, handleError] = useLocalNotification();

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

  async function doAutomaticLogin(username: string, password: string) {
    await handleError(async () => {
      const resp = await lib
        .auth(username)
        .createAccessTokenBasic(username, password, {
          scope: "readwrite",
          duration: { d_us: "forever" },
          refreshable: true,
        });
      if (resp.type === "ok") {
        onLoggedUser(
          username,
          createRFC8959AccessTokenEncoded(resp.body.access_token),
        );
      } else {
        switch (resp.case) {
          case HttpStatusCode.Unauthorized:
            return notify({
              type: "error",
              title: i18n.str`Wrong credentials for "${username}"`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case HttpStatusCode.NotFound:
            return notify({
              type: "error",
              title: i18n.str`Account not found`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          default:
            assertUnreachable(resp);
        }
      }
    });
  }

  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}
          routeWithdrawalDetails={publicPages.operationDetails}
          purpose="after-confirmation"
          onOperationAborted={() => navigateTo(publicPages.login.url({}))}
          routeClose={publicPages.login}
          onAuthorizationRequired={() =>
            navigateTo(publicPages.solveSecondFactor.url({}))
          }
        />
      );
    }
    case "register": {
      return (
        <Fragment>
          <LocalNotificationBanner notification={notification} />
          <RegistrationPage
            onRegistrationSuccesful={doAutomaticLogin}
            routeCancel={publicPages.login}
          />
        </Fragment>
      );
    }
    case "solveSecondFactor": {
      return (
        <SolveChallengePage
          onChallengeCompleted={() => navigateTo(publicPages.login.url({}))}
          routeClose={publicPages.login}
        />
      );
    }
    default:
      assertUnreachable(location);
  }
}

export 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"),
  solveSecondFactor: urlPattern(/\/2fa/, () => "#/2fa"),
  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}`,
  ),
};

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}
          routeWithdrawalDetails={privatePages.operationDetails}
          purpose="after-confirmation"
          onOperationAborted={() => navigateTo(privatePages.home.url({}))}
          routeClose={privatePages.home}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
        />
      );
    }
    case "startOperation": {
      return (
        <WithdrawalOperationPage
          operationId={location.values.wopid}
          routeWithdrawalDetails={privatePages.operationDetails}
          purpose="after-creation"
          onOperationAborted={() => navigateTo(privatePages.home.url({}))}
          routeClose={privatePages.home}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
        />
      );
    }
    case "solveSecondFactor": {
      return (
        <SolveChallengePage
          onChallengeCompleted={() => 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({}))}
          routeHere={privatePages.accountDetails}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          routeClose={privatePages.home}
        />
      );
    }
    case "accountChangePassword": {
      return (
        <UpdateAccountPassword
          focus
          account={location.values.account}
          routeHere={privatePages.accountChangePassword}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          routeClose={privatePages.home}
        />
      );
    }
    case "accountDelete": {
      return (
        <RemoveAccount
          account={location.values.account}
          routeHere={privatePages.accountDelete}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          routeCancel={privatePages.home}
        />
      );
    }
    case "accountCashouts": {
      return (
        <CashoutListForAccount
          account={location.values.account}
          routeCreateCashout={privatePages.cashoutCreate}
          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({}))}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
        />
      );
    }
    case "myAccountDelete": {
      return (
        <RemoveAccount
          account={username}
          routeHere={privatePages.accountDelete}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          routeCancel={privatePages.home}
        />
      );
    }
    case "myAccountDetails": {
      return (
        <ShowAccountDetails
          account={username}
          routeHere={privatePages.accountDetails}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeConversionConfig={privatePages.conversionConfig}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          routeClose={privatePages.home}
        />
      );
    }
    case "myAccountPassword": {
      return (
        <UpdateAccountPassword
          focus
          account={username}
          routeHere={privatePages.accountChangePassword}
          onUpdateSuccess={() => navigateTo(privatePages.home.url({}))}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          routeClose={privatePages.home}
        />
      );
    }
    case "myAccountCashouts": {
      return (
        <CashoutListForAccount
          account={username}
          routeCashoutDetails={privatePages.cashoutDetails}
          routeCreateCashout={privatePages.cashoutCreate}
          routeMyAccountCashout={privatePages.myAccountCashouts}
          routeMyAccountDelete={privatePages.myAccountDelete}
          routeMyAccountDetails={privatePages.myAccountDetails}
          routeMyAccountPassword={privatePages.myAccountPassword}
          routeConversionConfig={privatePages.conversionConfig}
          onCashout={() => navigateTo(privatePages.home.url({}))}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          routeClose={privatePages.home}
        />
      );
    }
    case undefined:
    case "home": {
      if (isAdmin) {
        return (
          <AdminHome
            onAuthorizationRequired={() =>
              navigateTo(privatePages.solveSecondFactor.url({}))
            }
            routeCreate={privatePages.accountCreate}
            routeRemoveAccount={privatePages.accountDelete}
            routeShowAccount={privatePages.accountDetails}
            routeShowCashoutsAccount={privatePages.accountCashouts}
            routeUpdatePasswordAccount={privatePages.accountChangePassword}
            routeCreateWireTransfer={privatePages.wireTranserCreate}
            routeDownloadStats={privatePages.statsDownload}
          />
        );
      }
      return (
        <AccountPage
          account={username}
          tab={undefined}
          routeCreateWireTransfer={privatePages.wireTranserCreate}
          routePublicAccounts={privatePages.publicAccountList}
          routeOperationDetails={privatePages.startOperation}
          routeChargeWallet={privatePages.homeChargeWallet}
          routeWireTransfer={privatePages.homeWireTransfer}
          routeSolveSecondFactor={privatePages.solveSecondFactor}
          routeCashout={privatePages.myAccountCashouts}
          routeClose={privatePages.home}
          onClose={() => navigateTo(privatePages.home.url({}))}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          onOperationCreated={(wopid) =>
            navigateTo(privatePages.startOperation.url({ wopid }))
          }
        />
      );
    }
    case "cashoutCreate": {
      return (
        <CreateCashout
          account={username}
          routeHere={privatePages.cashoutCreate}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          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}
          routeHere={privatePages.wireTranserCreate}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          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}
          routeSolveSecondFactor={privatePages.solveSecondFactor}
          routeClose={privatePages.home}
          onClose={() => navigateTo(privatePages.home.url({}))}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.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}
          routeSolveSecondFactor={privatePages.solveSecondFactor}
          routeCashout={privatePages.myAccountCashouts}
          routeClose={privatePages.home}
          onClose={() => navigateTo(privatePages.home.url({}))}
          onAuthorizationRequired={() =>
            navigateTo(privatePages.solveSecondFactor.url({}))
          }
          onOperationCreated={(wopid) =>
            navigateTo(privatePages.startOperation.url({ wopid }))
          }
        />
      );
    }
    case "notifications": {
      return <ShowNotifications />;
    }
    default:
      assertUnreachable(location);
  }
}
