/*
 This file is part of GNU Taler
 (C) 2022-2025 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,
  AmlSpaDialect,
  AmountJson,
  Amounts,
  assertUnreachable,
  AvailableMeasureSummary,
  Duration,
  ExchangeVersionResponse,
  KycRule,
  LegitimizationRuleSet,
  LimitOperationType,
  parsePaytoUri,
  PaytoString,
  PaytoUri,
  TalerError,
  TranslatedString,
} from "@gnu-taler/taler-util";
import {
  Attention,
  FormDesign,
  FormUI,
  InternationalizationAPI,
  onComponentUnload,
  useExchangeApiContext,
  useForm,
  useTranslationContext,
} from "@gnu-taler/web-util/browser";
import { Fragment, h, VNode } from "preact";
import { useState } from "preact/hooks";
import { RulesInfo } from "../../components/RulesInfo.js";
import { ShowDecisionLimitInfo } from "../../components/ShowDecisionLimitInfo.js";
import { ShowDefaultRules } from "../../components/ShowDefaultRules.js";
import { useCurrentDecisionRequest } from "../../hooks/decision-request.js";
import { usePreferences } from "../../hooks/preferences.js";
import { useServerMeasures } from "../../hooks/server-info.js";

const DEFAULT_MEASURE_IF_NONE = ["VERBOTEN"];
export const DEFAULT_LIMITS_WHEN_NEW_ACCOUNT: LegitimizationRuleSet = {
  custom_measures: {},
  expiration_time: AbsoluteTime.toProtocolTimestamp(AbsoluteTime.never()),
  rules: [],
};

const SHARED_RULES = [
  LimitOperationType.refund,
  LimitOperationType.transaction,
];
const ONLY_WALLET_RULES = [
  LimitOperationType.balance,
  LimitOperationType.merge,
];
const ONLY_BANK_RULES = [
  LimitOperationType.deposit,
  LimitOperationType.aggregate,
  LimitOperationType.withdraw,
  LimitOperationType.close,
];
export const WALLET_RULES = [...ONLY_WALLET_RULES, ...SHARED_RULES];
export const BANK_RULES = [...ONLY_BANK_RULES, ...SHARED_RULES];

export type RuleInconsistency =
  | "missing-wallet-rules"
  | "missing-bank-rules"
  | "shold-not-have-wallet-rules"
  | "shold-not-have-bank-rules";

function findRuleInconsistency(
  isWallet: boolean,
  rules: KycRule[],
): undefined | RuleInconsistency {
  const OPERATION_TYPE_PRESENT: Record<LimitOperationType, boolean> = {
    [LimitOperationType.balance]: false,
    [LimitOperationType.transaction]: false,
    [LimitOperationType.withdraw]: false,
    [LimitOperationType.deposit]: false,
    [LimitOperationType.aggregate]: false,
    [LimitOperationType.close]: false,
    [LimitOperationType.refund]: false,
    [LimitOperationType.merge]: false,
  };

  rules.forEach((r) => {
    OPERATION_TYPE_PRESENT[r.operation_type] = true;
  });

  if (isWallet) {
    const hasBankRule = ONLY_BANK_RULES.some(
      (otx) => OPERATION_TYPE_PRESENT[otx],
    );
    if (hasBankRule) {
      return "shold-not-have-bank-rules";
    }
    const hasAllWalletRules = WALLET_RULES.every(
      (otx) => OPERATION_TYPE_PRESENT[otx],
    );
    if (!hasAllWalletRules) {
      return "missing-wallet-rules";
    }
  } else {
    const hasWalletrule = ONLY_WALLET_RULES.some(
      (otx) => OPERATION_TYPE_PRESENT[otx],
    );
    if (hasWalletrule) {
      return "shold-not-have-wallet-rules";
    }
    const hasAllBankRules = BANK_RULES.every(
      (otx) => OPERATION_TYPE_PRESENT[otx],
    );
    if (!hasAllBankRules) {
      return "missing-bank-rules";
    }
  }
  return undefined;
}

/**
 * Defined new limits for the account
 * @param param0
 * @returns
 */
