use std::collections::HashMap;

use cards::CardNumber;
use common_enums::CardNetwork;
use common_types::payouts::PayoutsBillingDescriptor;
#[cfg(feature = "v2")]
use common_utils::types::BrowserInformation;
use common_utils::{
    consts::default_payouts_list_limit,
    crypto,
    errors::ValidationError,
    id_type, link_utils, payout_method_utils,
    pii::{self, Email},
    transformers::ForeignFrom,
    types::{UnifiedCode, UnifiedMessage},
};
use hyperswitch_masking::Secret;
#[cfg(feature = "v1")]
use payments::BrowserInformation;
use router_derive::FlatStruct;
use serde::{Deserialize, Serialize};
use time::PrimitiveDateTime;
use utoipa::ToSchema;

use crate::{admin, enums as api_enums, payment_methods::RequiredFieldInfo, payments};

#[derive(Debug, Serialize, Clone, ToSchema)]
pub enum PayoutRequest {
    PayoutActionRequest(PayoutActionRequest),
    PayoutCreateRequest(Box<PayoutCreateRequest>),
    PayoutRetrieveRequest(PayoutRetrieveRequest),
}

#[derive(
    Default, Debug, Deserialize, Serialize, Clone, ToSchema, router_derive::PolymorphicSchema,
)]
#[generate_schemas(PayoutsCreateRequest, PayoutUpdateRequest, PayoutConfirmRequest)]
#[serde(deny_unknown_fields)]
pub struct PayoutCreateRequest {
    /// Unique identifier for the payout. This ensures idempotency for multiple payouts that have been done by a single merchant. This field is auto generated and is returned in the API response, **not required to be included in the Payout Create/Update Request.**
    #[schema(
        value_type = Option<String>,
        min_length = 30,
        max_length = 30,
        example = "187282ab-40ef-47a9-9206-5099ba31e432"
    )]
    #[remove_in(PayoutsCreateRequest, PayoutUpdateRequest, PayoutConfirmRequest)]
    pub payout_id: Option<id_type::PayoutId>,

    /// This is an identifier for the merchant account. This is inferred from the API key provided during the request, **not required to be included in the Payout Create/Update Request.**
    #[schema(max_length = 255, value_type = Option<String>, example = "merchant_1668273825")]
    #[remove_in(PayoutsCreateRequest, PayoutUpdateRequest, PayoutConfirmRequest)]
    pub merchant_id: Option<id_type::MerchantId>,

    /// Your unique identifier for this payout or order. This ID helps you reconcile payouts on your system. If provided, it is passed to the connector if supported.
    #[schema(value_type = Option<String>, max_length = 255, example = "merchant_order_ref_123")]
    pub merchant_order_reference_id: Option<String>,

    /// The payout amount. Amount for the payout in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,
    #[schema(value_type = Option<u64>, example = 1000)]
    #[mandatory_in(PayoutsCreateRequest = u64)]
    #[remove_in(PayoutsConfirmRequest)]
    #[serde(default, deserialize_with = "payments::amount::deserialize_option")]
    pub amount: Option<payments::Amount>,

    /// The currency of the payout request can be specified here
    #[schema(value_type = Option<Currency>, example = "USD")]
    #[mandatory_in(PayoutsCreateRequest = Currency)]
    #[remove_in(PayoutsConfirmRequest)]
    pub currency: Option<api_enums::Currency>,

    /// Specifies routing algorithm for selecting a connector
    #[schema(value_type = Option<StaticRoutingAlgorithm>, example = json!({
        "type": "single",
        "data": "adyen"
    }))]
    pub routing: Option<serde_json::Value>,

    /// This field allows the merchant to manually select a connector with which the payout can go through.
    #[schema(value_type = Option<Vec<PayoutConnectors>>, max_length = 255, example = json!(["wise", "adyen"]))]
    pub connector: Option<Vec<api_enums::PayoutConnectors>>,

    /// This field is used when merchant wants to confirm the payout, thus useful for the payout _Confirm_ request. Ideally merchants should _Create_ a payout, _Update_ it (if required), then _Confirm_ it.
    #[schema(value_type = Option<bool>, example = true, default = false)]
    #[remove_in(PayoutConfirmRequest)]
    pub confirm: Option<bool>,

    /// The payout_type of the payout request can be specified here, this is a mandatory field to _Confirm_ the payout, i.e., should be passed in _Create_ request, if not then should be updated in the payout _Update_ request, then only it can be confirmed.
    #[schema(value_type = Option<PayoutType>, example = "card")]
    pub payout_type: Option<api_enums::PayoutType>,

    /// The payout method information required for carrying out a payout
    #[schema(value_type = Option<PayoutMethodData>)]
    pub payout_method_data: Option<PayoutMethodData>,

    /// The source bank data required for carrying out a payout
    #[schema(value_type = Option<BankTransfer>)]
    pub source_bank_data: Option<BankTransfer>,

    /// The billing address for the payout
    #[schema(value_type = Option<Address>, example = json!(r#"{
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Francisco",
            "state": "CA",
            "zip": "94122",
            "country": "US",
            "first_name": "John",
            "last_name": "Doe"
        },
        "phone": { "number": "9123456789", "country_code": "+1" }
    }"#))]
    pub billing: Option<payments::Address>,

    /// Set to true to confirm the payout without review, no further action required
    #[schema(value_type = Option<bool>, example = true, default = false)]
    pub auto_fulfill: Option<bool>,

    /// The identifier for the customer object. If not provided the customer ID will be autogenerated. _Deprecated: Use customer_id instead._
    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    pub customer_id: Option<id_type::CustomerId>,

    /// Passing this object creates a new customer or attaches an existing customer to the payout
    #[schema(value_type = Option<CustomerDetails>)]
    pub customer: Option<payments::CustomerDetails>,

    /// It's a token used for client side verification.
    #[schema(value_type = Option<String>, example = "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo")]
    #[remove_in(PayoutsCreateRequest)]
    #[mandatory_in(PayoutConfirmRequest = String)]
    pub client_secret: Option<String>,

    /// The URL to redirect after the completion of the operation
    #[schema(value_type = Option<String>, example = "https://hyperswitch.io")]
    pub return_url: Option<String>,

    /// Business country of the merchant for this payout. _Deprecated: Use profile_id instead._
    #[schema(deprecated, example = "US", value_type = Option<CountryAlpha2>)]
    pub business_country: Option<api_enums::CountryAlpha2>,

    /// Business label of the merchant for this payout. _Deprecated: Use profile_id instead._
    #[schema(deprecated, example = "food", value_type = Option<String>)]
    pub business_label: Option<String>,

    /// A description of the payout
    #[schema(example = "It's my first payout request", value_type = Option<String>)]
    pub description: Option<String>,

    /// Billing descriptor information for the payout. The reference is displayed on the beneficiary's bank statement.
    #[schema(value_type = Option<PayoutsBillingDescriptor>)]
    pub billing_descriptor: Option<PayoutsBillingDescriptor>,

    /// Type of entity to whom the payout is being carried out to, select from the given list of options
    #[schema(value_type = Option<PayoutEntityType>, example = "Individual")]
    pub entity_type: Option<api_enums::PayoutEntityType>,

    /// Specifies whether or not the payout request is recurring
    #[schema(value_type = Option<bool>, default = false)]
    pub recurring: Option<bool>,

    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
    #[schema(value_type = Option<Object>, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)]
    pub metadata: Option<pii::SecretSerdeValue>,

    /// Provide a reference to a stored payout method, used to process the payout.
    #[schema(example = "187282ab-40ef-47a9-9206-5099ba31e432", value_type = Option<String>)]
    pub payout_token: Option<String>,

    /// The business profile to use for this payout, especially if there are multiple business profiles associated with the account, otherwise default business profile associated with the merchant account will be used.
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<id_type::ProfileId>,

    /// The send method which will be required for processing payouts, check options for better understanding.
    #[schema(value_type = Option<PayoutSendPriority>, example = "instant")]
    pub priority: Option<api_enums::PayoutSendPriority>,

    /// Whether to get the payout link (if applicable). Merchant need to specify this during the Payout _Create_, this field can not be updated during Payout _Update_.
    #[schema(default = false, example = true, value_type = Option<bool>)]
    pub payout_link: Option<bool>,

    /// Custom payout link config for the particular payout, if payout link is to be generated.
    #[schema(value_type = Option<PayoutCreatePayoutLinkConfig>)]
    pub payout_link_config: Option<PayoutCreatePayoutLinkConfig>,

    /// Will be used to expire client secret after certain amount of time to be supplied in seconds
    /// (900) for 15 mins
    #[schema(value_type = Option<u32>, example = 900)]
    pub session_expiry: Option<u32>,

    /// Customer's email. _Deprecated: Use customer object instead._
    #[schema(deprecated, max_length = 255, value_type = Option<String>, example = "johntest@test.com")]
    pub email: Option<Email>,

    /// Customer's name. _Deprecated: Use customer object instead._
    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "John Test")]
    pub name: Option<Secret<String>>,

    /// Customer's phone. _Deprecated: Use customer object instead._
    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "9123456789")]
    pub phone: Option<Secret<String>>,

    /// Customer's phone country code. _Deprecated: Use customer object instead._
    #[schema(deprecated, max_length = 255, example = "+1")]
    pub phone_country_code: Option<String>,

    /// Identifier for payout method
    pub payout_method_id: Option<String>,

    /// Additional details required by 3DS 2.0
    #[schema(value_type = Option<BrowserInformation>)]
    pub browser_info: Option<BrowserInformation>,
}

