/*
 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 {
  Amounts,
  TalerError,
  TranslatedString,
  encodeCrock,
  getRandomBytes,
  parsePaytoUri
} from "@gnu-taler/taler-util";
import {
  Attention,
  ErrorLoading,
  Loading,
  LocalNotificationBanner,
  ShowInputErrorLabel,
  notifyInfo,
  useLocalNotification,
  useTranslationContext
} from "@gnu-taler/web-util/browser";
import { Fragment, VNode, h } from "preact";
import { useEffect, useState } from "preact/hooks";
import { mutate } from "swr";
import { useBankCoreApiContext } from "../../context/config.js";
import { useAccountDetails } from "../../hooks/access.js";
import { useBackendState } from "../../hooks/backend.js";
import {
  useConversionInfo,
  useEstimator
} from "../../hooks/circuit.js";
import {
  TanChannel,
  undefinedIfEmpty
} from "../../utils.js";
import { LoginForm } from "../LoginForm.js";
import { InputAmount, RenderAmount, doAutoFocus } from "../PaytoWireTransferForm.js";
import { assertUnreachable } from "../WithdrawalOperationPage.js";
import { getRandomPassword, getRandomUsername } from "../rnd.js";
import { ErrorLoadingWithDebug } from "../../components/ErrorLoadingWithDebug.js";

interface Props {
  account: string;
  focus?: boolean,
  onComplete: (id: string) => void;
  onCancel?: () => void;
}

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


export function CreateCashout({
  account: accountName,
  onComplete,
  focus,
  onCancel,
}: Props): VNode {
  const { i18n } = useTranslationContext();
  const resultAccount = useAccountDetails(accountName);
  const {
    estimateByCredit: calculateFromCredit,
    estimateByDebit: calculateFromDebit,
  } = useEstimator();
  const { state: credentials } = useBackendState();
  const creds = credentials.status !== "loggedIn" ? undefined : credentials

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

  if (!config.allow_conversion) {
    return <Attention type="warning" title={i18n.str`Unable to create a cashout`} onClose={onCancel}>
      <i18n.Translate>The bank configuration does not support cashout operations.</i18n.Translate>
    </Attention>
  }
  if (!resultAccount) {
    return <Loading />
  }
  if (resultAccount instanceof TalerError) {
    return <ErrorLoadingWithDebug error={resultAccount} />
  }
  if (resultAccount.type === "fail") {
    switch (resultAccount.case) {
      case "unauthorized": return <LoginForm currentUser={accountName} />
      case "not-found": return <LoginForm currentUser={accountName} />
      default: assertUnreachable(resultAccount)
    }
  }
  if (!info) {
    return <Loading />
  }

  if (info instanceof TalerError) {
    return <ErrorLoadingWithDebug error={info} />
  }

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

  const account = {
    balance: Amounts.parseOrThrow(resultAccount.body.balance.amount),
    balanceIsDebit: resultAccount.body.balance.credit_debit_indicator == "debit",
    debitThreshold: Amounts.parseOrThrow(resultAccount.body.debit_threshold)
  }

  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 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 [calc, setCalc] = useState(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 () => {
        if (Amounts.isNonZero(inputAmount)) {
          const resp = await (form.isDebit ?
            calculateFromDebit(inputAmount, sellFee) :
            calculateFromCredit(inputAmount, sellFee));
          setCalc(resp)
        }
      })
    }
    doAsync()
  }, [form.amount, form.isDebit]);

  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`could not be parsed`
        : Amounts.cmp(limit, calc.debit) === -1
          ? i18n.str`balance is not enough`
          : Amounts.cmp(calc.credit, sellFee) === -1
            ? i18n.str`need to be higher due to fees`
            : Amounts.isZero(calc.credit)
              ? i18n.str`the total transfer at destination will be zero`
              : undefined,
    channel: !form.channel ? i18n.str`required` : undefined,
  });
  const trimmedAmountStr = form.amount?.trim();

  async function createCashout() {
    const request_uid = encodeCrock(getRandomBytes(32))
    await handleError(async () => {
      if (!creds || !form.subject || !form.channel) return;

      const resp = await api.createCashout(creds, {
        request_uid,
        amount_credit: Amounts.stringify(calc.credit),
        amount_debit: Amounts.stringify(calc.debit),
        subject: form.subject,
        tan_channel: form.channel,
      })
      if (resp.type === "ok") {
        notifyInfo(i18n.str`Cashout created`)
      } else {
        switch (resp.case) {
          case "account-not-found": return notify({
            type: "error",
            title: i18n.str`Account not found`,
            description: resp.detail.hint as TranslatedString,
            debug: resp.detail,
          });
          case "request-already-used": return notify({
            type: "error",
            title: i18n.str`Duplicated request detected, check if the operation succeded or try again.`,
            description: resp.detail.hint as TranslatedString,
            debug: resp.detail,
          });
          case "incorrect-exchange-rate": return notify({
            type: "error",
            title: i18n.str`The exchange rate was incorrectly applied`,
            description: resp.detail.hint as TranslatedString,
            debug: resp.detail,
          });
          case "no-contact-info": return notify({
            type: "error",
            title: i18n.str`Missing contact info before to create the cashout`,
            description: resp.detail.hint as TranslatedString,
            debug: resp.detail,
          });
          case "no-enough-balance": return notify({
            type: "error",
            title: i18n.str`The account does not have sufficient funds`,
            description: resp.detail.hint as TranslatedString,
            debug: resp.detail,
          });
          case "cashout-not-supported": return notify({
            type: "error",
            title: i18n.str`Cashouts are not supported`,
            description: resp.detail.hint as TranslatedString,
            debug: resp.detail,
          });
          case "tan-failed": return notify({
            type: "error",
            title: i18n.str`Sending the confirmation code failed.`,
            description: resp.detail.hint as TranslatedString,
            debug: resp.detail,
          });
        }
        assertUnreachable(resp)
      }
    })
  }
  const cashoutAccount = !resultAccount.body.cashout_payto_uri ? undefined :
    parsePaytoUri(resultAccount.body.cashout_payto_uri);
  const cashoutAccountName = !cashoutAccount ? undefined : cashoutAccount.targetPath
  return (
    <div>
      <LocalNotificationBanner notification={notification} />

      <div class="grid grid-cols-1 gap-x-8 gap-y-8 pt-10 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>Convertion 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 ?
              <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">
                <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`}
                </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={!resultAccount.body.cashout_payto_uri}
                    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>

              {/* 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"
                  >
                    {form.isDebit
                      ? i18n.str`Amount to send`
                      : i18n.str`Amount to receive`}
                  </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={limit.currency}
                    value={trimmedAmountStr}
                    onChange={!resultAccount.body.cashout_payto_uri ? 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>
                        {/* <a href="#" class="ml-2 shrink-0 text-gray-400 bkx">
                  <span class="sr-only">Learn more about how shipping is calculated</span>
                  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"
                    class="w-5 h-5"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM8.94 6.94a.75.75 0 11-1.061-1.061 3 3 0 112.871 5.026v.345a.75.75 0 01-1.5 0v-.5c0-.72.57-1.172 1.081-1.287A1.5 1.5 0 108.94 6.94zM10 15a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path></svg>
                </a> */}
                      </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>
                          {/* <a href="#" class="ml-2 shrink-0 text-gray-400 bkx">
                  <span class="sr-only">Learn more about how shipping is calculated</span>
                  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"
                    class="w-5 h-5"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zM8.94 6.94a.75.75 0 11-1.061-1.061 3 3 0 112.871 5.026v.345a.75.75 0 01-1.5 0v-.5c0-.72.57-1.172 1.081-1.287A1.5 1.5 0 108.94 6.94zM10 15a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"></path></svg>
                </a> */}
                        </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>
              )}

              {/* channel */}
              {config.supported_tan_channels.length === 0 ? undefined :
                <div class="sm:col-span-5">
                  <label
                    class="block text-sm font-medium leading-6 text-gray-900"
                    for="channel"
                  >
                    {i18n.str`Confirmation the operation using`}
                  </label>
                  <div class="mt-2 max-w-xl text-sm text-gray-500">
                    <div class="px-4 mt-4 grid grid-cols-1 gap-y-6">
                      {config.supported_tan_channels.indexOf(TanChannel.EMAIL) === -1 ? undefined :
                        <label onClick={() => {
                          if (!resultAccount.body.contact_data?.email) return;
                          form.channel = TanChannel.EMAIL
                          updateForm(structuredClone(form))
                        }} data-disabled={!resultAccount.body.contact_data?.email} data-selected={form.channel === TanChannel.EMAIL}
                          class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border bg-white data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600">
                          <input type="radio" name="channel" value="Newsletter" class="sr-only" />
                          <span class="flex flex-1">
                            <span class="flex flex-col">
                              <span id="project-type-0-label" class="block text-sm font-medium text-gray-900 ">
                                <i18n.Translate>Email</i18n.Translate>
                              </span>
                              {!resultAccount.body.contact_data?.email && i18n.str`add a email in your profile to enable this option`}
                            </span>
                          </span>
                          <svg data-selected={form.channel === TanChannel.EMAIL} class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" 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>
                        </label>
                      }

                      {config.supported_tan_channels.indexOf(TanChannel.SMS) === -1 ? undefined :
                        <label onClick={() => {
                          if (!resultAccount.body.contact_data?.phone) return;
                          form.channel = TanChannel.SMS
                          updateForm(structuredClone(form))
                        }} data-disabled={!resultAccount.body.contact_data?.phone} data-selected={form.channel === TanChannel.SMS}
                          class="relative flex data-[disabled=false]:cursor-pointer rounded-lg border data-[disabled=true]:bg-gray-200 p-4 shadow-sm focus:outline-none border-gray-300 data-[selected=true]:ring-2 data-[selected=true]:ring-indigo-600">
                          <input type="radio" name="channel" value="Existing Customers" class="sr-only" />
                          <span class="flex flex-1">
                            <span class="flex flex-col">
                              <span id="project-type-1-label" class="block text-sm font-medium text-gray-900">
                                <i18n.Translate>SMS</i18n.Translate>
                              </span>
                              {!resultAccount.body.contact_data?.phone && i18n.str`add a phone number in your profile to enable this option`}
                            </span>
                          </span>
                          <svg data-selected={form.channel === TanChannel.SMS} class="h-5 w-5 text-indigo-600 data-[selected=false]:hidden" 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>
                        </label>
                      }
                    </div>
                  </div>

                </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">
            {onCancel ?
              <button type="button" class="text-sm font-semibold leading-6 text-gray-900"
                onClick={onCancel}
              >
                <i18n.Translate>Cancel</i18n.Translate>
              </button>
              : <div />
            }
            <button type="submit"
              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>
  );
}