export function Rules({ newPayto }: { newPayto?: string }): VNode {
  const { i18n } = useTranslationContext();
  const { config } = useExchangeApiContext();
  const [request] = useCurrentDecisionRequest();

  let newPaytoParsed: PaytoUri | undefined;
  const isNewAccountAWallet =
    newPayto === undefined
      ? undefined
      : (newPaytoParsed = parsePaytoUri(newPayto)) === undefined
        ? undefined
        : newPaytoParsed.isKnown &&
          (newPaytoParsed.targetType === "taler-reserve" ||
            newPaytoParsed.targetType === "taler-reserve-http");

  // info may be undefined if this is a new account
  // for which we use the payto:// parameter
  const isWallet = request.original?.is_wallet ?? isNewAccountAWallet;

  const measures = useServerMeasures();
  const rootMeasures =
    !measures || measures instanceof TalerError || measures.type === "fail"
      ? undefined
      : measures.body.roots;

  const defaultRules = (
    !measures || measures instanceof TalerError || measures.type === "fail"
      ? []
      : measures.body.default_rules
  ).filter((r) => {
    return isWallet
      ? WALLET_RULES.includes(r.operation_type)
      : BANK_RULES.includes(r.operation_type);
  });

  return (
    <div>
      <UpdateRulesForm
        rootMeasures={rootMeasures}
        defaultRules={defaultRules}
        config={config.config}
        isWallet={isWallet ?? false}
        limits={request.original?.limits ?? DEFAULT_LIMITS_WHEN_NEW_ACCOUNT}
      />

      <div>
        <h2 class="mt-4 mb-2">
          <i18n.Translate>Current active rules</i18n.Translate>
        </h2>
        {request.original === undefined ? (
          <Fragment>
            <p>
              <i18n.Translate>
                There are no rules for this account yet.
              </i18n.Translate>
            </p>
            <ShowDefaultRules rules={defaultRules} />
          </Fragment>
        ) : (
          <ShowDecisionLimitInfo
            fixed
            since={AbsoluteTime.fromProtocolTimestamp(
              request.original.decision_time,
            )}
            until={AbsoluteTime.fromProtocolTimestamp(
              request.original.limits.expiration_time,
            )}
            rules={request.original.limits.rules}
            startOpen
            measure={request.original.limits.successor_measure ?? ""}
          />
        )}
      </div>
    </div>
  );
}

function AddNewRuleForm({
  onAdd,
  onClose,
  config,
  measureList,
  isWallet,
}: {
  onAdd: (nr: RuleFormType) => void;
  config: ExchangeVersionResponse;
  measureList: string[];
  onClose: () => void;
  isWallet: boolean;
}): VNode {
  const { i18n } = useTranslationContext();
  const ruleFormDesign = ruleFormDesignTemplate(
    i18n,
    config.currency,
    measureList,
    isWallet,
  );

  const ruleForm = useForm<RuleFormType>(ruleFormDesign, {});
  return (
    <Fragment>
      <h2 class="mt-4 mb-2">
        <i18n.Translate>New rule form</i18n.Translate>
      </h2>
      <FormUI design={ruleFormDesign} model={ruleForm.model} />

      <button
        disabled={ruleForm.status.status === "fail"}
        onClick={() => {
          onAdd(ruleForm.status.result as RuleFormType);
          onClose();
        }}
        class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
      >
        <i18n.Translate>Add</i18n.Translate>
      </button>
      <button
        onClick={() => {
          onClose();
        }}
        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"
      >
        <i18n.Translate>Cancel</i18n.Translate>
      </button>
    </Fragment>
  );
}

