/*
 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 {
  EmptyObject,
  HttpStatusCode,
  TalerError,
  TranslatedString
} from "@gnu-taler/taler-util";
import {
  Attention,
  Button,
  LocalNotificationBanner,
  RouteDefinition,
  ShowInputErrorLabel,
  Time,
  useChallengerApiContext,
  useLocalNotificationHandler,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { useChallengeSession } from "../hooks/challenge.js";
import { SessionId, useSessionState } from "../hooks/session.js";
import { doAutoFocus } from "./AnswerChallenge.js";

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

type Props = {
  onSendSuccesful: () => void;
  session: SessionId;
  routeSolveChallenge: RouteDefinition<EmptyObject>;
  focus?: boolean;
};

export function AskChallenge({
  onSendSuccesful,
  routeSolveChallenge,
  session,
  focus,
}: Props): VNode {
  const { state, sent, saveAddress, completed } = useSessionState();
  const { lib, config } = useChallengerApiContext();

  const { i18n } = useTranslationContext();
  const [notification, withErrorHandler] = useLocalNotificationHandler();
  const [address, setEmail] = useState<string | undefined>();
  const [repeat, setRepeat] = useState<string | undefined>();
  const [remember, setRemember] = useState<boolean>(false);
  const [addrIndex, setAddrIndex] = useState<number | undefined>();

  const restrictionKeys = !config.restrictions
    ? []
    : Object.keys(config.restrictions);
  const restrictionKey = !restrictionKeys.length
    ? undefined
    : restrictionKeys[0];

  const result = useChallengeSession(session);

  if (!restrictionKey) {
    return (
      <div>
        invalid server configuration, there is no restriction in /config
      </div>
    );
  }

  const regexEmail = !config.restrictions
    ? undefined
    : config.restrictions[restrictionKey];
  const regexTest =
    regexEmail && regexEmail.regex ? new RegExp(regexEmail.regex) : EMAIL_REGEX;
  const regexHint =
    regexEmail && regexEmail.hint ? regexEmail.hint : i18n.str`invalid email`;

  const lastStatus =
    result && !(result instanceof TalerError) && result.type !== "fail"
      ? result.body
      : undefined;

  const prevAddr = !lastStatus?.last_address
    ? undefined
    : lastStatus.last_address[restrictionKey];

  const errors = undefinedIfEmpty({
    address: !address
      ? i18n.str`required`
      : !regexTest.test(address)
        ? regexHint
        : prevAddr !== undefined && address === prevAddr
          ? i18n.str`can't use the same address`
          : undefined,
    repeat: !repeat
      ? i18n.str`required`
      : address !== repeat
        ? i18n.str`doesn't match`
        : undefined,
  });

  const contact = address ? { [restrictionKey]: address } : undefined;

  const usableAddrs =
    !state?.lastAddress || !state.lastAddress.length
      ? []
      : state.lastAddress.filter((d) => !!d.address[restrictionKey]);

  const onSend =
    errors || !contact 
      ? undefined
      : withErrorHandler(
          async () => {
            return lib.challenger.challenge(session.nonce, contact);
          },
          (ok) => {
            if (ok.body.type === "completed") {
              completed(ok.body);
            } else {
              if (remember) {
                saveAddress(config.address_type, contact);
              }
              sent(ok.body);
            }
            onSendSuccesful();
          },
          (fail) => {
            switch (fail.case) {
              case HttpStatusCode.BadRequest:
                return i18n.str`The request was not accepted, try reloading the app.`;
              case HttpStatusCode.NotFound:
                return i18n.str`Challenge not found.`;
              case HttpStatusCode.NotAcceptable:
                return i18n.str`Server templates are missing due to misconfiguration.`;
              case HttpStatusCode.TooManyRequests:
                return i18n.str`There have been too many attempts to request challenge transmissions.`;
              case HttpStatusCode.InternalServerError:
                return i18n.str`Server is not able to respond due to internal problems.`;
            }
          },
        );

  if (!lastStatus) {
    return <div>no status loaded</div>;
  }

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

      <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>Enter contact details</i18n.Translate>
          </h2>
          {config.address_type === "email" ? (
            <p class="mt-2 text-lg leading-8 text-gray-600">
              <i18n.Translate>
                You will receive an email with a TAN code that must be provided
                on the next page.
              </i18n.Translate>
            </p>
          ) : config.address_type === "phone" ? (
            <p class="mt-2 text-lg leading-8 text-gray-600">
              <i18n.Translate>
                You will receive an SMS with a TAN code that must be provided on
                the next page.
              </i18n.Translate>
            </p>
          ) : (
            <p class="mt-2 text-lg leading-8 text-gray-600">
              <i18n.Translate>
                You will receive an message with a TAN code that must be
                provided on the next page.
              </i18n.Translate>
            </p>
          )}
        </div>

        {lastStatus?.last_address && (
          <Fragment>
            <Attention title={i18n.str`A code has been sent to ${prevAddr}`}>
              <i18n.Translate>
                <a href={routeSolveChallenge.url({})} class="underline">
                  <i18n.Translate>Complete the challenge here.</i18n.Translate>
                </a>
              </i18n.Translate>
            </Attention>
          </Fragment>
        )}

        {!usableAddrs.length ? undefined : (
          <div class="mx-auto max-w-xl mt-4">
            <h3>
              <i18n.Translate>Previous address</i18n.Translate>
            </h3>
            <fieldset>
              <div class="relative -space-y-px rounded-md bg-white">
                {usableAddrs.map((addr, idx) => {
                  return (
                    <label
                      data-checked={addrIndex === idx}
                      key={idx}
                      class="relative flex border-gray-200 data-[checked=true]:z-10 data-[checked=true]:bg-indigo-50 cursor-pointer flex-col rounded-tl-md rounded-tr-md border p-4 focus:outline-none md:grid md:grid-cols-2 md:pl-4 md:pr-6"
                    >
                      <span class="flex items-center text-sm">
                        <input
                          type="radio"
                          name={`addr-${idx}`}
                          value={addr.address[restrictionKey]}
                          checked={addrIndex === idx}
                          onClick={() => {
                            setAddrIndex(idx);
                            setEmail(addr.address[restrictionKey]);
                            setRepeat(addr.address[restrictionKey]);
                          }}
                          class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600 active:ring-2 active:ring-indigo-600 active:ring-offset-2"
                        />
                        <span
                          data-checked={addrIndex === idx}
                          class="ml-3 font-medium text-gray-900 data-[checked=true]:text-indigo-900 "
                        >
                          {addr.address[restrictionKey]}
                        </span>
                      </span>
                      <span
                        data-checked={addrIndex === idx}
                        class="ml-6 pl-1 text-sm md:ml-0 md:pl-0 md:text-right text-gray-500 data-[checked=true]:text-indigo-700"
                      >
                        <i18n.Translate>
                          Last used at{" "}
                          <Time
                            format="dd/MM/yyyy HH:mm:ss"
                            timestamp={addr.savedAt}
                          />
                        </i18n.Translate>
                      </span>
                    </label>
                  );
                })}
                <label
                  data-checked={addrIndex === undefined}
                  class="relative rounded-bl-md rounded-br-md flex border-gray-200 data-[checked=true]:z-10 data-[checked=true]:bg-indigo-50 cursor-pointer flex-col rounded-tl-md rounded-tr-md border p-4 focus:outline-none md:grid md:grid-cols-2 md:pl-4 md:pr-6"
                >
                  <span class="flex items-center text-sm">
                    <input
                      type="radio"
                      name="new-addr"
                      value="new-addr"
                      checked={addrIndex === undefined}
                      onClick={() => {
                        setAddrIndex(undefined);
                        setEmail(undefined);
                        setRepeat(undefined);
                      }}
                      class="h-4 w-4 border-gray-300 text-indigo-600 focus:ring-indigo-600 active:ring-2 active:ring-indigo-600 active:ring-offset-2"
                    />
                    <span
                      data-checked={addrIndex === undefined}
                      class="ml-3 font-medium text-gray-900 data-[checked=true]:text-indigo-900 "
                    >
                      <i18n.Translate>Use new address</i18n.Translate>
                    </span>
                  </span>
                </label>
              </div>
            </fieldset>
          </div>
        )}

        <form
          method="POST"
          class="mx-auto mt-4 max-w-xl "
          onSubmit={(e) => {
            e.preventDefault();
          }}
        >
          <div class="sm:col-span-2">
            <label
              for="address"
              class="block text-sm font-semibold leading-6 text-gray-900"
            >
              {(function (): TranslatedString {
                switch (config.address_type) {
                  case "email":
                    return i18n.str`Email`;
                  case "phone":
                    return i18n.str`Phone`;
                }
              })()}
            </label>
            <div class="mt-2.5">
              <input
                type="text"
                name="address"
                id="address"
                ref={focus ? doAutoFocus : undefined}
                maxLength={512}
                autocomplete={(function (): string {
                  switch (config.address_type) {
                    case "email":
                      return "email";
                    case "phone":
                      return "phone";
                  }
                })()}
                value={address ?? ""}
                onChange={(e) => {
                  setEmail(e.currentTarget.value);
                }}
                placeholder={prevAddr}
                readOnly={lastStatus.fix_address || addrIndex !== undefined}
                class="block w-full read-only:bg-slate-200 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?.address}
                isDirty={address !== undefined}
              />
            </div>
          </div>

          {lastStatus.fix_address || addrIndex !== undefined ? undefined : (
            <div class="sm:col-span-2">
              <label
                for="repeat-address"
                class="block text-sm font-semibold leading-6 text-gray-900"
              >
                {(function (): TranslatedString {
                  switch (config.address_type) {
                    case "email":
                      return i18n.str`Repeat email`;
                    case "phone":
                      return i18n.str`Repeat phone`;
                  }
                })()}
              </label>
              <div class="mt-2.5">
                <input
                  type="text"
                  name="repeat-address"
                  id="repeat-address"
                  value={repeat ?? ""}
                  onChange={(e) => {
                    setRepeat(e.currentTarget.value);
                  }}
                  autocomplete={(function (): string {
                    switch (config.address_type) {
                      case "email":
                        return "email";
                      case "phone":
                        return "phone";
                    }
                  })()}
                  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?.repeat}
                  isDirty={repeat !== undefined}
                />
              </div>
            </div>
          )}

          {lastStatus === undefined ? undefined : (
            <p class="mt-2 text-sm leading-6 text-gray-400">
              {lastStatus.changes_left < 1 ? (
                <i18n.Translate>
                  You can&#39;t change the contact address anymore.
                </i18n.Translate>
              ) : lastStatus.changes_left === 1 ? (
                <i18n.Translate>
                  You can change the contact address one last time.
                </i18n.Translate>
              ) : (
                <i18n.Translate>
                  You can change the contact address {lastStatus.changes_left}{" "}
                  more times.
                </i18n.Translate>
              )}
            </p>
          )}

          <div class="flex items-center justify-between py-2">
            <span class="flex flex-grow flex-col">
              <span
                class="text-sm text-black font-medium leading-6 "
                id="availability-label"
              >
                <i18n.Translate>
                  Remember this address for future challenges.
                </i18n.Translate>
              </span>
            </span>
            <button
              type="button"
              name={`remember switch`}
              data-enabled={remember}
              class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
              role="switch"
              aria-checked="false"
              aria-labelledby="availability-label"
              aria-describedby="availability-description"
              onClick={() => {
                setRemember(!remember);
              }}
            >
              <span
                aria-hidden="true"
                data-enabled={remember}
                class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
              ></span>
            </button>
          </div>
        </form>
        <div class="mx-auto mt-4 max-w-xl ">
          {!prevAddr ? (
            <div class="mt-10">
              <Button
                type="submit"
                disabled={!onSend}
                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={onSend}
              >
                {(function (): TranslatedString {
                  switch (config.address_type) {
                    case "email":
                      return i18n.str`Send email`;
                    case "phone":
                      return i18n.str`Send SMS`;
                  }
                })()}
              </Button>
            </div>
          ) : (
            <div class="mt-10">
              <Button
                type="submit"
                disabled={!onSend}
                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={onSend}
              >
                {(function (): TranslatedString {
                  switch (config.address_type) {
                    case "email":
                      return i18n.str`Change email`;
                    case "phone":
                      return i18n.str`Change phone`;
                  }
                })()}
              </Button>
            </div>
          )}
        </div>
      </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;
}
