/*
 This file is part of GNU Taler
 (C) 2024 Taler Systems S.A.

 GNU Taler is free software; you can redistribute it and/or modify it under the
 terms of the GNU Affero 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 Affero General Public License along with
 GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>

 SPDX-License-Identifier: AGPL-3.0-or-later
 */

import { codecForAmountString } from "./amounts.js";
import {
  Codec,
  buildCodecForObject,
  buildCodecForUnion,
  codecForBoolean,
  codecForConstString,
  codecForEither,
  codecForList,
  codecForNumber,
  codecForString,
  codecOptional,
  codecOptionalDefault,
} from "./codec.js";
import { PaytoString, codecForPaytoString } from "./payto.js";
import { codecForTalerUriString, TalerUriString } from "./taleruri.js";
import { codecForTimestamp } from "./time.js";
import { codecForConversionRate, ConversionRate, RoundingMode } from "./types-taler-bank-conversion.js";
import { WithdrawalOperationStatusFlag } from "./types-taler-bank-integration.js";
import {
  AmountString,
  CurrencySpecification,
  DecimalNumber,
  Integer,
  ShortHashCode,
  Timestamp,
  codecForCurrencySpecificiation,
  codecForDecimalNumber,
} from "./types-taler-common.js";
import { TanChannel } from "./types-taler-merchant.js";

export interface IntegrationConfig {
  // libtool-style representation of the Bank protocol version, see
  // https://www.gnu.org/software/libtool/manual/html_node/Versioning.html#Versioning
  // The format is "current:revision:age".
  version: string;

  currency: string;

  // How the bank SPA should render this currency.
  currency_specification: CurrencySpecification;

  // Name of the API.
  name: "taler-bank-integration";

  implementation?: string;
}

export interface TalerCorebankConfigResponse {
  /**
   * Name of this API, always "taler-corebank".
   *
   * For legacy reasons, libeufin-bank will also be accepted for some time.
   */
  name: "libeufin-bank" | "taler-corebank";

  // API version in the form $n:$n:$n
  version: string;

  // Bank display name to be used in user interfaces.
  // For consistency use "Taler Bank" if missing.
  // @since v4, will become mandatory in the next version.
  bank_name?: string;

  // Advertised base URL to use when you sharing an URL with another
  // program.
  // @since v4.
  base_url?: string;

  // If 'true' the server provides local currency conversion support
  // If 'false' some parts of the API are not supported and return 501
  allow_conversion?: boolean;

  // If 'true' anyone can register
  // If 'false' only the admin can
  allow_registrations?: boolean;

  // If 'true' account can delete themselves
  // If 'false' only the admin can delete accounts
  allow_deletions?: boolean;

  // If 'true' anyone can edit their name
  // If 'false' only admin can
  allow_edit_name?: boolean;

  // If 'true' anyone can edit their cashout account
  // If 'false' only the admin
  allow_edit_cashout_payto_uri?: boolean;

  // Default debt limit for newly created accounts
  default_debit_threshold?: AmountString;

  // Currency used by this bank.
  currency: string;

  // How the bank SPA should render this currency.
  currency_specification: CurrencySpecification;

  // TAN channels supported by the server
  supported_tan_channels?: TanChannel[];

  // Wire transfer type supported by the bank.
  // Default to 'iban' is missing
  // @since v4, may become mandatory in the future.
  wire_type?: string;

  // Wire transfer execution fees.
  // @since v4, will become mandatory in the next version.
  wire_transfer_fees?: AmountString;

  // Minimum wire transfer amount allowed. Only applies to bank transactions and withdrawals.
  // @since **v4**, will become mandatory in the next version.
  min_wire_transfer_amount?: AmountString;

  // Maximum wire transfer amount allowed. Only applies to bank transactions and withdrawals.
  // @since **v4**, will become mandatory in the next version.
  max_wire_transfer_amount?: AmountString;
}

export interface BankAccountCreateWithdrawalRequest {
  // Amount to withdraw.  If given, the wallet
  // cannot change the amount.
  // Optional since **vC2EC**.
  amount?: AmountString;

  // Suggested amount to withdraw. The wallet can
  // still change the suggestion.
  // @since **vC2EC**
  suggested_amount?: AmountString;

  // If true, tell the wallet not to allow the user to
  // specify an amount to withdraw and to not provide
  // any amount when registering with the withdrawal
  // operation. The amount to withdraw will be set
  // by the final /withdrawals/$WITHDRAWAL_ID/confirm step.
  // @since **v8**
  no_amount_to_wallet?: boolean;
}