function UpdateRulesForm({
  config,
  limits,
  rootMeasures,
  defaultRules,
  isWallet,
}: {
  config: ExchangeVersionResponse;
  limits: LegitimizationRuleSet;
  isWallet: boolean | undefined;
  rootMeasures: AvailableMeasureSummary["roots"] | undefined;
  defaultRules: KycRule[];
}): VNode {
  const { i18n } = useTranslationContext();
  const [pref] = usePreferences();
  const dialect =
    (pref.testingDialect ? undefined : config.aml_spa_dialect) ??
    AmlSpaDialect.TESTING;
  const [request, updateRequest] = useCurrentDecisionRequest();
  const [showAddRuleForm, setShowAddRuleForm] = useState(false);
  const measureList = !rootMeasures ? [] : Object.keys(rootMeasures);
  const customMeasures = Object.keys(request.custom_measures ?? {});
  const allMeasure = [...measureList, ...customMeasures];
  const expirationFormDesign = expirationFormDesignTemplate(i18n, allMeasure);
  const [currentRules, setRules] = useState<KycRule[]>(
    !request.rules ? limits.rules : request.rules,
  );
  let defaultSuccessorMeasure =
    request.onExpire_measure ?? limits.successor_measure;

  if (
    defaultSuccessorMeasure &&
    !allMeasure.includes(defaultSuccessorMeasure)
  ) {
    console.warn(
      `configuration problem: default successor measure ${defaultSuccessorMeasure} not in known measures`,
    );
    defaultSuccessorMeasure = undefined;
  }
  const expirationForm = useForm<ExpirationFormType>(expirationFormDesign, {
    expiration:
      request.deadline ??
      AbsoluteTime.fromProtocolTimestamp(limits.expiration_time),
    measure: defaultSuccessorMeasure,
  });

  onComponentUnload(() => {
    const deadline =
      expirationForm.status.status === "fail"
        ? undefined
        : expirationForm.status.result.expiration;
    const doesntExpire = !deadline || AbsoluteTime.isNever(deadline);
    updateRequest("unload rules", {
      rules: currentRules,
      deadline,
      onExpire_measure:
        expirationForm.status.status === "fail" || doesntExpire
          ? ""
          : expirationForm.status.result.measure,
    });
  });

  function addNewRule(nr: RuleFormType) {
    const result = !currentRules ? [] : [...currentRules];
    const clean = (nr.measures ?? []).filter((m) => !!m);
    const measures = !clean.length ? DEFAULT_MEASURE_IF_NONE : clean;
    result.push({
      timeframe: !nr.timeframe
        ? Duration.toTalerProtocolDuration(Duration.getForever())
        : Duration.toTalerProtocolDuration(nr.timeframe),
      threshold: Amounts.stringify(nr.threshold),
      operation_type: nr.operation_type,
      display_priority: 1,
      exposed: nr.exposed,
      is_and_combinator: nr.all,
      measures,
    });
    setRules(result);
  }

  const ruleInconsistency =
    isWallet === undefined
      ? undefined
      : findRuleInconsistency(isWallet, currentRules);

  const ButtonRulesBasicPlan = () => (
    <button
      onClick={() => {
        setRules(BASIC_PLAN(config.currency, isWallet));
      }}
      class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
    >
      <i18n.Translate>Basic plan</i18n.Translate>
    </button>
  );

  const ButtonRulesPremiumPlan = () => (
    <button
      onClick={() => {
        setRules(PREMIUM_PLAN(config.currency, isWallet));
      }}
      class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
    >
      <i18n.Translate>Premium</i18n.Translate>
    </button>
  );

  const ButtonRulesEcommerce = () => (
    <button
      onClick={() => {
        setRules(E_COMMERCE(config.currency, isWallet));
      }}
      class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
    >
      <i18n.Translate>E-commerce</i18n.Translate>
    </button>
  );

  const ButtonRulesPoS = () => (
    <button
      onClick={() => {
        setRules(POINT_OF_SALE(config.currency, isWallet));
      }}
      class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
    >
      <i18n.Translate>Point-of-sale</i18n.Translate>
    </button>
  );

  const ButtonRulesGlsMerchantRegisteredEcommerce = () => (
    <button
      onClick={() => {
        setRules(GLS_MERCHANT_REGISTERED_ECOMMERCE(config.currency, isWallet));
      }}
      class="m-4 rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
    >
      <i18n.Translate>GLS Registered E-Commerce Merchant</i18n.Translate>
    </button>
  );

  return (
    <div>
      {!ruleInconsistency ? undefined : (
        <Attention
          title={i18n.str`Check rules`}
          type={(function (state: RuleInconsistency) {
            switch (state) {
              case "shold-not-have-bank-rules":
              case "shold-not-have-wallet-rules": {
                return "warning";
              }
              case "missing-wallet-rules":
              case "missing-bank-rules":
                return "info";
              default: {
                assertUnreachable(state);
              }
            }
          })(ruleInconsistency)}
        >
          {(function (state: RuleInconsistency) {
            switch (state) {
              case "shold-not-have-bank-rules": {
                return (
                  <i18n.Translate>
                    The account is a wallet and there are bank account
                    operation's limits which may trigger actions that are not
                    required: {ONLY_BANK_RULES.join(",")}
                  </i18n.Translate>
                );
              }
              case "shold-not-have-wallet-rules": {
                return (
                  <i18n.Translate>
                    The account isn't a wallet and there are wallet operation's
                    limits which may trigger actions that are not required:{" "}
                    {ONLY_WALLET_RULES.join(",")}
                  </i18n.Translate>
                );
              }
              case "missing-wallet-rules": {
                return (
                  <i18n.Translate>
                    The account is a wallet and there are wallet operation's
                    limits which are not limited: {WALLET_RULES.join(",")}
                  </i18n.Translate>
                );
              }
              case "missing-bank-rules": {
                return (
                  <i18n.Translate>
                    The AML account is bank account, but not all account-related
                    operations are limited ({BANK_RULES.join(",")}).
                  </i18n.Translate>
                );
              }
              default: {
                assertUnreachable(state);
              }
            }
          })(ruleInconsistency)}
        </Attention>
      )}

      {!showAddRuleForm ? undefined : (
        <AddNewRuleForm
          measureList={measureList}
          config={config}
          isWallet={isWallet ?? false}
          onAdd={addNewRule}
          onClose={() => {
            setShowAddRuleForm(false);
          }}
        />
      )}

      <h2 class="mt-4 mb-2">
        <i18n.Translate>New rules</i18n.Translate>
      </h2>

      <RulesInfo
        rules={currentRules}
        onRemove={(r, idx) => {
          const nr = [...currentRules];
          nr.splice(idx, 1);
          setRules(nr);
        }}
        onNew={() => {
          setShowAddRuleForm(true);
        }}
      />

      <button
        onClick={() => {
          setRules(limits.rules);
        }}
        class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
      >
        <i18n.Translate>Reset rules to current state</i18n.Translate>
      </button>
      <button
        onClick={() => {
          setRules(defaultRules);
        }}
        class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
      >
        <i18n.Translate>Set rules to default</i18n.Translate>
      </button>
      <button
        onClick={() => {
          setRules(FREEZE_PLAN(config.currency, isWallet));
        }}
        class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
      >
        <i18n.Translate>Freeze account</i18n.Translate>
      </button>
      {dialect == AmlSpaDialect.TESTING ? (
        <>
          <ButtonRulesBasicPlan />
          <ButtonRulesPremiumPlan />
          <ButtonRulesEcommerce />
          <ButtonRulesPoS />
        </>
      ) : null}
      {dialect == AmlSpaDialect.GLS ? (
        <>
          <ButtonRulesGlsMerchantRegisteredEcommerce />
        </>
      ) : null}
      <h2 class="mt-4 mb-2">
        <i18n.Translate>Behavior on rule expiration</i18n.Translate>
      </h2>
      <FormUI design={expirationFormDesign} model={expirationForm.model} />
      <button
        onClick={() => {
          expirationForm.model
            .getHandlerForAttributeKey("measure")
            .onChange(defaultSuccessorMeasure);
        }}
        class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
      >
        <i18n.Translate>Reset measure</i18n.Translate>
      </button>
      <button
        onClick={() => {
          const c =
            expirationForm.model.getHandlerForAttributeKey("expiration");
          c.onChange(
            AbsoluteTime.fromProtocolTimestamp(limits.expiration_time),
          );
        }}
        class="m-4  rounded-md w-fit border-0 px-3 py-2 text-center text-sm bg-indigo-700 text-white shadow-sm hover:bg-indigo-700 disabled:bg-gray-600"
      >
        <i18n.Translate>Reset expiration</i18n.Translate>
      </button>
    </div>
  );
}

