/*
 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 {
  AccessToken,
  HttpStatusCode,
  PaytoParseError,
  PaytoString,
  Paytos,
  TalerMerchantApi,
  opEmptySuccess,
  succeedOrThrow,
} from "@gnu-taler/taler-util";
import {
  ButtonBetterBulma,
  LocalNotificationBanner,
  useChallengeHandler,
  useCommonPreferences,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useState } from "preact/hooks";
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 { SolveMFAChallenges } from "../../../../components/SolveMFA.js";
import { useSessionContext } from "../../../../context/session.js";
import { WithId } from "../../../../declaration.js";
import { undefinedIfEmpty } from "../../../../utils/table.js";
import { TestRevenueErrorType, testRevenueAPI } from "../create/index.js";
import { FragmentPersonaFlag } from "../../../../components/menu/SideBar.js";
import { UIElement, usePreference } from "../../../../hooks/preference.js";


const TALER_SCREEN_ID = 36;

type Entity = TalerMerchantApi.BankAccountDetail & WithId;
type FormType = TalerMerchantApi.AccountPatchDetails & {
  verified: boolean;
  payto_uri?: PaytoString;
};
interface Props {
  onUpdated: () => void;
  onBack?: () => void;
  account: Entity;
}

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

  const [{ persona }] = usePreference();
  const accountAuthType = persona === "developer"
    ? ["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<Paytos.URI | undefined>(
    // parsePaytoUri("payto://x-taler-bank/asd.com:1010/asd/pepe"),
    undefined,
  );
  const parsed = !state.payto_uri
    ? undefined
    : Paytos.fromString(state.payto_uri);
  const safeParsed = parsed?.type === "fail" ? undefined : parsed?.body;

  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 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",
              };
  const { state: session, lib } = useSessionContext();
  const [notification, safeFunctionHandler] = useLocalNotificationBetter();

  async function changeBankAccount(
    token: AccessToken,
    newPayto: string | undefined,
    id: string,
    data: TalerMerchantApi.AccountPatchDetails,
    challengeIds: string[],
  ) {
    if (newPayto) {
      const details: TalerMerchantApi.AccountAddDetails = {
        ...data,
        payto_uri: newPayto,
      };
      const created = await lib.instance.addBankAccount(token, details, {
        challengeIds,
      });
      if (created.type === "fail") return created;
      const deleted = await lib.instance.deleteBankAccount(token, id);
      if (deleted.type === "fail") return deleted;
    } else {
      const resp = await lib.instance.updateBankAccount(token, id, data);
      if (resp.type === "fail") return resp;
    }
    return opEmptySuccess();
  }

  const mfa = useChallengeHandler();
  const update = safeFunctionHandler(
    changeBankAccount,
    !session.token
      ? undefined
      : [
          session.token,
          replacingAccountId ? state.payto_uri! : undefined,
          account.h_wire,
          {
            credit_facade_credentials,
            credit_facade_url,
          },
          [],
        ],
  );
  update.onSuccess = onUpdated;
  update.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.Accepted:
        mfa.onChallengeRequired(fail.body);
        return undefined;
      case HttpStatusCode.Unauthorized:
        return i18n.str`Unauthorized`;
      case HttpStatusCode.NotFound:
        return i18n.str`Not found`;
      case HttpStatusCode.Conflict:
        return i18n.str`Conflict`;
    }
  };
  const repeat = update.lambda((ids: string[]) => [
    update.args![0],
    update.args![1],
    update.args![2],
    update.args![3],
    ids,
  ]);

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

  const test = safeFunctionHandler(
    testRevenueAPI,
    !revenueAPI || !state.credit_facade_url
      ? undefined
      : [revenueAPI, state.credit_facade_credentials],
  );
  test.onSuccess = (success) => {
    const match = state.payto_uri === Paytos.toFullString(success);
    setState({
      ...state,
      verified: match,
    });
    if (!match) {
      setRevenuePayto(success);
    }
  };
  test.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.BadRequest:
        return i18n.str`Server replied with "bad request".`;
      case HttpStatusCode.Unauthorized:
        return i18n.str`Unauthorized, check credentials.`;
      case HttpStatusCode.NotFound:
        return i18n.str`The endpoint does not seem to be a Taler Revenue API.`;
      case TestRevenueErrorType.CANT_VALIDATE:
        return 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.`;
      case PaytoParseError.UNSUPPORTED:
      case PaytoParseError.COMPONENTS_LENGTH:
      case PaytoParseError.INVALID_TARGET_PATH:
      case PaytoParseError.WRONG_PREFIX:
      case PaytoParseError.INCOMPLETE:
        return i18n.str`Unsupported type of account`;
    }
  };

  if (mfa.pendingChallenge) {
    return (
      <SolveMFAChallenges
        currentChallenge={mfa.pendingChallenge}
        onCompleted={repeat}
        onCancel={mfa.doCancelChallenge}
      />
    );
  }

  return (
    <Fragment>
      <LocalNotificationBanner notification={notification} />
      <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`}
                />
                <FragmentPersonaFlag point={UIElement.action_useRevenueApi}>
                  <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
                    side={
                      <ButtonBetterBulma
                        class="button is-info"
                        data-tooltip={i18n.str`Compare info from server with account form`}
                        onClick={test}
                      >
                        <i18n.Translate>Test</i18n.Translate>
                      </ButtonBetterBulma>
                    }
                  />
                </FragmentPersonaFlag>
              </FormProvider>

              <div class="buttons is-right mt-5">
                {onBack && (
                  <button class="button" onClick={onBack}>
                    <i18n.Translate>Cancel</i18n.Translate>
                  </button>
                )}
                <ButtonBetterBulma
                  data-tooltip={
                    hasErrors
                      ? i18n.str`Please complete the marked fields`
                      : i18n.str`Confirm operation`
                  }
                  onClick={update}
                  type="submit"
                >
                  <i18n.Translate>Confirm</i18n.Translate>
                </ButtonBetterBulma>
              </div>
            </div>
          </div>
        </section>
      </section>
      {!revenuePayto ? undefined : (
        <CompareAccountsModal
          onCancel={() => {
            setRevenuePayto(undefined);
          }}
          confirm={safeFunctionHandler(async () => {
            setState({
              ...state,
              payto_uri: Paytos.toFullString(revenuePayto),
            });
            setRevenuePayto(undefined);
            return opEmptySuccess();
          }, [])}
          formPayto={safeParsed}
          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;
  }
}