export interface BankAccountConfirmWithdrawalRequest {
  // Selected amount to be transferred. Optional if the
  // backend already knows the amount.
  // @since **v6**
  amount?: AmountString;
}

export interface BankAccountCreateWithdrawalResponse {
  // ID of the withdrawal, can be used to view/modify the withdrawal operation.
  withdrawal_id: string;

  // URI that can be passed to the wallet to initiate the withdrawal.
  taler_withdraw_uri: TalerUriString;
}

export interface WithdrawalPublicInfo {
  // Current status of the operation
  // pending: the operation is pending parameters selection (exchange and reserve public key)
  // selected: the operations has been selected and is pending confirmation
  // aborted: the operation has been aborted
  // confirmed: the transfer has been confirmed and registered by the bank
  status: WithdrawalOperationStatusFlag;

  // Amount that will be withdrawn with this operation
  // (raw amount without fee considerations).
  amount?: AmountString;

  // Suggestion for the amount to be withdrawn with this
  // operation.  Given if a suggestion was made but the
  // user may still change the amount.
  // Optional since **vC2EC**.
  suggested_amount?: AmountString;

  // Account username
  username: string;

  // Reserve public key selected by the exchange,
  // only non-null if status is selected or confirmed.
  selected_reserve_pub?: string;

  // Exchange account selected by the wallet
  // only non-null if status is selected or confirmed.
  selected_exchange_account?: PaytoString;

  // If true, the wallet must not allow the user to
  // specify an amount to withdraw and to not provide
  // any amount when registering with the withdrawal
  // operation. The amount to withdraw will be set
  // by the final /withdrawals/$WITHDRAWAL_ID/confirm step.
  // @since **v8**
  no_amount_to_wallet?: boolean;
}

export interface BankAccountTransactionsResponse {
  transactions: BankAccountTransactionInfo[];
}

export interface BankAccountTransactionInfo {
  creditor_payto_uri: PaytoString;
  debtor_payto_uri: PaytoString;

  amount: AmountString;
  direction: "debit" | "credit";

  subject: string;

  // Transaction unique ID.  Matches
  // $transaction_id from the URI.
  row_id: number;
  date: Timestamp;
}

export interface CreateTransactionRequest {
  // Address in the Payto format of the wire transfer receiver.
  // It needs at least the 'message' query string parameter.
  payto_uri: PaytoString;

  // Transaction amount (in the $currency:x.y format), optional.
  // However, when not given, its value must occupy the 'amount'
  // query string parameter of the 'payto' field.  In case it
  // is given in both places, the paytoUri's takes the precedence.
  amount?: AmountString;

  // Nonce to make the request idempotent.  Requests with the same
  // request_uid that differ in any of the other fields
  // are rejected.
  // @since v4, will become mandatory in the next version.
  request_uid?: ShortHashCode;
}

export interface CreateTransactionResponse {
  // ID identifying the transaction being created
  row_id: Integer;
}

export interface RegisterAccountResponse {
  // Internal payto URI of this bank account.
  internal_payto_uri: PaytoString;
}

export interface RegisterAccountRequest {
  // Username
  // Must match [a-zA-Z0-9\-\._~]{1, 126}
  username: string;

  // Password.
  password: string;

  // Legal name of the account owner
  name: string;

  // Make this account visible to anyone?
  // Defaults to false.
  is_public?: boolean;

  // Is this a taler exchange account?
  // If true:
  // - incoming transactions to the account that do not
  //   have a valid reserve public key are automatically
  // - the account provides the taler-wire-gateway-api endpoints
  // Defaults to false.
  is_taler_exchange?: boolean;

  // Addresses where to send the TAN for transactions.
  contact_data?: ChallengeContactData;

  // 'payto' address of a fiat bank account.
  // Payments will be sent to this bank account
  // when the user wants to convert the regional currency
  // back to fiat currency outside bank.
  cashout_payto_uri?: PaytoString;

  // Internal payto URI of this bank account.
  // Used mostly for testing.
  payto_uri?: PaytoString;

  // If present, set the max debit allowed for this user
  // Only admin can set this property.
  debit_threshold?: AmountString;

  // If present, set the user conversion rate class
  // Only admin can set this property.
  // @since **v9**
  conversion_rate_class_id?: Integer;

  // If present, enables 2FA and set the TAN channel used for challenges
  // Only admin can set this property, other user can reconfig their account
  // after creation.
  tan_channel?: TanChannel;

