/*
 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 {
  ErrorType,
  HttpResponsePaginated,
  RequestError,
  useTranslationContext,
} from "@gnu-taler/web-util/lib/index.browser";
import { Fragment, h, VNode } from "preact";
import { 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,
  useRatiosAndFeeConfig,
} from "../hooks/circuit.js";
import { TanChannel, undefinedIfEmpty } from "../utils.js";
import { ShowAccountDetails, UpdateAccountPassword } from "./AdminPage.js";
import { ErrorBanner } 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;
};

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 [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 maybeBalance = Amounts.parse(result.data.balance.amount);
  if (!maybeBalance) return <div>error</div>;
  const balance = maybeBalance;
  const zero = Amounts.zeroOfCurrency(balance.currency);

  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.ratios_and_fees.fiat_currency;

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

  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 + 3);
    console.log(str, truncated);
    return Amounts.parseOrThrow(truncated);
  }

  const amount = Amounts.parse(`${balance.currency}:${form.amount}`);
  const amount_debit = !amount
    ? zero
    : form.isDebit
    ? amount
    : truncate(Amounts.divide(Amounts.add(amount, sellFee).amount, sellRate));
  const credit_before_fee = !amount
    ? zero
    : form.isDebit
    ? truncate(Amounts.divide(amount, 1 / sellRate))
    : Amounts.add(amount, sellFee).amount;

  const __amount_credit = Amounts.sub(credit_before_fee, sellFee).amount;
  const amount_credit = Amounts.parseOrThrow(
    `${fiatCurrency}:${Amounts.stringifyValue(__amount_credit)}`,
  );

  const balanceAfter = Amounts.sub(balance, amount_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(balance, amount_debit) === -1
      ? i18n.str`balance is not enough`
      : Amounts.cmp(credit_before_fee, sellFee) === -1
      ? i18n.str`amount is not enough`
      : Amounts.isZero(amount_credit)
      ? i18n.str`amount is not enough`
      : undefined,
    channel: !form.channel ? i18n.str`required` : undefined,
  });

  return (
    <div>
      {error && (
        <ErrorBanner 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={
                !form.isDebit ? fiatCurrency.length : balance.currency.length
              }
              maxLength={
                !form.isDebit ? fiatCurrency.length : balance.currency.length
              }
              tabIndex={-1}
              value={!form.isDebit ? fiatCurrency : balance.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={amount_debit ? Amounts.stringifyValue(amount_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(credit_before_fee)}
                />
              </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={amount_credit ? Amounts.stringifyValue(amount_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(amount_credit),
                  amount_debit: Amounts.stringify(amount_debit),
                  subject: form.subject,
                  tan_channel: form.channel,
                });
                onComplete(res.data.uuid);
              } catch (error) {
                if (error instanceof RequestError) {
                  const e = error as RequestError<SandboxBackend.SandboxError>;
                  switch (e.cause.type) {
                    case ErrorType.TIMEOUT: {
                      saveError({
                        title: i18n.str`Request timeout, try again later.`,
                      });
                      break;
                    }
                    case ErrorType.CLIENT: {
                      const errorData = e.cause.error;

                      if (
                        e.cause.status === HttpStatusCode.PreconditionFailed
                      ) {
                        saveError({
                          title: i18n.str`The account does not have sufficient funds`,
                          description: errorData.error.description,
                          debug: JSON.stringify(error.info),
                        });
                      } else if (e.cause.status === HttpStatusCode.Conflict) {
                        saveError({
                          title: i18n.str`No contact information for this channel`,
                          description: errorData.error.description,
                          debug: JSON.stringify(error.info),
                        });
                      } else {
                        saveError({
                          title: i18n.str`New cashout gave response error`,
                          description: errorData.error.description,
                          debug: JSON.stringify(error.info),
                        });
                      }
                      break;
                    }
                    case ErrorType.SERVER: {
                      const errorData = e.cause.error;
                      if (
                        e.cause.status === HttpStatusCode.ServiceUnavailable
                      ) {
                        saveError({
                          title: i18n.str`The bank does not support the TAN channel for this operation`,
                          description: errorData.error.description,
                          debug: JSON.stringify(error.info),
                        });
                      } else {
                        saveError({
                          title: i18n.str`Creating cashout returned with a server error`,
                          description: errorData.error.description,
                          debug: JSON.stringify(error.cause),
                        });
                      }
                      break;
                    }
                    case ErrorType.UNEXPECTED: {
                      saveError({
                        title: i18n.str`Unexpected error trying to create cashout.`,
                        debug: JSON.stringify(error.cause),
                      });
                      break;
                    }
                    default: {
                      assertUnreachable(e.cause);
                    }
                  }
                } else if (error instanceof Error) {
                  saveError({
                    title: i18n.str`Cashout failed, please report`,
                    description: error.message,
                  });
                }
              }
            }}
          >
            {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 && (
        <ErrorBanner 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 {
                  const rest = await abortCashout(id);
                  onCancel();
                } catch (error) {
                  if (error instanceof RequestError) {
                    const e =
                      error as RequestError<SandboxBackend.SandboxError>;
                    switch (e.cause.type) {
                      case ErrorType.TIMEOUT: {
                        saveError({
                          title: i18n.str`Request timeout, try again later.`,
                        });
                        break;
                      }
                      case ErrorType.CLIENT: {
                        const errorData = e.cause.error;
                        if (
                          e.cause.status === HttpStatusCode.PreconditionFailed
                        ) {
                          saveError({
                            title: i18n.str`Cashout was already aborted`,
                            description: errorData.error.description,
                            debug: JSON.stringify(error.info),
                          });
                        } else {
                          saveError({
                            title: i18n.str`Aborting cashout gave response error`,
                            description: errorData.error.description,
                            debug: JSON.stringify(error.info),
                          });
                        }

                        saveError({
                          title: i18n.str`Aborting cashout gave response error`,
                          description: errorData.error.description,
                          debug: JSON.stringify(error.cause),
                        });
                        break;
                      }
                      case ErrorType.SERVER: {
                        const errorData = e.cause.error;
                        saveError({
                          title: i18n.str`Aborting cashout returned with a server error`,
                          description: errorData.error.description,
                          debug: JSON.stringify(error.cause),
                        });
                        break;
                      }
                      case ErrorType.UNEXPECTED: {
                        saveError({
                          title: i18n.str`Unexpected error trying to abort cashout.`,
                          debug: JSON.stringify(error.cause),
                        });
                        break;
                      }
                      default: {
                        assertUnreachable(e.cause);
                      }
                    }
                  } else if (error instanceof Error) {
                    saveError({
                      title: i18n.str`Aborting failed, please report`,
                      description: error.message,
                    });
                  }
                }
              }}
            >
              {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) {
                    const e =
                      error as RequestError<SandboxBackend.SandboxError>;
                    switch (e.cause.type) {
                      case ErrorType.TIMEOUT: {
                        saveError({
                          title: i18n.str`Request timeout, try again later.`,
                        });
                        break;
                      }
                      case ErrorType.CLIENT: {
                        const errorData = e.cause.error;
                        saveError({
                          title: i18n.str`Confirmation of cashout gave response error`,
                          description: errorData.error.description,
                          debug: JSON.stringify(error.cause),
                        });
                        break;
                      }
                      case ErrorType.SERVER: {
                        const errorData = e.cause.error;
                        saveError({
                          title: i18n.str`Confirmation of cashout gave response error`,
                          description: errorData.error.description,
                          debug: JSON.stringify(error.cause),
                        });
                        break;
                      }
                      case ErrorType.UNEXPECTED: {
                        saveError({
                          title: i18n.str`Unexpected error trying to cashout.`,
                          debug: JSON.stringify(error.cause),
                        });
                        break;
                      }
                      default: {
                        assertUnreachable(e.cause);
                      }
                    }
                  } else if (error instanceof Error) {
                    saveError({
                      title: i18n.str`Confirmation failed, please report`,
                      description: error.message,
                    });
                  }
                }
              }}
            >
              {i18n.str`Confirm`}
            </button>
          </div>
        ) : (
          <div />
        )}
      </div>
    </div>
  );
}

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