/*
 This file is part of GNU Taler
 (C) 2021-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/>
 */

/**
 *
 * @author Sebastian Javier Marchano (sebasjm)
 */

import {
  AbsoluteTime,
  HttpStatusCode,
  TalerError,
  TranslatedString,
} from "@gnu-taler/taler-util";
import { urlPattern, useTranslationContext } from "@gnu-taler/web-util/browser";
import { createHashHistory } from "history";
import { Fragment, VNode, h } from "preact";
import { Route, Router, route } from "preact-router";
import { useEffect, useErrorBoundary, useState } from "preact/hooks";
import { Loading } from "./components/exception/loading.js";
import {
  Menu,
  NotConnectedAppMenu,
  NotificationCard,
} from "./components/menu/index.js";
import { useSessionContext } from "./context/session.js";
import { useInstanceBankAccounts } from "./hooks/bank.js";
import { useInstanceKYCDetailsLongPolling } from "./hooks/instance.js";
import { usePreference } from "./hooks/preference.js";
import InstanceCreatePage from "./paths/admin/create/index.js";
import InstanceListPage from "./paths/admin/list/index.js";
import AccessTokenCreatePage from "./paths/instance/accessTokens/create/index.js";
import AccessTokenListPage from "./paths/instance/accessTokens/list/index.js";
import BankAccountCreatePage from "./paths/instance/accounts/create/index.js";
import BankAccountListPage from "./paths/instance/accounts/list/index.js";
import BankAccountUpdatePage from "./paths/instance/accounts/update/index.js";
import CreateCategory from "./paths/instance/categories/create/index.js";
import ListCategories from "./paths/instance/categories/list/index.js";
import UpdateCategory from "./paths/instance/categories/update/index.js";
import ListKYCPage from "./paths/instance/kyc/list/index.js";
import OrderCreatePage from "./paths/instance/orders/create/index.js";
import OrderDetailsPage from "./paths/instance/orders/details/index.js";
import OrderListPage from "./paths/instance/orders/list/index.js";
import ValidatorCreatePage from "./paths/instance/otp_devices/create/index.js";
import ValidatorListPage from "./paths/instance/otp_devices/list/index.js";
import ValidatorUpdatePage from "./paths/instance/otp_devices/update/index.js";
import PasswordPage, {
  AdminPassword as InstanceAdminTokenPage,
  Props as InstanceAdminTokenProps,
} from "./paths/instance/password/index.js";
import ProductCreatePage from "./paths/instance/products/create/index.js";
import ProductListPage from "./paths/instance/products/list/index.js";
import ProductUpdatePage from "./paths/instance/products/update/index.js";
import TemplateCreatePage from "./paths/instance/templates/create/index.js";
import TemplateListPage from "./paths/instance/templates/list/index.js";
import TemplateQrPage from "./paths/instance/templates/qr/index.js";
import TemplateUpdatePage from "./paths/instance/templates/update/index.js";
import TemplateUsePage from "./paths/instance/templates/use/index.js";
import TokenFamilyCreatePage from "./paths/instance/tokenfamilies/create/index.js";
import TokenFamilyListPage from "./paths/instance/tokenfamilies/list/index.js";
import TokenFamilyUpdatePage from "./paths/instance/tokenfamilies/update/index.js";
import TransferCreatePage from "./paths/instance/transfers/create/index.js";
import TransferListPage from "./paths/instance/transfers/list/index.js";
import InstanceUpdatePage, {
  AdminUpdate as InstanceAdminUpdatePage,
  Props as InstanceUpdatePageProps,
} from "./paths/instance/update/index.js";
import WebhookCreatePage from "./paths/instance/webhooks/create/index.js";
import WebhookListPage from "./paths/instance/webhooks/list/index.js";
import WebhookUpdatePage from "./paths/instance/webhooks/update/index.js";
import { LoginPage } from "./paths/login/index.js";
import { NewAccount } from "./paths/newAccount/index.js";
import { Settings } from "./paths/settings/index.js";
import { Notification } from "./utils/types.js";
import { ResetAccount } from "./paths/resetAccount/index.js";

export enum InstancePaths {
  error = "/error",
  settings = "/settings",
  password = "/password",

  access_token_list = "/access-token",
  access_token_new = "/access-token/new",

  bank_list = "/bank",
  bank_update = "/bank/:bid/update",
  bank_new = "/bank/new",

  category_list = "/category",
  category_update = "/category/:cid/update",
  category_new = "/category/new",

  inventory_list = "/inventory",
  inventory_update = "/inventory/:pid/update",
  inventory_new = "/inventory/new",