type RuleFormType = {
  operation_type: LimitOperationType;
  threshold: AmountJson;
  timeframe: Duration;
  exposed: boolean;
  measures: string[];
  all: boolean;
};
type ExpirationFormType = {
  expiration: AbsoluteTime;
  measure: string | undefined;
};

function labelForOperationType(
  op: LimitOperationType,
  i18n: InternationalizationAPI,
): TranslatedString {
  switch (op) {
    case LimitOperationType.withdraw:
      return i18n.ctx("operation type")`Withdraw`;
    case LimitOperationType.deposit:
      return i18n.ctx("operation type")`Deposit`;
    case LimitOperationType.merge:
      return i18n.ctx("operation type")`Merge`;
    case LimitOperationType.aggregate:
      return i18n.ctx("operation type")`Aggregate`;
    case LimitOperationType.balance:
      return i18n.ctx("operation type")`Balance`;
    case LimitOperationType.refund:
      return i18n.ctx("operation type")`Refund`;
    case LimitOperationType.close:
      return i18n.ctx("operation type")`Close`;
    case LimitOperationType.transaction:
      return i18n.ctx("operation type")`Transaction`;
  }
}

const ruleFormDesignTemplate = (
  i18n: InternationalizationAPI,
  currency: string,
  measureNames: string[],
  isWallet: boolean,
): FormDesign => ({
  type: "single-column",
  fields: [
    {
      id: "operation_type",
      type: "choiceHorizontal",
      label: i18n.str`Operation type`,
      required: true,
      choices: (isWallet ? WALLET_RULES : BANK_RULES).map((op) => {
        return {
          value: op,
          label: labelForOperationType(op, i18n),
        };
      }),
    },
    {
      id: "threshold",
      type: "amount",
      required: true,
      label: i18n.str`Threshold`,
      currency,
    },
    {
      id: "timeframe",
      type: "durationText",
      required: true,
      placeholder: "1Y 2M 3D 4h 5m 6s",
      label: i18n.str`Timeframe`,
      help: `Use YMDhms next to a number as a unit for Year, Month, Day, hour, minute and seconds.`,
    },
    {
      id: "exposed",
      type: "toggle",
      label: i18n.str`Exposed`,
      help: i18n.str`Is this limit communicated to the customer?`,
    },
    {
      type: "selectMultiple",
      unique: true,
      choices: measureNames.map((name) => {
        return {
          value: name,
          label: name,
        };
      }),
      id: "measures",
      label: i18n.str`Esclation measure`,
      help: i18n.str`Measures that the customer will need to satisfy to apply for a new threshold.`,
    },
    {
      id: "all",
      type: "toggle",
      label: i18n.str`All measures`,
      help: i18n.str`Hint the customer that all measure should be completed`,
    },
  ],
});