  // @deprecated in **v9**, use conversion_rate_class_id instead
  // min_cashout?: Amount;
}

export type EmailAddress = string;
export type PhoneNumber = string;

export interface ChallengeContactData {
  // E-Mail address
  email?: EmailAddress;

  // Phone number.
  phone?: PhoneNumber;
}

export interface AccountReconfiguration {
  // Addresses where to send the TAN for transactions.
  // Currently only used for cashouts.
  // If missing, cashouts will fail.
  // In the future, might be used for other transactions
  // as well.
  // Only admin can change this property.
  contact_data?: ChallengeContactData;

  // 'payto' URI of a fiat bank account.
  // Payments will be sent to this bank account
  // when the user wants to convert the regional currency
  // back to fiat currency outside bank.
  // Only admin can change this property if not allowed in config
  cashout_payto_uri?: PaytoString | null;

  // If present, change the legal name associated with $username.
  // Only admin can change this property if not allowed in config
  name?: string;

  // Make this account visible to anyone?
  is_public?: boolean;

  // If present, change the max debit allowed for this user
  // Only admin can change this property.
  debit_threshold?: AmountString;

  // If present, set the user conversion rate class
  // Only admin can set this property.
  // @since **v9**
  conversion_rate_class_id?: Integer | null;

  // If present, enables 2FA and set the TAN channel used for challenges
  tan_channel?: TanChannel | null;

  // @deprecated in **v9**, user conversion rate classes instead
  // min_cashout?: Amount;
}

export interface AccountPasswordChange {
  // New password.
  new_password: string;
  // Old password. If present, check that the old password matches.
  // Optional for admin account.
  old_password?: string;
}

export interface PublicAccountsResponse {
  public_accounts: PublicAccount[];
}
export interface PublicAccount {
  // Username of the account
  username: string;

  // Internal payto URI of this bank account.
  payto_uri: string;

  // Current balance of the account
  balance: Balance;

  // Is this a taler exchange account?
  is_taler_exchange: boolean;

  // Opaque unique ID used for pagination.
  // @since v4, will become mandatory in the future.
  row_id?: Integer;
}

export interface ListBankAccountsResponse {
  accounts: AccountMinimalData[];
}

export interface GetBankAccountByIdResponse {
  account: AccountMinimalData;
}

export interface Balance {
  amount: AmountString;
  credit_debit_indicator: "credit" | "debit";
}

export interface AccountMinimalData {
  // Username
  username: string;

  // Legal name of the account owner.
  name: string;

  // Internal payto URI of this bank account.
  payto_uri: PaytoString;

  // current balance of the account
  balance: Balance;

  // Number indicating the max debit allowed for the requesting user.
  debit_threshold: AmountString;

  // Custom minimum cashout amount for this account.
  // If null or absent, the global conversion fee is used.
  // @since v6
  // @deprecated in **v9**, use conversion_rate_class_id instead
  // min_cashout?: AmountString;

  // Is this account visible to anyone?
  is_public: boolean;

  // Is this a taler exchange account?
  is_taler_exchange: boolean;

  // Opaque unique ID used for pagination.
  // @since v4, will become mandatory in the future.
  row_id?: Integer;

  // Is the account locked.
  // Defaults to false.
  // @deprecated since **v7**
  // is_locked?: boolean;

  // Current status of the account
  // active: the account can be used
  // locked: the account can be used but cannot create new tokens
  // @since **v7**
  // deleted: the account has been deleted but is retained for compliance
  // reasons, only the administrator can access it
  // Defaults to 'active' is missing
  // @since **v4**, will become mandatory in the next version.
  status?: AccountStatus;

  // Conversion rate class of the user
  conversion_rate_class_id?: Integer;

  // Applied conversion rate
  conversion_rate?: ConversionRate;
}

export type AccountStatus = "active" | "locked" | "deleted";

export interface ConversionRateClass {
  // The name of this class
  name: string;

  // A description of the class
  description?: string;

  // Class unique ID
  conversion_rate_class_id: Integer;

  // Number of users affected to this class
  num_users: Integer;

  // Minimum fiat amount authorised for cashin before conversion
  cashin_min_amount?: AmountString;

  // Exchange rate to buy regional currency from fiat
  cashin_ratio?: DecimalNumber;

  // Regional amount fee to subtract after applying the cashin ratio.
  cashin_fee?: AmountString;

  // Rounding mode used during cashin conversion
  cashin_rounding_mode?: RoundingMode;