impl PayoutCreateRequest {
    pub fn get_customer_id(&self) -> Option<&id_type::CustomerId> {
        self.customer_id.as_ref().or(self
            .customer
            .as_ref()
            .and_then(|customer| customer.id.as_ref()))
    }
}

/// Custom payout link config for the particular payout, if payout link is to be generated.
#[derive(Default, Debug, Deserialize, Serialize, Clone, ToSchema)]
pub struct PayoutCreatePayoutLinkConfig {
    /// The unique identifier for the collect link.
    #[schema(value_type = Option<String>, example = "pm_collect_link_2bdacf398vwzq5n422S1")]
    pub payout_link_id: Option<String>,

    #[serde(flatten)]
    #[schema(value_type = Option<GenericLinkUiConfig>)]
    pub ui_config: Option<link_utils::GenericLinkUiConfig>,

    /// List of payout methods shown on collect UI
    #[schema(value_type = Option<Vec<EnabledPaymentMethod>>, example = r#"[{"payment_method": "bank_transfer", "payment_method_types": ["ach", "bacs"]}]"#)]
    pub enabled_payment_methods: Option<Vec<link_utils::EnabledPaymentMethod>>,

    /// Form layout of the payout link
    #[schema(value_type = Option<UIWidgetFormLayout>, max_length = 255, example = "tabs")]
    pub form_layout: Option<api_enums::UIWidgetFormLayout>,

    /// `test_mode` allows for opening payout links without any restrictions. This removes
    /// - domain name validations
    /// - check for making sure link is accessed within an iframe
    #[schema(value_type = Option<bool>, example = false)]
    pub test_mode: Option<bool>,
}

/// The payout method information required for carrying out a payout
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum PayoutMethodData {
    Card(CardPayout),
    Bank(Bank), // Deprecated - Use BankTransfer instead
    Wallet(Wallet),
    BankRedirect(BankRedirect),
    Passthrough(Passthrough),
    BankTransfer(BankTransfer),
}

impl Default for PayoutMethodData {
    fn default() -> Self {
        Self::Card(CardPayout::default())
    }
}

impl PayoutMethodData {
    pub fn normalize(self) -> Result<Self, error_stack::Report<ValidationError>> {
        match self {
            Self::Bank(bank) => Ok(Self::BankTransfer(BankTransfer::try_from(bank)?)),
            other => Ok(other),
        }
    }
}

impl TryFrom<Bank> for BankTransfer {
    type Error = error_stack::Report<ValidationError>;
    fn try_from(bank: Bank) -> Result<Self, Self::Error> {
        match bank {
            Bank::Ach(ach) => Ok(Self::Ach(ach)),
            Bank::Bacs(bacs) => Ok(Self::Bacs(bacs)),
            Bank::Sepa(sepa) => Ok(Self::Sepa(sepa)),
            Bank::Pix(pix) => {
                match (pix.bank_account_number, pix.pix_key, pix.emv) {
                    // If bank account number is present then it's PixAccountBankTransfer
                    (Some(bank_account_number), None, None) => Ok(Self::Pix(PixAccountBankTransfer {
                        bank_name: pix.bank_name,
                        bank_branch: pix.bank_branch,
                        bank_account_number,
                        tax_id: pix.tax_id,
                        ispb: pix.ispb,
                        bank_code: pix.bank_code,
                        bank_account_type: pix.bank_account_type,
                        account_holder_name: pix.account_holder_name,
                    })),
                    // If pix key is present then it's PixKeyBankTransfer
                    (None, Some(pix_key), None) => Ok(Self::PixKey(PixKeyBankTransfer {
                        pix_key,
                    })),
                    // If emv is present then it's PixEmvBankTransfer
                    (None, None, Some(emv)) => Ok(Self::PixEmv(PixEmvBankTransfer {
                        emv,
                    })),
                    // If none of the fields are present or more than one field is present then it's an invalid request
                    _ => Err(ValidationError::InvalidValue {
                        message: "Invalid bank transfer data for Pix, expected either bank account number or pix key or emv".to_string(),
                    }.into()),
                }
            }
            Bank::Trustly(trustly) => Ok(Self::Trustly(TrustlyBankTransferData {
                iban: trustly.iban,
                bank_country_code: trustly.country_code,
                bank_account_number: trustly.account_number,
                bank_number: trustly.bank_number,
            })),
            Bank::OpenBanking(open_banking) => Ok(Self::OpenBanking(open_banking)),
        }
    }
}

