/*
 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 {
  assertUnreachable,
  ChallengerApi,
  EmptyObject,
  HttpStatusCode,
  InternationalizationAPI,
  TalerError,
  TalerFormAttributes,
  TranslatedString,
} from "@gnu-taler/taler-util";
import {
  Attention,
  ButtonBetter,
  countryNameList,
  ErrorLoading,
  FormDesign,
  FormUI,
  LocalNotificationBanner,
  RouteDefinition,
  useChallengerApiContext,
  useForm,
  useLocalNotificationBetter,
  useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { useChallengeSession } from "../hooks/challenge.js";
import { SessionId, useSessionState } from "../hooks/session.js";
import { getAddressDescriptionFromAddrType } from "./AnswerChallenge.js";

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, safeFunctionHandler] = useLocalNotificationBetter();
  // const [address, setEmail] = useState<string | undefined>();
  const [addrIndex, setAddrIndex] = useState<number | undefined>();

  const result = useChallengeSession(session);

  if (!result) {
    return (
      <div>
        <i18n.Translate>loading...</i18n.Translate>
      </div>
    );
  }
  if (result instanceof TalerError) {
    return <ErrorLoading error={result} />;
  }
  if (result.type === "fail") {
    switch (result.case) {
      case HttpStatusCode.BadRequest: {
        return (
          <Attention
            type="danger"
            title={i18n.str`Couldn't get information about the validation process`}
          >
            <i18n.Translate>Bad request</i18n.Translate>
          </Attention>
        );
      }
      case HttpStatusCode.NotFound: {
        return (
          <Attention
            type="danger"
            title={i18n.str`Couldn't get information about the validation process`}
          >
            <i18n.Translate>Not found</i18n.Translate>
          </Attention>
        );
      }
      case HttpStatusCode.NotAcceptable: {
        return (
          <Attention
            type="danger"
            title={i18n.str`Couldn't get information about the validation process`}
          >
            <i18n.Translate>Not acceptable</i18n.Translate>
          </Attention>
        );
      }
      case HttpStatusCode.TooManyRequests: {
        return (
          <Attention
            type="danger"
            title={i18n.str`Couldn't get information about the validation process`}
          >
            <i18n.Translate>Too many request</i18n.Translate>
          </Attention>
        );
      }
      case HttpStatusCode.InternalServerError: {
        return (
          <Attention
            type="danger"
            title={i18n.str`Couldn't get information about the validation process`}
          >
            <i18n.Translate>Server error</i18n.Translate>
          </Attention>
        );
      }
    }
  }

  const lastStatus = result.body;

  const initial = lastStatus.last_address ?? {};
  if (config.address_type === "postal-ch") {
    initial[TalerFormAttributes.ADDRESS_COUNTRY] = "CH";
  }

  const design = getFormDesignBasedOnAddressType(
    i18n,
    config.address_type,
    config.restrictions ?? {},
    initial,
    lastStatus.fix_address,
  );
  const form = useForm(design, initial);

  const prevAddr = !lastStatus?.last_address
    ? undefined
    : getAddressDescriptionFromAddrType(
        config.address_type,
        lastStatus.last_address,
      );

  const contact =
    form.status.status === "fail"
      ? undefined
      : (form.status.result as Record<string, string>);

  const info = lastStatus.fix_address ? lastStatus.last_address! : contact;

  const send = safeFunctionHandler(
    lib.challenger.challenge.bind(lib.challenger),
    form.status.errors || !info ? undefined : [session.nonce, info],
  );
  send.onSuccess = (ok) => {
    if (ok.type === "completed") {
      completed(ok);
    } else {
      sent(ok);
    }
    onSendSuccesful();
  };
  send.onFail = (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 unable to respond due to internal problems.`;
    }
  };

  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>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 &&
          lastStatus.last_address &&
          lastStatus.auth_attempts_left > 0 && (
            <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]);
                          }}
                          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);
                      }}
                      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
          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 "address":
                    return i18n.str`Address`;
                  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 "address":
                      return "address";
                    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>
        </form> */}
        <div class="mx-auto mt-4 max-w-xl ">
          <FormUI
            design={design}
            model={form.model}
            onSubmit={send.call}
          />
        </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> */}
        <div class="mx-auto mt-4 max-w-xl ">
          {!prevAddr ? (
            <div class="mt-10">
              <ButtonBetter
                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"
                onClick={send}
              >
                {(function (): TranslatedString {
                  switch (config.address_type) {
                    case "email":
                      return i18n.str`Send email`;
                    case "postal":
                    case "postal-ch":
                      return i18n.str`Send letter`;
                    case "phone":
                      return i18n.str`Send SMS`;
                  }
                })()}
              </ButtonBetter>
            </div>
          ) : (
            <div class="mt-10">
              <ButtonBetter
                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"
                onClick={send}
              >
                {(function (): TranslatedString {
                  switch (config.address_type) {
                    case "email":
                      return lastStatus.fix_address
                        ? i18n.str`Confirm email`
                        : i18n.str`Change email`;
                    case "postal":
                    case "postal-ch":
                      return lastStatus.fix_address
                        ? i18n.str`Confirm address`
                        : i18n.str`Change address`;
                    case "phone":
                      return lastStatus.fix_address
                        ? i18n.str`Confirm phone`
                        : i18n.str`Change phone`;
                  }
                })()}
              </ButtonBetter>
            </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;
}

const ADDRESS_EXAMPLE_INTERNATIONAL = `Street name 1
2. OG xxxx 
12345 City_name 
country_name `;

const ADDRESS_EXAMPLE_CH = `Street name 1
5. OG xxxx 
12345 City_name 
country_name `;

export const EMAIL_REGEX = /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/;
export const PHONE_REGEX =
  /^(\+\d{1,2}\s?)?\(?\d{3}\)?[\s.-]?\d{3}[\s.-]?\d{4}$/;
export const CONTACT_REGEX = /.*/;
export const ZIPCODE_REGEX = /.*/;
export const ADDR_LINES_REGEX = /.*/;

function getRestriction(
  i18n: InternationalizationAPI,
  serverConfig: ChallengerApi.Restriction | undefined,
  fallback?: RegExp,
): { regex: undefined | RegExp; hint: TranslatedString } {
  const regexText =
    serverConfig && serverConfig.regex ? serverConfig.regex : undefined;
  const hint =
    serverConfig && serverConfig.hint
      ? (serverConfig.hint as TranslatedString)
      : i18n.str`Invalid field`;

  let regex;
  if (regexText) {
    try {
      regex = new RegExp(regexText);
    } catch (e) {
      console.error(`Invalid server regular expression configuration. Server restriction
          is "${regexText}" but it didn't compile: ${String(e)}`);
      // return (
      //   <Attention title={i18n.str`Server configuration error`} type="danger">
      //     <i18n.Translate>
      //       Invalid server regular expression configuration. Server
      //       restriction is "{regexText}" but it didn't compile: {String(e)}
      //     </i18n.Translate>
      //   </Attention>
      // );
      regex = fallback;
    }
  } else {
    regex = fallback;
  }
  return { regex, hint };
}
function getFormDesignBasedOnAddressType(
  i18n: InternationalizationAPI,
  type: ChallengerApi.ChallengerTermsOfServiceResponse["address_type"],
  restrictions: Record<string, ChallengerApi.Restriction | undefined>,
  prevValue: Record<string, string>,
  read_only: boolean,
): FormDesign {
  switch (type) {
    case "email":
      return {
        type: "single-column",
        fields: [
          {
            type: "text",
            id: TalerFormAttributes.CONTACT_EMAIL,
            required: true,
            label: i18n.str`Email`,
            disabled: read_only,
            validator(text) {
              const restriction = getRestriction(
                i18n,
                restrictions[TalerFormAttributes.CONTACT_EMAIL],
                EMAIL_REGEX,
              );
              if (restriction.regex && !restriction.regex.test(text)) {
                return restriction.hint;
              }
              // const prev = prevValue[TalerFormAttributes.CONTACT_EMAIL];
              // if (prev === text) {
              //   return i18n.str`Can't use the same address`;
              // }
              return undefined;
            },
          },
        ],
      };
    case "phone":
      return {
        type: "single-column",
        fields: [
          {
            type: "phone",
            id: TalerFormAttributes.CONTACT_PHONE,
            required: true,
            label: i18n.str`Phone`,
            disabled: read_only,
            validator(text) {
              const restriction = getRestriction(
                i18n,
                restrictions[TalerFormAttributes.CONTACT_PHONE],
                PHONE_REGEX,
              );
              if (restriction.regex && !restriction.regex.test(text)) {
                return restriction.hint;
              }
              // const prev = prevValue[TalerFormAttributes.CONTACT_PHONE];
              // if (prev === text) {
              //   return i18n.str`Can't use the same number`;
              // }
              return undefined;
            },
          },
        ],
      };
    case "postal":
      return {
        type: "single-column",
        fields: [
          {
            type: "text",
            id: TalerFormAttributes.CONTACT_NAME,
            required: true,
            disabled: read_only,
            label: i18n.str`Contact name`,
            placeholder: i18n.str`Person full name or name of the business`,
            validator(text) {
              const restriction = getRestriction(
                i18n,
                restrictions[TalerFormAttributes.CONTACT_NAME],
                CONTACT_REGEX,
              );
              if (restriction.regex && !restriction.regex.test(text)) {
                return restriction.hint;
              }
              return undefined;
            },
          },
          {
            type: "textArea",
            id: TalerFormAttributes.ADDRESS_LINES,
            required: true,
            disabled: read_only,
            label: i18n.str`Address`,
            placeholder: ADDRESS_EXAMPLE_INTERNATIONAL,
            validator(text) {
              const restriction = getRestriction(
                i18n,
                restrictions[TalerFormAttributes.ADDRESS_LINES],
                ADDR_LINES_REGEX,
              );
              if (restriction.regex && !restriction.regex.test(text)) {
                return restriction.hint;
              }
              return undefined;
            },
          },
          {
            id: TalerFormAttributes.ADDRESS_COUNTRY,
            label: i18n.str`Country`,
            type: "selectOne",
            disabled: read_only,
            choices: countryNameList(i18n),
            required: true,
            preferredChoiceVals: ["CH", "DE"],
            validator(text) {
              const restriction = getRestriction(
                i18n,
                restrictions[TalerFormAttributes.ADDRESS_COUNTRY],
              );
              if (restriction.regex && !restriction.regex.test(text)) {
                return restriction.hint;
              }
              return undefined;
            },
          },
        ],
      };

    case "postal-ch":
      return {
        type: "single-column",
        fields: [
          {
            type: "text",
            id: TalerFormAttributes.CONTACT_NAME,
            required: true,
            disabled: read_only,
            label: i18n.str`Contact name`,
            placeholder: i18n.str`Your full name`,
            validator(text) {
              const restriction = getRestriction(
                i18n,
                restrictions[TalerFormAttributes.CONTACT_NAME],
              );
              if (restriction.regex && !restriction.regex.test(text)) {
                return restriction.hint;
              }
              return undefined;
            },
          },
          {
            type: "textArea",
            id: TalerFormAttributes.ADDRESS_LINES,
            required: true,
            disabled: read_only,
            label: i18n.str`Swiss address`,
            help: i18n.str`Make sure that this address is for Switzerland`,
            placeholder: ADDRESS_EXAMPLE_CH,
            validator(text) {
              const restriction = getRestriction(
                i18n,
                restrictions[TalerFormAttributes.ADDRESS_LINES],
              );
              if (restriction.regex && !restriction.regex.test(text)) {
                return restriction.hint;
              }
              return undefined;
            },
          },
          {
            id: TalerFormAttributes.ADDRESS_COUNTRY,
            label: i18n.str`Country`,
            type: "selectOne",
            choices: countryNameList(i18n),
            required: true,
            disabled: true,
            preferredChoiceVals: ["CH"],
            validator(text) {
              const restriction = getRestriction(
                i18n,
                restrictions[TalerFormAttributes.ADDRESS_COUNTRY],
              );
              if (restriction.regex && !restriction.regex.test(text)) {
                return restriction.hint;
              }
              return undefined;
            },
          },
        ],
      };
    default: {
      assertUnreachable(type);
    }
  }
}