  // Minimum regional amount authorised for cashout before conversion
  cashout_min_amount?: AmountString;

  // Exchange rate to sell regional currency for fiat
  cashout_ratio?: DecimalNumber;

  // Fiat amount fee to subtract after applying the cashout ratio.
  cashout_fee?: AmountString;

  // Rounding mode used during cashout conversion
  cashout_rounding_mode?: RoundingMode;
}

export interface ConversionRateClasses {
  classes: ConversionRateClass[];
}

export interface AccountConversionRateClass {
  // Class unique ID
  conversion_rate_class_id: Integer;

  // Minimum fiat amount authorised for cashin before conversion
  cashin_min_amount?: AmountString;

  // Exchange rate to buy regional currency from fiat
  cashin_ratio?: DecimalNumber;

  // Regional amount fee to subtract after applying the cashin ratio.
  cashin_fee?: AmountString;

  // Rounding mode used during cashin conversion
  cashin_rounding_mode?: RoundingMode;

  // Minimum regional amount authorised for cashout before conversion
  cashout_min_amount?: AmountString;

  // Exchange rate to sell regional currency for fiat
  cashout_ratio?: DecimalNumber;

  // Fiat amount fee to subtract after applying the cashout ratio.
  cashout_fee?: AmountString;

  // Rounding mode used during cashout conversion
  cashout_rounding_mode?: RoundingMode;
}

export interface ConversionRateClassInput {
  // The name of this class
  name: string;

  // A description of the class
  description?: string;

  // Minimum fiat amount authorised for cashin before conversion
  cashin_min_amount?: AmountString;

  // Exchange rate to buy regional currency from fiat
  cashin_ratio?: DecimalNumber;

  // Regional amount fee to subtract after applying the cashin ratio.
  cashin_fee?: AmountString;

  // Rounding mode used during cashin conversion
  cashin_rounding_mode?: RoundingMode;

  // Minimum regional amount authorised for cashout before conversion
  cashout_min_amount?: AmountString;

  // Exchange rate to sell regional currency for fiat
  cashout_ratio?: DecimalNumber;

  // Fiat amount fee to subtract after applying the cashout ratio.
  cashout_fee?: AmountString;

  // Rounding mode used during cashout conversion
  cashout_rounding_mode?: RoundingMode;
}

export interface ConversionRateClassResponse {
  // ID identifying the conversion rate class being created
  conversion_rate_class_id: Integer;
}

export interface AccountData {
  // Legal name of the account owner.
  name: string;

  // Available balance on the account.
  balance: Balance;

  // payto://-URI of the account.
  payto_uri: PaytoString;

  // Number indicating the max debit allowed for the requesting user.
  debit_threshold: AmountString;

  // Custom minimum cashout amount for this account.
  // If null or absent, the global conversion fee is used.
  // @since v6
  // @deprecated in **v9**, use conversion_rate_class_id instead
  // min_cashout?: AmountString;

  // Addresses where to send the TAN for transactions.
  // Currently only used for cashouts.
  // If missing, cashouts will fail.
  // In the future, might be used for other transactions
  // as well.
  contact_data?: ChallengeContactData;

  // Full 'payto' URI of a fiat bank account where to send cashouts with
  // ``name`` as the 'receiver-name'.
  // This field is optional
  // because not all the accounts are required to participate
  // in the merchants' circuit.  One example is the exchange:
  // that never cashouts.  Registering these accounts can
  // be done via the access API.
  cashout_payto_uri?: PaytoString;

  // Is this account visible to anyone?
  is_public: boolean;

  // Is this a taler exchange account?
  is_taler_exchange: boolean;

  // Is the account locked.
  // Defaults to false.
  // @deprecated since **v7**
  // is_locked?: boolean;

  // Is 2FA enabled and what channel is used for challenges?
  tan_channel?: TanChannel;

  // Current status of the account
  // active: the account can be used
  // locked: the account can be used but cannot create new tokens
  // @since **v7**
  // deleted: the account has been deleted but is retained for compliance
  // reasons, only the administrator can access it
  // Defaults to 'active' is missing
  // @since **v4**, will become mandatory in the next version.
  status?: AccountStatus;

  // Conversion rate class of the user
  conversion_rate_class_id?: Integer;
}

export interface CashoutRequest {
  // Nonce to make the request idempotent.  Requests with the same
  // request_uid that differ in any of the other fields
  // are rejected.
  request_uid: ShortHashCode;

