/*
 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,
  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 { useInstanceKYCDetails } 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 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 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 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 TokenPage from "./paths/instance/token/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 { NotFoundPage } from "./paths/notfound/index.js";
import { Settings } from "./paths/settings/index.js";
import { Notification } from "./utils/types.js";

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

  bank_list = "/bank",
  bank_update = "/bank/:bid/update",
  bank_new = "/bank/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",

  reserves_list = "/reserves",
  reserves_details = "/reserves/:rid/details",
  reserves_new = "/reserves/new",

  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",

  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",
}

// eslint-disable-next-line @typescript-eslint/no-empty-function
// const noop = () => { };

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

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 { i18n } = useTranslationContext();
  const { state } = useSessionContext();

  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();
  const accounts =
    !instance || 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) > 1);

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

  // function ServerErrorRedirectTo(to: InstancePaths | AdminPaths) {
  //   return function ServerErrorRedirectToImpl(
  //     error: HttpError<TalerErrorDetail>,
  //   ) {
  //     if (error.type === ErrorType.TIMEOUT) {
  //       setGlobalNotification({
  //         message: i18n.str`The request to the backend take too long and was cancelled`,
  //         description: i18n.str`Diagnostic from ${error.info.url} is "${error.message}"`,
  //         type: "ERROR",
  //         to,
  //       });
  //     } else {
  //       setGlobalNotification({
  //         message: i18n.str`The backend reported a problem: HTTP status #${error.status}`,
  //         description: i18n.str`Diagnostic from ${error.info.url} is '${error.message}'`,
  //         details:
  //           error.type === ErrorType.CLIENT || error.type === ErrorType.SERVER
  //             ? error.payload.hint
  //             : undefined,
  //         type: "ERROR",
  //         to,
  //       });
  //     }
  //     return <Redirect to={to} />;
  //   };
  // }

  // const LoginPageAccessDeniend = onUnauthorized
  // const LoginPageAccessDenied = () => {
  //   return (
  //     <Fragment>
  //       <NotificationCard
  //         notification={{
  //           message: i18n.str`Access denied`,
  //           description: i18n.str`Session expired or password changed.`,
  //           type: "ERROR",
  //         }}
  //       />
  //       <LoginPage />
  //     </Fragment>
  //   );
  // };

  // function IfAdminCreateDefaultOr<T>(Next: FunctionComponent<unknown>) {
  //   return function IfAdminCreateDefaultOrImpl(props?: T) {
  //     if (state.isAdmin && state.instance === DEFAULT_ADMIN_USERNAME) {
  //       return (
  //         <Fragment>
  //           <NotificationCard
  //             notification={{
  //               message: i18n.str`No 'default' instance configured yet.`,
  //               description: i18n.str`Create a 'default' instance to begin using the merchant backoffice.`,
  //               type: "INFO",
  //             }}
  //           />
  //           <InstanceCreatePage
  //             forceId={DEFAULT_ADMIN_USERNAME}
  //             onConfirm={() => {
  //               route(InstancePaths.bank_list);
  //             }}
  //           />
  //         </Fragment>
  //       );
  //     }
  //     if (props) {
  //       return <Next {...props} />;
  //     }
  //     return <Next />;
  //   };
  // }

  if (shouldLogin) {
    return (
      <Fragment>
        <NotConnectedAppMenu title="Welcome!" />
        <LoginPage />
      </Fragment>
    );
  }

  if (shouldWarnAboutMissingBankAccounts) {
    return (
      <Fragment>
        <Menu />
        <BankAccountBanner />
        <BankAccountCreatePage
          onConfirm={() => {
            route(InstancePaths.bank_list);
          }}
        />
      </Fragment>
    );
  }

  return (
    <Fragment>
      <Menu />
      <KycBanner />
      <NotificationCard notification={globalNotification} />
      {error && (
        <NotificationCard
          notification={{
            message: "Internal error, please repot",
            type: "ERROR",
            description: (
              <pre>
                {
                  (error instanceof Error
                    ? error.stack
                    : String(error)) as TranslatedString
                }
              </pre>
            ),
          }}
        />
      )}

      <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}
            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);
            }}
          />
        )}
        {/**
         * Update instance page
         */}
        <Route
          path={InstancePaths.settings}
          component={InstanceUpdatePage}
          onBack={() => {
            route(`/`);
          }}
          onConfirm={() => {
            route(`/`);
          }}
        />
        {/**
         * Update instance page
         */}
        <Route
          path={InstancePaths.token}
          component={TokenPage}
          onChange={() => {
            route(`/`);
          }}
          onCancel={() => {
            route(InstancePaths.order_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);
          }}
        />
        {/**
         * 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);
          }}
        />
        {/**
         * 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} />
        <Route path={InstancePaths.interface} component={Settings} />
        {/**
         * Example pages
         */}
        <Route path="/loading" component={Loading} />
        <Route default component={NotFoundPage} />
      </Router>
    </Fragment>
  );
}

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

function AdminInstanceUpdatePage({
  id,
  ...rest
}: { id: string } & InstanceUpdatePageProps): VNode {
  // const { i18n } = useTranslationContext();

  return (
    <Fragment>
      <InstanceAdminUpdatePage
        {...rest}
        instanceId={id}
        // onLoadError={(error: HttpError<TalerErrorDetail>) => {
        //   const notif =
        //     error.type === ErrorType.TIMEOUT
        //       ? {
        //         message: i18n.str`The request to the backend take too long and was cancelled`,
        //         description: i18n.str`Diagnostic from ${error.info.url} is '${error.message}'`,
        //         type: "ERROR" as const,
        //       }
        //       : {
        //         message: i18n.str`The backend reported a problem: HTTP status #${error.status}`,
        //         description: i18n.str`Diagnostic from ${error.info.url} is '${error.message}'`,
        //         details:
        //           error.type === ErrorType.CLIENT ||
        //             error.type === ErrorType.SERVER
        //             ? error.payload.hint
        //             : undefined,
        //         type: "ERROR" as const,
        //       };
        //   return (
        //     <Fragment>
        //       <NotificationCard notification={notif} />
        //       <LoginPage />
        //     </Fragment>
        //   );
        // }}
        // onUnauthorized={() => {
        //   return (
        //     <Fragment>
        //       <NotificationCard
        //         notification={{
        //           message: i18n.str`Access denied`,
        //           description: i18n.str`The access token provided is invalid`,
        //           type: "ERROR",
        //         }}
        //       />
        //       <LoginPage />
        //     </Fragment>
        //   );
        // }}
      />
    </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 = useInstanceKYCDetails();
  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;

  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: "KYC verification needed",
        description: (
          <div>
            <p>
              <i18n.Translate>
                Some transfer are on hold until a KYC process is completed. Go
                to the KYC section in the left panel 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>
        ),
      }}
    />
  );
}
