import {
  AbsoluteTime,
  Challenge,
  ChallengeResponse,
  HttpStatusCode,
  opEmptySuccess,
  TalerErrorCode,
  TanChannel,
  TranslatedString,
} from "@gnu-taler/taler-util";
import {
  ButtonBetter,
  LocalNotificationBanner,
  SafeHandlerTemplate,
  ShowInputErrorLabel,
  Time,
  undefinedIfEmpty,
  useBankCoreApiContext,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { doAutoFocus } from "./PaytoWireTransferForm.js";

const TALER_SCREEN_ID = 9;
export interface Props {
  onCompleted: SafeHandlerTemplate<[challenges: string[]], any>;
  username: string;
  onCancel(): void;
  description: TranslatedString;
  currentChallenge: ChallengeResponse;
}

function SolveChallenge({
  challenge,
  onCancel,
  onSolved,
  username,
  expiration,
}: {
  onCancel: () => void;
  challenge: Challenge;
  expiration: AbsoluteTime;
  onSolved: () => void;
  username: string;
}): VNode {
  const { i18n } = useTranslationContext();
  const [tanCode, setTanCode] = useState<string>();
  const {
    lib: { bank: api },
  } = useBankCoreApiContext();
  const [notification, safeFunctionHandler] = useLocalNotificationBetter();

  const [showExpired, setExpired] = useState(
    expiration !== undefined && AbsoluteTime.isExpired(expiration),
  );

  const errors = undefinedIfEmpty({
    code: !tanCode ? i18n.str`Required` : undefined,
  });

  useEffect(() => {
    if (showExpired) return;
    const remain = AbsoluteTime.remaining(expiration).d_ms;
    if (remain === "forever") return;
    const handler = setTimeout(() => {
      setExpired(true);
    }, remain);
    return () => {
      clearTimeout(handler);
    };
  }, []);

  const doVerification = safeFunctionHandler(
    (tan: string) =>
      api.confirmChallenge(username, challenge.challenge_id, { tan }),
    !errors ? [tanCode!] : undefined,
  );
  doVerification.onFail = (resp) => {
    switch (resp.case) {
      case TalerErrorCode.BANK_TRANSACTION_NOT_FOUND:
        return i18n.str`Unknown challenge.`;
      case HttpStatusCode.Unauthorized:
        return i18n.str`Failed to validate the verification code.`;
      case HttpStatusCode.TooManyRequests:
        return i18n.str`Too many challenges are active right now, you must wait or confirm current challenges.`;
      case TalerErrorCode.BANK_TAN_CHALLENGE_FAILED:
        return i18n.str`Wrong authentication number.`;
      case TalerErrorCode.BANK_TAN_CHALLENGE_EXPIRED:
        return i18n.str`Expired challenge.`;
    }
  };
  doVerification.onSuccess = onSolved;

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

      <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-6 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
        <div class="px-4 sm:px-0">
          <h2 class="text-base font-semibold leading-7 text-gray-900">
            <span
              class="text-sm text-black font-semibold leading-6 "
              id="availability-label"
            >
              <i18n.Translate>
                Submit the transmitted code number.
              </i18n.Translate>
            </span>
          </h2>
          <p class="mt-2 text-sm text-gray-500">
            {(function (c): VNode {
              switch (c.tan_channel) {
                case TanChannel.EMAIL:
                  return (
                    <i18n.Translate>
                      The verification code sent to the email address starting
                      with <b>"{c.tan_info}"</b>
                    </i18n.Translate>
                  );
                case TanChannel.SMS:
                  return (
                    <i18n.Translate>
                      The verification code sent to the phone number starting
                      with <b>"{c.tan_info}"</b>
                    </i18n.Translate>
                  );
              }
            })(challenge)}
          </p>
        </div>

        <div class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2">
          <div class="px-4 mt-4 ">
            <form
              class="space-y-6"
              noValidate
              onSubmit={(e) => {
                e.preventDefault();
              }}
              autoCapitalize="none"
              autoCorrect="off"
            >
              <div>
                <label
                  for="username"
                  class="block text-sm font-medium leading-6 text-gray-900"
                >
                  <i18n.Translate>Code</i18n.Translate>
                </label>
                <div class="mt-2">
                  <input
                    ref={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={tanCode ?? ""}
                    enterkeyhint="next"
                    placeholder="T-12345678"
                    autocomplete="username"
                    title={i18n.str`Username of the account`}
                    required
                    onInput={(e): void => {
                      setTanCode(e.currentTarget.value);
                    }}
                  />
                  <ShowInputErrorLabel
                    message={errors?.code}
                    isDirty={tanCode !== undefined}
                  />
                </div>
              </div>
            </form>
            {expiration.t_ms === "never" ? undefined : (
              <p class="text-gray-400 text-sm mt-2">
                <i18n.Translate>
                  It will expired at{" "}
                  <Time format="HH:mm" timestamp={expiration} />
                </i18n.Translate>
              </p>
            )}
            {showExpired ? (
              <p class="text-sm">
                <i18n.Translate>
                  The challenge is expired and can't be solved but you can go
                  back and create a new challenge.
                </i18n.Translate>
              </p>
            ) : undefined}

            <div class="mt-6 mb-4 flex justify-between">
              <button
                type="button"
                name="cancel"
                class="text-sm font-semibold leading-6 text-gray-900"
                onClick={onCancel}
              >
                <i18n.Translate>Back</i18n.Translate>
              </button>

              <ButtonBetter
                type="submit"
                name="send again"
                class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 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"
                onClick={doVerification}
              >
                <i18n.Translate>Verify</i18n.Translate>
              </ButtonBetter>
            </div>
          </div>
        </div>
      </div>
    </Fragment>
  );
}

export function SolveMFAChallenges({
  currentChallenge,
  username,
  description,
  onCompleted,
  onCancel,
}: Props): VNode {
  const { i18n } = useTranslationContext();

  const [solved, setSolved] = useState<string[]>([]);
  const [selected, setSelected] = useState<{
    ch: Challenge;
    expiration: AbsoluteTime;
  }>();
  const [notification, safeFunctionHandler] = useLocalNotificationBetter();

  const {
    lib: { bank: api },
  } = useBankCoreApiContext();
  // FIXME: we should save here also the expiration of the
  // tan channel to be used when the user press "i have the code"
  const [retransmission, setRetransmission] = useState<
    Record<string, AbsoluteTime | undefined>
  >({});

  if (selected) {
    return (
      <SolveChallenge
        onCancel={() => setSelected(undefined)}
        challenge={selected.ch}
        expiration={selected.expiration}
        username={username}
        onSolved={() => {
          setSelected(undefined);
          const total = [...solved, selected.ch.challenge_id];
          const enough = currentChallenge.combi_and
            ? total.length === currentChallenge.challenges.length
            : total.length > 0;

          if (enough) {
            onCompleted.withArgs(total).call();
          } else {
            setSolved(total);
          }
        }}
      />
    );
  }

  const currentSolved = currentChallenge.challenges.filter(
    ({ challenge_id }) => solved.indexOf(challenge_id) !== -1,
  );
  const hasSolvedEnough = currentChallenge.combi_and
    ? currentSolved.length === currentChallenge.challenges.length
    : currentSolved.length > 0;

  const sendMessage = safeFunctionHandler((ch: Challenge) =>
    api.sendChallenge(username, ch.challenge_id),
  );
  sendMessage.onSuccess = (success, ch) => {
    if (success.earliest_retransmission) {
      setRetransmission({
        ...retransmission,
        [ch.challenge_id]: AbsoluteTime.fromProtocolTimestamp(
          success.earliest_retransmission,
        ),
      });
    }
    setSelected({
      ch,
      expiration: !success.solve_expiration
        ? AbsoluteTime.never()
        : AbsoluteTime.fromProtocolTimestamp(success.solve_expiration),
    });
  };

  sendMessage.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.Unauthorized:
        return i18n.str`Failed to send the verification code.`;
      case HttpStatusCode.Forbidden:
        return i18n.str`The request was valid, but the server is refusing action.`;
      case HttpStatusCode.NotFound:
        return i18n.str`The backend is not aware of the specified MFA challenge.`;
      case HttpStatusCode.TooManyRequests:
        return i18n.str`It is too early to request another transmission of the challenge.`;
      case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED:
        return i18n.str`Code transmission failed.`;
    }
  };

  const complete = onCompleted.withArgs(solved);

  const selectChallenge = safeFunctionHandler(async (ch: Challenge) => {
    setSelected({
      ch,
      expiration: AbsoluteTime.never(),
    });
    return opEmptySuccess();
  });

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

      <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-6 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
        <div class="px-4 sm:px-0">
          <h2 class="text-base font-semibold leading-7 text-gray-900">
            <span
              class="text-sm text-black font-semibold leading-6 "
              id="availability-label"
            >
              <i18n.Translate>
                Multi-factor authentication required
              </i18n.Translate>
            </span>
          </h2>
          <p class="mt-2 text-sm text-gray-500">
            <i18n.Translate>
              This operation is protected with second factor authentication. In
              order to complete it we need to verify your identity using the
              authentication channel you provided.
            </i18n.Translate>
          </p>
        </div>

        <div class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2">
          <div class="px-4 mt-4 ">
            <div class="w-full">
              <div class="border-gray-100">
                <h2 class="text-base font-semibold leading-10 text-gray-900">
                  <span class=" text-black font-semibold leading-6 ">
                    {description}
                  </span>
                </h2>
              </div>
            </div>

            <h2 class="text-base  leading-7 text-gray-900 ">
              <span class="text-sm  leading-6 " id="availability-label">
                {currentChallenge.challenges.length === 1 ? (
                  <i18n.Translate>
                    The next challenge needs to be completed to confirm the
                    operation.
                  </i18n.Translate>
                ) : currentChallenge.combi_and ? (
                  <i18n.Translate>
                    All the next challenges need to be completed to confirm the
                    operation.
                  </i18n.Translate>
                ) : (
                  <i18n.Translate>
                    One of the next challenges need to be completed to confirm
                    the operation.
                  </i18n.Translate>
                )}
              </span>
            </h2>
            {currentChallenge.challenges.map((challenge) => {
              const time = retransmission[challenge.challenge_id] ?? AbsoluteTime.now();
              const alreadySent = !AbsoluteTime.isExpired(time);
              const noNeedToComplete =
                hasSolvedEnough ||
                solved.indexOf(challenge.challenge_id) !== -1;

              const doSelect = noNeedToComplete
                ? selectChallenge
                : selectChallenge.withArgs(challenge);

              const doSend =
                alreadySent || noNeedToComplete
                  ? sendMessage
                  : sendMessage.withArgs(challenge);

              return (
                <div class="rounded-xl border px-2 my-2">
                  <dl class="divide-y divide-gray-100">
                    <div class="px-4 py-2 sm:grid  sm:gap-4 sm:px-0">
                      <dt class="text-sm font-medium leading-6 text-gray-900">
                        {((ch: TanChannel): VNode => {
                          switch (ch) {
                            case TanChannel.SMS:
                              return (
                                <i18n.Translate>
                                  To an phone starting with "
                                  {challenge.tan_info}"
                                </i18n.Translate>
                              );
                            case TanChannel.EMAIL:
                              return (
                                <i18n.Translate>
                                  To an email starting with "
                                  {challenge.tan_info}"
                                </i18n.Translate>
                              );
                          }
                        })(challenge.tan_channel)}
                      </dt>
                      <dd class="mt-1 text-sm leading-6 text-gray-700  sm:mt-0">
                        <div class="flex justify-between">
                          <ButtonBetter
                            type="button"
                            name="cancel"
                            class="text-sm font-semibold leading-6 text-gray-900"
                            onClick={doSelect}
                          >
                            <i18n.Translate>I have a code</i18n.Translate>
                          </ButtonBetter>

                          <ButtonBetter
                            type="submit"
                            name="send again"
                            class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 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"
                            onClick={doSend}
                          >
                            <i18n.Translate>Send me a message</i18n.Translate>
                          </ButtonBetter>
                        </div>
                      </dd>
                      {alreadySent && time.t_ms !== "never" ? (
                        <p class="text-sm text-gray-600">
                          <i18n.Translate>
                            You have to wait until{" "}
                            <Time format="HH:mm" timestamp={time} /> to send a
                            new code.
                          </i18n.Translate>
                        </p>
                      ) : undefined}
                    </div>
                  </dl>
                </div>
              );
            })}

            <div class="mt-6 mb-4 flex justify-between">
              <button
                type="button"
                name="cancel"
                class="text-sm font-semibold leading-6 text-gray-900"
                onClick={onCancel}
              >
                <i18n.Translate>Cancel</i18n.Translate>
              </button>

              <ButtonBetter
                type="submit"
                name="send again"
                class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-indigo-600 px-3 py-2 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"
                onClick={complete}
              >
                <i18n.Translate>Complete</i18n.Translate>
              </ButtonBetter>
            </div>
          </div>
        </div>
      </div>
    </Fragment>
  );
}