  order_list = "/orders",
  order_new = "/order/new",
  order_details = "/order/:oid/details",

  kyc = "/kyc",

  transfers_list = "/transfers",
  transfers_new = "/transfer/new",

  templates_list = "/templates",
  templates_update = "/templates/:tid/update",
  templates_new = "/templates/new",
  templates_use = "/templates/:tid/use",
  templates_qr = "/templates/:tid/qr",

  token_family_list = "/tokenfamilies",
  token_family_update = "/tokenfamilies/:slug/update",
  token_family_new = "/tokenfamilies/new",

  webhooks_list = "/webhooks",
  webhooks_update = "/webhooks/:tid/update",
  webhooks_new = "/webhooks/new",

  otp_devices_list = "/otp-devices",
  otp_devices_update = "/otp-devices/:vid/update",
  otp_devices_new = "/otp-devices/new",

  interface = "/interface",

  newAccount = "/account/new",
  resetAccount = "/account/reset/:id",
}

export enum AdminPaths {
  list_instances = "/instances",
  new_instance = "/instance/new",
  update_instance = "/instance/:id/update",
  update_instance_auth = "/instance/:id/token",
}

export interface Props {}

export const privatePages = {
  home: urlPattern(/\/home/, () => "#/home"),
  go: urlPattern(/\/home/, () => "#/home"),
};
export const publicPages = {
  home: urlPattern(/\/home/, () => "#/home"),
  go: urlPattern(/\/home/, () => "#/home"),
};

