import {
  Amounts,
  assertUnreachable,
  HttpStatusCode,
  InternationalizationAPI,
  RoundingMode,
  TalerBankConversionApi,
  TalerCorebankApi,
  TalerError,
} from "@gnu-taler/taler-util";
import {
  Attention,
  ButtonBetter,
  ErrorLoading,
  InputText,
  InputToggle,
  Loading,
  LocalNotificationBanner,
  RenderAmount,
  RouteDefinition,
  ShowInputErrorLabel,
  useBankCoreApiContext,
  useLocalNotificationBetter,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useEffect, useState } from "preact/hooks";

import {
  FormErrors,
  FormStatus,
  FormValues,
  useFormState,
} from "../hooks/form.js";
import {
  revalidateConversionRateClassDetails,
  revalidateConversionRateClassUsers,
  TransferCalculation,
  useCashinEstimatorForClass,
  useCashoutEstimatorForClass,
  useConversionInfo,
  useConversionRateClassDetails,
  useConversionRateClassUsers,
} from "../hooks/regional.js";
import { useSessionState } from "../hooks/session.js";
import { RecursivePartial, undefinedIfEmpty } from "../utils.js";
import { DescribeConversion } from "./admin/ConversionClassList.js";
import { doAutoFocus, InputAmount } from "./PaytoWireTransferForm.js";
import { ConversionForm } from "./regional/ConversionConfig.js";
import { AccessToken } from "@gnu-taler/taler-util";
import { TalerErrorCode } from "@gnu-taler/taler-util";
import { opFixedSuccess } from "@gnu-taler/taler-util";
import { AmountJson } from "@gnu-taler/taler-util";

const TALER_SCREEN_ID = 11;
interface Props {
  classId: number;
  routeCancel: RouteDefinition;
  onClassDeleted: () => void;
}

type FormType = {
  name: string;
  description: string;
  conv: Omit<
    Omit<TalerBankConversionApi.ConversionRate, "cashout_tiny_amount">,
    "cashin_tiny_amount"
  >;
};

export function ConversionRateClassDetails({
  routeCancel,
  classId,
  onClassDeleted,
}: Props): VNode {
  const { i18n } = useTranslationContext();

  const detailsResult = useConversionRateClassDetails(classId);
  const conversionInfoResult = useConversionInfo();
  const conversionInfo =
    conversionInfoResult &&
    !(conversionInfoResult instanceof TalerError) &&
    conversionInfoResult.type === "ok"
      ? conversionInfoResult.body
      : undefined;

  if (!detailsResult || !conversionInfo) {
    return <Loading />;
  }
  if (detailsResult instanceof TalerError) {
    return <ErrorLoading error={detailsResult} />;
  }
  if (detailsResult.type === "fail") {
    switch (detailsResult.case) {
      case HttpStatusCode.Unauthorized:
      case HttpStatusCode.Forbidden:
      case HttpStatusCode.NotFound:
      case HttpStatusCode.NotImplemented:
        return (
          <Attention type="danger" title={i18n.str`Conversion is disabled`}>
            <i18n.Translate>
              Conversion should be enabled in the configuration, the conversion
              rate should be initialized with fee(s), rates and a rounding mode.
            </i18n.Translate>
          </Attention>
        );
      default:
        assertUnreachable(detailsResult);
    }
  }
  return (
    <Form
      conversionInfo={conversionInfo}
      detailsResult={detailsResult.body}
      routeCancel={routeCancel}
      classId={classId}
      onClassDeleted={onClassDeleted}
    />
  );
}