  // Optional subject to associate to the
  // cashout operation.  This data will appear
  // as the incoming wire transfer subject in
  // the user's fiat bank account.
  subject?: string;

  // That is the plain amount that the user specified
  // to cashout.  Its $currency is the (regional) currency of the
  // bank instance.
  amount_debit: AmountString;

  // That is the amount that will effectively be
  // transferred by the bank to the user's bank
  // account, that is external to the regional currency.
  // It is expressed in the fiat currency and
  // is calculated after the cashout fee and the
  // exchange rate.  See the /cashout-rates call.
  // The client needs to calculate this amount
  // correctly based on the amount_debit and the cashout rate,
  // otherwise the request will fail.
  amount_credit: AmountString;
}

export interface CashoutResponse {
  // ID identifying the operation being created
  cashout_id: number;
}

/**
 * @deprecated since 4, use 2fa
 */
export interface CashoutConfirmRequest {
  // the TAN that confirms $CASHOUT_ID.
  tan: string;
}

export interface Cashouts {
  // Every string represents a cash-out operation ID.
  cashouts: CashoutInfo[];
}

export interface CashoutInfo {
  cashout_id: number;
}
export interface GlobalCashouts {
  // Every string represents a cash-out operation ID.
  cashouts: GlobalCashoutInfo[];
}
export interface GlobalCashoutInfo {
  cashout_id: number;
  username: string;
}

export interface CashoutStatusResponse {
  // Amount debited to the internal
  // regional currency bank account.
  amount_debit: AmountString;

  // Amount credited to the external bank account.
  amount_credit: AmountString;

  // Transaction subject.
  subject: string;

  // Time when the cashout was created.
  creation_time: Timestamp;
}

export interface ConversionRatesResponse {
  // Exchange rate to buy the local currency from the external one
  buy_at_ratio: DecimalNumber;

  // Exchange rate to sell the local currency for the external one
  sell_at_ratio: DecimalNumber;

  // Fee to subtract after applying the buy ratio.
  buy_in_fee: DecimalNumber;

  // Fee to subtract after applying the sell ratio.
  sell_out_fee: DecimalNumber;
}

export enum MonitorTimeframeParam {
  hour,
  day,
  month,
  year,
  decade,
}

export type MonitorResponse = MonitorNoConversion | MonitorWithConversion;

// Monitoring stats when conversion is not supported
export interface MonitorNoConversion {
  type: "no-conversions";

  // How many payments were made to a Taler exchange by another
  // bank account.
  talerInCount: number;

  // Overall volume that has been paid to a Taler
  // exchange by another bank account.
  talerInVolume: AmountString;

  // How many payments were made by a Taler exchange to another
  // bank account.
  talerOutCount: number;

  // Overall volume that has been paid by a Taler
  // exchange to another bank account.
  talerOutVolume: AmountString;
}

// Monitoring stats when conversion is supported
export interface MonitorWithConversion {
  type: "with-conversions";

  // How many cashin operations were confirmed by a
  // wallet owner. Note: wallet owners
  // are NOT required to be customers of the libeufin-bank.
  cashinCount: number;

  // Overall regional currency that has been paid by the regional admin account
  // to regional bank accounts to fulfill all the confirmed cashin operations.
  cashinRegionalVolume: AmountString;

  // Overall fiat currency that has been paid to the fiat admin account
  // by fiat bank accounts to fulfill all the confirmed cashin operations.
  cashinFiatVolume: AmountString;

  // How many cashout operations were confirmed.
  cashoutCount: number;

  // Overall regional currency that has been paid to the regional admin account
  // by fiat bank accounts to fulfill all the confirmed cashout operations.
  cashoutRegionalVolume: AmountString;

  // Overall fiat currency that has been paid by the fiat admin account
  // to fiat bank accounts to fulfill all the confirmed cashout operations.
  cashoutFiatVolume: AmountString;

  // How many payments were made to a Taler exchange by another
  // bank account.
  talerInCount: number;

  // Overall volume that has been paid to a Taler
  // exchange by another bank account.
  talerInVolume: AmountString;

  // How many payments were made by a Taler exchange to another
  // bank account.
  talerOutCount: number;

  // Overall volume that has been paid by a Taler
  // exchange to another bank account.
  talerOutVolume: AmountString;
}



export const codecForIntegrationBankConfig = (): Codec<IntegrationConfig> =>
  buildCodecForObject<IntegrationConfig>()
    .property("name", codecForConstString("taler-bank-integration"))
    .property("version", codecForString())
    .property("currency", codecForString())
    .property("currency_specification", codecForCurrencySpecificiation())
    .property("implementation", codecOptional(codecForString()))
    .build("TalerCorebankApi.IntegrationConfig");