const expirationFormDesignTemplate = (
  i18n: InternationalizationAPI,
  measureNames: string[],
): FormDesign => ({
  type: "single-column",
  fields: [
    {
      type: "choiceHorizontal",
      label: i18n.str`Expiration`,
      help: i18n.str`Predefined shortcuts`,
      id: "expiration",
      choices: [
        {
          label: i18n.str`In a week`,
          // @ts-expect-error we have to format as string 'dd/MM/yyyy' and convert back
          value: AbsoluteTime.addDuration(
            AbsoluteTime.now(),
            Duration.fromSpec({ days: 7 }),
          ),
        },
        {
          label: i18n.str`In a month`,
          // @ts-expect-error we have to format as string 'dd/MM/yyyy' and convert back
          value: AbsoluteTime.addDuration(
            AbsoluteTime.now(),
            Duration.fromSpec({ months: 1 }),
          ),
        },
        {
          label: i18n.str`In a year`,
          // @ts-expect-error we have to format as string 'dd/MM/yyyy' and convert back
          value: AbsoluteTime.addDuration(
            AbsoluteTime.now(),
            Duration.fromSpec({ years: 1 }),
          ),
        },
        {
          label: i18n.str`Never`,
          // @ts-expect-error we have to format as string 'dd/MM/yyyy' and convert back
          value: AbsoluteTime.never(),
        },
      ],
    },
    {
      id: "expiration",
      type: "absoluteTimeText",
      placeholder: "dd/MM/yyyy",
      pattern: "dd/MM/yyyy",
      label: i18n.str`Expiration date`,
      help: i18n.str`Until when are the new rules active.`,
    },
    {
      type: "selectOne",
      choices: measureNames.map((name) => {
        return {
          value: name,
          label: name,
        };
      }),
      id: "measure",
      label: i18n.str`Successor measure`,
      help: i18n.str`Measure taken automatically upon expiration of the current decision.`,
    },
  ],
});

