/*
 This file is part of GNU Taler
 (C) 2021-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/>
 */

/**
 *
 * @author Sebastian Javier Marchano (sebasjm)
 */

import {
  HttpStatusCode,
  PaytoString,
  PaytoUri,
  Paytos,
  TalerError,
  TalerMerchantApi,
  TranslatedString,
  assertUnreachable,
  parsePaytoUri,
  succeedOrThrow,
} from "@gnu-taler/taler-util";
import { useTranslationContext } from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
import { AsyncButton } from "../../../../components/exception/AsyncButton.js";
import {
  FormErrors,
  FormProvider,
} from "../../../../components/form/FormProvider.js";
import { Input } from "../../../../components/form/Input.js";
import { InputPaytoForm } from "../../../../components/form/InputPaytoForm.js";
import { InputSelector } from "../../../../components/form/InputSelector.js";
import { InputToggle } from "../../../../components/form/InputToggle.js";
import { CompareAccountsModal } from "../../../../components/modal/index.js";
import { WithId } from "../../../../declaration.js";
import { usePreference } from "../../../../hooks/preference.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
import { TestRevenueErrorType, testRevenueAPI } from "../create/index.js";

type Entity = TalerMerchantApi.BankAccountDetail & WithId;
type FormType = TalerMerchantApi.AccountPatchDetails & {
  verified: boolean;
  payto_uri?: PaytoString;
};
interface Props {
  onUpdate: (d: TalerMerchantApi.AccountPatchDetails) => Promise<void>;
  onReplace: (
    prev: TalerMerchantApi.BankAccountDetail,
    next: TalerMerchantApi.AccountAddDetails,
  ) => Promise<void>;
  onBack?: () => void;
  account: Entity;
}