function Form({
  conversionInfo,
  detailsResult,
  routeCancel,
  classId,
  onClassDeleted,
}: {
  conversionInfo: TalerBankConversionApi.TalerConversionInfoConfig;
  detailsResult: TalerCorebankApi.ConversionRateClass;
  routeCancel: RouteDefinition;
  classId: number;
  onClassDeleted: () => void;
}) {
  const { i18n } = useTranslationContext();
  const { state: credentials } = useSessionState();
  const creds = credentials.status !== "loggedIn" ? undefined : credentials;
  const { lib, config } = useBankCoreApiContext();
  const [notification, safeFunctionHandler] = useLocalNotificationBetter();
  const [section, setSection] = useState<
    "detail" | "cashout" | "cashin" | "users" | "test" | "delete"
  >("detail");

  const initalState: FormValues<FormType> = {
    name: detailsResult.name,
    description: detailsResult.description,
    conv: {
      cashin_min_amount: detailsResult.cashin_min_amount?.split(":")[1],
      cashin_fee: detailsResult.cashin_fee?.split(":")[1],
      cashin_ratio: detailsResult?.cashin_ratio,
      cashin_rounding_mode: detailsResult?.cashin_rounding_mode,
      cashout_min_amount: detailsResult.cashout_min_amount?.split(":")[1],
      cashout_fee: detailsResult.cashout_fee?.split(":")[1],
      cashout_ratio: detailsResult.cashout_ratio,
      cashout_rounding_mode: detailsResult.cashout_rounding_mode,
    },
  };

  const [form, status] = useFormState<FormType>(
    initalState,
    createFormValidator(
      i18n,
      conversionInfo.regional_currency,
      conversionInfo.fiat_currency,
    ),
  );

  const deleteClass = safeFunctionHandler(
    (token: AccessToken) => lib.bank.deleteConversionRateClass(token, classId),
    !creds || section !== "delete" || detailsResult.num_users > 0
      ? undefined
      : [creds.token],
  );
  deleteClass.onSuccess = onClassDeleted;
  deleteClass.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.Unauthorized:
        return i18n.str`Unauthorized`;
      case HttpStatusCode.Forbidden:
        return i18n.str`Forbidden`;
      case HttpStatusCode.NotFound:
        return i18n.str`NotFound`;
      case HttpStatusCode.NotImplemented:
        return i18n.str`NotImplemented`;
    }
  };

  const input: TalerCorebankApi.ConversionRateClassInput | undefined =
    status.status === "fail"
      ? undefined
      : {
          name: status.result.name,
          description: status.result.description,

          cashin_fee: status.result.conv.cashin_fee,
          cashin_min_amount: status.result.conv.cashin_min_amount,
          cashin_ratio: status.result.conv.cashin_ratio,
          cashin_rounding_mode: status.result.conv.cashin_rounding_mode,

          cashout_fee: status.result.conv.cashout_fee,
          cashout_min_amount: status.result.conv.cashout_min_amount,
          cashout_ratio: status.result.conv.cashout_ratio,
          cashout_rounding_mode: status.result.conv.cashout_rounding_mode,
        };

  const updateClass = safeFunctionHandler(
    lib.bank.updateConversionRateClass.bind(lib.bank),
    !creds || !input ? undefined : [creds.token, classId, input],
  );
  updateClass.onSuccess = () => {
    setSection("detail");
  };
  updateClass.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.Unauthorized:
        return i18n.str`Unauthorized`;
      case HttpStatusCode.Forbidden:
        return i18n.str`Forbidden`;
      case HttpStatusCode.NotFound:
        return i18n.str`Not Found`;
      case HttpStatusCode.NotImplemented:
        return i18n.str`Not implemented`;
      case TalerErrorCode.BANK_NAME_REUSE:
        return i18n.str`The name of the conversion is already used.`;
    }
  };

  const updateRequest: TalerCorebankApi.ConversionRateClassInput | undefined =
    status.status === "fail"
      ? undefined
      : {
          name: status.result.name,
          description: status.result.description,

          cashin_fee: status.result.conv.cashin_fee,
          cashin_min_amount: status.result.conv.cashin_min_amount,
          cashin_ratio: status.result.conv.cashin_ratio,
          cashin_rounding_mode: status.result.conv.cashin_rounding_mode,

          cashout_fee: status.result.conv.cashout_fee,
          cashout_min_amount: status.result.conv.cashout_min_amount,
          cashout_ratio: status.result.conv.cashout_ratio,
          cashout_rounding_mode: status.result.conv.cashout_rounding_mode,
        };

  const updateDetails = updateClass.lambda(
    (
      t: AccessToken,
      id: number,
      r: TalerCorebankApi.ConversionRateClassInput,
    ) => [t, id, r],
    !creds ||
      !updateRequest ||
      section !== "detail" ||
      status.errors?.name ||
      status.errors?.description ||
      (status.result.name === initalState.name &&
        status.result.description === initalState.description)
      ? undefined
      : [creds.token, classId, updateRequest],
  );

  // const doUpdateDetails1 =
  //   !creds ||
  //   section !== "detail" ||
  //   status.errors?.name ||
  //   status.errors?.description ||
  //   (status.result.name === initalState.name &&
  //     status.result.description === initalState.description)
  //     ? undefined
  //     : doUpdateClass2;

  const updateCashin = updateClass.lambda(
    (
      t: AccessToken,
      id: number,
      r: TalerCorebankApi.ConversionRateClassInput,
    ) => [t, id, r],
    !creds ||
      !updateRequest ||
      section !== "cashin" ||
      status.errors?.conv?.cashin_fee ||
      status.errors?.conv?.cashin_min_amount ||
      status.errors?.conv?.cashin_ratio ||
      status.errors?.conv?.cashin_rounding_mode
      ? undefined
      : [creds.token, classId, updateRequest],
  );
  // const doUpdateCashin1 =
  //   !creds ||
  //   section !== "cashin" ||
  //   status.errors?.conv?.cashin_fee ||
  //   status.errors?.conv?.cashin_min_amount ||
  //   status.errors?.conv?.cashin_ratio ||
  //   status.errors?.conv?.cashin_rounding_mode
  //     ? undefined
  //     : doUpdateClass2;

  const updateCashout = updateClass.lambda(
    (
      t: AccessToken,
      id: number,
      r: TalerCorebankApi.ConversionRateClassInput,
    ) => [t, id, r],
    !creds ||
      !updateRequest ||
      section !== "cashout" ||
      // no errors on fields
      status.errors?.conv?.cashout_fee ||
      status.errors?.conv?.cashout_min_amount ||
      status.errors?.conv?.cashout_ratio ||
      status.errors?.conv?.cashout_rounding_mode ||
      // at least on field changed
      (status.result?.conv?.cashout_fee === initalState.conv.cashout_fee &&
        status.result?.conv?.cashout_min_amount ===
          initalState.conv.cashout_min_amount &&
        status.result?.conv?.cashout_ratio === initalState.conv.cashout_ratio &&
        status.result?.conv?.cashout_rounding_mode ===
          initalState.conv.cashout_rounding_mode)
      ? undefined
      : [creds.token, classId, updateRequest],
  );

  // const doUpdateCashout1 =
  //   !creds ||
  //   section !== "cashout" ||
  //   // no errors on fields
  //   status.errors?.conv?.cashout_fee ||
  //   status.errors?.conv?.cashout_min_amount ||
  //   status.errors?.conv?.cashout_ratio ||
  //   status.errors?.conv?.cashout_rounding_mode ||
  //   // at least on field changed
  //   (status.result?.conv?.cashout_fee === initalState.conv.cashout_fee &&
  //     status.result?.conv?.cashout_min_amount ===
  //       initalState.conv.cashout_min_amount &&
  //     status.result?.conv?.cashout_ratio === initalState.conv.cashout_ratio &&
  //     status.result?.conv?.cashout_rounding_mode ===
  //       initalState.conv.cashout_rounding_mode)
  //     ? undefined
  //     : doUpdateClass2;

  const default_rate = conversionInfo.conversion_rate;

  const final_cashin_ratio =
    detailsResult.cashin_ratio ?? default_rate.cashin_ratio;
  const final_cashin_fee = detailsResult.cashin_fee ?? default_rate.cashin_fee;
  const final_cashin_min =
    detailsResult.cashin_min_amount ?? default_rate.cashin_min_amount;
  const final_cashin_rounding =
    detailsResult.cashin_rounding_mode ?? default_rate.cashin_rounding_mode;

  const final_cashout_ratio =
    detailsResult.cashout_ratio ?? default_rate.cashout_ratio;
  const final_cashout_fee =
    detailsResult.cashout_fee ?? default_rate.cashout_fee;
  const final_cashout_min =
    detailsResult.cashout_min_amount ?? default_rate.cashout_min_amount;
  const final_cashout_rounding =
    detailsResult.cashout_rounding_mode ?? default_rate.cashout_rounding_mode;

  const in_ratio = Number.parseFloat(final_cashin_ratio);
  const out_ratio = Number.parseFloat(final_cashout_ratio);

  const both_high = in_ratio > 1 && out_ratio > 1;
  const both_low = in_ratio < 1 && out_ratio < 1;

  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">
        <div class="px-4 sm:px-0">
          <h2 class="text-base font-semibold leading-7 text-gray-900">
            <i18n.Translate>Conversion rate class</i18n.Translate>
          </h2>
          <div class="px-2 mt-2 grid grid-cols-1 gap-y-4 sm:gap-x-4">
            <label
              data-enabled={section === "detail"}
              class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none border-gray-300 data-[enabled=true]:border-indigo-600 data-[enabled=true]:ring-2 data-[enabled=true]:ring-indigo-600"
            >
              <input
                type="radio"
                name="project-type"
                value="Newsletter"
                class="sr-only"
                aria-labelledby="project-type-0-label"
                aria-describedby="project-type-0-description-0 project-type-0-description-1"
                onChange={() => {
                  setSection("detail");
                }}
              />
              <span class="flex flex-1">
                <span class="flex flex-col">
                  <span class="block text-sm  font-medium text-gray-900">
                    <i18n.Translate>Details</i18n.Translate>
                  </span>
                </span>
              </span>
            </label>
            <label
              data-enabled={section === "cashout"}
              class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none border-gray-300 -- data-[enabled=true]:border-indigo-600 data-[enabled=true]:ring-2 data-[enabled=true]:ring-indigo-600"
            >
              <input
                type="radio"
                name="project-type"
                value="Existing Customers"
                class="sr-only"
                aria-labelledby="project-type-1-label"
                aria-describedby="project-type-1-description-0 project-type-1-description-1"
                onChange={() => {
                  setSection("cashout");
                }}
              />
              <span class="flex flex-1">
                <span class="flex flex-col">
                  <span class="block text-sm font-medium text-gray-900">
                    <i18n.Translate>Config cashout</i18n.Translate>
                  </span>
                </span>
              </span>
            </label>
            <label
              data-enabled={section === "cashin"}
              class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none border-gray-300 -- data-[enabled=true]:border-indigo-600 data-[enabled=true]:ring-2 data-[enabled=true]:ring-indigo-600"
            >
              <input
                type="radio"
                name="project-type"
                value="Existing Customers"
                class="sr-only"
                aria-labelledby="project-type-1-label"
                aria-describedby="project-type-1-description-0 project-type-1-description-1"
                onChange={() => {
                  setSection("cashin");
                }}
              />
              <span class="flex flex-1">
                <span class="flex flex-col">
                  <span class="block text-sm font-medium text-gray-900">
                    <i18n.Translate>Config cashin</i18n.Translate>
                  </span>
                </span>
              </span>
            </label>
            <label
              data-enabled={section === "users"}
              class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none border-gray-300 data-[enabled=true]:border-indigo-600 data-[enabled=true]:ring-2 data-[enabled=true]:ring-indigo-600"
            >
              <input
                type="radio"
                name="project-type"
                value="Newsletter"
                class="sr-only"
                aria-labelledby="project-type-0-label"
                aria-describedby="project-type-0-description-0 project-type-0-description-1"
                onChange={() => {
                  setSection("users");
                }}
              />
              <span class="flex flex-1">
                <span class="flex flex-col">
                  <span class="block text-sm  font-medium text-gray-900">
                    <i18n.Translate>Accounts</i18n.Translate>
                  </span>
                </span>
              </span>
            </label>
            <label
              data-enabled={section === "test"}
              class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none border-gray-300 data-[enabled=true]:border-indigo-600 data-[enabled=true]:ring-2 data-[enabled=true]:ring-indigo-600"
            >
              <input
                type="radio"
                name="project-type"
                value="Newsletter"
                class="sr-only"
                aria-labelledby="project-type-0-label"
                aria-describedby="project-type-0-description-0 project-type-0-description-1"
                onChange={() => {
                  setSection("test");
                }}
              />
              <span class="flex flex-1">
                <span class="flex flex-col">
                  <span class="block text-sm  font-medium text-gray-900">
                    <i18n.Translate>Test</i18n.Translate>
                  </span>
                </span>
              </span>
            </label>{" "}
            <label
              data-enabled={section === "delete"}
              class="relative flex cursor-pointer rounded-lg border bg-white p-4 shadow-sm focus:outline-none border-gray-300 data-[enabled=true]:border-indigo-600 data-[enabled=true]:ring-2 data-[enabled=true]:ring-indigo-600"
            >
              <input
                type="radio"
                name="project-type"
                value="Newsletter"
                class="sr-only"
                aria-labelledby="project-type-0-label"
                aria-describedby="project-type-0-description-0 project-type-0-description-1"
                onChange={() => {
                  setSection("delete");
                }}
              />
              <span class="flex flex-1">
                <span class="flex flex-col">
                  <span class="block text-sm  font-medium text-gray-900">
                    <i18n.Translate>Delete</i18n.Translate>
                  </span>
                </span>
              </span>
            </label>
          </div>
        </div>

        <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();
          }}
        >
          {section == "cashin" && (
            <ConversionForm
              id="cashin"
              inputCurrency={conversionInfo.fiat_currency}
              outputCurrency={conversionInfo.regional_currency}
              fee={form?.conv?.cashin_fee}
              minimum={form?.conv?.cashin_min_amount}
              ratio={form?.conv?.cashin_ratio}
              rounding={form?.conv?.cashin_rounding_mode}
              tiny={undefined}
              fallback_fee={default_rate.cashin_fee.split(":")[1]}
              fallback_minimum={default_rate.cashin_min_amount.split(":")[1]}
              fallback_ratio={default_rate.cashin_ratio}
              fallback_rounding={default_rate.cashin_rounding_mode}
              fallback_tiny={default_rate.cashin_tiny_amount}
            />
          )}

          {section == "cashout" && (
            <Fragment>
              <ConversionForm
                id="cashout"
                inputCurrency={conversionInfo.regional_currency}
                outputCurrency={conversionInfo.fiat_currency}
                fee={form?.conv?.cashout_fee}
                minimum={form?.conv?.cashout_min_amount}
                ratio={form?.conv?.cashout_ratio}
                rounding={form?.conv?.cashout_rounding_mode}
                tiny={undefined}
                fallback_fee={default_rate.cashout_fee.split(":")[1]}
                fallback_minimum={default_rate.cashout_min_amount.split(":")[1]}
                fallback_ratio={default_rate.cashout_ratio}
                fallback_rounding={default_rate.cashout_rounding_mode}
                fallback_tiny={default_rate.cashout_tiny_amount}
              />
            </Fragment>
          )}

          {section == "detail" && (
            <Fragment>
              <div class="px-6 pt-6">
                <div class="justify-between items-center flex ">
                  <dt class="text-sm text-gray-600">
                    <i18n.Translate>Name</i18n.Translate>
                  </dt>
                  <dd class="text-sm text-gray-900">
                    <input
                      ref={doAutoFocus}
                      type="text"
                      name="name"
                      id="name"
                      class="block w-full disabled:bg-gray-200 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                      value={form?.name?.value ?? ""}
                      // disabled={fixedUser}
                      enterkeyhint="next"
                      placeholder="identification"
                      autocomplete="username"
                      title={i18n.str`Username of the account`}
                      required
                      onInput={(e): void => {
                        form?.name?.onUpdate(e.currentTarget.value);
                      }}
                    />
                    <ShowInputErrorLabel
                      message={form?.name?.error}
                      isDirty={form?.name?.value !== undefined}
                    />
                  </dd>
                </div>
              </div>

              <div class="px-6 pt-6">
                <div class="justify-between items-center flex ">
                  <dt class="text-sm text-gray-600">
                    <i18n.Translate>Description</i18n.Translate>
                  </dt>
                  <dd class="text-sm text-gray-900">
                    <input
                      type="text"
                      name="description"
                      id="description"
                      class="block w-full disabled:bg-gray-200 rounded-md border-0 py-1.5 text-gray-900 shadow-sm ring-1 ring-inset ring-gray-300 placeholder:text-gray-400 focus:ring-2 focus:ring-inset focus:ring-indigo-600 sm:text-sm sm:leading-6"
                      value={form?.description?.value ?? ""}
                      enterkeyhint="next"
                      // placeholder="identification"
                      autocomplete="username"
                      title={i18n.str`Username of the account`}
                      onInput={(e): void => {
                        form?.description?.onUpdate(e.currentTarget.value);
                      }}
                    />
                    <ShowInputErrorLabel
                      message={form?.description?.error}
                      isDirty={form?.description?.value !== undefined}
                    />
                  </dd>
                </div>
              </div>
              <div class="px-6 pt-6">
                <div class="justify-between items-center flex ">
                  <dt class="text-sm text-gray-600">
                    <i18n.Translate>Cashin</i18n.Translate>
                  </dt>
                  <dd class="text-sm text-gray-900">
                    <DescribeConversion
                      ratio={final_cashin_ratio}
                      fee={final_cashin_fee}
                      min={final_cashin_min}
                      rounding={final_cashin_rounding}
                      minSpec={conversionInfo.fiat_currency_specification}
                      feeSpec={conversionInfo.regional_currency_specification}
                    />
                  </dd>
                </div>
              </div>

              <div class="px-6 pt-6">
                <div class="justify-between items-center flex ">
                  <dt class="text-sm text-gray-600">
                    <i18n.Translate>Cashout</i18n.Translate>
                  </dt>
                  <dd class="text-sm text-gray-900">
                    <DescribeConversion
                      ratio={final_cashout_ratio}
                      fee={final_cashout_fee}
                      min={final_cashout_min}
                      rounding={final_cashout_rounding}
                      minSpec={conversionInfo.regional_currency_specification}
                      feeSpec={conversionInfo.fiat_currency_specification}
                    />
                  </dd>
                </div>
              </div>

              <div class="px-6 pt-6">
                <div class="justify-between items-center flex ">
                  <dt class="text-sm text-gray-600">
                    <i18n.Translate>Users</i18n.Translate>
                  </dt>
                  <dd class="text-sm text-gray-900">
                    {detailsResult.num_users}
                  </dd>
                </div>
              </div>

              {both_low || both_high ? (
                <div class="p-4">
                  <Attention title={i18n.str`Bad ratios`} type="warning">
                    <i18n.Translate>
                      One of the ratios should be higher or equal than 1 an the
                      other should be lower or equal than 1.
                    </i18n.Translate>
                  </Attention>
                </div>
              ) : undefined}
            </Fragment>
          )}

          {section == "users" && (
            <AccountsOnConversionClass classId={classId} />
          )}
          {section == "delete" && (
            <DeleteConversionClass
              classId={classId}
              userCount={detailsResult.num_users}
            />
          )}

          {section == "test" && (
            <TestConversionClass classId={classId} info={conversionInfo} />
          )}

          <div class="flex items-center justify-between mt-4 gap-x-6 border-t border-gray-900/10 px-4 py-4">
            <a
              name="cancel"
              href={routeCancel.url({})}
              class="text-sm font-semibold leading-6 text-gray-900"
            >
              <i18n.Translate>Cancel</i18n.Translate>
            </a>
            {section == "cashin" ? (
              <Fragment>
                <ButtonBetter
                  type="submit"
                  name="update conversion"
                  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"
                  onClick={updateCashin}
                >
                  <i18n.Translate>Update</i18n.Translate>
                </ButtonBetter>
              </Fragment>
            ) : undefined}
            {section == "cashout" ? (
              <Fragment>
                <ButtonBetter
                  type="submit"
                  name="update conversion"
                  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"
                  onClick={updateCashout}
                >
                  <i18n.Translate>Update</i18n.Translate>
                </ButtonBetter>
              </Fragment>
            ) : undefined}
            {section == "detail" ? (
              <Fragment>
                <ButtonBetter
                  type="submit"
                  name="update conversion"
                  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"
                  onClick={updateDetails}
                >
                  <i18n.Translate>Update</i18n.Translate>
                </ButtonBetter>
              </Fragment>
            ) : undefined}
            {section == "delete" ? (
              <Fragment>
                <ButtonBetter
                  type="submit"
                  name="update conversion"
                  class="disabled:opacity-50 disabled:cursor-default cursor-pointer rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
                  onClick={deleteClass}
                >
                  <i18n.Translate>Delete</i18n.Translate>
                </ButtonBetter>
              </Fragment>
            ) : undefined}
          </div>
        </form>
      </div>
    </div>
  );
}