export const codecForCoreBankConfig = (): Codec<TalerCorebankConfigResponse> =>
  buildCodecForObject<TalerCorebankConfigResponse>()
    .property(
      "name",
      codecForEither(
        codecForConstString("taler-corebank"),
        codecForConstString("libeufin-bank"),
      ),
    )
    .property("version", codecForString())
    .property("bank_name", codecOptional(codecForString()))
    .property("base_url", codecOptional(codecForString()))
    .property("allow_conversion", codecOptional(codecForBoolean()))
    .property("allow_registrations", codecOptional(codecForBoolean()))
    .property("allow_deletions", codecOptional(codecForBoolean()))
    .property("allow_edit_name", codecOptional(codecForBoolean()))
    .property("allow_edit_cashout_payto_uri", codecOptional(codecForBoolean()))
    .property("default_debit_threshold", codecOptional(codecForAmountString()))
    .property("currency", codecForString())
    .property("currency_specification", codecForCurrencySpecificiation())
    .property(
      "supported_tan_channels",
      codecOptional(
        codecForList(
          codecForEither(
            codecForConstString(TanChannel.SMS),
            codecForConstString(TanChannel.EMAIL),
          ),
        ),
      ),
    )
    .property("wire_type", codecOptionalDefault(codecForString(), "iban"))
    .property("wire_transfer_fees", codecOptional(codecForAmountString()))
    .property("min_wire_transfer_amount", codecOptional(codecForAmountString()))
    .property("max_wire_transfer_amount", codecOptional(codecForAmountString()))
    .build("TalerCorebankApi.Config");

const codecForBalance = (): Codec<Balance> =>
  buildCodecForObject<Balance>()
    .property("amount", codecForAmountString())
    .property(
      "credit_debit_indicator",
      codecForEither(
        codecForConstString("credit"),
        codecForConstString("debit"),
      ),
    )
    .build("TalerCorebankApi.Balance");

const codecForPublicAccount = (): Codec<PublicAccount> =>
  buildCodecForObject<PublicAccount>()
    .property("username", codecForString())
    .property("balance", codecForBalance())
    .property("payto_uri", codecForPaytoString())
    .property("is_taler_exchange", codecForBoolean())
    .property("row_id", codecOptional(codecForNumber()))
    .build("TalerCorebankApi.PublicAccount");

export const codecForPublicAccountsResponse =
  (): Codec<PublicAccountsResponse> =>
    buildCodecForObject<PublicAccountsResponse>()
      .property("public_accounts", codecForList(codecForPublicAccount()))
      .build("TalerCorebankApi.PublicAccountsResponse");

export const codecForAccountMinimalData = (): Codec<AccountMinimalData> =>
  buildCodecForObject<AccountMinimalData>()
    .property("username", codecForString())
    .property("name", codecForString())
    .property("payto_uri", codecForPaytoString())
    .property("balance", codecForBalance())
    .property("row_id", codecForNumber())
    .property("debit_threshold", codecForAmountString())
    .property("is_public", codecForBoolean())
    .property("is_taler_exchange", codecForBoolean())
    .property(
      "status",
      codecOptional(
        codecForEither(
          codecForConstString("active"),
          codecForConstString("locked"),
          codecForConstString("deleted"),
        ),
      ),
    )
    .property("conversion_rate_class_id", codecOptional(codecForNumber()))
    .property("conversion_rate", codecOptional(codecForConversionRate()))
    .build("TalerCorebankApi.AccountMinimalData");

export const codecForListBankAccountsResponse =
  (): Codec<ListBankAccountsResponse> =>
    buildCodecForObject<ListBankAccountsResponse>()
      .property("accounts", codecForList(codecForAccountMinimalData()))
      .build("TalerCorebankApi.ListBankAccountsResponse");

export const codecForAccountData = (): Codec<AccountData> =>
  buildCodecForObject<AccountData>()
    .property("name", codecForString())
    .property("balance", codecForBalance())
    .property("payto_uri", codecForPaytoString())
    .property("debit_threshold", codecForAmountString())
    .property("contact_data", codecOptional(codecForChallengeContactData()))
    .property("cashout_payto_uri", codecOptional(codecForPaytoString()))
    .property("is_public", codecForBoolean())
    .property("is_taler_exchange", codecForBoolean())
    .property("conversion_rate_class_id", codecOptional(codecForNumber()))
    .property(
      "tan_channel",
      codecOptional(
        codecForEither(
          codecForConstString(TanChannel.SMS),
          codecForConstString(TanChannel.EMAIL),
        ),
      ),
    )
    .property(
      "status",
      codecOptional(
        codecForEither(
          codecForConstString("active"),
          codecForConstString("locked"),
          codecForConstString("deleted"),
        ),
      ),
    )
    .build("TalerCorebankApi.AccountData");

