/*
 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 {
  AbsoluteTime,
  Amounts,
  HttpStatusCode,
  TalerError,
  TalerErrorCode,
  TranslatedString,
  assertUnreachable,
  encodeCrock,
  getRandomBytes,
  parsePaytoUri,
} from "@gnu-taler/taler-util";
import {
  Attention,
  Loading,
  LocalNotificationBanner,
  RouteDefinition,
  ShowInputErrorLabel,
  notifyInfo,
  useBankCoreApiContext,
  useLocalNotification,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";
import { useAccountDetails } from "../../hooks/account.js";
import { useBankState } from "../../hooks/bank-state.js";
import {
  TransferCalculation,
  useCashoutEstimator,
  useConversionInfo,
} from "../../hooks/regional.js";
import { useSessionState } from "../../hooks/session.js";
import { TanChannel, undefinedIfEmpty } from "../../utils.js";
import { LoginForm } from "../LoginForm.js";
import {
  InputAmount,
  RenderAmount,
  doAutoFocus,
} from "../PaytoWireTransferForm.js";

interface Props {
  account: string;
  focus?: boolean;
  onAuthorizationRequired: () => void;
  onCashout: () => void;
  routeClose: RouteDefinition;
  routeHere: RouteDefinition;
}

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

export function CreateCashout({
  account: accountName,
  onAuthorizationRequired,
  onCashout,
  focus,
  routeHere,
  routeClose,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  const resultAccount = useAccountDetails(accountName);
  const {
    estimateByCredit: calculateFromCredit,
    estimateByDebit: calculateFromDebit,
  } = useCashoutEstimator();
  const { state: credentials } = useSessionState();
  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
  const [, updateBankState] = useBankState();

  const {
    lib: { bank: api },
    config,
  } = useBankCoreApiContext();
  const [form, setForm] = useState<Partial<FormType>>({ isDebit: true });
  const [notification, notify, handleError] = useLocalNotification();
  const info = useConversionInfo();

  if (!config.allow_conversion) {
    return (
      <Fragment>
        <Attention type="warning" title={i18n.str`Unable to create a cashout`}>
          <i18n.Translate>
            The bank configuration does not support cashout operations.
          </i18n.Translate>
        </Attention>
        <div class="mt-5 sm:mt-6">
          <a
            href={routeClose.url({})}
            name="close"
            class="inline-flex w-full justify-center 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>Close</i18n.Translate>
          </a>
        </div>
      </Fragment>
    );
  }

  if (!resultAccount) {
    return <Loading />;
  }
  if (resultAccount instanceof TalerError) {
    return <ErrorLoadingWithDebug error={resultAccount} />;
  }
  if (resultAccount.type === "fail") {
    switch (resultAccount.case) {
      case HttpStatusCode.Unauthorized:
        return <LoginForm currentUser={accountName} />;
      case HttpStatusCode.NotFound:
        return <LoginForm currentUser={accountName} />;
      default:
        assertUnreachable(resultAccount);
    }
  }
  if (!info) {
    return <Loading />;
  }

  if (info instanceof TalerError) {
    return <ErrorLoadingWithDebug error={info} />;
  }
  if (info.type === "fail") {
    switch (info.case) {
      case HttpStatusCode.NotImplemented: {
        return (
          <Attention type="danger" title={i18n.str`Cashout are disabled`}>
            <i18n.Translate>
              Cashout should be enable by configuration and the conversion rate
              should be initialized with fee, ratio and rounding mode.
            </i18n.Translate>
          </Attention>
        );
      }
      default:
        assertUnreachable(info.case);
    }
  }

  const conversionInfo = info.body.conversion_rate;
  if (!conversionInfo) {
    return (
      <div>conversion enabled but server replied without conversion_rate</div>
    );
  }

  const {
    fiat_currency,
    regional_currency,
    fiat_currency_specification,
    regional_currency_specification,
  } = info.body;
  const regionalZero = Amounts.zeroOfCurrency(regional_currency);
  const fiatZero = Amounts.zeroOfCurrency(fiat_currency);

  const account = {
    balance: Amounts.parseOrThrow(resultAccount.body.balance.amount),
    balanceIsDebit:
      resultAccount.body.balance.credit_debit_indicator == "debit",
    debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold),
    minCashout:
      resultAccount.body.min_cashout === undefined
        ? regionalZero
        : Amounts.parseOrThrow(resultAccount.body.min_cashout),
  };

  const limit = account.balanceIsDebit
    ? Amounts.sub(account.debitThreshold, account.balance).amount
    : Amounts.add(account.balance, account.debitThreshold).amount;

  const zeroCalc = {
    debit: regionalZero,
    credit: fiatZero,
    beforeFee: fiatZero,
  };
  const [calculationResult, setCalculation] =
    useState<TransferCalculation>(zeroCalc);
  const sellFee = Amounts.parseOrThrow(conversionInfo.cashout_fee);
  const sellRate = conversionInfo.cashout_ratio;
  /**
   * can be in regional currency or fiat currency
   * depending on the isDebit flag
   */
  const inputAmount = Amounts.parseOrThrow(
    `${form.isDebit ? regional_currency : fiat_currency}:${
      !form.amount ? "0" : form.amount
    }`,
  );

  useEffect(() => {
    async function doAsync() {
      await handleError(async () => {
        const higerThanMin = form.isDebit
          ? Amounts.cmp(inputAmount, conversionInfo.cashout_min_amount) === 1
          : true;
        const notZero = Amounts.isNonZero(inputAmount);
        if (notZero && higerThanMin) {
          const resp = await (form.isDebit
            ? calculateFromDebit(inputAmount, sellFee)
            : calculateFromCredit(inputAmount, sellFee));
          setCalculation(resp);
        } else {
          setCalculation(zeroCalc);
        }
      });
    }
    doAsync();
  }, [form.amount, form.isDebit]);

  const calc =
    calculationResult === "amount-is-too-small" ? zeroCalc : calculationResult;

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

  function updateForm(newForm: typeof form): void {
    setForm(newForm);
  }
  const errors = undefinedIfEmpty<ErrorFrom<typeof form>>({
    subject: !form.subject ? i18n.str`Required` : undefined,
    amount: !form.amount
      ? i18n.str`Required`
      : !inputAmount
        ? i18n.str`Invalid`
        : Amounts.cmp(limit, calc.debit) === -1
          ? i18n.str`Balance is not enough`
          : calculationResult === "amount-is-too-small"
            ? i18n.str`Amount needs to be higher`
            : Amounts.cmp(calc.debit, conversionInfo.cashout_min_amount) < 0
              ? i18n.str`No account can't cashout less than ${
                  Amounts.stringifyValueWithSpec(
                    Amounts.parseOrThrow(conversionInfo.cashout_min_amount),
                    regional_currency_specification,
                  ).normal
                }`
              : Amounts.cmp(calc.debit, account.minCashout) < 0
                ? i18n.str`Your account can't cashout less than ${
                    Amounts.stringifyValueWithSpec(
                      Amounts.parseOrThrow(account.minCashout),
                      regional_currency_specification,
                    ).normal
                  }`
                : Amounts.isZero(calc.credit)
                  ? i18n.str`The total transfer at destination will be zero`
                  : undefined,
  });
  const trimmedAmountStr = form.amount?.trim();

  async function createCashout() {
    const request_uid = encodeCrock(getRandomBytes(32));
    await handleError(async () => {
      if (!creds || !form.subject) return;
      const request = {
        request_uid,
        amount_credit: Amounts.stringify(calc.credit),
        amount_debit: Amounts.stringify(calc.debit),
        subject: form.subject,
      };
      const resp = await api.createCashout(creds, request);
      if (resp.type === "ok") {
        notifyInfo(i18n.str`Cashout created`);
        onCashout();
      } else {
        switch (resp.case) {
          case HttpStatusCode.Accepted: {
            updateBankState("currentChallenge", {
              operation: "create-cashout",
              id: String(resp.body.challenge_id),
              sent: AbsoluteTime.never(),
              location: routeHere.url({}),
              request,
            });
            return onAuthorizationRequired();
          }
          case HttpStatusCode.NotFound:
            return notify({
              type: "error",
              title: i18n.str`Account not found`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case TalerErrorCode.BANK_TRANSFER_REQUEST_UID_REUSED:
            return notify({
              type: "error",
              title: i18n.str`Duplicated request detected, check if the operation succeeded or try again.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case TalerErrorCode.BANK_BAD_CONVERSION:
            return notify({
              type: "error",
              title: i18n.str`The conversion rate was incorrectly applied`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case TalerErrorCode.BANK_UNALLOWED_DEBIT:
            return notify({
              type: "error",
              title: i18n.str`The account does not have sufficient funds`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case HttpStatusCode.NotImplemented:
            return notify({
              type: "error",
              title: i18n.str`Cashout are disabled`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case TalerErrorCode.BANK_CONFIRM_INCOMPLETE:
            return notify({
              type: "error",
              title: i18n.str`Missing cashout URI in the profile`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
          case TalerErrorCode.BANK_CONVERSION_AMOUNT_TO_SMALL:
            return notify({
              type: "error",
              title: i18n.str`The amount is less than the minimum allowed.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });

          case TalerErrorCode.BANK_TAN_CHANNEL_SCRIPT_FAILED:
            return notify({
              type: "error",
              title: i18n.str`Sending the confirmation message failed, retry later or contact the administrator.`,
              description: resp.detail.hint as TranslatedString,
              debug: resp.detail,
              when: AbsoluteTime.now(),
            });
        }
        assertUnreachable(resp);
      }
    });
  }
  const cashoutDisabled =
    (config.supported_tan_channels ?? []).length < 1 ||
    !resultAccount.body.cashout_payto_uri;

  const cashoutAccount = !resultAccount.body.cashout_payto_uri
    ? undefined
    : parsePaytoUri(resultAccount.body.cashout_payto_uri);
  const cashoutAccountName = !cashoutAccount
    ? undefined
    : cashoutAccount.targetPath;

  const cashoutLegalName = !cashoutAccount
    ? undefined
    : cashoutAccount.params["receiver-name"];

  return (
    <div>
      <LocalNotificationBanner notification={notification} />

      <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-6 md:grid-cols-3 bg-gray-100 my-4 px-4 pb-4 rounded-lg">
        <section class="mt-4 rounded-sm px-4 py-6 p-8 ">
          <h2 id="summary-heading" class="font-medium text-lg">
            <i18n.Translate>Cashout</i18n.Translate>
          </h2>

          <dl class="mt-4 space-y-4">
            <div class="justify-between items-center flex">
              <dt class="text-sm text-gray-600">
                <i18n.Translate>Conversion rate</i18n.Translate>
              </dt>
              <dd class="text-sm text-gray-900">{sellRate}</dd>
            </div>

            <div class="flex items-center justify-between border-t-2 afu pt-4">
              <dt class="flex items-center text-sm text-gray-600">
                <span>
                  <i18n.Translate>Balance</i18n.Translate>
                </span>
              </dt>
              <dd class="text-sm text-gray-900">
                <RenderAmount
                  value={account.balance}
                  spec={regional_currency_specification}
                />
              </dd>
            </div>
            <div class="flex items-center justify-between border-t-2 afu pt-4">
              <dt class="flex items-center text-sm text-gray-600">
                <span>
                  <i18n.Translate>Fee</i18n.Translate>
                </span>
              </dt>
              <dd class="text-sm text-gray-900">
                <RenderAmount
                  value={sellFee}
                  spec={fiat_currency_specification}
                />
              </dd>
            </div>
            {cashoutAccountName && cashoutLegalName ? (
              <Fragment>
                <div class="flex items-center justify-between border-t-2 afu pt-4">
                  <dt class="flex items-center text-sm text-gray-600">
                    <span>
                      <i18n.Translate>To account</i18n.Translate>
                    </span>
                  </dt>
                  <dd class="text-sm text-gray-900">{cashoutAccountName}</dd>
                </div>
                <div class="flex items-center justify-between border-t-2 afu pt-4">
                  <dt class="flex items-center text-sm text-gray-600">
                    <span>
                      <i18n.Translate>Legal name</i18n.Translate>
                    </span>
                  </dt>
                  <dd class="text-sm text-gray-900">{cashoutLegalName}</dd>
                </div>
                <p class="mt-2 text-sm text-gray-500">
                  <i18n.Translate>
                    If this name doesn't match the account holder's name your
                    transaction may fail.
                  </i18n.Translate>
                </p>
              </Fragment>
            ) : (
              <div class="flex items-center justify-between border-t-2 afu pt-4">
                <Attention type="warning" title={i18n.str`No cashout account`}>
                  <i18n.Translate>
                    Before doing a cashout you need to complete your profile
                  </i18n.Translate>
                </Attention>
              </div>
            )}
          </dl>
        </section>
        <form
          class="bg-white shadow-sm ring-1 ring-gray-900/5 sm:rounded-xl md:col-span-2"
          autoCapitalize="none"
          autoCorrect="off"
          onSubmit={(e) => {
            e.preventDefault();
          }}
        >
          <div class="px-4 py-6 sm:p-8">
            <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
              {/* subject */}

              <div class="sm:col-span-5">
                <label
                  class="block text-sm font-medium leading-6 text-gray-900"
                  for="subject"
                >
                  {i18n.str`Transfer subject`}
                  <b style={{ color: "red" }}> *</b>
                </label>
                <div class="mt-2">
                  <input
                    ref={focus ? doAutoFocus : undefined}
                    type="text"
                    class="block w-full rounded-md disabled:bg-gray-200 border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 data-[error=true]:ring-red-500 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                    name="subject"
                    id="subject"
                    disabled={cashoutDisabled}
                    data-error={!!errors?.subject && form.subject !== undefined}
                    value={form.subject ?? ""}
                    onChange={(e) => {
                      form.subject = e.currentTarget.value;
                      updateForm(structuredClone(form));
                    }}
                    autocomplete="off"
                  />
                  <ShowInputErrorLabel
                    message={errors?.subject}
                    isDirty={form.subject !== undefined}
                  />
                </div>
              </div>

              <div class="sm:col-span-5">
                <label
                  class="block text-sm font-medium leading-6 text-gray-900"
                  for="subject"
                >
                  {i18n.str`Currency`}
                </label>

                <div class="mt-2">
                  <button
                    type="button"
                    name="set 50"
                    class="               inline-flex p-4 text-sm items-center rounded-l-md bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
                    onClick={(e) => {
                      e.preventDefault();
                      form.isDebit = true;
                      updateForm(structuredClone(form));
                    }}
                  >
                    {form.isDebit ? (
                      <svg
                        class="self-center flex-none h-5 w-5 text-indigo-600"
                        viewBox="0 0 20 20"
                        fill="currentColor"
                        aria-hidden="true"
                      >
                        <path
                          fill-rule="evenodd"
                          d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
                          clip-rule="evenodd"
                        />
                      </svg>
                    ) : (
                      <svg
                        fill="none"
                        viewBox="0 0 24 24"
                        stroke-width="1.5"
                        stroke="currentColor"
                        class="w-5 h-5"
                      >
                        <path d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
                      </svg>
                    )}

                    <i18n.Translate>Send {regional_currency}</i18n.Translate>
                  </button>
                  <button
                    type="button"
                    name="set 25"
                    class=" -ml-px -mr-px inline-flex p-4 text-sm items-center rounded-r-md              bg-white text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus:z-10"
                    onClick={(e) => {
                      e.preventDefault();
                      form.isDebit = false;
                      updateForm(structuredClone(form));
                    }}
                  >
                    {!form.isDebit ? (
                      <svg
                        class="self-center flex-none h-5 w-5 text-indigo-600"
                        viewBox="0 0 20 20"
                        fill="currentColor"
                        aria-hidden="true"
                      >
                        <path
                          fill-rule="evenodd"
                          d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.857-9.809a.75.75 0 00-1.214-.882l-3.483 4.79-1.88-1.88a.75.75 0 10-1.06 1.061l2.5 2.5a.75.75 0 001.137-.089l4-5.5z"
                          clip-rule="evenodd"
                        />
                      </svg>
                    ) : (
                      <svg
                        fill="none"
                        viewBox="0 0 24 24"
                        stroke-width="1.5"
                        stroke="currentColor"
                        class="w-5 h-5"
                      >
                        <path d="M15 12H9m12 0a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
                      </svg>
                    )}

                    <i18n.Translate>Receive {fiat_currency}</i18n.Translate>
                  </button>
                </div>
              </div>

              {/* amount */}
              <div class="sm:col-span-5">
                <div class="flex justify-between">
                  <label
                    class="block text-sm font-medium leading-6 text-gray-900"
                    for="amount"
                  >
                    {i18n.str`Amount`}
                    <b style={{ color: "red" }}> *</b>
                  </label>
                  {/* <button
                    type="button"
                    data-enabled={form.isDebit}
                    class="bg-indigo-600 data-[enabled=false]:bg-gray-200 relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2"
                    role="switch"
                    aria-checked="false"
                    aria-labelledby="availability-label"
                    aria-describedby="availability-description"
                    onClick={() => {
                      form.isDebit = !form.isDebit;
                      updateForm(structuredClone(form));
                    }}
                  >
                    <span
                      aria-hidden="true"
                      data-enabled={form.isDebit}
                      class="translate-x-5 data-[enabled=false]:translate-x-0 pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
                    ></span>
                  </button> */}
                </div>
                <div class="mt-2">
                  <InputAmount
                    name="amount"
                    left
                    currency={form.isDebit ? regional_currency : fiat_currency}
                    value={trimmedAmountStr}
                    onChange={
                      cashoutDisabled
                        ? undefined
                        : (value) => {
                            form.amount = value;
                            updateForm(structuredClone(form));
                          }
                    }
                  />
                  <ShowInputErrorLabel
                    message={errors?.amount}
                    isDirty={form.amount !== undefined}
                  />
                </div>
              </div>

              {Amounts.isZero(calc.credit) ? undefined : (
                <div class="sm:col-span-5">
                  <dl class="mt-4 space-y-4">
                    <div class="justify-between items-center flex ">
                      <dt class="text-sm text-gray-600">
                        <i18n.Translate>Total cost</i18n.Translate>
                      </dt>
                      <dd class="text-sm text-gray-900">
                        <RenderAmount
                          value={calc.debit}
                          negative
                          withColor
                          spec={regional_currency_specification}
                        />
                      </dd>
                    </div>

                    <div class="flex items-center justify-between border-t-2 afu pt-4">
                      <dt class="flex items-center text-sm text-gray-600">
                        <span>
                          <i18n.Translate>Balance left</i18n.Translate>
                        </span>
                      </dt>
                      <dd class="text-sm text-gray-900">
                        <RenderAmount
                          value={balanceAfter}
                          spec={regional_currency_specification}
                        />
                      </dd>
                    </div>
                    {Amounts.isZero(sellFee) ||
                    Amounts.isZero(calc.beforeFee) ? undefined : (
                      <div class="flex items-center justify-between border-t-2 afu pt-4">
                        <dt class="flex items-center text-sm text-gray-600">
                          <span>
                            <i18n.Translate>Before fee</i18n.Translate>
                          </span>
                        </dt>
                        <dd class="text-sm text-gray-900">
                          <RenderAmount
                            value={calc.beforeFee}
                            spec={fiat_currency_specification}
                          />
                        </dd>
                      </div>
                    )}
                    <div class="flex justify-between items-center border-t-2 afu pt-4">
                      <dt class="text-lg text-gray-900 font-medium">
                        <i18n.Translate>Total cashout transfer</i18n.Translate>
                      </dt>
                      <dd class="text-lg text-gray-900 font-medium">
                        <RenderAmount
                          value={calc.credit}
                          withColor
                          spec={fiat_currency_specification}
                        />
                      </dd>
                    </div>
                  </dl>
                </div>
              )}
            </div>
          </div>

          <div class="flex items-center justify-between gap-x-6 border-t border-gray-900/10 px-4 py-4 sm:px-8">
            <a
              href={routeClose.url({})}
              name="cancel"
              type="button"
              class="text-sm font-semibold leading-6 text-gray-900"
            >
              <i18n.Translate>Cancel</i18n.Translate>
            </a>
            <button
              type="submit"
              name="cashout"
              class="disabled:opacity-50 disabled:cursor-default cursor-pointer 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"
              disabled={!!errors}
              onClick={(e) => {
                e.preventDefault();
                createCashout();
              }}
            >
              <i18n.Translate>Cashout</i18n.Translate>
            </button>
          </div>
        </form>
      </div>
    </div>
  );
}