const BASIC_PLAN: TemplateRulesFunction = (currency, isWallet) =>
  [
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.balance,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 10000,
      }),
      timeframe: Duration.toTalerProtocolDuration(Duration.getForever()),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.transaction,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(Duration.getForever()),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.withdraw,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.merge,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.deposit,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 5 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.deposit,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 50 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ years: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.aggregate,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 5 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.aggregate,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 50 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ years: 1 }),
      ),
    },
  ].filter((r) => {
    return isWallet
      ? WALLET_RULES.includes(r.operation_type)
      : BANK_RULES.includes(r.operation_type);
  });

type TemplateRulesFunction = (
  currency: string,
  isWallet: boolean | undefined,
) => KycRule[];

const PREMIUM_PLAN: TemplateRulesFunction = (currency, isWallet) =>
  [
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.balance,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 10 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(Duration.getForever()),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.transaction,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(Duration.getForever()),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.withdraw,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.merge,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.deposit,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 15 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.deposit,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 150 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ years: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.aggregate,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 15 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.aggregate,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 150 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ years: 1 }),
      ),
    },
  ].filter((r) => {
    return isWallet
      ? WALLET_RULES.includes(r.operation_type)
      : BANK_RULES.includes(r.operation_type);
  });

const FREEZE_PLAN: TemplateRulesFunction = (
  currency,
  isWallet: boolean | undefined,
) =>
  (isWallet === undefined
    ? Object.values(LimitOperationType)
    : isWallet
      ? WALLET_RULES
      : BANK_RULES
  ).map((operation_type) => ({
    display_priority: 1,
    measures: ["VERBOTEN"],
    operation_type,
    threshold: Amounts.stringify(Amounts.zeroOfCurrency(currency)),
    timeframe: Duration.toTalerProtocolDuration(Duration.getForever()),
  }));

const POINT_OF_SALE: TemplateRulesFunction = (currency, isWallet) =>
  [
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.withdraw,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 0,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.merge,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 25 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.deposit,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 25 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.aggregate,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 25 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
  ].filter((r) => {
    return isWallet
      ? WALLET_RULES.includes(r.operation_type)
      : BANK_RULES.includes(r.operation_type);
  });

const GLS_MERCHANT_REGISTERED_ECOMMERCE: TemplateRulesFunction = (
  currency,
  isWallet,
) =>
  [
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.withdraw,
      threshold: Amounts.stringify(`${currency}:0`),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.close,
      threshold: Amounts.stringify(`${currency}:0`),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.merge,
      threshold: Amounts.stringify(`${currency}:0`),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.deposit,
      threshold: Amounts.stringify(`${currency}:100000`),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.aggregate,
      threshold: Amounts.stringify(`${currency}:100000`),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
  ].filter((r) => {
    return isWallet
      ? WALLET_RULES.includes(r.operation_type)
      : BANK_RULES.includes(r.operation_type);
  });

const E_COMMERCE: TemplateRulesFunction = (currency, isWallet) =>
  [
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.withdraw,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 0,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["VERBOTEN"],
      operation_type: LimitOperationType.merge,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 0,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.deposit,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 25 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
    {
      display_priority: 1,
      measures: ["preserve-investigate"],
      operation_type: LimitOperationType.aggregate,
      threshold: Amounts.stringify({
        currency,
        fraction: 0,
        value: 25 * 1000,
      }),
      timeframe: Duration.toTalerProtocolDuration(
        Duration.fromSpec({ months: 1 }),
      ),
    },
  ].filter((r) => {
    return isWallet
      ? WALLET_RULES.includes(r.operation_type)
      : BANK_RULES.includes(r.operation_type);
  });
