/*
 This file is part of GNU Taler
 (C) 2022 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 {
  AmountJson,
  Amounts,
  HttpStatusCode,
  TranslatedString,
} from "@gnu-taler/taler-util";
import {
  HttpResponse,
  HttpResponsePaginated,
  RequestError,
  useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact";
import { useEffect, useMemo, useState } from "preact/hooks";
import { Cashouts } from "../components/Cashouts/index.js";
import { useBackendContext } from "../context/backend.js";
import { ErrorMessage, usePageContext } from "../context/pageState.js";
import { useAccountDetails } from "../hooks/access.js";
import {
  useCashoutDetails,
  useCircuitAccountAPI,
  useEstimator,
  useRatiosAndFeeConfig,
} from "../hooks/circuit.js";
import {
  buildRequestErrorMessage,
  TanChannel,
  undefinedIfEmpty,
} from "../utils.js";
import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js";
import { ErrorBannerFloat } from "./BankFrame.js";
import { LoginForm } from "./LoginForm.js";
import { ShowInputErrorLabel } from "./ShowInputErrorLabel.js";

interface Props {
  onClose: () => void;
  onRegister: () => void;
  onLoadNotOk: <T>(
    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
  ) => VNode;
}
export function BusinessAccount({
  onClose,
  onLoadNotOk,
  onRegister,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  const { pageStateSetter } = usePageContext();
  const backend = useBackendContext();
  const [updatePassword, setUpdatePassword] = useState(false);
  const [newCashout, setNewcashout] = useState(false);
  const [showCashoutDetails, setShowCashoutDetails] = useState<
    string | undefined
  >();
  function showInfoMessage(info: TranslatedString): void {
    pageStateSetter((prev) => ({
      ...prev,
      info,
    }));
  }

  if (backend.state.status === "loggedOut") {
    return <LoginForm onRegister={onRegister} />;
  }

  if (newCashout) {
    return (
      <CreateCashout
        account={backend.state.username}
        onLoadNotOk={onLoadNotOk}
        onCancel={() => {
          setNewcashout(false);
        }}
        onComplete={(id) => {
          showInfoMessage(
            i18n.str`Cashout created. You need to confirm the operation to complete the transaction.`,
          );
          setNewcashout(false);
          setShowCashoutDetails(id);
        }}
      />
    );
  }
  if (showCashoutDetails) {
    return (
      <ShowCashoutDetails
        id={showCashoutDetails}
        onLoadNotOk={onLoadNotOk}
        onCancel={() => {
          setShowCashoutDetails(undefined);
        }}
      />
    );
  }
  if (updatePassword) {
    return (
      <UpdateAccountPassword
        account={backend.state.username}
        onLoadNotOk={onLoadNotOk}
        onUpdateSuccess={() => {
          showInfoMessage(i18n.str`Password changed`);
          setUpdatePassword(false);
        }}
        onClear={() => {
          setUpdatePassword(false);
        }}
      />
    );
  }
  return (
    <div>
      <ShowAccountDetails
        account={backend.state.username}
        onLoadNotOk={onLoadNotOk}
        onUpdateSuccess={() => {
          showInfoMessage(i18n.str`Account updated`);
        }}
        onChangePassword={() => {
          setUpdatePassword(true);
        }}
        onClear={onClose}
      />
      <section style={{ marginTop: "2em" }}>
        <div class="active">
          <h3>{i18n.str`Latest cashouts`}</h3>
          <Cashouts
            account={backend.state.username}
            onSelected={(id) => {
              setShowCashoutDetails(id);
            }}
          />
        </div>
        <br />
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <div />
          <input
            class="pure-button pure-button-primary content"
            type="submit"
            value={i18n.str`New cashout`}
            onClick={async (e) => {
              e.preventDefault();
              setNewcashout(true);
            }}
          />
        </div>
      </section>
    </div>
  );
}

interface PropsCashout {
  account: string;
  onComplete: (id: string) => void;
  onCancel: () => void;
  onLoadNotOk: <T>(
    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
  ) => VNode;
}

type FormType = {
  isDebit: boolean;
  amount: string;
  subject: string;
  channel: TanChannel;
};
type ErrorFrom<T> = {
  [P in keyof T]+?: string;
};

// check #7719
function useRatiosAndFeeConfigWithChangeDetection(): HttpResponse<
  SandboxBackend.Circuit.Config & { hasChanged?: boolean },
  SandboxBackend.SandboxError
> {
  const result = useRatiosAndFeeConfig();
  const [oldResult, setOldResult] = useState<
    SandboxBackend.Circuit.Config | undefined
  >(undefined);
  const dataFromBackend = result.ok ? result.data : undefined;
  useEffect(() => {
    // save only the first result of /config to the backend
    if (!dataFromBackend || oldResult !== undefined) return;
    setOldResult(dataFromBackend);
  }, [dataFromBackend]);

  if (!result.ok) return result;

  const data = !oldResult ? result.data : oldResult;
  const hasChanged =
    oldResult &&
    (result.data.name !== oldResult.name ||
      result.data.version !== oldResult.version ||
      result.data.ratios_and_fees.buy_at_ratio !==
        oldResult.ratios_and_fees.buy_at_ratio ||
      result.data.ratios_and_fees.buy_in_fee !==
        oldResult.ratios_and_fees.buy_in_fee ||
      result.data.ratios_and_fees.sell_at_ratio !==
        oldResult.ratios_and_fees.sell_at_ratio ||
      result.data.ratios_and_fees.sell_out_fee !==
        oldResult.ratios_and_fees.sell_out_fee ||
      result.data.fiat_currency !== oldResult.fiat_currency);

  return {
    ...result,
    data: { ...data, hasChanged },
  };
}

function CreateCashout({
  account,
  onComplete,
  onCancel,
  onLoadNotOk,
}: PropsCashout): VNode {
  const { i18n } = useTranslationContext();
  const ratiosResult = useRatiosAndFeeConfig();
  const result = useAccountDetails(account);
  const [error, saveError] = useState<ErrorMessage | undefined>();
  const {
    estimateByCredit: calculateFromCredit,
    estimateByDebit: calculateFromDebit,
  } = useEstimator();
  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });

  const { createCashout } = useCircuitAccountAPI();
  if (!result.ok) return onLoadNotOk(result);
  if (!ratiosResult.ok) return onLoadNotOk(ratiosResult);
  const config = ratiosResult.data;

  const balance = Amounts.parseOrThrow(result.data.balance.amount);
  const debitThreshold = Amounts.parseOrThrow(result.data.debitThreshold);
  const zero = Amounts.zeroOfCurrency(balance.currency);
  const balanceIsDebit = result.data.balance.credit_debit_indicator == "debit";
  const limit = balanceIsDebit
    ? Amounts.sub(debitThreshold, balance).amount
    : Amounts.add(balance, debitThreshold).amount;

  const zeroCalc = { debit: zero, credit: zero, beforeFee: zero };
  const [calc, setCalc] = useState(zeroCalc);
  const sellRate = config.ratios_and_fees.sell_at_ratio;
  const sellFee = !config.ratios_and_fees.sell_out_fee
    ? zero
    : Amounts.fromFloat(config.ratios_and_fees.sell_out_fee, balance.currency);
  const fiatCurrency = config.fiat_currency;

  if (!sellRate || sellRate < 0) return <div>error rate</div>;

  const amount = Amounts.parseOrThrow(
    `${!form.isDebit ? fiatCurrency : balance.currency}:${
      !form.amount ? "0" : form.amount
    }`,
  );

  useEffect(() => {
    if (form.isDebit) {
      calculateFromDebit(amount, sellFee, sellRate)
        .then((r) => {
          setCalc(r);
          saveError(undefined);
        })
        .catch((error) => {
          saveError(
            error instanceof RequestError
              ? buildRequestErrorMessage(i18n, error.cause)
              : {
                  title: i18n.str`Could not estimate the cashout`,
                  description: error.message,
                },
          );
        });
    } else {
      calculateFromCredit(amount, sellFee, sellRate)
        .then((r) => {
          setCalc(r);
          saveError(undefined);
        })
        .catch((error) => {
          saveError(
            error instanceof RequestError
              ? buildRequestErrorMessage(i18n, error.cause)
              : {
                  title: i18n.str`Could not estimate the cashout`,
                  description: error.message,
                },
          );
        });
    }
  }, [form.amount, form.isDebit]);

  const balanceAfter = Amounts.sub(balance, calc.debit).amount;

  function updateForm(newForm: typeof form): void {
    setForm(newForm);
  }
  const errors = undefinedIfEmpty<ErrorFrom<typeof form>>({
    amount: !form.amount
      ? i18n.str`required`
      : !amount
      ? i18n.str`could not be parsed`
      : Amounts.cmp(limit, calc.debit) === -1
      ? i18n.str`balance is not enough`
      : Amounts.cmp(calc.beforeFee, sellFee) === -1
      ? i18n.str`the total amount to transfer does not cover the fees`
      : Amounts.isZero(calc.credit)
      ? i18n.str`the total transfer at destination will be zero`
      : undefined,
    channel: !form.channel ? i18n.str`required` : undefined,
  });

  return (
    <div>
      {error && (
        <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
      )}
      <h1>New cashout</h1>
      <form class="pure-form">
        <fieldset>
          <label>{i18n.str`Subject`}</label>
          <input
            value={form.subject ?? ""}
            onChange={(e) => {
              form.subject = e.currentTarget.value;
              updateForm(structuredClone(form));
            }}
          />
          <ShowInputErrorLabel
            message={errors?.subject}
            isDirty={form.subject !== undefined}
          />
        </fieldset>
        <fieldset>
          <label>
            {form.isDebit
              ? i18n.str`Amount to send`
              : i18n.str`Amount to receive`}
          </label>
          <div style={{ width: "max-content" }}>
            <input
              type="text"
              readonly
              class="currency-indicator"
              size={amount?.currency.length ?? 0}
              maxLength={amount?.currency.length ?? 0}
              tabIndex={-1}
              value={amount?.currency ?? ""}
            />
            &nbsp;
            <input
              type="number"
              // ref={ref}
              id="withdraw-amount"
              name="withdraw-amount"
              value={form.amount ?? ""}
              onChange={(e): void => {
                form.amount = e.currentTarget.value;
                updateForm(structuredClone(form));
              }}
            />
            &nbsp;
            <label class="toggle">
              <input
                class="toggle-checkbox"
                type="checkbox"
                onChange={(e): void => {
                  form.isDebit = !form.isDebit;
                  updateForm(structuredClone(form));
                }}
              />
              <div class="toggle-switch"></div>
            </label>
          </div>
          <ShowInputErrorLabel
            message={errors?.amount}
            isDirty={form.amount !== undefined}
          />
        </fieldset>
        <fieldset>
          <label>{i18n.str`Conversion rate`}</label>
          <input value={sellRate} disabled />
        </fieldset>
        <fieldset>
          <label>{i18n.str`Balance now`}</label>
          <div style={{ width: "max-content" }}>
            <input
              type="text"
              readonly
              class="currency-indicator"
              size={balance.currency.length}
              maxLength={balance.currency.length}
              tabIndex={-1}
              value={balance.currency}
            />
            &nbsp;
            <input
              type="number"
              id="withdraw-amount"
              disabled
              name="withdraw-amount"
              value={Amounts.stringifyValue(balance)}
            />
          </div>
        </fieldset>
        <fieldset>
          <label
            style={{ fontWeight: "bold", color: "red" }}
          >{i18n.str`Total cost`}</label>
          <div style={{ width: "max-content" }}>
            <input
              type="text"
              readonly
              class="currency-indicator"
              size={balance.currency.length}
              maxLength={balance.currency.length}
              tabIndex={-1}
              value={balance.currency}
            />
            &nbsp;
            <input
              type="number"
              // ref={ref}
              id="withdraw-amount"
              disabled
              name="withdraw-amount"
              value={Amounts.stringifyValue(calc.debit)}
            />
          </div>
        </fieldset>
        <fieldset>
          <label>{i18n.str`Balance after`}</label>
          <div style={{ width: "max-content" }}>
            <input
              type="text"
              readonly
              class="currency-indicator"
              size={balance.currency.length}
              maxLength={balance.currency.length}
              tabIndex={-1}
              value={balance.currency}
            />
            &nbsp;
            <input
              type="number"
              // ref={ref}
              id="withdraw-amount"
              disabled
              name="withdraw-amount"
              value={balanceAfter ? Amounts.stringifyValue(balanceAfter) : ""}
            />
          </div>
        </fieldset>{" "}
        {Amounts.isZero(sellFee) ? undefined : (
          <Fragment>
            <fieldset>
              <label>{i18n.str`Amount after conversion`}</label>
              <div style={{ width: "max-content" }}>
                <input
                  type="text"
                  readonly
                  class="currency-indicator"
                  size={fiatCurrency.length}
                  maxLength={fiatCurrency.length}
                  tabIndex={-1}
                  value={fiatCurrency}
                />
                &nbsp;
                <input
                  // type="number"
                  style={{ color: "black" }}
                  disabled
                  value={Amounts.stringifyValue(calc.beforeFee)}
                />
              </div>
            </fieldset>

            <fieldset>
              <label>{i18n.str`Cashout fee`}</label>
              <div style={{ width: "max-content" }}>
                <input
                  type="text"
                  readonly
                  class="currency-indicator"
                  size={fiatCurrency.length}
                  maxLength={fiatCurrency.length}
                  tabIndex={-1}
                  value={fiatCurrency}
                />
                &nbsp;
                <input
                  // type="number"
                  style={{ color: "black" }}
                  disabled
                  value={Amounts.stringifyValue(sellFee)}
                />
              </div>
            </fieldset>
          </Fragment>
        )}
        <fieldset>
          <label
            style={{ fontWeight: "bold", color: "green" }}
          >{i18n.str`Total cashout transfer`}</label>
          <div style={{ width: "max-content" }}>
            <input
              type="text"
              readonly
              class="currency-indicator"
              size={fiatCurrency.length}
              maxLength={fiatCurrency.length}
              tabIndex={-1}
              value={fiatCurrency}
            />
            &nbsp;
            <input
              type="number"
              // ref={ref}
              id="withdraw-amount"
              disabled
              name="withdraw-amount"
              value={Amounts.stringifyValue(calc.credit)}
            />
          </div>
        </fieldset>
        <fieldset>
          <label>{i18n.str`Confirmation channel`}</label>

          <div class="channel">
            <input
              class={
                "pure-button content " +
                (form.channel === TanChannel.EMAIL
                  ? "pure-button-primary"
                  : "pure-button-secondary")
              }
              type="submit"
              value={i18n.str`Email`}
              onClick={async (e) => {
                e.preventDefault();
                form.channel = TanChannel.EMAIL;
                updateForm(structuredClone(form));
              }}
            />
            <input
              class={
                "pure-button content " +
                (form.channel === TanChannel.SMS
                  ? "pure-button-primary"
                  : "pure-button-secondary")
              }
              type="submit"
              value={i18n.str`SMS`}
              onClick={async (e) => {
                e.preventDefault();
                form.channel = TanChannel.SMS;
                updateForm(structuredClone(form));
              }}
            />
            <input
              class={
                "pure-button content " +
                (form.channel === TanChannel.FILE
                  ? "pure-button-primary"
                  : "pure-button-secondary")
              }
              type="submit"
              value={i18n.str`FILE`}
              onClick={async (e) => {
                e.preventDefault();
                form.channel = TanChannel.FILE;
                updateForm(structuredClone(form));
              }}
            />
          </div>
          <ShowInputErrorLabel
            message={errors?.channel}
            isDirty={form.channel !== undefined}
          />
        </fieldset>
        <br />
        <div style={{ display: "flex", justifyContent: "space-between" }}>
          <button
            class="pure-button pure-button-secondary btn-cancel"
            onClick={(e) => {
              e.preventDefault();
              onCancel();
            }}
          >
            {i18n.str`Cancel`}
          </button>

          <button
            class="pure-button pure-button-primary btn-register"
            type="submit"
            disabled={!!errors}
            onClick={async (e) => {
              e.preventDefault();

              if (errors) return;
              try {
                const res = await createCashout({
                  amount_credit: Amounts.stringify(calc.credit),
                  amount_debit: Amounts.stringify(calc.debit),
                  subject: form.subject,
                  tan_channel: form.channel,
                });
                onComplete(res.data.uuid);
              } catch (error) {
                if (error instanceof RequestError) {
                  saveError(
                    buildRequestErrorMessage(i18n, error.cause, {
                      onClientError: (status) =>
                        status === HttpStatusCode.BadRequest
                          ? i18n.str`The exchange rate was incorrectly applied`
                          : status === HttpStatusCode.Forbidden
                          ? i18n.str`A institutional user tried the operation`
                          : status === HttpStatusCode.Conflict
                          ? i18n.str`Need a contact data where to send the TAN`
                          : status === HttpStatusCode.PreconditionFailed
                          ? i18n.str`The account does not have sufficient funds`
                          : undefined,
                      onServerError: (status) =>
                        status === HttpStatusCode.ServiceUnavailable
                          ? i18n.str`The bank does not support the TAN channel for this operation`
                          : undefined,
                    }),
                  );
                } else {
                  saveError({
                    title: i18n.str`Operation failed, please report`,
                    description:
                      error instanceof Error
                        ? error.message
                        : JSON.stringify(error),
                  });
                }
              }
            }}
          >
            {i18n.str`Create`}
          </button>
        </div>
      </form>
    </div>
  );
}

interface ShowCashoutProps {
  id: string;
  onCancel: () => void;
  onLoadNotOk: <T>(
    error: HttpResponsePaginated<T, SandboxBackend.SandboxError>,
  ) => VNode;
}
export function ShowCashoutDetails({
  id,
  onCancel,
  onLoadNotOk,
}: ShowCashoutProps): VNode {
  const { i18n } = useTranslationContext();
  const result = useCashoutDetails(id);
  const { abortCashout, confirmCashout } = useCircuitAccountAPI();
  const [code, setCode] = useState<string | undefined>(undefined);
  const [error, saveError] = useState<ErrorMessage | undefined>();
  if (!result.ok) return onLoadNotOk(result);
  const errors = undefinedIfEmpty({
    code: !code ? i18n.str`required` : undefined,
  });
  const isPending = String(result.data.status).toUpperCase() === "PENDING";
  return (
    <div>
      <h1>Cashout details {id}</h1>
      {error && (
        <ErrorBannerFloat error={error} onClear={() => saveError(undefined)} />
      )}
      <form class="pure-form">
        <fieldset>
          <label>
            <i18n.Translate>Subject</i18n.Translate>
          </label>
          <input readOnly value={result.data.subject} />
        </fieldset>
        <fieldset>
          <label>
            <i18n.Translate>Created</i18n.Translate>
          </label>
          <input readOnly value={result.data.creation_time ?? ""} />
        </fieldset>
        <fieldset>
          <label>
            <i18n.Translate>Confirmed</i18n.Translate>
          </label>
          <input readOnly value={result.data.confirmation_time ?? ""} />
        </fieldset>
        <fieldset>
          <label>
            <i18n.Translate>Debited</i18n.Translate>
          </label>
          <input readOnly value={result.data.amount_debit} />
        </fieldset>
        <fieldset>
          <label>
            <i18n.Translate>Credit</i18n.Translate>
          </label>
          <input readOnly value={result.data.amount_credit} />
        </fieldset>
        <fieldset>
          <label>
            <i18n.Translate>Status</i18n.Translate>
          </label>
          <input readOnly value={result.data.status} />
        </fieldset>
        <fieldset>
          <label>
            <i18n.Translate>Destination</i18n.Translate>
          </label>
          <input readOnly value={result.data.cashout_address} />
        </fieldset>
        {isPending ? (
          <fieldset>
            <label>
              <i18n.Translate>Code</i18n.Translate>
            </label>
            <input
              value={code ?? ""}
              onChange={(e) => {
                setCode(e.currentTarget.value);
              }}
            />
            <ShowInputErrorLabel
              message={errors?.code}
              isDirty={code !== undefined}
            />
          </fieldset>
        ) : undefined}
      </form>
      <br />
      <div style={{ display: "flex", justifyContent: "space-between" }}>
        <button
          class="pure-button pure-button-secondary btn-cancel"
          onClick={(e) => {
            e.preventDefault();
            onCancel();
          }}
        >
          {i18n.str`Back`}
        </button>
        {isPending ? (
          <div>
            <button
              type="submit"
              class="pure-button pure-button-primary button-error"
              onClick={async (e) => {
                e.preventDefault();
                try {
                  await abortCashout(id);
                  onCancel();
                } catch (error) {
                  if (error instanceof RequestError) {
                    saveError(
                      buildRequestErrorMessage(i18n, error.cause, {
                        onClientError: (status) =>
                          status === HttpStatusCode.NotFound
                            ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
                            : status === HttpStatusCode.PreconditionFailed
                            ? i18n.str`Cashout was already confimed`
                            : undefined,
                      }),
                    );
                  } else {
                    saveError({
                      title: i18n.str`Operation failed, please report`,
                      description:
                        error instanceof Error
                          ? error.message
                          : JSON.stringify(error),
                    });
                  }
                }
              }}
            >
              {i18n.str`Abort`}
            </button>
            &nbsp;
            <button
              type="submit"
              disabled={!code}
              class="pure-button pure-button-primary "
              onClick={async (e) => {
                e.preventDefault();
                try {
                  if (!code) return;
                  const rest = await confirmCashout(id, {
                    tan: code,
                  });
                } catch (error) {
                  if (error instanceof RequestError) {
                    saveError(
                      buildRequestErrorMessage(i18n, error.cause, {
                        onClientError: (status) =>
                          status === HttpStatusCode.NotFound
                            ? i18n.str`Cashout not found. It may be also mean that it was already aborted.`
                            : status === HttpStatusCode.PreconditionFailed
                            ? i18n.str`Cashout was already confimed`
                            : status === HttpStatusCode.Conflict
                            ? i18n.str`Confirmation failed. Maybe the user changed their cash-out address between the creation and the confirmation`
                            : status === HttpStatusCode.Forbidden
                            ? i18n.str`Invalid code`
                            : undefined,
                      }),
                    );
                  } else {
                    saveError({
                      title: i18n.str`Operation failed, please report`,
                      description:
                        error instanceof Error
                          ? error.message
                          : JSON.stringify(error),
                    });
                  }
                }
              }}
            >
              {i18n.str`Confirm`}
            </button>
          </div>
        ) : (
          <div />
        )}
      </div>
    </div>
  );
}

const MAX_AMOUNT_DIGIT = 2;
/**
 * Truncate the amount of digits to display
 * in the form based on the fee calculations
 *
 * Backend must have the same truncation
 * @param a
 * @returns
 */
function truncate(a: AmountJson): AmountJson {
  const str = Amounts.stringify(a);
  const idx = str.indexOf(".");
  if (idx === -1) {
    return a;
  }
  const truncated = str.substring(0, idx + 1 + MAX_AMOUNT_DIGIT);
  return Amounts.parseOrThrow(truncated);
}

export function assertUnreachable(x: never): never {
  throw new Error("Didn't expect to get here");
}