impl From<BankTransfer> for Bank {
    fn from(bank: BankTransfer) -> Self {
        match bank {
            BankTransfer::Ach(ach) => Self::Ach(ach),
            BankTransfer::Bacs(bacs) => Self::Bacs(bacs),
            BankTransfer::Sepa(sepa) => Self::Sepa(sepa),
            BankTransfer::Pix(pix) => Self::Pix(PixBankTransfer {
                bank_name: pix.bank_name,
                bank_branch: pix.bank_branch,
                bank_account_number: Some(pix.bank_account_number),
                pix_key: None,
                tax_id: pix.tax_id,
                emv: None,
                ispb: pix.ispb,
                bank_code: pix.bank_code,
                bank_account_type: pix.bank_account_type,
                account_holder_name: pix.account_holder_name,
            }),
            BankTransfer::Trustly(trustly) => Self::Trustly(TrustlyBankTransfer {
                iban: trustly.iban,
                country_code: trustly.bank_country_code,
                account_number: trustly.bank_account_number,
                bank_number: trustly.bank_number,
            }),
            BankTransfer::PixEmv(pix_emv) => Self::Pix(PixBankTransfer {
                bank_name: None,
                bank_branch: None,
                bank_account_number: None,
                pix_key: None,
                tax_id: None,
                emv: Some(pix_emv.emv),
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                account_holder_name: None,
            }),
            BankTransfer::PixKey(pix_key) => Self::Pix(PixBankTransfer {
                bank_name: None,
                bank_branch: None,
                bank_account_number: None,
                pix_key: Some(pix_key.pix_key),
                tax_id: None,
                emv: None,
                ispb: None,
                bank_code: None,
                bank_account_type: None,
                account_holder_name: None,
            }),
            BankTransfer::OpenBanking(open_banking) => Self::OpenBanking(open_banking),
        }
    }
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct CardPayout {
    /// The card number
    #[schema(value_type = String, example = "4242424242424242")]
    pub card_number: CardNumber,

    /// The card's expiry month
    #[schema(value_type = String)]
    pub expiry_month: Secret<String>,

    /// The card's expiry year
    #[schema(value_type = String)]
    pub expiry_year: Secret<String>,

    /// The card holder's name
    #[schema(value_type = String, example = "John Doe")]
    pub card_holder_name: Option<Secret<String>>,

    /// The card's network
    #[schema(value_type = Option<CardNetwork>, example = "Visa")]
    pub card_network: Option<CardNetwork>,
}

/// Deprecated - Use `BankTransfer` instead
#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(untagged)]
pub enum Bank {
    Ach(AchBankTransfer),
    Bacs(BacsBankTransfer),
    Trustly(TrustlyBankTransfer),
    Sepa(SepaBankTransfer),
    Pix(PixBankTransfer),
    OpenBanking(OpenBanking),
}

#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(tag = "payout_method_type", rename_all = "snake_case")]
pub enum BankTransfer {
    Ach(AchBankTransfer),
    Bacs(BacsBankTransfer),
    Sepa(SepaBankTransfer),
    Pix(PixAccountBankTransfer),
    PixKey(PixKeyBankTransfer),
    PixEmv(PixEmvBankTransfer),
    Trustly(TrustlyBankTransferData),
    OpenBanking(OpenBanking),
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct AchBankTransfer {
    /// Bank name
    #[schema(value_type = Option<String>, example = "Deutsche Bank")]
    pub bank_name: Option<String>,

    /// Bank country code
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub bank_country_code: Option<api_enums::CountryAlpha2>,

    /// Bank city
    #[schema(value_type = Option<String>, example = "California")]
    pub bank_city: Option<String>,

    /// Bank account number is an unique identifier assigned by a bank to a customer.
    #[schema(value_type = String, example = "000123456")]
    pub bank_account_number: Secret<String>,

    /// [9 digits] Routing number - used in USA for identifying a specific bank.
    #[schema(value_type = String, example = "110000000")]
    pub bank_routing_number: Secret<String>,

    /// Account holder name
    #[schema(value_type = Option<String>, example = "John Doe")]
    pub account_holder_name: Option<Secret<String>>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct BacsBankTransfer {
    /// Bank name
    #[schema(value_type = Option<String>, example = "Deutsche Bank")]
    pub bank_name: Option<String>,

    /// Bank country code
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub bank_country_code: Option<api_enums::CountryAlpha2>,

    /// Bank city
    #[schema(value_type = Option<String>, example = "California")]
    pub bank_city: Option<String>,

    /// Bank account number is an unique identifier assigned by a bank to a customer.
    #[schema(value_type = String, example = "000123456")]
    pub bank_account_number: Secret<String>,

    /// [6 digits] Sort Code - used in UK and Ireland for identifying a bank and it's branches.
    #[schema(value_type = String, example = "98-76-54")]
    pub bank_sort_code: Secret<String>,

    /// Account holder name
    #[schema(value_type = Option<String>, example = "John Doe")]
    pub account_holder_name: Option<Secret<String>>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
// The SEPA (Single Euro Payments Area) is a pan-European network that allows you to send and receive payments in euros between two cross-border bank accounts in the eurozone.
pub struct SepaBankTransfer {
    /// Bank name
    #[schema(value_type = Option<String>, example = "Deutsche Bank")]
    pub bank_name: Option<String>,

    /// Bank country code
    #[schema(value_type = Option<CountryAlpha2>, example = "US")]
    pub bank_country_code: Option<api_enums::CountryAlpha2>,

    /// Bank city
    #[schema(value_type = Option<String>, example = "California")]
    pub bank_city: Option<String>,

    /// International Bank Account Number (iban) - used in many countries for identifying a bank along with it's customer.
    #[schema(value_type = String, example = "DE89370400440532013000")]
    pub iban: Secret<String>,

    /// [8 / 11 digits] Bank Identifier Code (bic) / Swift Code - used in many countries for identifying a bank and it's branches
    #[schema(value_type = String, example = "HSBCGB2LXXX")]
    pub bic: Option<Secret<String>>,

    /// Account holder name
    #[schema(value_type = Option<String>, example = "John Doe")]
    pub account_holder_name: Option<Secret<String>>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct PixBankTransfer {
    /// Bank name
    #[schema(value_type = Option<String>, example = "Deutsche Bank")]
    pub bank_name: Option<String>,

    /// Bank branch
    #[schema(value_type = Option<String>, example = "3707")]
    pub bank_branch: Option<String>,

    /// Bank account number is an unique identifier assigned by a bank to a customer.
    #[schema(value_type = String, example = "000123456")]
    pub bank_account_number: Option<Secret<String>>,

    /// Unique key for pix customer
    #[schema(value_type = String, example = "000123456")]
    pub pix_key: Option<Secret<String>>,

    /// Individual taxpayer identification number
    #[schema(value_type = Option<String>, example = "000123456")]
    pub tax_id: Option<Secret<String>>,

    /// String formatted QR code for pix payout
    #[schema(value_type = String, example = "00020126580014br.gov.bcb.pix0114000123456785204000053039865802BR5925John Doe6009Sao Paulo61080540900062070503***63041D3D")]
    pub emv: Option<Secret<String>>,

    /// ispb code is a unique identifier assigned by Brazilian Central Bank to identify the financial institution of the recipient's bank account in Pix transactions.
    #[schema(value_type = Option<String>, example = "60701190")]
    pub ispb: Option<String>,

    /// 3-digit COMPE/FEBRABAN bank code used to identify the financial institution for routing PIX payouts.
    #[schema(value_type = Option<String>, example = "033")]
    pub bank_code: Option<String>,

    /// Bank account type for PIX payouts.
    #[schema(value_type = Option<BankType>)]
    pub bank_account_type: Option<api_enums::BankType>,

    /// Name of the account holder
    #[schema(value_type = Option<String>, example = "John Doe")]
    pub account_holder_name: Option<Secret<String>>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct PixAccountBankTransfer {
    /// Bank name
    #[schema(value_type = Option<String>, example = "Deutsche Bank")]
    pub bank_name: Option<String>,

    /// Bank branch
    #[schema(value_type = Option<String>, example = "3707")]
    pub bank_branch: Option<String>,

    /// Bank account number is an unique identifier assigned by a bank to a customer.
    #[schema(value_type = String, example = "000123456")]
    pub bank_account_number: Secret<String>,

    /// Individual taxpayer identification number
    #[schema(value_type = Option<String>, example = "000123456")]
    pub tax_id: Option<Secret<String>>,

    /// ispb code is a unique identifier assigned by Brazilian Central Bank to identify the financial institution of the recipient's bank account in Pix transactions.
    #[schema(value_type = Option<String>, example = "60701190")]
    pub ispb: Option<String>,

    /// 3-digit COMPE/FEBRABAN bank code used to identify the financial institution for routing PIX payouts.
    #[schema(value_type = Option<String>, example = "033")]
    pub bank_code: Option<String>,

    /// Bank account type for PIX payouts.
    #[schema(value_type = Option<BankType>)]
    pub bank_account_type: Option<api_enums::BankType>,

    /// Name of the account holder
    #[schema(value_type = Option<String>, example = "John Doe")]
    pub account_holder_name: Option<Secret<String>>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct PixKeyBankTransfer {
    /// Unique key for pix customer
    #[schema(value_type = String, example = "000123456")]
    pub pix_key: Secret<String>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct PixEmvBankTransfer {
    /// String formatted QR code for pix payout
    #[schema(value_type = String, example = "00020126580014br.gov.bcb.pix0114000123456785204000053039865802BR5925John Doe6009Sao Paulo61080540900062070503***63041D3D")]
    pub emv: Secret<String>,
}

#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum Wallet {
    ApplePayDecrypt(ApplePayDecrypt),
    GooglePayDecrypt(GooglePayDecrypt),
    Paypal(Paypal),
    Venmo(Venmo),
}

#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum BankRedirect {
    Interac(Interac),
    OpenBankingUk(OpenBankingUk),
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct Interac {
    /// Customer email linked with interac account
    #[schema(value_type = String, example = "john.doe@example.com")]
    pub email: Email,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct OpenBankingUk {
    /// Account holder name
    #[schema(value_type = String, example = "John Doe")]
    pub account_holder_name: Secret<String>,
    /// International Bank Account Number (iban) - used in many countries for identifying a bank along with it's customer.
    #[schema(value_type = String, example = "DE89370400440532013000")]
    pub iban: Secret<String>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct OpenBanking {
    /// Account holder name
    #[schema(value_type = String, example = "John Doe")]
    pub account_holder_name: Secret<String>,
    /// International Bank Account Number (iban) - used in many countries for identifying a bank along with it's customer.
    #[schema(value_type = String, example = "DE89370400440532013000")]
    pub iban: Secret<String>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct TrustlyBankTransfer {
    /// International Bank Account Number (iban) - used in many countries for identifying a bank along with it's customer.
    #[schema(value_type = String, example = "token_12345")]
    pub iban: Option<Secret<String>>,
    /// country code of the customer's bank account.
    #[schema(value_type = CountryAlpha2, example = "US")]
    pub country_code: api_enums::CountryAlpha2,
    /// The account number, identifying the end-user's account in the bank.
    #[schema(value_type = String, example = "69706212")]
    pub account_number: Option<Secret<String>>,
    /// The bank number identifying the end-user's bank in the given clearing house.
    #[schema(value_type = String, example = "6112")]
    pub bank_number: Option<Secret<String>>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct TrustlyBankTransferData {
    /// International Bank Account Number (iban) - used in many countries for identifying a bank along with it's customer.
    #[schema(value_type = String, example = "token_12345")]
    pub iban: Option<Secret<String>>,
    /// country code of the customer's bank account.
    #[schema(value_type = CountryAlpha2, example = "US")]
    pub bank_country_code: api_enums::CountryAlpha2,
    /// The account number, identifying the end-user's account in the bank.
    #[schema(value_type = String, example = "69706212")]
    pub bank_account_number: Option<Secret<String>>,
    /// The bank number identifying the end-user's bank in the given clearing house.
    #[schema(value_type = String, example = "6112")]
    pub bank_number: Option<Secret<String>>,
}

#[derive(Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub struct Passthrough {
    /// PSP token generated for the payout method
    #[schema(value_type = String, example = "token_12345")]
    pub psp_token: Secret<String>,

    /// PSP customer ID
    #[schema(value_type = String, example = "customer_12345")]
    pub psp_customer_id: Option<Secret<String>>,

    /// Payout method type of the token
    #[schema(value_type = PaymentMethodType, example = "paypal")]
    pub token_type: api_enums::PaymentMethodType,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct Paypal {
    /// Email linked with paypal account
    #[schema(value_type = String, example = "john.doe@example.com")]
    pub email: Option<Email>,

    /// mobile number linked to paypal account
    #[schema(value_type = String, example = "16608213349")]
    pub telephone_number: Option<Secret<String>>,

    /// id of the paypal account
    #[schema(value_type = String, example = "G83KXTJ5EHCQ2")]
    pub paypal_id: Option<Secret<String>>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct Venmo {
    /// mobile number linked to venmo account
    #[schema(value_type = String, example = "16608213349")]
    pub telephone_number: Option<Secret<String>>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct ApplePayDecrypt {
    /// The dpan number associated with card number
    #[schema(value_type = String, example = "4242424242424242")]
    pub dpan: CardNumber,

    /// The card's expiry month
    #[schema(value_type = String)]
    pub expiry_month: Secret<String>,

    /// The card's expiry year
    #[schema(value_type = String)]
    pub expiry_year: Secret<String>,

    /// The card holder's name
    #[schema(value_type = String, example = "John Doe")]
    pub card_holder_name: Option<Secret<String>>,

    /// The card's network
    #[schema(value_type = Option<CardNetwork>, example = "Visa")]
    pub card_network: Option<CardNetwork>,
}

#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)]
pub struct GooglePayDecrypt {
    /// The dpan number associated with card number
    #[schema(value_type = String, example = "4242424242424242")]
    pub application_primary_account_number: CardNumber,

    /// The card's expiry month
    #[schema(value_type = String)]
    pub expiry_month: Secret<String>,

    /// The card's expiry year
    #[schema(value_type = String)]
    pub expiry_year: Secret<String>,

    /// The card holder's name
    #[schema(value_type = String, example = "John Doe")]
    pub card_holder_name: Option<Secret<String>>,

    /// The card's network
    #[schema(value_type = Option<CardNetwork>, example = "Visa")]
    pub card_network: Option<CardNetwork>,
}

#[derive(Debug, ToSchema, Clone, Serialize, router_derive::PolymorphicSchema)]
#[serde(deny_unknown_fields)]
pub struct PayoutCreateResponse {
    /// Unique identifier for the payout. This ensures idempotency for multiple payouts
    /// that have been done by a single merchant. This field is auto generated and is returned in the API response.
    #[schema(
        value_type = String,
        min_length = 30,
        max_length = 30,
        example = "187282ab-40ef-47a9-9206-5099ba31e432"
    )]
    pub payout_id: id_type::PayoutId,

    /// This is an identifier for the merchant account. This is inferred from the API key
    /// provided during the request
    #[schema(max_length = 255, value_type = String, example = "merchant_1668273825")]
    pub merchant_id: id_type::MerchantId,

    /// Your unique identifier for this payout or order. This ID helps you reconcile payouts on your system. If provided, it is passed to the connector if supported.
    #[schema(value_type = Option<String>, max_length = 255, example = "merchant_order_ref_123")]
    pub merchant_order_reference_id: Option<String>,

    /// The payout amount. Amount for the payout in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,
    #[schema(value_type = i64, example = 1000)]
    pub amount: common_utils::types::MinorUnit,

    /// Recipient's currency for the payout request
    #[schema(value_type = Currency, example = "USD")]
    pub currency: api_enums::Currency,

    /// The connector used for the payout
    #[schema(example = "wise")]
    pub connector: Option<String>,

    /// The payout method that is to be used
    #[schema(value_type = Option<PayoutType>, example = "bank")]
    pub payout_type: Option<api_enums::PayoutType>,

    /// The payout method details for the payout
    #[schema(value_type = Option<PayoutMethodDataResponse>, example = json!(r#"{
        "card": {
            "last4": "2503",
            "card_type": null,
            "card_network": null,
            "card_issuer": null,
            "card_issuing_country": null,
            "card_isin": "400000",
            "card_extended_bin": null,
            "card_exp_month": "08",
            "card_exp_year": "25",
            "card_holder_name": null,
            "payment_checks": null,
            "authentication_data": null
        }
    }"#))]
    pub payout_method_data: Option<PayoutMethodDataResponse>,

    /// Masked source bank data for the payout
    #[schema(value_type = Option<PayoutMethodDataResponse>, example = json!(r#"{
        {
            "iban": "NL46T********69112",
            "bank_name": "Test Bank",
            "bank_country_code": "NL",
            "bank_city": "Amsterdam",
            "bic": "ABN**L2A"
        }
    }"#))]
    pub source_bank_data: Option<payout_method_utils::BankAdditionalData>,

    /// The billing address for the payout
    #[schema(value_type = Option<Address>, example = json!(r#"{
        "address": {
            "line1": "1467",
            "line2": "Harrison Street",
            "line3": "Harrison Street",
            "city": "San Francisco",
            "state": "CA",
            "zip": "94122",
            "country": "US",
            "first_name": "John",
            "last_name": "Doe"
        },
        "phone": { "number": "9123456789", "country_code": "+1" }
    }"#))]
    pub billing: Option<payments::Address>,

    /// Set to true to confirm the payout without review, no further action required
    #[schema(value_type = bool, example = true, default = false)]
    pub auto_fulfill: bool,

    /// The identifier for the customer object. If not provided the customer ID will be autogenerated. _Deprecated: Use the customer object instead._
    #[schema(deprecated, value_type = String, max_length = 255, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    pub customer_id: Option<id_type::CustomerId>,

    /// Passing this object creates a new customer or attaches an existing customer to the payout
    #[schema(value_type = Option<CustomerDetailsResponse>)]
    pub customer: Option<payments::CustomerDetailsResponse>,

    /// It's a token used for client side verification.
    #[schema(value_type = String, example = "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo")]
    pub client_secret: Option<String>,

    /// The URL to redirect after the completion of the operation
    #[schema(value_type = String, example = "https://hyperswitch.io")]
    pub return_url: Option<String>,

    /// Business country of the merchant for this payout. _Deprecated: Use profile_id instead._
    #[schema(deprecated, example = "US", value_type = CountryAlpha2)]
    pub business_country: Option<api_enums::CountryAlpha2>,

    /// Business label of the merchant for this payout. _Deprecated: Use profile_id instead._
    #[schema(deprecated, example = "food", value_type = Option<String>)]
    pub business_label: Option<String>,

    /// A description of the payout
    #[schema(example = "It's my first payout request", value_type = Option<String>)]
    pub description: Option<String>,

    /// Billing descriptor information for the payout
    #[schema(value_type = Option<PayoutsBillingDescriptor>)]
    pub billing_descriptor: Option<PayoutsBillingDescriptor>,

    /// Type of entity to whom the payout is being carried out to
    #[schema(value_type = PayoutEntityType, example = "Individual")]
    pub entity_type: api_enums::PayoutEntityType,

    /// Specifies whether or not the payout request is recurring
    #[schema(value_type = bool, default = false)]
    pub recurring: bool,

    /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.
    #[schema(value_type = Option<Object>, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)]
    pub metadata: Option<pii::SecretSerdeValue>,

    /// Unique identifier of the merchant connector account
    #[schema(value_type = Option<String>, example = "mca_sAD3OZLATetvjLOYhUSy")]
    pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,

    /// Current status of the Payout
    #[schema(value_type = PayoutStatus, example = RequiresConfirmation)]
    pub status: api_enums::PayoutStatus,

    /// If there was an error while calling the connector the error message is received here
    #[schema(value_type = Option<String>, example = "Failed while verifying the card")]
    pub error_message: Option<String>,

    /// If there was an error while calling the connectors the code is received here
    #[schema(value_type = Option<String>, example = "E0001")]
    pub error_code: Option<String>,

    /// The business profile that is associated with this payout
    #[schema(value_type = String)]
    pub profile_id: id_type::ProfileId,

    /// Time when the payout was created
    #[schema(example = "2022-09-10T10:11:12Z")]
    #[serde(with = "common_utils::custom_serde::iso8601::option")]
    pub created: Option<PrimitiveDateTime>,

    /// Underlying processor's payout resource ID
    #[schema(value_type = Option<String>, example = "S3FC9G9M2MVFDXT5")]
    pub connector_transaction_id: Option<String>,

    /// Payout's send priority (if applicable)
    #[schema(value_type = Option<PayoutSendPriority>, example = "instant")]
    pub priority: Option<api_enums::PayoutSendPriority>,

    /// List of attempts
    #[schema(value_type = Option<Vec<PayoutAttemptResponse>>)]
    #[serde(skip_serializing_if = "Option::is_none")]
    pub attempts: Option<Vec<PayoutAttemptResponse>>,

    /// If payout link was requested, this contains the link's ID and the URL to render the payout widget
    #[schema(value_type = Option<PayoutLinkResponse>)]
    pub payout_link: Option<PayoutLinkResponse>,

    /// Customer's email. _Deprecated: Use customer object instead._
    #[schema(deprecated, max_length = 255, value_type = Option<String>, example = "johntest@test.com")]
    pub email: crypto::OptionalEncryptableEmail,

    /// Customer's name. _Deprecated: Use customer object instead._
    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "John Test")]
    pub name: crypto::OptionalEncryptableName,

    /// Customer's phone. _Deprecated: Use customer object instead._
    #[schema(deprecated, value_type = Option<String>, max_length = 255, example = "9123456789")]
    pub phone: crypto::OptionalEncryptablePhone,

    /// Customer's phone country code. _Deprecated: Use customer object instead._
    #[schema(deprecated, max_length = 255, example = "+1")]
    pub phone_country_code: Option<String>,

    /// (This field is not live yet)
    /// Error code unified across the connectors is received here in case of errors while calling the underlying connector
    #[remove_in(PayoutCreateResponse)]
    #[schema(value_type = Option<String>, max_length = 255, example = "UE_000")]
    pub unified_code: Option<UnifiedCode>,

    /// (This field is not live yet)
    /// Error message unified across the connectors is received here in case of errors while calling the underlying connector
    #[remove_in(PayoutCreateResponse)]
    #[schema(value_type = Option<String>, max_length = 1024, example = "Invalid card details")]
    pub unified_message: Option<UnifiedMessage>,

    /// Identifier for payout method
    pub payout_method_id: Option<String>,
}

/// The payout method information for response
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema)]
#[serde(rename_all = "snake_case")]
pub enum PayoutMethodDataResponse {
    #[schema(value_type = CardAdditionalData)]
    Card(Box<payout_method_utils::CardAdditionalData>),
    #[schema(value_type = BankAdditionalData)]
    Bank(Box<payout_method_utils::BankAdditionalData>),
    #[schema(value_type = WalletAdditionalData)]
    Wallet(Box<payout_method_utils::WalletAdditionalData>),
    #[schema(value_type = BankRedirectAdditionalData)]
    BankRedirect(Box<payout_method_utils::BankRedirectAdditionalData>),
    #[schema(value_type = PassthroughAdditionalData)]
    Passthrough(Box<payout_method_utils::PassthroughAdditionalData>),
}

#[derive(
    Default, Debug, serde::Serialize, Clone, PartialEq, ToSchema, router_derive::PolymorphicSchema,
)]
pub struct PayoutAttemptResponse {
    /// Unique identifier for the attempt
    pub attempt_id: String,
    /// The status of the attempt
    #[schema(value_type = PayoutStatus, example = "failed")]
    pub status: api_enums::PayoutStatus,
    /// The payout attempt amount. Amount for the payout in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,
    #[schema(value_type = i64, example = 6583)]
    pub amount: common_utils::types::MinorUnit,
    /// The currency of the amount of the payout attempt
    #[schema(value_type = Option<Currency>, example = "USD")]
    pub currency: Option<api_enums::Currency>,
    /// The connector used for the payout
    pub connector: Option<String>,
    /// Connector's error code in case of failures
    pub error_code: Option<String>,
    /// Connector's error message in case of failures
    pub error_message: Option<String>,
    /// The payout method that was used
    #[schema(value_type = Option<PayoutType>, example = "bank")]
    pub payment_method: Option<api_enums::PayoutType>,
    /// Payment Method Type
    #[schema(value_type = Option<PaymentMethodType>, example = "bacs")]
    pub payout_method_type: Option<api_enums::PaymentMethodType>,
    /// A unique identifier for a payout provided by the connector
    pub connector_transaction_id: Option<String>,
    /// If the payout was cancelled the reason provided here
    pub cancellation_reason: Option<String>,
    /// (This field is not live yet)
    /// Error code unified across the connectors is received here in case of errors while calling the underlying connector
    #[remove_in(PayoutAttemptResponse)]
    #[schema(value_type = Option<String>, max_length = 255, example = "UE_000")]
    pub unified_code: Option<UnifiedCode>,
    /// (This field is not live yet)
    /// Error message unified across the connectors is received here in case of errors while calling the underlying connector
    #[remove_in(PayoutAttemptResponse)]
    #[schema(value_type = Option<String>, max_length = 1024, example = "Invalid card details")]
    pub unified_message: Option<UnifiedMessage>,
}

#[derive(Default, Debug, Clone, Deserialize, ToSchema)]
pub struct PayoutRetrieveBody {
    pub force_sync: Option<bool>,
    #[schema(value_type = Option<String>)]
    pub merchant_id: Option<id_type::MerchantId>,
    pub expand_attempts: Option<bool>,
}

#[derive(Debug, Serialize, ToSchema, Clone, Deserialize)]
pub struct PayoutRetrieveRequest {
    /// Unique identifier for the payout. This ensures idempotency for multiple payouts
    /// that have been done by a single merchant. This field is auto generated and is returned in the API response.
    #[schema(
        value_type = String,
        min_length = 30,
        max_length = 30,
        example = "187282ab-40ef-47a9-9206-5099ba31e432"
    )]
    pub payout_id: id_type::PayoutId,