export const codecForConversionRateClassResponse =
  (): Codec<ConversionRateClassResponse> =>
    buildCodecForObject<ConversionRateClassResponse>()
      .property("conversion_rate_class_id", codecForNumber())
      .build("TalerCorebankApi.ConversionRateClassResponse");

export const codecForConversionRateClass = (): Codec<ConversionRateClass> =>
  buildCodecForObject<ConversionRateClass>()
    .property("cashin_fee", codecOptional(codecForAmountString()))
    .property("cashin_min_amount", codecOptional(codecForAmountString()))
    .property("cashin_ratio", codecOptional(codecForDecimalNumber()))
    .property(
      "cashin_rounding_mode",
      codecOptional(
        codecForEither(
          codecForConstString("zero"),
          codecForConstString("up"),
          codecForConstString("nearest"),
        ),
      ),
    )
    .property("cashout_fee", codecOptional(codecForAmountString()))
    .property("cashout_min_amount", codecOptional(codecForAmountString()))
    .property("cashout_ratio", codecOptional(codecForDecimalNumber()))
    .property(
      "cashout_rounding_mode",
      codecOptional(codecForEither(
        codecForConstString("zero"),
        codecForConstString("up"),
        codecForConstString("nearest"),
      )),
    )
    .property("conversion_rate_class_id", codecForNumber())
    .property("description", codecOptional(codecForString()))
    .property("name", codecForString())
    .property("num_users", codecForNumber())
    .build("TalerCorebankApi.ConversionRateClass");

export const codecForConversionRateClasses = (): Codec<ConversionRateClasses> =>
  buildCodecForObject<ConversionRateClasses>()
    .property("classes", codecForList(codecForConversionRateClass()))
    .build("TalerCorebankApi.ConversionRateClasses");

export const codecForChallengeContactData = (): Codec<ChallengeContactData> =>
  buildCodecForObject<ChallengeContactData>()
    .property("email", codecOptional(codecForString()))
    .property("phone", codecOptional(codecForString()))
    .build("TalerCorebankApi.ChallengeContactData");

export const codecForWithdrawalPublicInfo = (): Codec<WithdrawalPublicInfo> =>
  buildCodecForObject<WithdrawalPublicInfo>()
    .property(
      "status",
      codecForEither(
        codecForConstString("pending"),
        codecForConstString("selected"),
        codecForConstString("aborted"),
        codecForConstString("confirmed"),
      ),
    )
    .property("amount", codecOptional(codecForAmountString()))
    .property("suggested_amount", codecOptional(codecForAmountString()))
    .property("username", codecForString())
    .property("selected_reserve_pub", codecOptional(codecForString()))
    .property("selected_exchange_account", codecOptional(codecForPaytoString()))
    .property("no_amount_to_wallet", codecOptional(codecForBoolean()))
    .build("TalerCorebankApi.WithdrawalPublicInfo");

export const codecForBankAccountTransactionsResponse =
  (): Codec<BankAccountTransactionsResponse> =>
    buildCodecForObject<BankAccountTransactionsResponse>()
      .property(
        "transactions",
        codecForList(codecForBankAccountTransactionInfo()),
      )
      .build("TalerCorebankApi.BankAccountTransactionsResponse");

export const codecForBankAccountTransactionInfo =
  (): Codec<BankAccountTransactionInfo> =>
    buildCodecForObject<BankAccountTransactionInfo>()
      .property("creditor_payto_uri", codecForPaytoString())
      .property("debtor_payto_uri", codecForPaytoString())
      .property("amount", codecForAmountString())
      .property(
        "direction",
        codecForEither(
          codecForConstString("debit"),
          codecForConstString("credit"),
        ),
      )
      .property("subject", codecForString())
      .property("row_id", codecForNumber())
      .property("date", codecForTimestamp)
      .build("TalerCorebankApi.BankAccountTransactionInfo");

export const codecForCreateTransactionResponse =
  (): Codec<CreateTransactionResponse> =>
    buildCodecForObject<CreateTransactionResponse>()
      .property("row_id", codecForNumber())
      .build("TalerCorebankApi.CreateTransactionResponse");

