/*
 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 {
  AbsoluteTime,
  Duration,
  HttpStatusCode,
  TalerErrorCode,
  TokenRequest,
  createRFC8959AccessTokenEncoded,
} from "@gnu-taler/taler-util";
import {
  Attention,
  ButtonBetter,
  LocalNotificationBanner,
  RouteDefinition,
  ShowInputErrorLabel,
  useBankCoreApiContext,
  useChallengeHandler,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { VNode, h } from "preact";
import { useState } from "preact/hooks";
import { useSessionState } from "../hooks/session.js";
import { undefinedIfEmpty } from "../utils.js";
import { doAutoFocus } from "./PaytoWireTransferForm.js";
import { USERNAME_REGEX } from "./RegistrationPage.js";
import { SolveMFAChallenges } from "./SolveMFA.js";
import { opEmptySuccess } from "@gnu-taler/taler-util";

const TALER_SCREEN_ID = 104;

export const SESSION_DURATION = Duration.toTalerProtocolDuration(
  Duration.fromSpec({
    // seconds: 6,
    minutes: 30,
  }),
);

/**
 * Collect and submit login data.
 */
LoginForm.SCREEN_ID = TALER_SCREEN_ID;
export function LoginForm({
  currentUser,
  fixedUser,
  routeRegister,
}: {
  fixedUser?: boolean;
  currentUser?: string;
  routeRegister?: RouteDefinition;
}): VNode {
  const session = useSessionState();

  const sessionUser =
    session.state.status !== "loggedOut" ? session.state.username : undefined;
  const [username, setUsername] = useState<string | undefined>(
    currentUser ?? sessionUser,
  );
  const [password, setPassword] = useState<string | undefined>();
  const { i18n } = useTranslationContext();
  const {
    lib: { bank: api },
  } = useBankCoreApiContext();
  const [notification, safeFunctionHandler] = useLocalNotificationBetter();

  const mfa = useChallengeHandler();
  const { config } = useBankCoreApiContext();

  const errors = undefinedIfEmpty({
    username: !username
      ? i18n.str`Missing username`
      : !USERNAME_REGEX.test(username)
        ? i18n.str`Use letters, numbers or any of these characters: - . _ ~`
        : undefined,
    password: !password ? i18n.str`Missing password` : undefined,
  });

  const logout = safeFunctionHandler(async () => {
    session.logOut();
    return opEmptySuccess();
  }, []);
  logout.onSuccess = session.logOut;

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

  const login = safeFunctionHandler(
    (username: string, password: string, challengeIds: string[]) =>
      api.createAccessToken(
        username,
        { type: "basic", password },
        tokenRequest,
        { challengeIds },
      ),
    !!errors ? undefined : [username!, password!, []],
  );

  login.onSuccess = (result, username) => {
    session.logIn({
      username,
      token: createRFC8959AccessTokenEncoded(result.access_token),
      expiration: AbsoluteTime.fromProtocolTimestamp(result.expiration),
    });
  };

  login.onFail = (fail, username) => {
    switch (fail.case) {
      case HttpStatusCode.Accepted: {
        mfa.onChallengeRequired(fail.body);
        return i18n.str`A second factor authentication is required.`;
      }
      case TalerErrorCode.GENERIC_FORBIDDEN:
        return i18n.str`You have no permission to this account.`;
      case TalerErrorCode.BANK_ACCOUNT_LOCKED:
        return i18n.str`You have no permission to this account.`;
      case HttpStatusCode.Unauthorized:
        return i18n.str`Wrong credentials for "${username}"`;
      case HttpStatusCode.NotFound:
        return i18n.str`Account not found`;
    }
  };

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

  if (mfa.pendingChallenge) {
    return (
      <SolveMFAChallenges
        currentChallenge={mfa.pendingChallenge}
        description={i18n.str`Account login.`}
        onCancel={mfa.doCancelChallenge}
        username={username!}
        onCompleted={retryLogin}
      />
    );
  }
  const onlyThisUser = fixedUser || session.state.status !== "loggedOut";
  return (
    <div class="flex min-h-full flex-col justify-center ">
      <LocalNotificationBanner notification={notification} />
      <div class="sm:mx-auto sm:w-full sm:max-w-sm">
        {session.state.status !== "expired" ? undefined : (
          <Attention title={i18n.str`Session expired`} type="warning" />
        )}
        <form
          class="mt-10  space-y-6"
          noValidate
          onSubmit={(e) => {
            e.preventDefault();
            e.stopPropagation();
          }}
          autoCapitalize="none"
          autoCorrect="off"
        >
          <div>
            <label
              for="username"
              class="block text-sm font-medium leading-6 text-gray-900"
            >
              <i18n.Translate>Username</i18n.Translate>
            </label>
            <div class="mt-2">
              <input
                ref={onlyThisUser ? undefined : doAutoFocus}
                type="text"
                name="username"
                id="username"
                class="block w-full disabled:bg-gray-200 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                value={username ?? ""}
                disabled={onlyThisUser}
                enterkeyhint="next"
                placeholder="identification"
                autocomplete="username"
                title={i18n.str`Username of the account`}
                required
                onChange={(e): void => {
                  setUsername(e.currentTarget.value);
                }}
              />
              <ShowInputErrorLabel
                message={errors?.username}
                isDirty={username !== undefined}
              />
            </div>
          </div>

          <div>
            <div class="flex items-center justify-between">
              <label
                for="password"
                class="block text-sm font-medium leading-6 text-gray-900"
              >
                <i18n.Translate>Password</i18n.Translate>
              </label>
            </div>
            <div class="mt-2">
              <input
                type="password"
                name="password"
                id="password"
                autocomplete="current-password"
                ref={!onlyThisUser ? undefined : doAutoFocus}
                class="block w-full rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                enterkeyhint="send"
                value={password ?? ""}
                placeholder="Password"
                title={i18n.str`Password of the account`}
                required
                onChange={(e): void => {
                  setPassword(e.currentTarget.value);
                }}
              />
              <ShowInputErrorLabel
                message={errors?.password}
                isDirty={password !== undefined}
              />
            </div>
          </div>

          {session.state.status !== "loggedOut" ? (
            <div class="flex justify-between">
              <ButtonBetter
                type="button"
                name="cancel"
                class="rounded-md bg-white-600 px-3 py-1.5 text-sm font-semibold leading-6 text-black shadow-sm hover:bg-gray-100 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-600"
                onClick={logout}
              >
                <i18n.Translate>Forget</i18n.Translate>
              </ButtonBetter>

              <ButtonBetter
                type="submit"
                name="check"
                class="rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
                onClick={login}
              >
                <i18n.Translate>Verify</i18n.Translate>
              </ButtonBetter>
            </div>
          ) : (
            <div>
              <ButtonBetter
                type="submit"
                name="login"
                class="flex w-full justify-center rounded-md bg-indigo-600 disabled:bg-gray-300 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
                onClick={login}
              >
                <i18n.Translate>Log in</i18n.Translate>
              </ButtonBetter>
            </div>
          )}
        </form>

        {config.allow_registrations && routeRegister && (
          <a
            name="register"
            href={routeRegister.url({})}
            class="flex justify-center border-t mt-4 rounded-md bg-blue-600 px-3 py-1.5 text-sm font-semibold leading-6 text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
          >
            <i18n.Translate>Register</i18n.Translate>
          </a>
        )}
      </div>
    </div>
  );
}
