/*
 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 {
  ChallengerApi,
  HttpStatusCode,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import {
  Attention,
  Button,
  LocalNotificationBanner,
  ShowInputErrorLabel,
  useChallengerApiContext,
  useLocalNotificationHandler,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { useSessionState } from "../hooks/session.js";

export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/;

type Props = {
  nonce: string;
  onComplete: () => void;
};

export function AnswerChallenge({ nonce, onComplete }: Props): VNode {
  const { lib } = useChallengerApiContext();
  const { i18n } = useTranslationContext();
  const { state, accepted, completed } = useSessionState();
  const [notification, withErrorHandler] = useLocalNotificationHandler();
  const [pin, setPin] = useState<string | undefined>();
  const [lastTryError, setLastTryError] =
    useState<ChallengerApi.InvalidPinResponse>();
  const errors = undefinedIfEmpty({
    pin: !pin ? i18n.str`Can't be empty` : undefined,
  });

  const lastEmail = !state
    ? undefined
    : !state.lastStatus
      ? undefined
      : ((state.lastStatus.last_address as any)["email"] as string);

  const onSendAgain =
    !state || lastEmail === undefined
      ? undefined
      : withErrorHandler(
          async () => {
            if (!lastEmail) return;
            return await lib.bank.challenge(nonce, { email: lastEmail });
          },
          (ok) => {
            if ("redirectURL" in ok.body) {
              completed(ok.body.redirectURL);
            } else {
              accepted({
                attemptsLeft: ok.body.attempts_left,
                nextSend: ok.body.next_tx_time,
                transmitted: ok.body.transmitted,
              });
            }
            return undefined;
          },
          (fail) => {
            switch (fail.case) {
              case HttpStatusCode.BadRequest:
                return i18n.str``;
              case HttpStatusCode.NotFound:
                return i18n.str``;
              case HttpStatusCode.NotAcceptable:
                return i18n.str``;
              case HttpStatusCode.TooManyRequests:
                return i18n.str``;
              case HttpStatusCode.InternalServerError:
                return i18n.str``;
            }
          },
        );

  const onCheck =
    lastTryError && lastTryError.exhausted
      ? undefined
      : withErrorHandler(
          async () => {
            return lib.bank.solve(nonce, { pin: pin! });
          },
          (ok) => {
            completed(ok.body.redirectURL as URL);
            onComplete();
          },
          (fail) => {
            switch (fail.case) {
              case HttpStatusCode.BadRequest:
                return i18n.str`Invalid request`;
              case HttpStatusCode.Forbidden: {
                setLastTryError(fail.body);
                return i18n.str`Invalid pin`;
              }
              case HttpStatusCode.NotFound:
                return i18n.str``;
              case HttpStatusCode.NotAcceptable:
                return i18n.str``;
              case HttpStatusCode.TooManyRequests:
                return i18n.str``;
              case HttpStatusCode.InternalServerError:
                return i18n.str``;
              default:
                assertUnreachable(fail);
            }
          },
        );

  if (!state) {
    return <div>no state</div>;
  }

  if (!state.lastTry) {
    return <div>you should do a challenge first</div>;
  }

  return (
    <Fragment>
      <LocalNotificationBanner notification={notification} />

      <div class="isolate bg-white px-6 py-12">
        <div class="mx-auto max-w-2xl text-center">
          <h2 class="text-3xl font-bold tracking-tight text-gray-900 sm:text-4xl">
            <i18n.Translate>
              Please enter the TAN you received to authenticate.
            </i18n.Translate>
          </h2>
          <p class="mt-2 text-lg leading-8 text-gray-600">
            {state.lastTry.transmitted ? (
              <i18n.Translate>
                A TAN was sent to your address &quot;{lastEmail}&quot;.
              </i18n.Translate>
            ) : (
              <Attention title={i18n.str`Resend failed`} type="warning">
                <i18n.Translate>
                  We recently already sent a TAN to your address &quot;
                  {lastEmail}&quot;. A new TAN will not be transmitted again
                  before &quot;{state.lastTry.nextSend}&quot;.
                </i18n.Translate>
              </Attention>
            )}
          </p>
          {!lastTryError ? undefined : (
            <p class="mt-2 text-lg leading-8 text-gray-600">
              <i18n.Translate>
                You can try another PIN but just{" "}
                {lastTryError.auth_attempts_left} times more.
              </i18n.Translate>
            </p>
          )}
        </div>
        <form
          method="POST"
          class="mx-auto mt-16 max-w-xl sm:mt-20"
          onSubmit={(e) => {
            e.preventDefault();
          }}
        >
          <div class="grid grid-cols-1 gap-x-8 gap-y-6">
            <div class="sm:col-span-2">
              <label
                for="pin"
                class="block text-sm font-semibold leading-6 text-gray-900"
              >
                <i18n.Translate>TAN code</i18n.Translate>
              </label>
              <div class="mt-2.5">
                <input
                  type="number"
                  name="pin"
                  id="pin"
                  maxLength={64}
                  value={pin}
                  onChange={(e) => {
                    setPin(e.currentTarget.value);
                  }}
                  placeholder="12345678"
                  class="block w-full rounded-md border-0 px-3.5 py-2 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"
                />
                <ShowInputErrorLabel
                  message={errors?.pin}
                  isDirty={pin !== undefined}
                />
              </div>
            </div>

            <p class="mt-3 text-sm leading-6 text-gray-400">
              <i18n.Translate>
                You have {state.lastTry.attemptsLeft} attempts left.
              </i18n.Translate>
            </p>
          </div>

          <div class="mt-10">
            <Button
              type="submit"
              class="block w-full disabled:bg-gray-300 rounded-md bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold 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"
              disabled={!onCheck}
              handler={onCheck}
            >
              <i18n.Translate>Check</i18n.Translate>
            </Button>
          </div>
          <div class="mt-10">
            <Button
              type="submit"
              disabled={!onSendAgain}
              class="block w-full disabled:bg-gray-300 rounded-md bg-indigo-600 px-3.5 py-2.5 text-center text-sm font-semibold 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"
              handler={onSendAgain}
            >
              <i18n.Translate>Send again</i18n.Translate>
            </Button>
          </div>
        </form>
      </div>
    </Fragment>
  );
}

export function undefinedIfEmpty<T extends object>(obj: T): T | undefined {
  return Object.keys(obj).some(
    (k) => (obj as Record<string, T>)[k] !== undefined,
  )
    ? obj
    : undefined;
}
