import {
  AbsoluteTime,
  assertUnreachable,
  Challenge,
  ChallengeResponse,
  HttpStatusCode,
  opEmptySuccess,
  TalerErrorCode,
  TanChannel,
} from "@gnu-taler/taler-util";
import {
  ButtonBetterBulma,
  LocalNotificationBannerBulma,
  SafeHandlerTemplate,
  undefinedIfEmpty,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { format } from "date-fns";
import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";
import { useSessionContext } from "../context/session.js";
import {
  datetimeFormatForSettings,
  usePreference,
} from "../hooks/preference.js";
import { FormErrors, FormProvider } from "./form/FormProvider.js";
import { Input } from "./form/Input.js";


const TALER_SCREEN_ID = 5;

export interface Props {
  onCompleted: SafeHandlerTemplate<[challenges: string[]], any>;
  onCancel(): void;
  currentChallenge: ChallengeResponse;
}

interface Form {
  code: string;
}

function SolveChallenge({
  challenge,
  expiration,
  onCancel,
  onSolved,
}: {
  onCancel: () => void;
  challenge: Challenge;
  expiration: AbsoluteTime;
  onSolved: () => void;
}): VNode {
  const { i18n } = useTranslationContext();
  const { state: session, lib, logIn } = useSessionContext();

  const [value, setValue] = useState<Partial<Form>>({});

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

  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 errors = undefinedIfEmpty<FormErrors<Form>>({
    code: showExpired
      ? i18n.str`Expired`
      : !value.code
        ? i18n.str`Required`
        : undefined,
  });
  function valueHandler(s: (d: Partial<Form>) => Partial<Form>): void {
    const next = s(value);
    const v: Form = {
      code: next.code ?? "",
    };
    setValue(v);
  }
  const data = !value.code || !!errors ? undefined : { tan: value.code };
  const [notification, safeFunctionHandler] = useLocalNotificationBetter();
  const verify = safeFunctionHandler(
    lib.instance.confirmChallenge.bind(lib.instance),
    !data ? undefined : [challenge.challenge_id, data],
  );
  verify.onSuccess = onSolved;
  verify.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.Unauthorized:
        return i18n.str`Unauthorized`;
      case TalerErrorCode.MERCHANT_TAN_CHALLENGE_FAILED:
        return i18n.str`Sending the code failed`;
      case TalerErrorCode.MERCHANT_TAN_CHALLENGE_UNKNOWN:
        return i18n.str`Challenge expired`;
      case TalerErrorCode.MERCHANT_TAN_TOO_MANY_ATTEMPTS:
        return i18n.str`Too many attempts`;
    }
  };

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

      <div class="columns is-centered" style={{ margin: "auto" }}>
        <div class="column is-two-thirds ">
          <div class="modal-card" style={{ width: "100%", margin: 0 }}>
            <header
              class="modal-card-head"
              style={{ border: "1px solid", borderBottom: 0 }}
            >
              <p class="modal-card-title">
                <i18n.Translate>Validation code sent.</i18n.Translate>
              </p>
            </header>
            <section
              class="modal-card-body"
              style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }}
            >
              {(function (): VNode {
                switch (challenge.tan_channel) {
                  case TanChannel.SMS:
                    return (
                      <i18n.Translate>
                        The verification code sent to the phone number starting
                        with "<b>{challenge.tan_info}</b>"
                      </i18n.Translate>
                    );
                  case TanChannel.EMAIL:
                    return (
                      <i18n.Translate>
                        The verification code sent to the email address starting
                        with "<b>{challenge.tan_info}</b>"
                      </i18n.Translate>
                    );
                }
              })()}
              <FormProvider<Form>
                name="settings"
                errors={errors}
                object={value}
                valueHandler={valueHandler}
              >
                <Input<Form>
                  label={i18n.str`Verification code`}
                  name="code"
                  readonly={showExpired}
                />
              </FormProvider>
              {expiration.t_ms === "never" ? undefined : (
                <p>
                  <i18n.Translate>
                    It will expire at{" "}
                    {format(
                      expiration.t_ms,
                      datetimeFormatForSettings(settings),
                    )}
                  </i18n.Translate>
                </p>
              )}
              {showExpired ? (
                <p>
                  <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}
            </section>
            <footer
              class="modal-card-foot "
              style={{
                justifyContent: "space-between",
                border: "1px solid",
                borderTop: 0,
              }}
            >
              <button class="button" type="button" onClick={onCancel}>
                <i18n.Translate>Back</i18n.Translate>
              </button>
              <ButtonBetterBulma type="submit" onClick={verify}>
                <i18n.Translate>Verify</i18n.Translate>
              </ButtonBetterBulma>
            </footer>
          </div>
        </div>
      </div>
    </Fragment>
  );
}