export function UpdatePage({
  account,
  onUpdate,
  onBack,
  onReplace,
}: Props): VNode {
  const { i18n } = useTranslationContext();

  const [{ developerMode }] = usePreference();
  const accountAuthType = developerMode
    ? ["unedit", "none", "basic", "bearer"]
    : ["unedit", "none", "basic"];

  const [state, setState] = useState<Partial<FormType>>({
    payto_uri: account.payto_uri,
    credit_facade_url: account.credit_facade_url,
    credit_facade_credentials: {
      // @ts-expect-error unofficial unedited value
      type: "unedit",
    },
  });

  const [revenuePayto, setRevenuePayto] = useState<PaytoUri | undefined>(
    // parsePaytoUri("payto://x-taler-bank/asd.com:1010/asd/pepe"),
    undefined,
  );
  const [testError, setTestError] = useState<TranslatedString | undefined>(
    undefined,
  );

  const replacingAccountId = state.payto_uri !== account.payto_uri;

  const facadeURL = safeConvertURL(state.credit_facade_url);

  const errors = undefinedIfEmpty<FormErrors<FormType>>({
    payto_uri: !state.payto_uri ? i18n.str`Required` : undefined,

    credit_facade_url: !state.credit_facade_url
      ? undefined
      : !facadeURL
        ? i18n.str`Invalid url`
        : !facadeURL.href.endsWith("/")
          ? i18n.str`URL must end with a '/'`
          : facadeURL.searchParams.size > 0
            ? i18n.str`URL must not contain params`
            : facadeURL.hash
              ? i18n.str`URL must not hash param`
              : undefined,
    credit_facade_credentials:
      !state.credit_facade_credentials || !state.credit_facade_url
        ? undefined
        : (undefinedIfEmpty({
          type:
            replacingAccountId &&
              // @ts-expect-error unedit is not in facade creds
              state.credit_facade_credentials?.type === "unedit"
              ? i18n.str`Required`
              : undefined,
          username:
            state.credit_facade_credentials?.type !== "basic"
              ? undefined
              : !state.credit_facade_credentials.username
                ? i18n.str`Required`
                : undefined,

          token:
            state.credit_facade_credentials?.type !== "bearer"
              ? undefined
              : !state.credit_facade_credentials.token
                ? i18n.str`Required`
                : undefined,

          password:
            state.credit_facade_credentials?.type !== "basic"
              ? undefined
              : !state.credit_facade_credentials.password
                ? i18n.str`Required`
                : undefined,
        }) as any),
  });

  const hasErrors = errors !== undefined;

  const submitForm = () => {
    if (hasErrors) return Promise.reject();
    const credit_facade_url = !state.credit_facade_url
      ? undefined
      : facadeURL?.href;

    const credit_facade_credentials:
      | TalerMerchantApi.FacadeCredentials
      | undefined =
      credit_facade_url == undefined ||
        state.credit_facade_credentials === undefined
        ? undefined
        : // @ts-expect-error unedit is not in facade creds
        state.credit_facade_credentials.type === "unedit"
          ? undefined
          : state.credit_facade_credentials.type === "basic"
            ? {
              type: "basic",
              password: state.credit_facade_credentials.password,
              username: state.credit_facade_credentials.username,
            }
            : state.credit_facade_credentials.type === "bearer"
              ? {
                type: "bearer",
                token: state.credit_facade_credentials.token,
              }
              : {
                type: "none",
              };

    if (replacingAccountId) {
      return onReplace(account, {
        payto_uri: state.payto_uri!,
        credit_facade_credentials,
        credit_facade_url,
      });
    } else {
      return onUpdate({ credit_facade_credentials, credit_facade_url });
    }
  };

  async function testAccountInfo() {
    const revenueAPI = !state.credit_facade_url
      ? undefined
      : new URL("./", state.credit_facade_url);

    if (revenueAPI) {
      const resp = await testRevenueAPI(
        revenueAPI,
        state.credit_facade_credentials,
      );
      if (resp instanceof TalerError) {
        setTestError(i18n.str`The request to check the revenue API failed.`);
        setState({
          ...state,
          verified: undefined,
        });
        return;
      } else if (resp.type === "fail") {
        switch (resp.case) {
          case HttpStatusCode.BadRequest: {
            setTestError(i18n.str`Server replied with "bad request".`);
            setState({
              ...state,
              verified: undefined,
            });
            return;
          }
          case HttpStatusCode.Unauthorized: {
            setTestError(i18n.str`Unauthorized, check credentials.`);
            setState({
              ...state,
              verified: false,
            });
            return;
          }
          case HttpStatusCode.NotFound: {
            setTestError(
              i18n.str`The endpoint does not seem to be a Taler Revenue API.`,
            );
            setState({
              ...state,
              verified: undefined,
            });
            return;
          }
          case TestRevenueErrorType.CANT_VALIDATE: {
            setTestError(
              i18n.str`The request was made correctly, but the bank's server did not respond with the appropriate value for 'credit_account', so we cannot confirm that it is the same bank account.`,
            );
            setState({
              ...state,
              verified: undefined,
            });
            return;
          }
          default: {
            assertUnreachable(resp);
          }
        }
      } else {
        const found = resp.body;
        const match = state.payto_uri === found;
        setState({
          ...state,
          verified: match,
        });
        if (!match) {
          setRevenuePayto(parsePaytoUri(resp.body));
        }
        setTestError(undefined);
      }
    }
  }

  return (
    <Fragment>
      <section class="section">
        <section class="hero is-hero-bar">
          <div class="hero-body">
            <div class="level">
              <div class="level-left">
                <div class="level-item">
                  <span class="is-size-4">
                    <i18n.Translate>Account:</i18n.Translate>{" "}
                    <b>
                      {succeedOrThrow(Paytos.fromString(account.payto_uri)).displayName}
                    </b>
                  </span>
                </div>
              </div>
            </div>
          </div>
        </section>
        <hr />

        <section class="section is-main-section">
          <div class="columns">
            <div class="column is-four-fifths">
              <FormProvider
                object={state}
                valueHandler={setState}
                errors={errors}
              >
                <InputPaytoForm<FormType>
                  name="payto_uri"
                  label={i18n.str`Account`}
                />
                {!developerMode ? undefined : (
                  <Fragment>
                    <div class="message-body" style={{ marginBottom: 10 }}>
                      <p>
                        <i18n.Translate>
                          If the bank supports Taler Revenue API then you can add
                          the endpoint URL below to keep the revenue information in
                          sync.
                        </i18n.Translate>
                      </p>
                    </div>
                    <Input<Entity>
                      name="credit_facade_url"
                      label={i18n.str`Endpoint URL`}
                      help="https://bank.demo.taler.net/accounts/${USERNAME}/taler-revenue/"
                      expand
                      tooltip={i18n.str`From where the merchant can download information about incoming wire transfers to this account`}
                    />
                    <InputSelector
                      name="credit_facade_credentials.type"
                      label={i18n.str`Auth type`}
                      tooltip={i18n.str`Choose the authentication type for the account info URL`}
                      values={accountAuthType}
                      toStr={(str) => {
                        if (str === "none") return i18n.str`Without authentication`;
                        if (str === "basic")
                          return i18n.str`With username and password`;
                        if (str === "bearer") return i18n.str`With token`;
                        return i18n.str`Do not change`;
                      }}
                    />
                    {state.credit_facade_credentials?.type === "basic" ? (
                      <Fragment>
                        <Input
                          name="credit_facade_credentials.username"
                          label={i18n.str`Username`}
                          tooltip={i18n.str`Username to access the account information.`}
                        />
                        <Input
                          name="credit_facade_credentials.password"
                          inputType="password"
                          label={i18n.str`Password`}
                          tooltip={i18n.str`Password to access the account information.`}
                        />
                      </Fragment>
                    ) : undefined}
                    {state.credit_facade_credentials?.type === "bearer" ? (
                      <Fragment>
                        <Input
                          name="credit_facade_credentials.token"
                          label={i18n.str`Token`}
                          inputType="password"
                          tooltip={i18n.str`Access token to access the account information.`}
                        />
                      </Fragment>
                    ) : undefined}
                    <InputToggle<FormType>
                      label={i18n.str`Match`}
                      tooltip={i18n.str`Check where the information match against the server info.`}
                      name="verified"
                      readonly
                      threeState
                      help={
                        testError !== undefined
                          ? testError
                          : state.verified === undefined
                            ? i18n.str`Not verified`
                            : state.verified
                              ? i18n.str`Last test was ok`
                              : i18n.str`Last test failed`
                      }
                      side={
                        <button
                          class="button is-info"
                          data-tooltip={i18n.str`Compare info from server with account form`}
                          disabled={!state.credit_facade_url}
                          onClick={async () => {
                            await testAccountInfo();
                          }}
                        >
                          <i18n.Translate>Test</i18n.Translate>
                        </button>
                      }
                    />
                  </Fragment>
                )}
              </FormProvider>

              <div class="buttons is-right mt-5">
                {onBack && (
                  <button class="button" onClick={onBack}>
                    <i18n.Translate>Cancel</i18n.Translate>
                  </button>
                )}
                <AsyncButton
                  disabled={hasErrors}
                  data-tooltip={
                    hasErrors
                      ? i18n.str`Please complete the marked fields`
                      : i18n.str`Confirm operation`
                  }
                  onClick={submitForm}
                >
                  <i18n.Translate>Confirm</i18n.Translate>
                </AsyncButton>
              </div>
            </div>
          </div>
        </section>
      </section>
      {!revenuePayto ? undefined : (
        <CompareAccountsModal
          onCancel={() => {
            setRevenuePayto(undefined);
          }}
          onConfirm={(d) => {
            setState({
              ...state,
              payto_uri: d,
            });
            setRevenuePayto(undefined);
          }}
          formPayto={
            !state.payto_uri ? undefined : parsePaytoUri(state.payto_uri)
          }
          testPayto={revenuePayto}
        />
      )}
    </Fragment>
  );
}

//TODO: move to utils
export function safeConvertURL(s?: string): URL | undefined {
  if (!s) return undefined;
  try {
    return new URL(s);
  } catch (e) {
    return undefined;
  }
}