    /// `force_sync` with the connector to get payout details
    /// (defaults to false)
    #[schema(value_type = Option<bool>, default = false, example = true)]
    pub force_sync: Option<bool>,

    /// The identifier for the Merchant Account.
    #[schema(value_type = Option<String>)]
    pub merchant_id: Option<id_type::MerchantId>,

    /// Set to true to get the list of attempts along with payout details, no further action required
    #[schema(value_type = Option<bool>, default = false, example = true)]
    pub expand_attempts: Option<bool>,
}

#[derive(Debug, Serialize, Clone, ToSchema, router_derive::PolymorphicSchema)]
#[generate_schemas(PayoutCancelRequest, PayoutFulfillRequest)]
pub struct PayoutActionRequest {
    /// Unique identifier for the payout. This ensures idempotency for multiple payouts
    /// that have been done by a single merchant. This field is auto generated and is returned in the API response.
    #[schema(
        value_type = String,
        min_length = 30,
        max_length = 30,
        example = "187282ab-40ef-47a9-9206-5099ba31e432"
    )]
    pub payout_id: id_type::PayoutId,
}

#[derive(Default, Debug, ToSchema, Clone, Serialize, Deserialize)]
pub struct PayoutVendorAccountDetails {
    pub vendor_details: PayoutVendorDetails,
    pub individual_details: PayoutIndividualDetails,
}