export function SolveMFAChallenges({
  currentChallenge,
  onCompleted,
  onCancel,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  const { state: session, lib, logIn } = useSessionContext();

  const [solved, setSolved] = useState<string[]>([]);
  // 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<TanChannel, AbsoluteTime>
  >({
    email: AbsoluteTime.now(),
    sms: AbsoluteTime.now(),
  });

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

  if (selected) {
    return (
      <SolveChallenge
        onCancel={() => setSelected(undefined)}
        challenge={selected.ch}
        expiration={selected.expiration}
        // onSolved={() => {
        //   setSelected(undefined);
        //   const newSolved = [...solved, selected.ch.challenge_id];

        //   const done = currentChallenge.combi_and
        //     ? newSolved.length === currentChallenge.challenges.length
        //     : newSolved.length > 0;

        //   if (done) {
        //     onCompleted(newSolved);
        //   } else {
        //     setSolved(newSolved);
        //   }
        // }}
        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 hasSolvedEnough = currentChallenge.combi_and
    ? solved.length === currentChallenge.challenges.length
    : solved.length > 0;

  const sendMessage = safeFunctionHandler((ch: Challenge) =>
    lib.instance.sendChallenge(ch.challenge_id),
  );
  sendMessage.onSuccess = (success, ch) => {
    if (success.earliest_retransmission) {
      setRetransmission({
        ...retransmission,
        [ch.tan_channel]: 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 TalerErrorCode.MERCHANT_TAN_CHALLENGE_UNKNOWN:
        return i18n.str`The backend is not aware of the specified MFA challenge.`;
      case TalerErrorCode.MERCHANT_TAN_MFA_HELPER_EXEC_FAILED:
        return i18n.str`Code transmission failed.`;
      case TalerErrorCode.MERCHANT_TAN_CHALLENGE_SOLVED:
        return i18n.str`Already solved.`;
      case TalerErrorCode.MERCHANT_TAN_TOO_EARLY:
        return i18n.str`It is too early to request another transmission of the challenge.`;
    }
  };
  const doComplete = onCompleted.withArgs(solved);

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

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

      <div class="columns is-centered" style={{ margin: "auto" }}>
        <div class="column is-two-thirds ">
          <div class="modal-card" style={{ width: "100%", margin: 0 }}>
            <header
              class="modal-card-head"
              style={{ border: "1px solid", borderBottom: 0 }}
            >
              <p class="modal-card-title">
                <i18n.Translate>
                  Multi-factor authentication required.
                </i18n.Translate>
              </p>
            </header>
            <section
              class="modal-card-body"
              style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }}
            >
              {currentChallenge.combi_and ? (
                <i18n.Translate>
                  You need to complete all of this requirements.
                </i18n.Translate>
              ) : (
                <i18n.Translate>
                  You need to complete at least one of this requirements.
                </i18n.Translate>
              )}
            </section>
            {currentChallenge.challenges.map((challenge) => {
              const time = retransmission[challenge.tan_channel];
              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 (
                <section
                  class="modal-card-body"
                  style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }}
                >
                  {(function (ch: TanChannel): VNode {
                    switch (ch) {
                      case TanChannel.SMS:
                        return (
                          <i18n.Translate>
                            An SMS to the phone number starting with{" "}
                            {challenge.tan_info}
                          </i18n.Translate>
                        );
                      case TanChannel.EMAIL:
                        return (
                          <i18n.Translate>
                            An email to the address starting with{" "}
                            {challenge.tan_info}
                          </i18n.Translate>
                        );
                    }
                  })(challenge.tan_channel)}

                  {alreadySent && time.t_ms !== "never" ? (
                    <p>
                      <i18n.Translate>
                        You have to wait until{" "}
                        {format(time.t_ms, datetimeFormatForSettings(settings))}
                        to send a new code.
                      </i18n.Translate>
                    </p>
                  ) : undefined}
                  <div
                    style={{
                      justifyContent: "space-between",
                      display: "flex",
                    }}
                  >
                    <ButtonBetterBulma
                      // disabled={
                      //   hasSolvedEnough || solved.indexOf(d.challenge_id) !== -1
                      // }
                      class="button"
                      // onClick={() => {
                      //   setSelected({
                      //     ch: d,
                      //     expiration: AbsoluteTime.never(),
                      //   });
                      // }}
                      onClick={doSelect}
                    >
                      <i18n.Translate>I have a code</i18n.Translate>
                    </ButtonBetterBulma>
                    <ButtonBetterBulma
                      // disabled={
                      //   hasSolvedEnough ||
                      //   solved.indexOf(d.challenge_id) !== -1 ||
                      //   alreadySent
                      // }
                      onClick={doSend}
                      // onClick={() => doSendCodeImpl(d)}
                    >
                      <i18n.Translate>Send me a message</i18n.Translate>
                    </ButtonBetterBulma>
                  </div>
                </section>
              );
            })}
            <footer
              class="modal-card-foot "
              style={{
                justifyContent: "space-between",
                border: "1px solid",
                borderTop: 0,
              }}
            >
              <button class="button" onClick={onCancel}>
                <i18n.Translate>Cancel</i18n.Translate>
              </button>
              <div/>
            </footer>
          </div>
        </div>
      </div>
    </Fragment>
  );
}