export function createFormValidator(
  i18n: InternationalizationAPI,
  regional: string,
  fiat: string,
) {
  return function check(state: FormValues<FormType>): FormStatus<FormType> {
    const cashin_min_amount = Amounts.parse(
      `${fiat}:${state.conv.cashin_min_amount}`,
    );

    const cashin_fee = Amounts.parse(`${regional}:${state.conv.cashin_fee}`);

    const cashout_min_amount = Amounts.parse(
      `${regional}:${state.conv.cashout_min_amount}`,
    );
    const cashout_fee = Amounts.parse(`${fiat}:${state.conv.cashout_fee}`);

    const cashin_ratio_f = Number.parseFloat(state.conv.cashin_ratio ?? "");
    const cashout_ratio_f = Number.parseFloat(state.conv.cashout_ratio ?? "");

    const cashin_ratio = Number.isNaN(cashin_ratio_f)
      ? undefined
      : cashin_ratio_f;
    const cashout_ratio = Number.isNaN(cashout_ratio_f)
      ? undefined
      : cashout_ratio_f;

    const errors = undefinedIfEmpty<FormErrors<FormType>>({
      conv: undefinedIfEmpty<FormErrors<FormType["conv"]>>({
        cashin_min_amount: !state.conv.cashin_min_amount
          ? undefined
          : !cashin_min_amount
            ? i18n.str`Invalid`
            : undefined,
        cashin_fee: !state.conv.cashin_fee
          ? undefined
          : !cashin_fee
            ? i18n.str`Invalid`
            : undefined,

        cashout_min_amount: !state.conv.cashout_min_amount
          ? undefined
          : !cashout_min_amount
            ? i18n.str`Invalid`
            : undefined,
        cashout_fee: !state.conv.cashin_fee
          ? undefined
          : !cashout_fee
            ? i18n.str`Invalid`
            : undefined,

        cashin_rounding_mode: !state.conv.cashin_rounding_mode
          ? undefined
          : undefined,
        cashout_rounding_mode: !state.conv.cashout_rounding_mode
          ? undefined
          : undefined,

        cashin_ratio: !state.conv.cashin_ratio
          ? undefined
          : Number.isNaN(cashin_ratio)
            ? i18n.str`Invalid`
            : undefined,
        cashout_ratio: !state.conv.cashout_ratio
          ? undefined
          : Number.isNaN(cashout_ratio)
            ? i18n.str`Invalid`
            : undefined,
      }),

      description: undefined,
      name: !state.name ? i18n.str`Required` : undefined,
    });

    const result: RecursivePartial<FormType> = {
      name: !errors?.name ? state.name : undefined,
      description: state.description,
      conv: {
        cashin_fee:
          !errors?.conv?.cashin_fee && cashin_fee
            ? Amounts.stringify(cashin_fee)
            : undefined,
        cashin_min_amount:
          !errors?.conv?.cashin_min_amount && cashin_min_amount
            ? Amounts.stringify(cashin_min_amount)
            : undefined,
        cashin_ratio:
          !errors?.conv?.cashin_ratio && cashin_ratio
            ? String(cashin_ratio)
            : undefined,
        cashin_rounding_mode: !errors?.conv?.cashin_rounding_mode
          ? (state.conv.cashin_rounding_mode! as RoundingMode)
          : undefined,
        cashout_fee:
          !errors?.conv?.cashout_fee && cashout_fee
            ? Amounts.stringify(cashout_fee)
            : undefined,
        cashout_min_amount:
          !errors?.conv?.cashout_min_amount && cashout_min_amount
            ? Amounts.stringify(cashout_min_amount)
            : undefined,
        cashout_ratio:
          !errors?.conv?.cashout_ratio && cashout_ratio
            ? String(cashout_ratio)
            : undefined,
        cashout_rounding_mode: !errors?.conv?.cashout_rounding_mode
          ? (state.conv.cashout_rounding_mode! as RoundingMode)
          : undefined,
      },
    };
    return errors === undefined
      ? { status: "ok", result: result as FormType, errors }
      : { status: "fail", result: result as FormType, errors };
  };
}