#[derive(Default, Debug, Serialize, ToSchema, Clone, Deserialize)]
pub struct PayoutVendorDetails {
    pub account_type: String,
    pub business_type: String,
    pub business_profile_mcc: Option<i32>,
    pub business_profile_url: Option<String>,
    pub business_profile_name: Option<Secret<String>>,
    pub company_address_line1: Option<Secret<String>>,
    pub company_address_line2: Option<Secret<String>>,
    pub company_address_postal_code: Option<Secret<String>>,
    pub company_address_city: Option<Secret<String>>,
    pub company_address_state: Option<Secret<String>>,
    pub company_phone: Option<Secret<String>>,
    pub company_tax_id: Option<Secret<String>>,
    pub company_owners_provided: Option<bool>,
    pub capabilities_card_payments: Option<bool>,
    pub capabilities_transfers: Option<bool>,
}

#[derive(Default, Debug, Serialize, ToSchema, Clone, Deserialize)]
pub struct PayoutIndividualDetails {
    pub tos_acceptance_date: Option<i64>,
    pub tos_acceptance_ip: Option<Secret<String>>,
    pub individual_dob_day: Option<Secret<String>>,
    pub individual_dob_month: Option<Secret<String>>,
    pub individual_dob_year: Option<Secret<String>>,
    pub individual_id_number: Option<Secret<String>>,
    pub individual_ssn_last_4: Option<Secret<String>>,
    pub external_account_account_holder_type: Option<String>,
}