const history = createHashHistory();
export function Routing(_p: Props): VNode {
  const { state, config } = useSessionContext();
  const { i18n } = useTranslationContext();

  type GlobalNotifState =
    | (Notification & { to: string | undefined })
    | undefined;
  const [globalNotification, setGlobalNotification] =
    useState<GlobalNotifState>(undefined);

  const [error] = useErrorBoundary();
  const [preference] = usePreference();

  const now = AbsoluteTime.now();

  const instance = useInstanceBankAccounts();

  if (!instance) {
    return (
      <Fragment>
        <NotConnectedAppMenu title={i18n.str`Welcome!`} />
        <Loading />
      </Fragment>
    );
  }

  const accounts =
    instance instanceof TalerError || instance.type === "fail"
      ? undefined
      : instance.body;
  const shouldWarnAboutMissingBankAccounts =
    !state.isAdmin &&
    accounts !== undefined &&
    accounts.accounts.length < 1 &&
    (AbsoluteTime.isNever(preference.hideMissingAccountUntil) ||
      AbsoluteTime.cmp(now, preference.hideMissingAccountUntil) > 0);

  const unauthorized =
    !(instance instanceof TalerError) &&
    instance.type === "fail" &&
    instance.case === HttpStatusCode.Unauthorized;

  const shouldLogin = state.status === "loggedOut" || unauthorized;

  if (shouldLogin) {
    return (
      <Fragment>
        <NotConnectedAppMenu title={i18n.str`Welcome!`} />
        <Router history={history}>
          <Route
            path={InstancePaths.newAccount}
            component={NewAccount}
            onCancel={() => route(InstancePaths.order_list)}
            onCreated={() => route(InstancePaths.order_list)}
          />
          <Route
            path={InstancePaths.resetAccount}
            component={ResetAccount}
            onCancel={() => route(InstancePaths.order_list)}
            onReseted={() => route(InstancePaths.order_list)}
          />
          <Route default component={() => <LoginPage showCreateAccount={config.have_self_provisioning} />} />
        </Router>
      </Fragment>
    );
  }

  if (shouldWarnAboutMissingBankAccounts) {
    return (
      <Fragment>
        <Menu />
        <Router history={history}>
          <Route path={InstancePaths.interface} component={Settings} />
          <Route
            default
            component={() => {
              return (
                <Fragment>
                  <BankAccountBanner />
                  <BankAccountCreatePage
                    onConfirm={() => {
                      route(InstancePaths.bank_list);
                    }}
                  />
                </Fragment>
              );
            }}
          />
        </Router>
      </Fragment>
    );
  }

  return (
    <Fragment>
      <Menu />
      <KycBanner />
      <NotificationCard notification={globalNotification} />
      {error && (
        <Fragment>
          <NotificationCard
            notification={{
              message: "Internal error, please report",
              type: "ERROR",

              description: (
                <pre>
                  {
                    (error instanceof Error
                      ? error.stack
                      : String(error)) as TranslatedString
                  }
                </pre>
              ),
            }}
          />
        </Fragment>
      )}

      <Router
        history={history}
        onChange={(e) => {
          const movingOutFromNotification =
            globalNotification && e.url !== globalNotification.to;
          if (movingOutFromNotification) {
            setGlobalNotification(undefined);
          }
        }}
      >
        <Route path="/" component={Redirect} to={InstancePaths.order_list} />
        {/**
         * Admin pages
         */}
        {state.isAdmin && (
          <Route
            path={AdminPaths.list_instances}
            component={InstanceListPage}
            onChangePassword={(id: string): void => {
              route(`/instance/${id}/token`);
            }}
            onCreate={() => {
              route(AdminPaths.new_instance);
            }}
            onUpdate={(id: string): void => {
              route(`/instance/${id}/update`);
            }}
          />
        )}
        {state.isAdmin && (
          <Route
            path={AdminPaths.new_instance}
            component={InstanceCreatePage}
            onBack={() => route(AdminPaths.list_instances)}
            onConfirm={() => {
              route(AdminPaths.list_instances);
            }}
          />
        )}
        {state.isAdmin && (
          <Route
            path={AdminPaths.update_instance}
            component={AdminInstanceUpdatePage}
            onBack={() => route(AdminPaths.list_instances)}
            onConfirm={() => {
              route(AdminPaths.list_instances);
            }}
          />
        )}
        {state.isAdmin && (
          <Route
            path={AdminPaths.update_instance_auth}
            component={AdminInstanceUpdateTokenPage}
            onCancel={() => route(AdminPaths.list_instances)}
            onChange={() => {
              route(AdminPaths.list_instances);
            }}
          />
        )}
        {/**
         * Update instance page
         */}
        <Route
          path={InstancePaths.settings}
          component={InstanceUpdatePage}
          onBack={() => {
            route(`/`);
          }}
          onConfirm={() => {
            route(`/`);
          }}
        />
        {/**
         * Update instance page
         */}
        <Route
          path={InstancePaths.password}
          component={PasswordPage}
          onChange={() => {
            route(`/`);
          }}
          onCancel={() => {
            route(InstancePaths.order_list);
          }}
        />
        {/**
         * Category pages
         */}
        <Route
          path={InstancePaths.category_list}
          component={ListCategories}
          onCreate={() => {
            route(InstancePaths.category_new);
          }}
          onSelect={(id: string) => {
            route(InstancePaths.category_update.replace(":cid", id));
          }}
        />
        <Route
          path={InstancePaths.category_update}
          component={UpdateCategory}
          onConfirm={() => {
            route(InstancePaths.category_list);
          }}
          onBack={() => {
            route(InstancePaths.category_list);
          }}
        />
        <Route
          path={InstancePaths.category_new}
          component={CreateCategory}
          onConfirm={() => {
            route(InstancePaths.category_list);
          }}
          onBack={() => {
            route(InstancePaths.category_list);
          }}
        />
        {/**
         * Inventory pages
         */}
        <Route
          path={InstancePaths.inventory_list}
          component={ProductListPage}
          onCreate={() => {
            route(InstancePaths.inventory_new);
          }}
          onSelect={(id: string) => {
            route(InstancePaths.inventory_update.replace(":pid", id));
          }}
        />
        <Route
          path={InstancePaths.inventory_update}
          component={ProductUpdatePage}
          onConfirm={() => {
            route(InstancePaths.inventory_list);
          }}
          onBack={() => {
            route(InstancePaths.inventory_list);
          }}
        />
        <Route
          path={InstancePaths.inventory_new}
          component={ProductCreatePage}
          onConfirm={() => {
            route(InstancePaths.inventory_list);
          }}
          onBack={() => {
            route(InstancePaths.inventory_list);
          }}
        />
        {/**
         * Bank pages
         */}
        <Route
          path={InstancePaths.bank_list}
          component={BankAccountListPage}
          onCreate={() => {
            route(InstancePaths.bank_new);
          }}
          onSelect={(id: string) => {
            route(InstancePaths.bank_update.replace(":bid", id));
          }}
        />
        <Route
          path={InstancePaths.bank_update}
          component={BankAccountUpdatePage}
          onConfirm={() => {
            route(InstancePaths.bank_list);
          }}
          onBack={() => {
            route(InstancePaths.bank_list);
          }}
        />
        <Route
          path={InstancePaths.bank_new}
          component={BankAccountCreatePage}
          onConfirm={() => {
            route(InstancePaths.bank_list);
          }}
          onBack={() => {
            route(InstancePaths.bank_list);
          }}
        />
        {/**
         * Sessions pages
         */}
        <Route
          path={InstancePaths.access_token_list}
          component={AccessTokenListPage}
          onCreate={() => {
            route(InstancePaths.access_token_new);
          }}
        />
        <Route
          path={InstancePaths.access_token_new}
          component={AccessTokenCreatePage}
          onConfirm={() => {
            route(InstancePaths.access_token_list);
          }}
          onBack={() => {
            route(InstancePaths.access_token_list);
          }}
        />
        {/**
         * Order pages
         */}
        <Route
          path={InstancePaths.order_list}
          component={OrderListPage}
          onCreate={() => {
            route(InstancePaths.order_new);
          }}
          onSelect={(id: string) => {
            route(InstancePaths.order_details.replace(":oid", id));
          }}
        />
        <Route
          path={InstancePaths.order_details}
          component={OrderDetailsPage}
          onBack={() => {
            route(InstancePaths.order_list);
          }}
        />
        <Route
          path={InstancePaths.order_new}
          component={OrderCreatePage}
          onConfirm={(orderId: string) => {
            route(InstancePaths.order_details.replace(":oid", orderId));
          }}
          onBack={() => {
            route(InstancePaths.order_list);
          }}
        />
        {/**
         * Transfer pages
         */}
        <Route
          path={InstancePaths.transfers_list}
          component={TransferListPage}
          onCreate={() => {
            route(InstancePaths.transfers_new);
          }}
        />
        <Route
          path={InstancePaths.transfers_new}
          component={TransferCreatePage}
          onConfirm={() => {
            route(InstancePaths.transfers_list);
          }}
          onBack={() => {
            route(InstancePaths.transfers_list);
          }}
        />
        {/* *
         * Token family pages
         */}
        <Route
          path={InstancePaths.token_family_list}
          component={TokenFamilyListPage}
          onCreate={() => {
            route(InstancePaths.token_family_new);
          }}
          onSelect={(slug: string) => {
            route(InstancePaths.token_family_update.replace(":slug", slug));
          }}
        />
        <Route
          path={InstancePaths.token_family_update}
          component={TokenFamilyUpdatePage}
          onConfirm={() => {
            route(InstancePaths.token_family_list);
          }}
          onBack={() => {
            route(InstancePaths.token_family_list);
          }}
        />
        <Route
          path={InstancePaths.token_family_new}
          component={TokenFamilyCreatePage}
          onConfirm={() => {
            route(InstancePaths.token_family_list);
          }}
          onBack={() => {
            route(InstancePaths.token_family_list);
          }}
        />
        {/**
         * Webhooks pages
         */}
        <Route
          path={InstancePaths.webhooks_list}
          component={WebhookListPage}
          onCreate={() => {
            route(InstancePaths.webhooks_new);
          }}
          onSelect={(id: string) => {
            route(InstancePaths.webhooks_update.replace(":tid", id));
          }}
        />
        <Route
          path={InstancePaths.webhooks_update}
          component={WebhookUpdatePage}
          onConfirm={() => {
            route(InstancePaths.webhooks_list);
          }}
          onBack={() => {
            route(InstancePaths.webhooks_list);
          }}
        />
        <Route
          path={InstancePaths.webhooks_new}
          component={WebhookCreatePage}
          onConfirm={() => {
            route(InstancePaths.webhooks_list);
          }}
          onBack={() => {
            route(InstancePaths.webhooks_list);
          }}
        />
        {/**
         * Validator pages
         */}
        <Route
          path={InstancePaths.otp_devices_list}
          component={ValidatorListPage}
          onCreate={() => {
            route(InstancePaths.otp_devices_new);
          }}
          onSelect={(id: string) => {
            route(InstancePaths.otp_devices_update.replace(":vid", id));
          }}
        />
        <Route
          path={InstancePaths.otp_devices_update}
          component={ValidatorUpdatePage}
          onConfirm={() => {
            route(InstancePaths.otp_devices_list);
          }}
          onBack={() => {
            route(InstancePaths.otp_devices_list);
          }}
        />
        <Route
          path={InstancePaths.otp_devices_new}
          component={ValidatorCreatePage}
          onConfirm={() => {
            route(InstancePaths.otp_devices_list);
          }}
          onBack={() => {
            route(InstancePaths.otp_devices_list);
          }}
        />
        {/**
         * Templates pages
         */}
        <Route
          path={InstancePaths.templates_list}
          component={TemplateListPage}
          onCreate={() => {
            route(InstancePaths.templates_new);
          }}
          onNewOrder={(id: string) => {
            route(InstancePaths.templates_use.replace(":tid", id));
          }}
          onQR={(id: string) => {
            route(InstancePaths.templates_qr.replace(":tid", id));
          }}
          onSelect={(id: string) => {
            route(InstancePaths.templates_update.replace(":tid", id));
          }}
        />
        <Route
          path={InstancePaths.templates_update}
          component={TemplateUpdatePage}
          onConfirm={() => {
            route(InstancePaths.templates_list);
          }}
          onBack={() => {
            route(InstancePaths.templates_list);
          }}
        />
        <Route
          path={InstancePaths.templates_new}
          component={TemplateCreatePage}
          onConfirm={() => {
            route(InstancePaths.templates_list);
          }}
          onBack={() => {
            route(InstancePaths.templates_list);
          }}
        />
        <Route
          path={InstancePaths.templates_use}
          component={TemplateUsePage}
          onOrderCreated={(id: string) => {
            route(InstancePaths.order_details.replace(":oid", id));
          }}
          onBack={() => {
            route(InstancePaths.templates_list);
          }}
        />
        <Route
          path={InstancePaths.templates_qr}
          component={TemplateQrPage}
          onBack={() => {
            route(InstancePaths.templates_list);
          }}
        />

        <Route
          path={InstancePaths.kyc}
          component={ListKYCPage}
          // onSelect={(id: string) => {
          //   route(InstancePaths.bank_update.replace(":bid", id));
          // }}
        />
        <Route path={InstancePaths.interface} component={Settings} />
        {/**
         * Example pages
         */}
        <Route path="/loading" component={Loading} />
        <Route default component={Redirect} to={InstancePaths.order_list} />
      </Router>
    </Fragment>
  );
}

