/*
 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 {
  assertUnreachable,
  Duration,
  HttpStatusCode,
  LoginTokenScope,
  TalerMerchantApi,
} from "@gnu-taler/taler-util";
import {
  ButtonBetterBulma,
  LocalNotificationBannerBulma,
  useChallengeHandler,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import {
  FormErrors,
  FormProvider,
  TalerForm,
} from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
import { InputDuration } from "../../../../components/form/InputDuration.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { NotificationCard } from "../../../../components/menu/index.js";
import { SolveMFAChallenges } from "../../../../components/SolveMFA.js";
import { useSessionContext } from "../../../../context/session.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
import { getAvailableForPersona } from "../../../../components/menu/SideBar.js";
import { UIElement, usePreference } from "../../../../hooks/preference.js";

const TALER_SCREEN_ID = 29;

type Entity = {
  scope: TalerMerchantApi.LoginTokenRequest["scope"];
  duration: Duration;
  description: string;
  password: string;
} & TalerForm;

interface Props {
  onCreated: (asd: TalerMerchantApi.LoginTokenSuccessResponse) => void;
  onBack?: () => void;
}

const ALL_VALID_TOKEN_SCOPE = [
  "",
  LoginTokenScope.ReadOnly,
  LoginTokenScope.OrderSimple,
  LoginTokenScope.OrderPos,
  LoginTokenScope.OrderManagement,
  LoginTokenScope.OrderFull,
  // LoginTokenScope.All_Refreshable,
  LoginTokenScope.ReadOnly_Refreshable,
  LoginTokenScope.OrderSimple_Refreshable,
  LoginTokenScope.OrderPos_Refreshable,
  LoginTokenScope.OrderManagement_Refreshable,
  LoginTokenScope.OrderFull_Refreshable,
  LoginTokenScope.All,
];
const SANE_VALID_TOKEN_SCOPE = [
  "",
  LoginTokenScope.ReadOnly,
  LoginTokenScope.OrderSimple,
  LoginTokenScope.OrderPos,
  LoginTokenScope.OrderManagement,
  LoginTokenScope.OrderFull,
  LoginTokenScope.All,
];

export function CreatePage({ onCreated, onBack }: Props): VNode {
  const { i18n } = useTranslationContext();

  const [state, setState] = useState<Partial<Entity>>({});
  const { state: session, lib } = useSessionContext();

  const errors = undefinedIfEmpty<FormErrors<Entity>>({
    password: !state.password ? i18n.str`Required` : undefined,
    duration: !state.duration ? i18n.str`Required` : undefined,
    description: !state.description ? i18n.str`Required` : undefined,
    scope: !state.scope ? i18n.str`Required` : undefined,
  });
  const [{ persona }] = usePreference();
  const scopeList = getAvailableForPersona(persona ?? "developer")[
    UIElement.option_refreshableScopes
  ]
    ? ALL_VALID_TOKEN_SCOPE
    : SANE_VALID_TOKEN_SCOPE;

  const hasErrors = errors !== undefined;

  const [notification, safeFunctionHandler] = useLocalNotificationBetter();
  const mfa = useChallengeHandler();

  const data: TalerMerchantApi.LoginTokenRequest = {
    scope: state.scope!,
    description: state.description,
    duration: !state.duration
      ? undefined
      : Duration.toTalerProtocolDuration(state.duration),
  };
  const create = safeFunctionHandler(
    (
      pwd: string,
      request: TalerMerchantApi.LoginTokenRequest,
      challengeIds: string[],
    ) =>
      lib.instance.createAccessToken(session.instance, pwd, request, {
        challengeIds,
      }),
    !!errors || !state.password ? undefined : [state.password, data, []],
  );
  create.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.Accepted:
        mfa.onChallengeRequired(fail.body);
        return undefined;
      case HttpStatusCode.Unauthorized:
        return i18n.str`Check the password.`;
      case HttpStatusCode.NotFound:
        return i18n.str`Instance not found.`;
    }
  };
  create.onSuccess = onCreated;

  const retry = create.lambda((ids: string[]) => [
    create.args![0],
    create.args![1],
    ids,
  ]);

  if (mfa.pendingChallenge) {
    return (
      <SolveMFAChallenges
        currentChallenge={mfa.pendingChallenge}
        onCompleted={retry}
        onCancel={mfa.doCancelChallenge}
      />
    );
  }
  return (
    <Fragment>
      <LocalNotificationBannerBulma notification={notification} />
      <section class="section is-main-section">
        <div class="columns">
          <div class="column" />
          <div class="column is-four-fifths">
            <FormProvider
              object={state}
              valueHandler={setState}
              errors={errors}
            >
              <Input<Entity>
                name="description"
                label={i18n.str`Description`}
                help={i18n.str`Helps you remember where this token is being used before deleting it.`}
              />

              <InputDuration<Entity>
                name="duration"
                label={i18n.str`Duration`}
                withForever
                tooltip={i18n.str`Time the access token will be valid.`}
              />
              {state.scope?.endsWith(":refreshable") && (
                <NotificationCard
                  notification={{
                    type: "WARN",
                    message: i18n.str`Refreshable tokens can pose a security risk!`,
                    description: i18n.str`Refreshable tokens can be refreshed before their lifetime ends, effectively giving any bearer access without expiration. Only use this if you know what you are doing and you have evaluated associated risks especially in respect to the permissions granted by the scope.`,
                  }}
                />
              )}
              <InputSelector
                name="scope"
                label={i18n.str`Scope`}
                tooltip={i18n.str`The scope defines the set of permissions for the access token. Refreshable tokens has the permission to extend the expiration time.`}
                values={scopeList}
                help={((s) => {
                  if (!s) return "";
                  switch (s) {
                    case LoginTokenScope.All:
                      return i18n.str`Allows all operations without limit.`;
                    case LoginTokenScope.ReadOnly:
                      return i18n.str`Allows all operations to read information.`;
                    case LoginTokenScope.OrderSimple:
                      return i18n.str`Allows the creation of orders and checking of payment status.`;
                    case LoginTokenScope.OrderPos:
                      return i18n.str`Allows the creation of orders, checking of payment status and inventory locking.`;
                    case LoginTokenScope.OrderManagement:
                      return i18n.str`Allows the creation of orders, checking of payment status and refunds.`;
                    case LoginTokenScope.OrderFull:
                      return i18n.str`Allows the creation of orders, checking of payment status, inventory locking and refunds.`;
                    case LoginTokenScope.ReadOnly_Refreshable:
                      return i18n.str`Allows all operations to read information with extendable expiration time.`;
                    case LoginTokenScope.OrderSimple_Refreshable:
                      return i18n.str`Allows the creation of orders and checking of payment status with extendable expiration time.`;
                    case LoginTokenScope.OrderPos_Refreshable:
                      return i18n.str`Allows the creation of orders, checking of payment status and inventory locking with extendable expiration time.`;
                    case LoginTokenScope.OrderManagement_Refreshable:
                      return i18n.str`Allows the creation of orders, checking of payment status and refunds with extendable expiration time.`;
                    case LoginTokenScope.OrderFull_Refreshable:
                      return i18n.str`Allows the creation of orders, checking of payment status, inventory locking and refunds with extendable expiration time.`;
                    case LoginTokenScope.All_Refreshable:
                      return i18n.str`All (refreshable)`;
                    case LoginTokenScope.Spa:
                      return i18n.str`Spa`;
                    case LoginTokenScope.Spa_Refreshable:
                      return i18n.str`Spa (refreshable)`;
                    default: {
                      assertUnreachable(s);
                    }
                  }
                })(state.scope)}
                toStr={(str) => {
                  if (!str) {
                    return i18n.str`Choose one`;
                  }
                  const s = str as LoginTokenScope;
                  switch (s) {
                    case LoginTokenScope.ReadOnly:
                      return i18n.str`Read only`;
                    case LoginTokenScope.All:
                      return i18n.str`All`;
                    case LoginTokenScope.OrderSimple:
                      return i18n.str`Order simple`;
                    case LoginTokenScope.OrderPos:
                      return i18n.str`Order Point-of-Sale`;
                    case LoginTokenScope.OrderManagement:
                      return i18n.str`Order management`;
                    case LoginTokenScope.OrderFull:
                      return i18n.str`Order full`;
                    case LoginTokenScope.ReadOnly_Refreshable:
                      return i18n.str`Read only (refreshable)`;
                    case LoginTokenScope.OrderSimple_Refreshable:
                      return i18n.str`Order simple (refreshable)`;
                    case LoginTokenScope.OrderPos_Refreshable:
                      return i18n.str`Order Point-of-Sale (refreshable)`;
                    case LoginTokenScope.OrderManagement_Refreshable:
                      return i18n.str`Order management (refreshable)`;
                    case LoginTokenScope.OrderFull_Refreshable:
                      return i18n.str`Order full (refreshable)`;
                    case LoginTokenScope.All_Refreshable:
                      return i18n.str`All (refreshable)`;
                    case LoginTokenScope.Spa:
                      return i18n.str`Spa`;
                    case LoginTokenScope.Spa_Refreshable:
                      return i18n.str`Spa (refreshable)`;
                    default: {
                      assertUnreachable(s);
                    }
                  }
                }}
              />
              <Input<Entity>
                name="password"
                inputType="password"
                label={i18n.str`Current password`}
              />
              <div class="buttons is-right mt-5">
                {onBack && (
                  <button class="button" type="button" onClick={onBack}>
                    <i18n.Translate>Cancel</i18n.Translate>
                  </button>
                )}
                <ButtonBetterBulma
                  type="submit"
                  data-tooltip={
                    hasErrors
                      ? i18n.str`Please complete the marked fields`
                      : i18n.str`Confirm operation`
                  }
                  onClick={create}
                >
                  <i18n.Translate>Confirm</i18n.Translate>
                </ButtonBetterBulma>
              </div>
            </FormProvider>
          </div>
          <div class="column" />
        </div>
      </section>
    </Fragment>
  );
}