export const codecForRegisterAccountResponse =
  (): Codec<RegisterAccountResponse> =>
    buildCodecForObject<RegisterAccountResponse>()
      .property("internal_payto_uri", codecForPaytoString())
      .build("TalerCorebankApi.RegisterAccountResponse");

export const codecForBankAccountCreateWithdrawalResponse =
  (): Codec<BankAccountCreateWithdrawalResponse> =>
    buildCodecForObject<BankAccountCreateWithdrawalResponse>()
      .property("taler_withdraw_uri", codecForTalerUriString())
      .property("withdrawal_id", codecForString())
      .build("TalerCorebankApi.BankAccountCreateWithdrawalResponse");

export const codecForCashoutPending = (): Codec<CashoutResponse> =>
  buildCodecForObject<CashoutResponse>()
    .property("cashout_id", codecForNumber())
    .build("TalerCorebankApi.CashoutPending");

export const codecForCashouts = (): Codec<Cashouts> =>
  buildCodecForObject<Cashouts>()
    .property("cashouts", codecForList(codecForCashoutInfo()))
    .build("TalerCorebankApi.Cashouts");

export const codecForCashoutInfo = (): Codec<CashoutInfo> =>
  buildCodecForObject<CashoutInfo>()
    .property("cashout_id", codecForNumber())
    .build("TalerCorebankApi.CashoutInfo");

export const codecForGlobalCashouts = (): Codec<GlobalCashouts> =>
  buildCodecForObject<GlobalCashouts>()
    .property("cashouts", codecForList(codecForGlobalCashoutInfo()))
    .build("TalerCorebankApi.GlobalCashouts");

export const codecForGlobalCashoutInfo = (): Codec<GlobalCashoutInfo> =>
  buildCodecForObject<GlobalCashoutInfo>()
    .property("cashout_id", codecForNumber())
    .property("username", codecForString())
    .build("TalerCorebankApi.GlobalCashoutInfo");

export const codecForCashoutStatusResponse = (): Codec<CashoutStatusResponse> =>
  buildCodecForObject<CashoutStatusResponse>()
    .property("amount_debit", codecForAmountString())
    .property("amount_credit", codecForAmountString())
    .property("subject", codecForString())
    .property("creation_time", codecForTimestamp)
    .build("TalerCorebankApi.CashoutStatusResponse");

export const codecForConversionRatesResponse =
  (): Codec<ConversionRatesResponse> =>
    buildCodecForObject<ConversionRatesResponse>()
      .property("buy_at_ratio", codecForDecimalNumber())
      .property("buy_in_fee", codecForDecimalNumber())
      .property("sell_at_ratio", codecForDecimalNumber())
      .property("sell_out_fee", codecForDecimalNumber())
      .build("TalerCorebankApi.ConversionRatesResponse");

export const codecForMonitorResponse = (): Codec<MonitorResponse> =>
  buildCodecForUnion<MonitorResponse>()
    .discriminateOn("type")
    .alternative("no-conversions", codecForMonitorNoConversion())
    .alternative("with-conversions", codecForMonitorWithCashout())
    .build("TalerWireGatewayApi.IncomingBankTransaction");

export const codecForMonitorNoConversion = (): Codec<MonitorNoConversion> =>
  buildCodecForObject<MonitorNoConversion>()
    .property("type", codecForConstString("no-conversions"))
    .property("talerInCount", codecForNumber())
    .property("talerInVolume", codecForAmountString())
    .property("talerOutCount", codecForNumber())
    .property("talerOutVolume", codecForAmountString())
    .build("TalerCorebankApi.MonitorJustPayouts");

export const codecForMonitorWithCashout = (): Codec<MonitorWithConversion> =>
  buildCodecForObject<MonitorWithConversion>()
    .property("type", codecForConstString("with-conversions"))
    .property("cashinCount", codecForNumber())
    .property("cashinFiatVolume", codecForAmountString())
    .property("cashinRegionalVolume", codecForAmountString())
    .property("cashoutCount", codecForNumber())
    .property("cashoutFiatVolume", codecForAmountString())
    .property("cashoutRegionalVolume", codecForAmountString())
    .property("talerInCount", codecForNumber())
    .property("talerInVolume", codecForAmountString())
    .property("talerOutCount", codecForNumber())
    .property("talerOutVolume", codecForAmountString())
    .build("TalerCorebankApi.MonitorWithCashout");