export function Redirect({ to }: { to: string }): null {
  useEffect(() => {
    route(to, true);
  });
  return null;
}

function AdminInstanceUpdatePage({
  id,
  ...rest
}: { id: string } & InstanceUpdatePageProps): VNode {
  return (
    <Fragment>
      <InstanceAdminUpdatePage {...rest} instanceId={id} />
    </Fragment>
  );
}

function AdminInstanceUpdateTokenPage({
  id,
  ...rest
}: { id: string } & InstanceAdminTokenProps): VNode {
  return (
    <Fragment>
      <InstanceAdminTokenPage {...rest} instanceId={id} />
    </Fragment>
  );
}

function BankAccountBanner(): VNode {
  const { i18n } = useTranslationContext();

  const [, updatePref] = usePreference();
  const now = AbsoluteTime.now();
  const oneDay = { d_ms: 1000 * 60 * 60 * 24 };
  const tomorrow = AbsoluteTime.addDuration(now, oneDay);

  return (
    <NotificationCard
      notification={{
        type: "INFO",
        message: i18n.str`You need to associate a bank account to receive revenue.`,
        description: (
          <div>
            <p>
              <i18n.Translate>
                Without this the merchant backend will refuse to create new
                orders.
              </i18n.Translate>
            </p>
            <div class="buttons is-right">
              <button
                class="button"
                onClick={() => updatePref("hideMissingAccountUntil", tomorrow)}
              >
                <i18n.Translate>Hide for today</i18n.Translate>
              </button>
            </div>
          </div>
        ),
      }}
    />
  );
}