function TestConversionClass({
  classId,
  info,
}: {
  classId: number;
  info: TalerBankConversionApi.TalerConversionInfoConfig;
}): VNode {
  const { i18n } = useTranslationContext();
  const [notification, safeFunctionHandler] = useLocalNotificationBetter();

  const { estimateByDebit: calculateCashoutFromDebit } =
    useCashoutEstimatorForClass(classId);
  const { estimateByDebit: calculateCashinFromDebit } =
    useCashinEstimatorForClass(classId);

  const [amount, setAmount] = useState<string>("100");
  const [error, setError] = useState<string>();

  const [calculationResult, setCalc] = useState<{
    cashin: TransferCalculation;
    cashout: TransferCalculation;
  }>();

  const in_amount = !amount
    ? undefined
    : Amounts.parseOrThrow(`${info.fiat_currency}:${amount}`);

  const in_fee = Amounts.parseOrThrow(info.conversion_rate.cashin_fee);
  const out_fee = Amounts.parseOrThrow(info.conversion_rate.cashout_fee);

  const calculate = safeFunctionHandler(
    async (amount: AmountJson) => {
      const respCashin = await calculateCashinFromDebit(amount, in_fee);
      if (respCashin.type === "fail") {
        return respCashin;
      }
      const cashin = respCashin.body;
      const respCashout = await calculateCashoutFromDebit(
        cashin.credit,
        out_fee,
      );
      if (respCashout.type === "fail") {
        return respCashout;
      }
      const cashout = respCashout.body;
      return opFixedSuccess({ cashin, cashout });
    },
    !in_amount || !!error ? undefined : [in_amount],
  );

  calculate.onSuccess = (resp) => setCalc(resp);
  calculate.onFail = (fail) => {
    switch (fail.case) {
      case HttpStatusCode.BadRequest:
        return i18n.str`The server didn't understand the request.`;
      case HttpStatusCode.Conflict:
        return i18n.str`The amount is too small`;
      case HttpStatusCode.NotImplemented:
        return i18n.str`Conversion is not implemented.`;
      case TalerErrorCode.GENERIC_PARAMETER_MISSING:
        return i18n.str`At least debit or credit needs to be provided`;
      case TalerErrorCode.GENERIC_PARAMETER_MALFORMED:
        return i18n.str`The amount is malfored`;
      case TalerErrorCode.GENERIC_CURRENCY_MISMATCH:
        return i18n.str`The currency is not supported`;
    }
  };

  useEffect(() => {
    calculate.call();
  }, [amount]);

  const cashinCalc = calculationResult?.cashin;
  const cashoutCalc = calculationResult?.cashout;

  return (
    <Fragment>
      <div class="px-6 pt-6">
        <div class="grid max-w-2xl grid-cols-1 gap-x-6 gap-y-8 sm:grid-cols-6">
          <div class="sm:col-span-5">
            <label
              for="amount"
              class="block text-sm font-medium leading-6 text-gray-900"
            >{i18n.str`Initial amount`}</label>
            <InputAmount
              name="amount"
              left
              currency={info.fiat_currency}
              value={amount ?? ""}
              onChange={(d) => {
                setAmount(d);
              }}
            />
            <ShowInputErrorLabel
              message={error}
              isDirty={amount !== undefined}
            />
            <p class="mt-2 text-sm text-gray-500">
              <i18n.Translate>
                Use it to test how the conversion will affect the amount.
              </i18n.Translate>
            </p>
          </div>
        </div>
      </div>

      {!cashoutCalc || !cashinCalc ? undefined : (
        <div class="px-6 pt-6">
          <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>Sending to this bank</i18n.Translate>
                </dt>
                <dd class="text-sm text-gray-900">
                  <RenderAmount
                    value={cashinCalc.debit}
                    negative
                    withColor
                    spec={info.fiat_currency_specification}
                  />
                </dd>
              </div>

              {Amounts.isZero(cashinCalc.beforeFee) ? undefined : (
                <div class="flex items-center justify-between afu ">
                  <dt class="flex items-center text-sm text-gray-600">
                    <span>
                      <i18n.Translate>Converted</i18n.Translate>
                    </span>
                  </dt>
                  <dd class="text-sm text-gray-900">
                    <RenderAmount
                      value={cashinCalc.beforeFee}
                      spec={info.regional_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>Cashin after fee</i18n.Translate>
                </dt>
                <dd class="text-lg text-gray-900 font-medium">
                  <RenderAmount
                    value={cashinCalc.credit}
                    withColor
                    spec={info.regional_currency_specification}
                  />
                </dd>
              </div>
            </dl>
          </div>

          <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>Sending from this bank</i18n.Translate>
                </dt>
                <dd class="text-sm text-gray-900">
                  <RenderAmount
                    value={cashoutCalc.debit}
                    negative
                    withColor
                    spec={info.regional_currency_specification}
                  />
                </dd>
              </div>

              {Amounts.isZero(cashoutCalc.beforeFee) ? undefined : (
                <div class="flex items-center justify-between afu">
                  <dt class="flex items-center text-sm text-gray-600">
                    <span>
                      <i18n.Translate>Converted</i18n.Translate>
                    </span>
                  </dt>
                  <dd class="text-sm text-gray-900">
                    <RenderAmount
                      value={cashoutCalc.beforeFee}
                      spec={info.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>Cashout after fee</i18n.Translate>
                </dt>
                <dd class="text-lg text-gray-900 font-medium">
                  <RenderAmount
                    value={cashoutCalc.credit}
                    withColor
                    spec={info.fiat_currency_specification}
                  />
                </dd>
              </div>
            </dl>
          </div>
        </div>
      )}
    </Fragment>
  );
}
function DeleteConversionClass({
  classId,
  userCount,
}: {
  classId: number;
  userCount: number;
}): VNode {
  const { i18n } = useTranslationContext();

  return (
    <Fragment>
      <div class="px-4 mt-4">
        {userCount > 0 ? (
          <Attention
            type="danger"
            title={i18n.str`Can't remove the conversion rate class`}
          >
            <i18n.Translate>
              There are some user associated to this class. You need to remove
              them first.
            </i18n.Translate>
          </Attention>
        ) : (
          <Attention
            type="warning"
            title={i18n.str`You are going to remove the conversion rate class`}
          >
            <i18n.Translate>This step can't be undone.</i18n.Translate>
          </Attention>
        )}
      </div>
    </Fragment>
  );
}

function AccountsOnConversionClass({ classId }: { classId: number }): VNode {
  const { i18n } = useTranslationContext();

  const {
    lib: { bank },
    config,
  } = useBankCoreApiContext();
  const { state } = useSessionState();
  const resultInfo = useConversionInfo();
  const convInfo =
    !resultInfo || resultInfo instanceof Error || resultInfo.type === "fail"
      ? undefined
      : resultInfo.body;
  const token = state.status === "loggedIn" ? state.token : undefined;

  const [filter, setFilter] = useState<{
    showAll?: boolean;
    classId?: number;
    account?: string;
  }>({
    showAll: classId === undefined,
    classId,
  });
  const userListResult = useConversionRateClassUsers(
    filter.classId,
    filter.account,
  );
  if (!userListResult) {
    return <Loading />;
  }
  if (userListResult instanceof TalerError) {
    return <ErrorLoading error={userListResult} />;
  }
  if (userListResult.type === "fail") {
    switch (userListResult.case) {
      case HttpStatusCode.Unauthorized:
        return (
          <Attention type="danger" title={i18n.str`Conversion is disabled`}>
            <i18n.Translate>
              Conversion should be enabled in the configuration, the conversion
              rate should be initialized with fee(s), rates and a rounding mode.
            </i18n.Translate>
          </Attention>
        );
      default:
        assertUnreachable(userListResult);
    }
  }
  return (
    <Fragment>
      <div class="px-4 mt-4">
        <div class="sm:flex sm:items-center">
          <div class="sm:flex-auto">
            <h1 class="text-base font-semibold leading-6 text-gray-900">
              <i18n.Translate>Filters</i18n.Translate>
            </h1>
          </div>
        </div>
      </div>
      <div class="px-4 mt-2">
        <InputToggle
          label={i18n.str`Show from other classes`}
          name="show_all"
          threeState={false}
          handler={{
            value: filter.showAll,
            onChange(v) {
              filter.showAll = !!v;
              if (!v) {
                filter.classId = classId;
              } else {
                filter.classId = undefined;
              }
              setFilter(structuredClone(filter));
            },
            name: "show_all",
          }}
        />
        <InputText
          label={i18n.str`Account`}
          name="account"
          handler={{
            value: filter.account,
            onChange(v) {
              filter.account = v;
              setFilter(structuredClone(filter));
            },
            name: "account",
          }}
        />
        {filter.showAll ? (
          <InputText
            label={i18n.str`Group ID`}
            name="crcid"
            handler={{
              value: String(filter.classId),
              onChange(v) {
                const id = !v ? undefined : Number.parseInt(v, 10);
                filter.classId = id;
                setFilter(structuredClone(filter));
              },
              name: "crcid",
            }}
          />
        ) : undefined}
      </div>
      <div class="mt-4 flow-root">
        <div class="overflow-x-auto">
          <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
            {!userListResult.body.length ? (
              <div class="py-3.5 pl-4 pr-3 ">
                <i18n.Translate>
                  No users in this conversion rate class
                </i18n.Translate>
              </div>
            ) : (
              <table class="min-w-full divide-y divide-gray-300">
                <thead>
                  <tr>
                    <th
                      scope="col"
                      class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
                    >{i18n.str`Name`}</th>
                    <th
                      scope="col"
                      class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
                    >{i18n.str`Class`}</th>
                    <th
                      scope="col"
                      class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
                    >{i18n.str`Cashin`}</th>
                    <th
                      scope="col"
                      class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
                    >{i18n.str`Cashout`}</th>
                    <th
                      scope="col"
                      class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900"
                    >{i18n.str`Action`}</th>
                  </tr>
                </thead>
                <tbody class="divide-y divide-gray-200">
                  {userListResult.body.map((item, idx) => {
                    return (
                      <tr
                        key={idx}
                        class="data-[status=deleted]:bg-gray-100"
                        data-status={item.status}
                      >
                        <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
                          {item.name}
                        </td>
                        <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
                          {item.conversion_rate_class_id}
                        </td>
                        <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
                          <DescribeConversion
                            ratio={item.conversion_rate!.cashin_ratio}
                            fee={item.conversion_rate!.cashin_fee}
                            min={item.conversion_rate!.cashin_min_amount}
                            rounding={
                              item.conversion_rate!.cashin_rounding_mode
                            }
                            minSpec={convInfo!.fiat_currency_specification}
                            feeSpec={convInfo!.regional_currency_specification}
                          />
                        </td>
                        <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
                          <DescribeConversion
                            ratio={item.conversion_rate!.cashout_ratio}
                            fee={item.conversion_rate!.cashout_fee}
                            min={item.conversion_rate!.cashout_min_amount}
                            rounding={
                              item.conversion_rate!.cashout_rounding_mode
                            }
                            minSpec={convInfo!.fiat_currency_specification}
                            feeSpec={convInfo!.regional_currency_specification}
                          />
                        </td>
                        <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
                          {classId === item.conversion_rate_class_id ? (
                            <button
                              class="disabled:opacity-50 disabled:bg-gray-600 disabled:hover:bg-gray-600 disabled:cursor-default cursor-pointer rounded-md bg-red-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-red-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-600"
                              onClick={async () => {
                                if (token) {
                                  await bank.updateAccount(
                                    { username: item.username, token },
                                    { conversion_rate_class_id: null },
                                  );
                                  await revalidateConversionRateClassUsers();
                                  await revalidateConversionRateClassDetails();
                                }
                              }}
                            >
                              <i18n.Translate>Remove</i18n.Translate>
                            </button>
                          ) : (
                            <button
                              class="disabled:opacity-50 disabled:bg-gray-600 disabled:hover:bg-gray-600 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"
                              onClick={async () => {
                                if (token) {
                                  await bank.updateAccount(
                                    { username: item.username, token },
                                    { conversion_rate_class_id: classId },
                                  );
                                  await revalidateConversionRateClassUsers();
                                  await revalidateConversionRateClassDetails();
                                }
                              }}
                            >
                              <i18n.Translate>Add</i18n.Translate>
                            </button>
                          )}
                        </td>
                      </tr>
                    );
                  })}
                </tbody>
              </table>
            )}
          </div>
          {!userListResult.loadFirst && !userListResult.loadNext ? undefined : (
            <nav
              class="flex items-center justify-between border-t border-gray-200 bg-white px-4 py-3 sm:px-6 rounded-lg"
              aria-label="Pagination"
            >
              <div class="flex flex-1 justify-between sm:justify-end">
                <button
                  name="first page"
                  class="relative disabled:bg-gray-100 disabled:text-gray-500 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
                  disabled={!userListResult.loadFirst}
                  onClick={userListResult.loadFirst}
                >
                  <i18n.Translate>First page</i18n.Translate>
                </button>
                <button
                  name="next page"
                  class="relative disabled:bg-gray-100 disabled:text-gray-500 ml-3 inline-flex items-center rounded-md bg-white px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 focus-visible:outline-offset-0"
                  disabled={!userListResult.loadNext}
                  onClick={userListResult.loadNext}
                >
                  <i18n.Translate>Next</i18n.Translate>
                </button>
              </div>
            </nav>
          )}
        </div>
      </div>
    </Fragment>
  );
}