#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
#[serde(deny_unknown_fields)]
pub struct PayoutListConstraints {
    /// The identifier for customer
    #[schema(value_type = Option<String>, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    pub customer_id: Option<id_type::CustomerId>,

    /// A cursor for use in pagination, fetch the next list after some object
    #[schema(example = "payout_fafa124123", value_type = Option<String>,)]
    pub starting_after: Option<id_type::PayoutId>,

    /// A cursor for use in pagination, fetch the previous list before some object
    #[schema(example = "payout_fafa124123", value_type = Option<String>,)]
    pub ending_before: Option<id_type::PayoutId>,

    /// limit on the number of objects to return
    #[schema(default = 10, maximum = 100)]
    #[serde(default = "default_payouts_list_limit")]
    pub limit: u32,

    /// The time at which payout is created
    #[schema(example = "2022-09-10T10:11:12Z")]
    #[serde(default, with = "common_utils::custom_serde::iso8601::option")]
    pub created: Option<PrimitiveDateTime>,

    /// The time range for which objects are needed. TimeRange has two fields start_time and end_time from which objects can be filtered as per required scenarios (created_at, time less than, greater than etc).
    #[serde(flatten)]
    #[schema(value_type = Option<TimeRange>)]
    pub time_range: Option<common_utils::types::TimeRange>,
}

#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
#[serde(deny_unknown_fields)]
pub struct PayoutListFilterConstraints {
    /// The identifier for payout
    #[schema(
    value_type = Option<String>,
    min_length = 30,
    max_length = 30,
    example = "187282ab-40ef-47a9-9206-5099ba31e432"
)]
    pub payout_id: Option<id_type::PayoutId>,
    /// The merchant order reference ID for payout
    #[schema(value_type = Option<String>, max_length = 255, example = "merchant_order_ref_123")]
    pub merchant_order_reference_id: Option<String>,
    /// The identifier for business profile
    #[schema(value_type = Option<String>)]
    pub profile_id: Option<id_type::ProfileId>,
    /// The identifier for customer
    #[schema(value_type = Option<String>,example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")]
    pub customer_id: Option<id_type::CustomerId>,
    /// The limit on the number of objects. The default limit is 10 and max limit is 20
    #[serde(default = "default_payouts_list_limit")]
    pub limit: u32,
    /// The starting point within a list of objects
    pub offset: Option<u32>,
    /// The time range for which objects are needed. TimeRange has two fields start_time and end_time from which objects can be filtered as per required scenarios (created_at, time less than, greater than etc).
    #[serde(flatten)]
    #[schema(value_type = Option<TimeRange>)]
    pub time_range: Option<common_utils::types::TimeRange>,
    /// The list of connectors to filter payouts list
    #[schema(value_type = Option<Vec<PayoutConnectors>>, max_length = 255, example = json!(["wise", "adyen"]))]
    pub connector: Option<Vec<api_enums::PayoutConnectors>>,
    /// The list of currencies to filter payouts list
    #[schema(value_type = Currency, example = "USD")]
    pub currency: Option<Vec<api_enums::Currency>>,
    /// The list of payout status to filter payouts list
    #[schema(value_type = Option<Vec<PayoutStatus>>, example = json!(["pending", "failed"]))]
    pub status: Option<Vec<api_enums::PayoutStatus>>,
    /// The list of payout methods to filter payouts list
    #[schema(value_type = Option<Vec<PayoutType>>, example = json!(["bank", "card"]))]
    pub payout_method: Option<Vec<common_enums::PayoutType>>,
    /// Type of recipient
    #[schema(value_type = PayoutEntityType, example = "Individual")]
    pub entity_type: Option<common_enums::PayoutEntityType>,
}

#[derive(Clone, Debug, serde::Serialize, ToSchema)]
pub struct PayoutListResponse {
    /// The number of payouts included in the list
    pub size: usize,
    /// The list of payouts response objects
    pub data: Vec<PayoutCreateResponse>,
    /// The total number of available payouts for given constraints
    #[serde(skip_serializing_if = "Option::is_none")]
    pub total_count: Option<i64>,
}

#[derive(Clone, Debug, serde::Serialize, ToSchema)]
pub struct PayoutListFilters {
    /// The list of available connector filters
    #[schema(value_type = Vec<PayoutConnectors>)]
    pub connector: Vec<api_enums::PayoutConnectors>,
    /// The list of available currency filters
    #[schema(value_type = Vec<Currency>)]
    pub currency: Vec<common_enums::Currency>,
    /// The list of available payout status filters
    #[schema(value_type = Vec<PayoutStatus>)]
    pub status: Vec<common_enums::PayoutStatus>,
    /// The list of available payout method filters
    #[schema(value_type = Vec<PayoutType>)]
    pub payout_method: Vec<common_enums::PayoutType>,
}

#[derive(Clone, Debug, serde::Serialize, ToSchema)]
pub struct PayoutListFiltersV2 {
    /// The list of available connector filters
    #[schema(value_type = Vec<PayoutConnectors>)]
    pub connector: HashMap<String, Vec<admin::MerchantConnectorInfo>>,
    /// The list of available currency filters
    #[schema(value_type = Vec<Currency>)]
    pub currency: Vec<common_enums::Currency>,
    /// The list of available payout status filters
    #[schema(value_type = Vec<PayoutStatus>)]
    pub status: Vec<common_enums::PayoutStatus>,
    /// The list of available payout method filters
    #[schema(value_type = Vec<PayoutType>)]
    pub payout_method: Vec<common_enums::PayoutType>,
}

#[derive(Clone, Debug, serde::Serialize, ToSchema)]
pub struct PayoutLinkResponse {
    pub payout_link_id: String,
    #[schema(value_type = String)]
    pub link: Secret<url::Url>,
}