function KycBanner(): VNode {
  const kycStatus = useInstanceKYCDetailsLongPolling();
  const { i18n } = useTranslationContext();
  // const today = format(new Date(), dateFormatForSettings(settings));
  const [prefs, updatePref] = usePreference();

  const now = AbsoluteTime.now();

  const needsToBeShown =
    kycStatus !== undefined &&
    !(kycStatus instanceof TalerError) &&
    kycStatus.type === "ok" &&
    !!kycStatus.body &&
    kycStatus.body.kyc_data.findIndex(
      (d) => d.payto_kycauths !== undefined || d.access_token !== undefined,
    ) !== -1;
  const hidden = AbsoluteTime.cmp(now, prefs.hideKycUntil) < 1;
  if (hidden || !needsToBeShown) return <Fragment />;

  const oneDay = { d_ms: 1000 * 60 * 60 * 24 };
  const tomorrow = AbsoluteTime.addDuration(now, oneDay);

  return (
    <NotificationCard
      notification={{
        type: "WARN",
        message: i18n.str`KYC verification needed`,
        description: (
          <div>
            <p>
              <i18n.Translate>
                Some transfers are on hold until the KYC process is completed.
                Visit the KYC section in the left-hand menu for more
                information.
              </i18n.Translate>
            </p>
            <div class="buttons is-right">
              <button
                class="button"
                onClick={() => updatePref("hideKycUntil", tomorrow)}
              >
                <i18n.Translate>Hide for today</i18n.Translate>
              </button>
            </div>
          </div>
        ),
      }}
    />
  );
}
