/*
 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 {
  AccessToken,
  HttpStatusCode,
  KycRequirementInformation,
  KycRequirementInformationId,
  TalerFormAttributes,
  assertUnreachable,
} from "@gnu-taler/taler-util";
import {
  AcceptTermOfServiceContext,
  Attention,
  Button,
  ErrorsSummary,
  FormMetadata,
  FormUI,
  InternationalizationAPI,
  Loading,
  LocalNotificationBanner,
  preloadedForms,
  useAsyncAsHook,
  useExchangeApiContext,
  useLocalNotificationHandler,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useFormMeta } from "../../../web-util/src/hooks/useForm.js";
import { usePreferences } from "../context/preferences.js";
import { useUiFormsContext } from "../context/ui-forms.js";

type Props = {
  token: AccessToken;
  formId: string;
  requirement: KycRequirementInformation;
  onComplete: () => void;
};

type FormType = {
  [TalerFormAttributes.FORM_ID]: string;
  [TalerFormAttributes.FORM_VERSION]: number;
  [TalerFormAttributes.FORM_CONTEXT]?: any;
};

async function getContextByFormId(
  id: string,
  requirement: KycRequirementInformation,
): Promise<object> {
  if (id === "accept-tos") {
    const context: any = requirement.context;
    if (!context) {
      throw Error(
        "accept-tos form requires context with 'tos_url' property. No context present.",
      );
    }
    const tosUrl = context["tos_url"];
    if (!tosUrl) {
      throw Error(
        "accept-tos form requires context with 'tos_url' property. No URL present.",
      );
    }

    // If the tos_version is specified explicitly in the context,
    // we take that version.
    // Otherwise, we assume the tos_url is a Taler-style "/terms"
    // endpoint and request the current version from there.

    let tosVersion = context["tos_version"];

    if (!tosVersion) {
      const resp = await fetch(tosUrl);
      tosVersion = resp.headers.get("Taler-Terms-Version");

      if (!tosVersion) {
        throw Error(
          "accept-tos form requires 'Taler-Terms-Version' in request response to the 'tos_url'",
        );
      }
    }

    const ctx: AcceptTermOfServiceContext = {
      tos_url: tosUrl,
      tos_version: tosVersion,
      expiration_time: context["expiration_time"],
      provider_name: context["provider_name"],
      successor_measure: context["successor_measure"],
    };
    return ctx;
  }
  return requirement.context ?? {};
}

function ShowForm({
  theForm,
  reqId,
  formContext,
  onComplete,
}: {
  theForm: FormMetadata;
  reqId: KycRequirementInformationId;
  formContext: any;
  onComplete: () => void;
}): VNode {
  const { lib } = useExchangeApiContext();
  const [notification, withErrorHandler] = useLocalNotificationHandler();
  const [preferences] = usePreferences();
  const { i18n } = useTranslationContext();

  const {
    model: handler,
    status,
    design,
  } = useFormMeta<FormType>(theForm, formContext, {});
  const validatedForm = status.status !== "ok" ? undefined : status.result;

  const submitHandler =
    validatedForm === undefined
      ? undefined
      : withErrorHandler(
          async () => {
            validatedForm[TalerFormAttributes.FORM_ID] = theForm.id;
            validatedForm[TalerFormAttributes.FORM_VERSION] = theForm.version;
            validatedForm[TalerFormAttributes.FORM_CONTEXT] = formContext;
            return lib.exchange.uploadKycForm(reqId, validatedForm);
          },
          (res) => {
            onComplete();
          },
          (fail) => {
            switch (fail.case) {
              case HttpStatusCode.PayloadTooLarge:
                return i18n.str`The form is too big for the server, try uploading smaller files.`;
              case HttpStatusCode.NotFound:
                return i18n.str`The account was not found`;
              case HttpStatusCode.Conflict:
                return i18n.str`Officer disabled or more recent decision was already submitted.`;
              default:
                assertUnreachable(fail);
            }
          },
        );

  return (
    <div class="rounded-lg bg-white px-5 py-6 shadow m-4">
      <LocalNotificationBanner notification={notification} />
      <div class="space-y-10 divide-y -mt-5 divide-gray-900/10">
        <FormUI model={handler} design={design} />
      </div>

      {preferences.showDebugInfo ? (
        <div class="m-4">
          <p class="text-sm font-semibold leading-6 text-gray-300">
            <i18n.Translate>
              Debug information about the values in the form
            </i18n.Translate>
          </p>
          <pre class="text-sm text-gray-300">
            {JSON.stringify(status.result, undefined, 2)}
          </pre>
        </div>
      ) : (
        <Fragment />
      )}
      <div class="my-6 flex items-center justify-end gap-x-6">
        <button
          onClick={onComplete}
          class="text-sm font-semibold leading-6 text-gray-900"
        >
          <i18n.Translate>Cancel</i18n.Translate>
        </button>
        <Button
          type="submit"
          handler={submitHandler}
          disabled={!submitHandler}
          class="disabled:opacity-50 disabled:cursor-default 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"
        >
          <i18n.Translate>Submit</i18n.Translate>
        </Button>
      </div>
      {!status.errors ? undefined : <ErrorsSummary errors={status.errors} />}
    </div>
  );
}

export function FillForm({
  token,
  formId,
  requirement,
  onComplete,
}: Props): VNode {
  const { i18n } = useTranslationContext();

  const { forms } = useUiFormsContext();
  const hook = useAsyncAsHook(() => getContextByFormId(formId, requirement));
  if (hook === undefined) {
    return <Loading />;
  }
  if (hook.hasError) {
    return (
      <Attention
        title={i18n.str`Could not load context information`}
        type="danger"
      >
        {hook.operational ? hook.details.hint : hook.message}
      </Attention>
    );
  }
  const formContext = hook.response;
  const theForm = searchForm(i18n, forms, formId);
  const reqId = requirement.id;
  if (!theForm) {
    return (
      <Attention title={i18n.str`Could not find form`} type="danger">
        <i18n.Translate>
          Form with id '{formId}' is not registered in this application.
        </i18n.Translate>
      </Attention>
    );
  }
  if (!reqId) {
    return (
      <Attention title={i18n.str`Can't upload information`} type="danger">
        <i18n.Translate>The KYC requirement doesn't have an ID</i18n.Translate>
      </Attention>
    );
  }

  return (
    <ShowForm
      onComplete={onComplete}
      reqId={reqId}
      theForm={theForm}
      formContext={formContext}
    />
  );
}

function searchForm(
  i18n: InternationalizationAPI,
  forms: FormMetadata[],
  formId: string,
): FormMetadata | undefined {
  {
    const found = forms.find((v) => v.id === formId);
    if (found) return found;
  }
  {
    const pf = preloadedForms(i18n);
    const found = pf.find((v) => v.id === formId);
    if (found) return found;
  }
  return undefined;
}