#[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)]
pub struct PayoutLinkInitiateRequest {
    #[schema(value_type = String)]
    pub merchant_id: id_type::MerchantId,
    #[schema(value_type = String)]
    pub payout_id: id_type::PayoutId,
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct PayoutLinkDetails {
    pub publishable_key: Secret<String>,
    pub client_secret: Secret<String>,
    pub payout_link_id: String,
    pub payout_id: id_type::PayoutId,
    pub customer_id: id_type::CustomerId,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub session_expiry: PrimitiveDateTime,
    pub return_url: Option<url::Url>,
    #[serde(flatten)]
    pub ui_config: link_utils::GenericLinkUiConfigFormData,
    pub enabled_payment_methods: Vec<link_utils::EnabledPaymentMethod>,
    pub enabled_payment_methods_with_required_fields: Vec<PayoutEnabledPaymentMethodsInfo>,
    pub amount: common_utils::types::StringMajorUnit,
    pub currency: common_enums::Currency,
    pub locale: String,
    pub form_layout: Option<common_enums::UIWidgetFormLayout>,
    pub test_mode: bool,
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct PayoutEnabledPaymentMethodsInfo {
    pub payment_method: common_enums::PaymentMethod,
    pub payment_method_types_info: Vec<PaymentMethodTypeInfo>,
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct PaymentMethodTypeInfo {
    pub payment_method_type: api_enums::PaymentMethodType,
    pub required_fields: Option<HashMap<String, RequiredFieldInfo>>,
}

#[derive(Clone, Debug, serde::Serialize, FlatStruct)]
pub struct RequiredFieldsOverrideRequest {
    pub billing: Option<payments::Address>,
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct PayoutLinkStatusDetails {
    pub payout_link_id: String,
    pub payout_id: id_type::PayoutId,
    pub customer_id: id_type::CustomerId,
    #[serde(with = "common_utils::custom_serde::iso8601")]
    pub session_expiry: PrimitiveDateTime,
    pub return_url: Option<url::Url>,
    pub status: api_enums::PayoutStatus,
    pub error_code: Option<UnifiedCode>,
    pub error_message: Option<UnifiedMessage>,
    #[serde(flatten)]
    pub ui_config: link_utils::GenericLinkUiConfigFormData,
    pub test_mode: bool,
}

impl From<Bank> for payout_method_utils::BankAdditionalData {
    fn from(bank_data: Bank) -> Self {
        match bank_data {
            Bank::Ach(AchBankTransfer {
                bank_name,
                bank_country_code,
                bank_city,
                bank_account_number,
                bank_routing_number,
                account_holder_name,
            }) => Self::Ach(Box::new(
                payout_method_utils::AchBankTransferAdditionalData {
                    bank_name,
                    bank_country_code,
                    bank_city,
                    bank_account_number: bank_account_number.into(),
                    bank_routing_number: bank_routing_number.into(),
                    account_holder_name,
                },
            )),
            Bank::Bacs(BacsBankTransfer {
                bank_name,
                bank_country_code,
                bank_city,
                bank_account_number,
                bank_sort_code,
                account_holder_name,
            }) => Self::Bacs(Box::new(
                payout_method_utils::BacsBankTransferAdditionalData {
                    bank_name,
                    bank_country_code,
                    bank_city,
                    bank_account_number: bank_account_number.into(),
                    bank_sort_code: bank_sort_code.into(),
                    account_holder_name,
                },
            )),
            Bank::Sepa(SepaBankTransfer {
                bank_name,
                bank_country_code,
                bank_city,
                iban,
                bic,
                account_holder_name,
            }) => Self::Sepa(Box::new(
                payout_method_utils::SepaBankTransferAdditionalData {
                    bank_name,
                    bank_country_code,
                    bank_city,
                    iban: iban.into(),
                    bic: bic.map(From::from),
                    account_holder_name,
                },
            )),
            Bank::Pix(PixBankTransfer {
                bank_name,
                bank_branch,
                bank_account_number,
                pix_key,
                tax_id,
                emv,
                ispb,
                bank_code,
                bank_account_type,
                account_holder_name,
            }) => Self::Pix(Box::new(
                payout_method_utils::PixBankTransferAdditionalData {
                    bank_name,
                    bank_branch,
                    bank_account_number: bank_account_number.map(From::from),
                    pix_key: pix_key.map(From::from),
                    tax_id: tax_id.map(From::from),
                    emv: emv.map(From::from),
                    ispb,
                    account_holder_name,
                    bank_code,
                    bank_account_type,
                },
            )),
            Bank::Trustly(TrustlyBankTransfer {
                iban,
                country_code,
                account_number,
                bank_number,
            }) => Self::Trustly(Box::new(
                payout_method_utils::TrustlyBankTransferAdditionalData {
                    iban,
                    country_code,
                    account_number,
                    bank_number,
                },
            )),
            Bank::OpenBanking(OpenBanking {
                account_holder_name,
                iban,
            }) => Self::OpenBanking(Box::new(payout_method_utils::OpenBankingAdditionalData {
                account_holder_name,
                iban,
            })),
        }
    }
}

impl From<BankTransfer> for payout_method_utils::BankAdditionalData {
    fn from(bank_data: BankTransfer) -> Self {
        match bank_data {
            BankTransfer::Ach(AchBankTransfer {
                bank_name,
                bank_country_code,
                bank_city,
                bank_account_number,
                bank_routing_number,
                account_holder_name,
            }) => Self::Ach(Box::new(
                payout_method_utils::AchBankTransferAdditionalData {
                    bank_name,
                    bank_country_code,
                    bank_city,
                    bank_account_number: bank_account_number.into(),
                    bank_routing_number: bank_routing_number.into(),
                    account_holder_name,
                },
            )),
            BankTransfer::Bacs(BacsBankTransfer {
                bank_name,
                bank_country_code,
                bank_city,
                bank_account_number,
                bank_sort_code,
                account_holder_name,
            }) => Self::Bacs(Box::new(
                payout_method_utils::BacsBankTransferAdditionalData {
                    bank_name,
                    bank_country_code,
                    bank_city,
                    bank_account_number: bank_account_number.into(),
                    bank_sort_code: bank_sort_code.into(),
                    account_holder_name,
                },
            )),
            BankTransfer::Sepa(SepaBankTransfer {
                bank_name,
                bank_country_code,
                bank_city,
                iban,
                bic,
                account_holder_name,
            }) => Self::Sepa(Box::new(
                payout_method_utils::SepaBankTransferAdditionalData {
                    bank_name,
                    bank_country_code,
                    bank_city,
                    iban: iban.into(),
                    bic: bic.map(From::from),
                    account_holder_name,
                },
            )),
            BankTransfer::Pix(PixAccountBankTransfer {
                bank_name,
                bank_branch,
                bank_account_number,
                tax_id,
                ispb,
                bank_code,
                bank_account_type,
                account_holder_name,
            }) => Self::Pix(Box::new(
                payout_method_utils::PixBankTransferAdditionalData {
                    bank_name,
                    bank_branch,
                    bank_account_number: Some(bank_account_number.into()),
                    pix_key: None,
                    emv: None,
                    tax_id: tax_id.map(From::from),
                    ispb,
                    account_holder_name,
                    bank_code,
                    bank_account_type,
                },
            )),
            BankTransfer::PixKey(PixKeyBankTransfer { pix_key }) => Self::Pix(Box::new(
                payout_method_utils::PixBankTransferAdditionalData {
                    bank_name: None,
                    bank_branch: None,
                    bank_account_number: None,
                    pix_key: Some(pix_key.into()),
                    emv: None,
                    tax_id: None,
                    ispb: None,
                    account_holder_name: None,
                    bank_code: None,
                    bank_account_type: None,
                },
            )),
            BankTransfer::PixEmv(PixEmvBankTransfer { emv }) => Self::Pix(Box::new(
                payout_method_utils::PixBankTransferAdditionalData {
                    bank_name: None,
                    bank_branch: None,
                    bank_account_number: None,
                    pix_key: None,
                    emv: Some(emv.into()),
                    tax_id: None,
                    ispb: None,
                    account_holder_name: None,
                    bank_code: None,
                    bank_account_type: None,
                },
            )),

            BankTransfer::Trustly(TrustlyBankTransferData {
                iban,
                bank_country_code,
                bank_account_number,
                bank_number,
            }) => Self::Trustly(Box::new(
                payout_method_utils::TrustlyBankTransferAdditionalData {
                    iban,
                    country_code: bank_country_code,
                    account_number: bank_account_number,
                    bank_number,
                },
            )),
            BankTransfer::OpenBanking(OpenBanking {
                account_holder_name,
                iban,
            }) => Self::OpenBanking(Box::new(payout_method_utils::OpenBankingAdditionalData {
                account_holder_name,
                iban,
            })),
        }
    }
}

impl From<Wallet> for payout_method_utils::WalletAdditionalData {
    fn from(wallet_data: Wallet) -> Self {
        match wallet_data {
            Wallet::Paypal(Paypal {
                email,
                telephone_number,
                paypal_id,
            }) => {
                let paypal_additional_data = if let Some(e) = email {
                    payout_method_utils::PaypalAdditionalData::Email {
                        email: ForeignFrom::foreign_from(e),
                    }
                } else if let Some(id) = paypal_id {
                    payout_method_utils::PaypalAdditionalData::PaypalId {
                        paypal_id: From::from(id),
                    }
                } else {
                    payout_method_utils::PaypalAdditionalData::TelephoneNumber {
                        telephone_number: telephone_number.map(From::from),
                    }
                };
                Self::Paypal(Box::new(paypal_additional_data))
            }
            Wallet::Venmo(Venmo { telephone_number }) => {
                Self::Venmo(Box::new(payout_method_utils::VenmoAdditionalData {
                    telephone_number: telephone_number.map(From::from),
                }))
            }
            Wallet::GooglePayDecrypt(GooglePayDecrypt {
                expiry_month,
                expiry_year,
                card_holder_name,
                ..
            }) => Self::GooglePayDecrypt(Box::new(
                payout_method_utils::GooglePayDecryptAdditionalData {
                    card_exp_month: expiry_month,
                    card_exp_year: expiry_year,
                    card_holder_name,
                },
            )),
            Wallet::ApplePayDecrypt(ApplePayDecrypt {
                expiry_month,
                expiry_year,
                card_holder_name,
                ..
            }) => Self::ApplePayDecrypt(Box::new(
                payout_method_utils::ApplePayDecryptAdditionalData {
                    card_exp_month: expiry_month,
                    card_exp_year: expiry_year,
                    card_holder_name,
                },
            )),
        }
    }
}

impl From<BankRedirect> for payout_method_utils::BankRedirectAdditionalData {
    fn from(bank_redirect: BankRedirect) -> Self {
        match bank_redirect {
            BankRedirect::Interac(Interac { email }) => {
                Self::Interac(Box::new(payout_method_utils::InteracAdditionalData {
                    email: Some(ForeignFrom::foreign_from(email)),
                }))
            }
            BankRedirect::OpenBankingUk(OpenBankingUk {
                account_holder_name,
                iban,
            }) => Self::OpenBankingUk(Box::new(payout_method_utils::OpenBankingUkAdditionalData {
                account_holder_name,
                iban,
            })),
        }
    }
}

impl From<Passthrough> for payout_method_utils::PassthroughAdditionalData {
    fn from(passthrough_data: Passthrough) -> Self {
        Self {
            psp_token: passthrough_data.psp_token.into(),
            token_type: passthrough_data.token_type,
        }
    }
}

impl From<payout_method_utils::AdditionalPayoutMethodData> for PayoutMethodDataResponse {
    fn from(additional_data: payout_method_utils::AdditionalPayoutMethodData) -> Self {
        match additional_data {
            payout_method_utils::AdditionalPayoutMethodData::Card(card_data) => {
                Self::Card(card_data)
            }
            payout_method_utils::AdditionalPayoutMethodData::Bank(bank_data) => {
                Self::Bank(bank_data)
            }
            payout_method_utils::AdditionalPayoutMethodData::Wallet(wallet_data) => {
                Self::Wallet(wallet_data)
            }
            payout_method_utils::AdditionalPayoutMethodData::BankRedirect(bank_redirect) => {
                Self::BankRedirect(bank_redirect)
            }
            payout_method_utils::AdditionalPayoutMethodData::Passthrough(passthrough) => {
                Self::Passthrough(passthrough)
            }
        }
    }
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct PayoutsAggregateResponse {
    /// The list of intent status with their count
    pub status_with_count: HashMap<common_enums::PayoutStatus, i64>,
}

#[derive(Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema)]
pub struct PayoutsManualUpdateRequest {
    /// The identifier for the payout
    #[schema(value_type = String)]
    pub payout_id: id_type::PayoutId,
    /// The identifier for the payout attempt
    pub payout_attempt_id: String,
    /// Merchant ID
    #[schema(value_type = String)]
    pub merchant_id: id_type::MerchantId,
    /// The status of the payout attempt
    #[schema(value_type = Option<PayoutStatus>)]
    pub status: Option<api_enums::PayoutStatus>,
    /// Error code of the connector
    pub error_code: Option<String>,
    /// Error message of the connector
    pub error_message: Option<String>,
    /// A unique identifier for a payout provided by the connector
    pub connector_payout_id: Option<String>,
}

impl PayoutsManualUpdateRequest {
    pub fn is_update_parameter_present(&self) -> bool {
        self.status.is_some()
            || self.error_code.is_some()
            || self.error_message.is_some()
            || self.connector_payout_id.is_some()
    }
}

#[derive(Debug, serde::Serialize, Clone, ToSchema)]
pub struct PayoutsManualUpdateResponse {
    /// The identifier for the payout
    #[schema(value_type = String)]
    pub payout_id: id_type::PayoutId,
    /// The identifier for the payout attempt
    pub payout_attempt_id: String,
    /// Merchant ID
    #[schema(value_type = String)]
    pub merchant_id: id_type::MerchantId,
    /// The status of the payout attempt
    #[schema(value_type = PayoutStatus)]
    pub attempt_status: api_enums::PayoutStatus,
    /// Error code of the connector
    pub error_code: Option<String>,
    /// Error message of the connector
    pub error_message: Option<String>,
    /// A unique identifier for a payout provided by the connector
    pub connector_payout_id: Option<String>,
}

impl From<&PayoutMethodData> for api_enums::PaymentMethodType {
    fn from(data: &PayoutMethodData) -> Self {
        match data {
            // debit represent card payout methods, todo: consider renaming it to card in future
            PayoutMethodData::Card(_) => Self::Debit,
            PayoutMethodData::Bank(bank) => match bank {
                Bank::Ach(_) => Self::Ach,
                Bank::Bacs(_) => Self::Bacs,
                Bank::Sepa(_) => Self::SepaBankTransfer,
                Bank::Pix(_) => Self::Pix,
                Bank::Trustly(_) => Self::Trustly,
                Bank::OpenBanking(_) => Self::OpenBanking,
            },
            PayoutMethodData::BankTransfer(bank_transfer) => match bank_transfer {
                BankTransfer::Ach(_) => Self::Ach,
                BankTransfer::Bacs(_) => Self::Bacs,
                BankTransfer::Sepa(_) => Self::SepaBankTransfer,
                BankTransfer::Pix(_) => Self::Pix,
                BankTransfer::PixKey(_) => Self::PixKey,
                BankTransfer::PixEmv(_) => Self::PixEmv,
                BankTransfer::Trustly(_) => Self::Trustly,
                BankTransfer::OpenBanking(_) => Self::OpenBanking,
            },
            PayoutMethodData::Wallet(wallet) => match wallet {
                Wallet::ApplePayDecrypt(_) => Self::ApplePay,
                Wallet::Paypal(_) => Self::Paypal,
                Wallet::Venmo(_) => Self::Venmo,
                Wallet::GooglePayDecrypt(_) => Self::GooglePay,
            },
            PayoutMethodData::BankRedirect(bank_redirect) => match bank_redirect {
                BankRedirect::Interac(_) => Self::Interac,
                BankRedirect::OpenBankingUk(_) => Self::OpenBankingUk,
            },
            PayoutMethodData::Passthrough(passthrough) => passthrough.token_type,
        }
    }
}
