// File: crates/hyperswitch_domain_models/src/disputes.rs

use crate::errors;

pub struct DisputeListConstraints {
    pub dispute_id: Option<String>,
    pub payment_id: Option<common_utils::id_type::PaymentId>,
    pub limit: Option<u32>,
    pub offset: Option<u32>,
    pub profile_id: Option<Vec<common_utils::id_type::ProfileId>>,
    pub dispute_status: Option<Vec<common_enums::DisputeStatus>>,
    pub dispute_stage: Option<Vec<common_enums::DisputeStage>>,
    pub reason: Option<String>,
    pub connector: Option<Vec<String>>,
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    pub currency: Option<Vec<common_enums::Currency>>,
    pub time_range: Option<common_utils::types::TimeRange>,
}

impl
    TryFrom<(
        api_models::disputes::DisputeListGetConstraints,
        Option<Vec<common_utils::id_type::ProfileId>>,
    )> for DisputeListConstraints
{
    type Error = error_stack::Report<errors::api_error_response::ApiErrorResponse>;
    fn try_from(
        (value, auth_profile_id_list): (
            api_models::disputes::DisputeListGetConstraints,
            Option<Vec<common_utils::id_type::ProfileId>>,
        ),
    ) -> Result<Self, Self::Error> {
        let api_models::disputes::DisputeListGetConstraints {
            dispute_id,
            payment_id,
            limit,
            offset,
            profile_id,
            dispute_status,
            dispute_stage,
            reason,
            connector,
            merchant_connector_id,
            currency,
            time_range,
        } = value;
        let profile_id_from_request_body = profile_id;
        // Match both the profile ID from the request body and the list of authenticated profile IDs coming from auth layer
        let profile_id_list = match (profile_id_from_request_body, auth_profile_id_list) {
            (None, None) => None,
            // Case when the request body profile ID is None, but authenticated profile IDs are available, return the auth list
            (None, Some(auth_profile_id_list)) => Some(auth_profile_id_list),
            // Case when the request body profile ID is provided, but the auth list is None, create a vector with the request body profile ID
            (Some(profile_id_from_request_body), None) => Some(vec![profile_id_from_request_body]),
            (Some(profile_id_from_request_body), Some(auth_profile_id_list)) => {
                // Check if the profile ID from the request body is present in the authenticated profile ID list
                let profile_id_from_request_body_is_available_in_auth_profile_id_list =
                    auth_profile_id_list.contains(&profile_id_from_request_body);

                if profile_id_from_request_body_is_available_in_auth_profile_id_list {
                    Some(vec![profile_id_from_request_body])
                } else {
                    // If the profile ID is not valid, return an error indicating access is not available
                    return Err(error_stack::Report::new(
                        errors::api_error_response::ApiErrorResponse::PreconditionFailed {
                            message: format!(
                                "Access not available for the given profile_id {profile_id_from_request_body:?}",

                            ),
                        },
                    ));
                }
            }
        };

        Ok(Self {
            dispute_id,
            payment_id,
            limit,
            offset,
            profile_id: profile_id_list,
            dispute_status,
            dispute_stage,
            reason,
            connector,
            merchant_connector_id,
            currency,
            time_range,
        })
    }
}


// File: crates/hyperswitch_domain_models/src/subscription.rs

use common_utils::{
    errors::{CustomResult, ValidationError},
    events::ApiEventMetric,
    generate_id_with_default_len,
    pii::SecretSerdeValue,
    types::keymanager::{self, KeyManagerState},
};
use error_stack::ResultExt;
use masking::{ExposeInterface, PeekInterface, Secret};
use time::PrimitiveDateTime;

use crate::{errors::api_error_response::ApiErrorResponse, merchant_key_store::MerchantKeyStore};

const SECRET_SPLIT: &str = "_secret";

#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct ClientSecret(String);

impl ClientSecret {
    pub fn new(secret: String) -> Self {
        Self(secret)
    }

    pub fn get_subscription_id(&self) -> error_stack::Result<String, ApiErrorResponse> {
        let sub_id = self
            .0
            .split(SECRET_SPLIT)
            .next()
            .ok_or(ApiErrorResponse::MissingRequiredField {
                field_name: "client_secret",
            })
            .attach_printable("Failed to extract subscription_id from client_secret")?;

        Ok(sub_id.to_string())
    }
}

impl std::fmt::Display for ClientSecret {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl ApiEventMetric for ClientSecret {}

impl From<api_models::subscription::ClientSecret> for ClientSecret {
    fn from(api_secret: api_models::subscription::ClientSecret) -> Self {
        Self::new(api_secret.as_str().to_string())
    }
}

impl From<ClientSecret> for api_models::subscription::ClientSecret {
    fn from(domain_secret: ClientSecret) -> Self {
        Self::new(domain_secret.to_string())
    }
}

#[derive(Clone, Debug, serde::Serialize)]
pub struct Subscription {
    pub id: common_utils::id_type::SubscriptionId,
    pub status: String,
    pub billing_processor: Option<String>,
    pub payment_method_id: Option<String>,
    pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
    pub client_secret: Option<String>,
    pub connector_subscription_id: Option<String>,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub customer_id: common_utils::id_type::CustomerId,
    pub metadata: Option<SecretSerdeValue>,
    pub created_at: PrimitiveDateTime,
    pub modified_at: PrimitiveDateTime,
    pub profile_id: common_utils::id_type::ProfileId,
    pub merchant_reference_id: Option<String>,
    pub plan_id: Option<String>,
    pub item_price_id: Option<String>,
}

#[derive(Debug, Clone, serde::Serialize)]
pub enum SubscriptionStatus {
    Active,
    Created,
    InActive,
    Pending,
    Trial,
    Paused,
    Unpaid,
    Onetime,
    Cancelled,
    Failed,
}

impl std::fmt::Display for SubscriptionStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Active => write!(f, "Active"),
            Self::Created => write!(f, "Created"),
            Self::InActive => write!(f, "InActive"),
            Self::Pending => write!(f, "Pending"),
            Self::Trial => write!(f, "Trial"),
            Self::Paused => write!(f, "Paused"),
            Self::Unpaid => write!(f, "Unpaid"),
            Self::Onetime => write!(f, "Onetime"),
            Self::Cancelled => write!(f, "Cancelled"),
            Self::Failed => write!(f, "Failed"),
        }
    }
}

impl Subscription {
    pub fn generate_and_set_client_secret(&mut self) -> Secret<String> {
        let client_secret =
            generate_id_with_default_len(&format!("{}_secret", self.id.get_string_repr()));
        self.client_secret = Some(client_secret.clone());
        Secret::new(client_secret)
    }
}

#[async_trait::async_trait]
impl super::behaviour::Conversion for Subscription {
    type DstType = diesel_models::subscription::Subscription;
    type NewDstType = diesel_models::subscription::SubscriptionNew;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        let now = common_utils::date_time::now();
        Ok(diesel_models::subscription::Subscription {
            id: self.id,
            status: self.status,
            billing_processor: self.billing_processor,
            payment_method_id: self.payment_method_id,
            merchant_connector_id: self.merchant_connector_id,
            client_secret: self.client_secret,
            connector_subscription_id: self.connector_subscription_id,
            merchant_id: self.merchant_id,
            customer_id: self.customer_id,
            metadata: self.metadata.map(|m| m.expose()),
            created_at: now,
            modified_at: now,
            profile_id: self.profile_id,
            merchant_reference_id: self.merchant_reference_id,
            plan_id: self.plan_id,
            item_price_id: self.item_price_id,
        })
    }

    async fn convert_back(
        _state: &KeyManagerState,
        item: Self::DstType,
        _key: &Secret<Vec<u8>>,
        _key_manager_identifier: keymanager::Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        Ok(Self {
            id: item.id,
            status: item.status,
            billing_processor: item.billing_processor,
            payment_method_id: item.payment_method_id,
            merchant_connector_id: item.merchant_connector_id,
            client_secret: item.client_secret,
            connector_subscription_id: item.connector_subscription_id,
            merchant_id: item.merchant_id,
            customer_id: item.customer_id,
            metadata: item.metadata.map(SecretSerdeValue::new),
            created_at: item.created_at,
            modified_at: item.modified_at,
            profile_id: item.profile_id,
            merchant_reference_id: item.merchant_reference_id,
            plan_id: item.plan_id,
            item_price_id: item.item_price_id,
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        Ok(diesel_models::subscription::SubscriptionNew::new(
            self.id,
            self.status,
            self.billing_processor,
            self.payment_method_id,
            self.merchant_connector_id,
            self.client_secret,
            self.connector_subscription_id,
            self.merchant_id,
            self.customer_id,
            self.metadata,
            self.profile_id,
            self.merchant_reference_id,
            self.plan_id,
            self.item_price_id,
        ))
    }
}

#[async_trait::async_trait]
pub trait SubscriptionInterface {
    type Error;
    async fn insert_subscription_entry(
        &self,
        key_store: &MerchantKeyStore,
        subscription_new: Subscription,
    ) -> CustomResult<Subscription, Self::Error>;

    async fn find_by_merchant_id_subscription_id(
        &self,
        key_store: &MerchantKeyStore,
        merchant_id: &common_utils::id_type::MerchantId,
        subscription_id: String,
    ) -> CustomResult<Subscription, Self::Error>;

    async fn update_subscription_entry(
        &self,
        key_store: &MerchantKeyStore,
        merchant_id: &common_utils::id_type::MerchantId,
        subscription_id: String,
        data: SubscriptionUpdate,
    ) -> CustomResult<Subscription, Self::Error>;
}

pub struct SubscriptionUpdate {
    pub connector_subscription_id: Option<String>,
    pub payment_method_id: Option<String>,
    pub status: Option<String>,
    pub modified_at: PrimitiveDateTime,
    pub plan_id: Option<String>,
    pub item_price_id: Option<String>,
}

impl SubscriptionUpdate {
    pub fn new(
        connector_subscription_id: Option<String>,
        payment_method_id: Option<Secret<String>>,
        status: Option<String>,
        plan_id: Option<String>,
        item_price_id: Option<String>,
    ) -> Self {
        Self {
            connector_subscription_id,
            payment_method_id: payment_method_id.map(|pmid| pmid.peek().clone()),
            status,
            modified_at: common_utils::date_time::now(),
            plan_id,
            item_price_id,
        }
    }

    pub fn update_status(status: String) -> Self {
        Self::new(None, None, Some(status), None, None)
    }
}

#[async_trait::async_trait]
impl super::behaviour::Conversion for SubscriptionUpdate {
    type DstType = diesel_models::subscription::SubscriptionUpdate;
    type NewDstType = diesel_models::subscription::SubscriptionUpdate;

    async fn convert(self) -> CustomResult<Self::DstType, ValidationError> {
        Ok(diesel_models::subscription::SubscriptionUpdate {
            connector_subscription_id: self.connector_subscription_id,
            payment_method_id: self.payment_method_id,
            status: self.status,
            modified_at: self.modified_at,
            plan_id: self.plan_id,
            item_price_id: self.item_price_id,
        })
    }

    async fn convert_back(
        _state: &KeyManagerState,
        item: Self::DstType,
        _key: &Secret<Vec<u8>>,
        _key_manager_identifier: keymanager::Identifier,
    ) -> CustomResult<Self, ValidationError>
    where
        Self: Sized,
    {
        Ok(Self {
            connector_subscription_id: item.connector_subscription_id,
            payment_method_id: item.payment_method_id,
            status: item.status,
            modified_at: item.modified_at,
            plan_id: item.plan_id,
            item_price_id: item.item_price_id,
        })
    }

    async fn construct_new(self) -> CustomResult<Self::NewDstType, ValidationError> {
        Ok(diesel_models::subscription::SubscriptionUpdate {
            connector_subscription_id: self.connector_subscription_id,
            payment_method_id: self.payment_method_id,
            status: self.status,
            modified_at: self.modified_at,
            plan_id: self.plan_id,
            item_price_id: self.item_price_id,
        })
    }
}


// File: crates/hyperswitch_domain_models/src/address.rs

use masking::{PeekInterface, Secret};

#[derive(Default, Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct Address {
    pub address: Option<AddressDetails>,
    pub phone: Option<PhoneDetails>,
    pub email: Option<common_utils::pii::Email>,
}

impl masking::SerializableSecret for Address {}

impl Address {
    /// Unify the address, giving priority to `self` when details are present in both
    pub fn unify_address(&self, other: Option<&Self>) -> Self {
        let other_address_details = other.and_then(|address| address.address.as_ref());
        Self {
            address: self
                .address
                .as_ref()
                .map(|address| address.unify_address_details(other_address_details))
                .or(other_address_details.cloned()),
            email: self
                .email
                .clone()
                .or(other.and_then(|other| other.email.clone())),
            phone: {
                self.phone
                    .clone()
                    .and_then(|phone_details| {
                        if phone_details.number.is_some() {
                            Some(phone_details)
                        } else {
                            None
                        }
                    })
                    .or_else(|| other.and_then(|other| other.phone.clone()))
            },
        }
    }
}

#[derive(Clone, Default, Debug, Eq, serde::Deserialize, serde::Serialize, PartialEq)]
pub struct AddressDetails {
    pub city: Option<String>,
    pub country: Option<common_enums::CountryAlpha2>,
    pub line1: Option<Secret<String>>,
    pub line2: Option<Secret<String>>,
    pub line3: Option<Secret<String>>,
    pub zip: Option<Secret<String>>,
    pub state: Option<Secret<String>>,
    pub first_name: Option<Secret<String>>,
    pub last_name: Option<Secret<String>>,
    pub origin_zip: Option<Secret<String>>,
}

impl AddressDetails {
    pub fn get_optional_full_name(&self) -> Option<Secret<String>> {
        match (self.first_name.as_ref(), self.last_name.as_ref()) {
            (Some(first_name), Some(last_name)) => Some(Secret::new(format!(
                "{} {}",
                first_name.peek(),
                last_name.peek()
            ))),
            (Some(name), None) | (None, Some(name)) => Some(name.to_owned()),
            _ => None,
        }
    }

    /// Unify the address details, giving priority to `self` when details are present in both
    pub fn unify_address_details(&self, other: Option<&Self>) -> Self {
        if let Some(other) = other {
            let (first_name, last_name) = if self
                .first_name
                .as_ref()
                .is_some_and(|first_name| !first_name.peek().trim().is_empty())
            {
                (self.first_name.clone(), self.last_name.clone())
            } else {
                (other.first_name.clone(), other.last_name.clone())
            };

            Self {
                first_name,
                last_name,
                city: self.city.clone().or(other.city.clone()),
                country: self.country.or(other.country),
                line1: self.line1.clone().or(other.line1.clone()),
                line2: self.line2.clone().or(other.line2.clone()),
                line3: self.line3.clone().or(other.line3.clone()),
                zip: self.zip.clone().or(other.zip.clone()),
                state: self.state.clone().or(other.state.clone()),
                origin_zip: self.origin_zip.clone().or(other.origin_zip.clone()),
            }
        } else {
            self.clone()
        }
    }
}

#[derive(Debug, Clone, Default, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct PhoneDetails {
    pub number: Option<Secret<String>>,
    pub country_code: Option<String>,
}

impl From<api_models::payments::Address> for Address {
    fn from(address: api_models::payments::Address) -> Self {
        Self {
            address: address.address.map(AddressDetails::from),
            phone: address.phone.map(PhoneDetails::from),
            email: address.email,
        }
    }
}

impl From<api_models::payments::AddressDetails> for AddressDetails {
    fn from(address: api_models::payments::AddressDetails) -> Self {
        Self {
            city: address.city,
            country: address.country,
            line1: address.line1,
            line2: address.line2,
            line3: address.line3,
            zip: address.zip,
            state: address.state,
            first_name: address.first_name,
            last_name: address.last_name,
            origin_zip: address.origin_zip,
        }
    }
}

impl From<api_models::payments::PhoneDetails> for PhoneDetails {
    fn from(phone: api_models::payments::PhoneDetails) -> Self {
        Self {
            number: phone.number,
            country_code: phone.country_code,
        }
    }
}

impl From<Address> for api_models::payments::Address {
    fn from(address: Address) -> Self {
        Self {
            address: address
                .address
                .map(api_models::payments::AddressDetails::from),
            phone: address.phone.map(api_models::payments::PhoneDetails::from),
            email: address.email,
        }
    }
}

impl From<AddressDetails> for api_models::payments::AddressDetails {
    fn from(address: AddressDetails) -> Self {
        Self {
            city: address.city,
            country: address.country,
            line1: address.line1,
            line2: address.line2,
            line3: address.line3,
            zip: address.zip,
            state: address.state,
            first_name: address.first_name,
            last_name: address.last_name,
            origin_zip: address.origin_zip,
        }
    }
}

impl From<PhoneDetails> for api_models::payments::PhoneDetails {
    fn from(phone: PhoneDetails) -> Self {
        Self {
            number: phone.number,
            country_code: phone.country_code,
        }
    }
}


// File: crates/hyperswitch_domain_models/src/bulk_tokenization.rs

use api_models::{payment_methods as payment_methods_api, payments as payments_api};
use cards::CardNumber;
use common_enums as enums;
use common_utils::{
    errors,
    ext_traits::OptionExt,
    id_type, pii,
    transformers::{ForeignFrom, ForeignTryFrom},
};
use error_stack::report;

use crate::{
    address::{Address, AddressDetails, PhoneDetails},
    router_request_types::CustomerDetails,
};

#[derive(Debug)]
pub struct CardNetworkTokenizeRequest {
    pub data: TokenizeDataRequest,
    pub customer: CustomerDetails,
    pub billing: Option<Address>,
    pub metadata: Option<pii::SecretSerdeValue>,
    pub payment_method_issuer: Option<String>,
}

#[derive(Debug)]
pub enum TokenizeDataRequest {
    Card(TokenizeCardRequest),
    ExistingPaymentMethod(TokenizePaymentMethodRequest),
}

#[derive(Clone, Debug)]
pub struct TokenizeCardRequest {
    pub raw_card_number: CardNumber,
    pub card_expiry_month: masking::Secret<String>,
    pub card_expiry_year: masking::Secret<String>,
    pub card_cvc: Option<masking::Secret<String>>,
    pub card_holder_name: Option<masking::Secret<String>>,
    pub nick_name: Option<masking::Secret<String>>,
    pub card_issuing_country: Option<String>,
    pub card_network: Option<enums::CardNetwork>,
    pub card_issuer: Option<String>,
    pub card_type: Option<payment_methods_api::CardType>,
}

#[derive(Clone, Debug)]
pub struct TokenizePaymentMethodRequest {
    pub payment_method_id: String,
    pub card_cvc: Option<masking::Secret<String>>,
}

#[derive(Default, Debug, serde::Deserialize, serde::Serialize)]
pub struct CardNetworkTokenizeRecord {
    // Card details
    pub raw_card_number: Option<CardNumber>,
    pub card_expiry_month: Option<masking::Secret<String>>,
    pub card_expiry_year: Option<masking::Secret<String>>,
    pub card_cvc: Option<masking::Secret<String>>,
    pub card_holder_name: Option<masking::Secret<String>>,
    pub nick_name: Option<masking::Secret<String>>,
    pub card_issuing_country: Option<String>,
    pub card_network: Option<enums::CardNetwork>,
    pub card_issuer: Option<String>,
    pub card_type: Option<payment_methods_api::CardType>,

    // Payment method details
    pub payment_method_id: Option<String>,
    pub payment_method_type: Option<payment_methods_api::CardType>,
    pub payment_method_issuer: Option<String>,

    // Customer details
    pub customer_id: id_type::CustomerId,
    #[serde(rename = "name")]
    pub customer_name: Option<masking::Secret<String>>,
    #[serde(rename = "email")]
    pub customer_email: Option<pii::Email>,
    #[serde(rename = "phone")]
    pub customer_phone: Option<masking::Secret<String>>,
    #[serde(rename = "phone_country_code")]
    pub customer_phone_country_code: Option<String>,
    #[serde(rename = "tax_registration_id")]
    pub customer_tax_registration_id: Option<masking::Secret<String>>,
    // Billing details
    pub billing_address_city: Option<String>,
    pub billing_address_country: Option<enums::CountryAlpha2>,
    pub billing_address_line1: Option<masking::Secret<String>>,
    pub billing_address_line2: Option<masking::Secret<String>>,
    pub billing_address_line3: Option<masking::Secret<String>>,
    pub billing_address_zip: Option<masking::Secret<String>>,
    pub billing_address_state: Option<masking::Secret<String>>,
    pub billing_address_first_name: Option<masking::Secret<String>>,
    pub billing_address_last_name: Option<masking::Secret<String>>,
    pub billing_phone_number: Option<masking::Secret<String>>,
    pub billing_phone_country_code: Option<String>,
    pub billing_email: Option<pii::Email>,

    // Other details
    pub line_number: Option<u64>,
    pub merchant_id: Option<id_type::MerchantId>,
}

impl ForeignFrom<&CardNetworkTokenizeRecord> for payments_api::CustomerDetails {
    fn foreign_from(record: &CardNetworkTokenizeRecord) -> Self {
        Self {
            id: record.customer_id.clone(),
            name: record.customer_name.clone(),
            email: record.customer_email.clone(),
            phone: record.customer_phone.clone(),
            phone_country_code: record.customer_phone_country_code.clone(),
            tax_registration_id: record.customer_tax_registration_id.clone(),
        }
    }
}

impl ForeignFrom<&CardNetworkTokenizeRecord> for payments_api::Address {
    fn foreign_from(record: &CardNetworkTokenizeRecord) -> Self {
        Self {
            address: Some(payments_api::AddressDetails {
                first_name: record.billing_address_first_name.clone(),
                last_name: record.billing_address_last_name.clone(),
                line1: record.billing_address_line1.clone(),
                line2: record.billing_address_line2.clone(),
                line3: record.billing_address_line3.clone(),
                city: record.billing_address_city.clone(),
                zip: record.billing_address_zip.clone(),
                state: record.billing_address_state.clone(),
                country: record.billing_address_country,
                origin_zip: None,
            }),
            phone: Some(payments_api::PhoneDetails {
                number: record.billing_phone_number.clone(),
                country_code: record.billing_phone_country_code.clone(),
            }),
            email: record.billing_email.clone(),
        }
    }
}

impl ForeignTryFrom<CardNetworkTokenizeRecord> for payment_methods_api::CardNetworkTokenizeRequest {
    type Error = error_stack::Report<errors::ValidationError>;
    fn foreign_try_from(record: CardNetworkTokenizeRecord) -> Result<Self, Self::Error> {
        let billing = Some(payments_api::Address::foreign_from(&record));
        let customer = payments_api::CustomerDetails::foreign_from(&record);
        let merchant_id = record.merchant_id.get_required_value("merchant_id")?;

        match (
            record.raw_card_number,
            record.card_expiry_month,
            record.card_expiry_year,
            record.payment_method_id,
        ) {
            (Some(raw_card_number), Some(card_expiry_month), Some(card_expiry_year), None) => {
                Ok(Self {
                    merchant_id,
                    data: payment_methods_api::TokenizeDataRequest::Card(
                        payment_methods_api::TokenizeCardRequest {
                            raw_card_number,
                            card_expiry_month,
                            card_expiry_year,
                            card_cvc: record.card_cvc,
                            card_holder_name: record.card_holder_name,
                            nick_name: record.nick_name,
                            card_issuing_country: record.card_issuing_country,
                            card_network: record.card_network,
                            card_issuer: record.card_issuer,
                            card_type: record.card_type.clone(),
                        },
                    ),
                    billing,
                    customer,
                    metadata: None,
                    payment_method_issuer: record.payment_method_issuer,
                })
            }
            (None, None, None, Some(payment_method_id)) => Ok(Self {
                merchant_id,
                data: payment_methods_api::TokenizeDataRequest::ExistingPaymentMethod(
                    payment_methods_api::TokenizePaymentMethodRequest {
                        payment_method_id,
                        card_cvc: record.card_cvc,
                    },
                ),
                billing,
                customer,
                metadata: None,
                payment_method_issuer: record.payment_method_issuer,
            }),
            _ => Err(report!(errors::ValidationError::InvalidValue {
                message: "Invalid record in bulk tokenization - expected one of card details or payment method details".to_string()
            })),
        }
    }
}

impl ForeignFrom<&TokenizeCardRequest> for payment_methods_api::MigrateCardDetail {
    fn foreign_from(card: &TokenizeCardRequest) -> Self {
        Self {
            card_number: masking::Secret::new(card.raw_card_number.get_card_no()),
            card_exp_month: card.card_expiry_month.clone(),
            card_exp_year: card.card_expiry_year.clone(),
            card_holder_name: card.card_holder_name.clone(),
            nick_name: card.nick_name.clone(),
            card_issuing_country: card.card_issuing_country.clone(),
            card_network: card.card_network.clone(),
            card_issuer: card.card_issuer.clone(),
            card_type: card
                .card_type
                .as_ref()
                .map(|card_type| card_type.to_string()),
        }
    }
}

impl ForeignTryFrom<CustomerDetails> for payments_api::CustomerDetails {
    type Error = error_stack::Report<errors::ValidationError>;
    fn foreign_try_from(customer: CustomerDetails) -> Result<Self, Self::Error> {
        Ok(Self {
            id: customer.customer_id.get_required_value("customer_id")?,
            name: customer.name,
            email: customer.email,
            phone: customer.phone,
            phone_country_code: customer.phone_country_code,
            tax_registration_id: customer.tax_registration_id,
        })
    }
}

impl ForeignFrom<payment_methods_api::CardNetworkTokenizeRequest> for CardNetworkTokenizeRequest {
    fn foreign_from(req: payment_methods_api::CardNetworkTokenizeRequest) -> Self {
        Self {
            data: TokenizeDataRequest::foreign_from(req.data),
            customer: CustomerDetails::foreign_from(req.customer),
            billing: req.billing.map(ForeignFrom::foreign_from),
            metadata: req.metadata,
            payment_method_issuer: req.payment_method_issuer,
        }
    }
}

impl ForeignFrom<payment_methods_api::TokenizeDataRequest> for TokenizeDataRequest {
    fn foreign_from(req: payment_methods_api::TokenizeDataRequest) -> Self {
        match req {
            payment_methods_api::TokenizeDataRequest::Card(card) => {
                Self::Card(TokenizeCardRequest {
                    raw_card_number: card.raw_card_number,
                    card_expiry_month: card.card_expiry_month,
                    card_expiry_year: card.card_expiry_year,
                    card_cvc: card.card_cvc,
                    card_holder_name: card.card_holder_name,
                    nick_name: card.nick_name,
                    card_issuing_country: card.card_issuing_country,
                    card_network: card.card_network,
                    card_issuer: card.card_issuer,
                    card_type: card.card_type,
                })
            }
            payment_methods_api::TokenizeDataRequest::ExistingPaymentMethod(pm) => {
                Self::ExistingPaymentMethod(TokenizePaymentMethodRequest {
                    payment_method_id: pm.payment_method_id,
                    card_cvc: pm.card_cvc,
                })
            }
        }
    }
}

impl ForeignFrom<payments_api::CustomerDetails> for CustomerDetails {
    fn foreign_from(req: payments_api::CustomerDetails) -> Self {
        Self {
            customer_id: Some(req.id),
            name: req.name,
            email: req.email,
            phone: req.phone,
            phone_country_code: req.phone_country_code,
            tax_registration_id: req.tax_registration_id,
        }
    }
}

impl ForeignFrom<payments_api::Address> for Address {
    fn foreign_from(req: payments_api::Address) -> Self {
        Self {
            address: req.address.map(ForeignFrom::foreign_from),
            phone: req.phone.map(ForeignFrom::foreign_from),
            email: req.email,
        }
    }
}

impl ForeignFrom<payments_api::AddressDetails> for AddressDetails {
    fn foreign_from(req: payments_api::AddressDetails) -> Self {
        Self {
            city: req.city,
            country: req.country,
            line1: req.line1,
            line2: req.line2,
            line3: req.line3,
            zip: req.zip,
            state: req.state,
            first_name: req.first_name,
            last_name: req.last_name,
            origin_zip: req.origin_zip,
        }
    }
}

impl ForeignFrom<payments_api::PhoneDetails> for PhoneDetails {
    fn foreign_from(req: payments_api::PhoneDetails) -> Self {
        Self {
            number: req.number,
            country_code: req.country_code,
        }
    }
}


// File: crates/hyperswitch_domain_models/src/type_encryption.rs

use async_trait::async_trait;
use common_utils::{
    crypto,
    encryption::Encryption,
    errors::{self, CustomResult},
    ext_traits::AsyncExt,
    metrics::utils::record_operation_time,
    types::keymanager::{Identifier, KeyManagerState},
};
use encrypt::TypeEncryption;
use masking::Secret;
use router_env::{instrument, tracing};
use rustc_hash::FxHashMap;

mod encrypt {
    use async_trait::async_trait;
    use common_utils::{
        crypto,
        encryption::Encryption,
        errors::{self, CustomResult},
        ext_traits::ByteSliceExt,
        keymanager::call_encryption_service,
        transformers::{ForeignFrom, ForeignTryFrom},
        types::keymanager::{
            BatchDecryptDataResponse, BatchEncryptDataRequest, BatchEncryptDataResponse,
            DecryptDataResponse, EncryptDataRequest, EncryptDataResponse, Identifier,
            KeyManagerState, TransientBatchDecryptDataRequest, TransientDecryptDataRequest,
        },
    };
    use error_stack::ResultExt;
    use http::Method;
    use masking::{PeekInterface, Secret};
    use router_env::{instrument, logger, tracing};
    use rustc_hash::FxHashMap;

    use super::{metrics, EncryptedJsonType};

    #[async_trait]
    pub trait TypeEncryption<
        T,
        V: crypto::EncodeMessage + crypto::DecodeMessage,
        S: masking::Strategy<T>,
    >: Sized
    {
        async fn encrypt_via_api(
            state: &KeyManagerState,
            masked_data: Secret<T, S>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError>;

        async fn decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: Encryption,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError>;

        async fn encrypt(
            masked_data: Secret<T, S>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError>;

        async fn decrypt(
            encrypted_data: Encryption,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError>;

        async fn batch_encrypt_via_api(
            state: &KeyManagerState,
            masked_data: FxHashMap<String, Secret<T, S>>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError>;

        async fn batch_decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: FxHashMap<String, Encryption>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError>;

        async fn batch_encrypt(
            masked_data: FxHashMap<String, Secret<T, S>>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError>;

        async fn batch_decrypt(
            encrypted_data: FxHashMap<String, Encryption>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError>;
    }

    fn is_encryption_service_enabled(_state: &KeyManagerState) -> bool {
        #[cfg(feature = "encryption_service")]
        {
            _state.enabled
        }
        #[cfg(not(feature = "encryption_service"))]
        {
            false
        }
    }

    #[async_trait]
    impl<
            V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static,
            S: masking::Strategy<String> + Send + Sync,
        > TypeEncryption<String, V, S> for crypto::Encryptable<Secret<String, S>>
    {
        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn encrypt_via_api(
            state: &KeyManagerState,
            masked_data: Secret<String, S>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::encrypt(masked_data, key, crypt_algo).await
            } else {
                let result: Result<
                    EncryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/encrypt",
                    EncryptDataRequest::from((masked_data.clone(), identifier)),
                )
                .await;
                match result {
                    Ok(response) => Ok(ForeignFrom::foreign_from((masked_data.clone(), response))),
                    Err(err) => {
                        logger::error!("Encryption error {:?}", err);
                        metrics::ENCRYPTION_API_FAILURES.add(1, &[]);
                        logger::info!("Fall back to Application Encryption");
                        Self::encrypt(masked_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: Encryption,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::decrypt(encrypted_data, key, crypt_algo).await
            } else {
                let result: Result<
                    DecryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/decrypt",
                    TransientDecryptDataRequest::from((encrypted_data.clone(), identifier)),
                )
                .await;
                let decrypted = match result {
                    Ok(decrypted_data) => {
                        ForeignTryFrom::foreign_try_from((encrypted_data.clone(), decrypted_data))
                    }
                    Err(err) => {
                        logger::error!("Decryption error {:?}", err);
                        Err(err.change_context(errors::CryptoError::DecodingFailed))
                    }
                };

                match decrypted {
                    Ok(de) => Ok(de),
                    Err(_) => {
                        metrics::DECRYPTION_API_FAILURES.add(1, &[]);
                        logger::info!("Fall back to Application Decryption");
                        Self::decrypt(encrypted_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn encrypt(
            masked_data: Secret<String, S>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]);
            let encrypted_data = crypt_algo.encode_message(key, masked_data.peek().as_bytes())?;
            Ok(Self::new(masked_data, encrypted_data.into()))
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn decrypt(
            encrypted_data: Encryption,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]);
            let encrypted = encrypted_data.into_inner();
            let data = crypt_algo.decode_message(key, encrypted.clone())?;

            let value: String = std::str::from_utf8(&data)
                .change_context(errors::CryptoError::DecodingFailed)?
                .to_string();

            Ok(Self::new(value.into(), encrypted))
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_encrypt_via_api(
            state: &KeyManagerState,
            masked_data: FxHashMap<String, Secret<String, S>>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::batch_encrypt(masked_data, key, crypt_algo).await
            } else {
                let result: Result<
                    BatchEncryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/encrypt",
                    BatchEncryptDataRequest::from((masked_data.clone(), identifier)),
                )
                .await;
                match result {
                    Ok(response) => Ok(ForeignFrom::foreign_from((masked_data, response))),
                    Err(err) => {
                        metrics::ENCRYPTION_API_FAILURES.add(1, &[]);
                        logger::error!("Encryption error {:?}", err);
                        logger::info!("Fall back to Application Encryption");
                        Self::batch_encrypt(masked_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: FxHashMap<String, Encryption>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::batch_decrypt(encrypted_data, key, crypt_algo).await
            } else {
                let result: Result<
                    BatchDecryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/decrypt",
                    TransientBatchDecryptDataRequest::from((encrypted_data.clone(), identifier)),
                )
                .await;
                let decrypted = match result {
                    Ok(decrypted_data) => {
                        ForeignTryFrom::foreign_try_from((encrypted_data.clone(), decrypted_data))
                    }
                    Err(err) => {
                        logger::error!("Decryption error {:?}", err);
                        Err(err.change_context(errors::CryptoError::DecodingFailed))
                    }
                };
                match decrypted {
                    Ok(de) => Ok(de),
                    Err(_) => {
                        metrics::DECRYPTION_API_FAILURES.add(1, &[]);
                        logger::info!("Fall back to Application Decryption");
                        Self::batch_decrypt(encrypted_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_encrypt(
            masked_data: FxHashMap<String, Secret<String, S>>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]);
            masked_data
                .into_iter()
                .map(|(k, v)| {
                    Ok((
                        k,
                        Self::new(
                            v.clone(),
                            crypt_algo.encode_message(key, v.peek().as_bytes())?.into(),
                        ),
                    ))
                })
                .collect()
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_decrypt(
            encrypted_data: FxHashMap<String, Encryption>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]);
            encrypted_data
                .into_iter()
                .map(|(k, v)| {
                    let data = crypt_algo.decode_message(key, v.clone().into_inner())?;
                    let value: String = std::str::from_utf8(&data)
                        .change_context(errors::CryptoError::DecodingFailed)?
                        .to_string();
                    Ok((k, Self::new(value.into(), v.into_inner())))
                })
                .collect()
        }
    }

    #[async_trait]
    impl<
            V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static,
            S: masking::Strategy<serde_json::Value> + Send + Sync,
        > TypeEncryption<serde_json::Value, V, S>
        for crypto::Encryptable<Secret<serde_json::Value, S>>
    {
        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn encrypt_via_api(
            state: &KeyManagerState,
            masked_data: Secret<serde_json::Value, S>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::encrypt(masked_data, key, crypt_algo).await
            } else {
                let result: Result<
                    EncryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/encrypt",
                    EncryptDataRequest::from((masked_data.clone(), identifier)),
                )
                .await;
                match result {
                    Ok(response) => Ok(ForeignFrom::foreign_from((masked_data.clone(), response))),
                    Err(err) => {
                        logger::error!("Encryption error {:?}", err);
                        metrics::ENCRYPTION_API_FAILURES.add(1, &[]);
                        logger::info!("Fall back to Application Encryption");
                        Self::encrypt(masked_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: Encryption,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::decrypt(encrypted_data, key, crypt_algo).await
            } else {
                let result: Result<
                    DecryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/decrypt",
                    TransientDecryptDataRequest::from((encrypted_data.clone(), identifier)),
                )
                .await;
                let decrypted = match result {
                    Ok(decrypted_data) => {
                        ForeignTryFrom::foreign_try_from((encrypted_data.clone(), decrypted_data))
                    }
                    Err(err) => {
                        logger::error!("Decryption error {:?}", err);
                        Err(err.change_context(errors::CryptoError::EncodingFailed))
                    }
                };
                match decrypted {
                    Ok(de) => Ok(de),
                    Err(_) => {
                        metrics::DECRYPTION_API_FAILURES.add(1, &[]);
                        logger::info!("Fall back to Application Decryption");
                        Self::decrypt(encrypted_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn encrypt(
            masked_data: Secret<serde_json::Value, S>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]);
            let data = serde_json::to_vec(&masked_data.peek())
                .change_context(errors::CryptoError::DecodingFailed)?;
            let encrypted_data = crypt_algo.encode_message(key, &data)?;
            Ok(Self::new(masked_data, encrypted_data.into()))
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn decrypt(
            encrypted_data: Encryption,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]);
            let encrypted = encrypted_data.into_inner();
            let data = crypt_algo.decode_message(key, encrypted.clone())?;

            let value: serde_json::Value = serde_json::from_slice(&data)
                .change_context(errors::CryptoError::DecodingFailed)?;
            Ok(Self::new(value.into(), encrypted))
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_encrypt_via_api(
            state: &KeyManagerState,
            masked_data: FxHashMap<String, Secret<serde_json::Value, S>>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::batch_encrypt(masked_data, key, crypt_algo).await
            } else {
                let result: Result<
                    BatchEncryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/encrypt",
                    BatchEncryptDataRequest::from((masked_data.clone(), identifier)),
                )
                .await;
                match result {
                    Ok(response) => Ok(ForeignFrom::foreign_from((masked_data, response))),
                    Err(err) => {
                        metrics::ENCRYPTION_API_FAILURES.add(1, &[]);
                        logger::error!("Encryption error {:?}", err);
                        logger::info!("Fall back to Application Encryption");
                        Self::batch_encrypt(masked_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: FxHashMap<String, Encryption>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::batch_decrypt(encrypted_data, key, crypt_algo).await
            } else {
                let result: Result<
                    BatchDecryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/decrypt",
                    TransientBatchDecryptDataRequest::from((encrypted_data.clone(), identifier)),
                )
                .await;
                let decrypted = match result {
                    Ok(decrypted_data) => {
                        ForeignTryFrom::foreign_try_from((encrypted_data.clone(), decrypted_data))
                    }
                    Err(err) => {
                        logger::error!("Decryption error {:?}", err);
                        Err(err.change_context(errors::CryptoError::DecodingFailed))
                    }
                };
                match decrypted {
                    Ok(de) => Ok(de),
                    Err(_) => {
                        metrics::DECRYPTION_API_FAILURES.add(1, &[]);
                        logger::info!("Fall back to Application Decryption");
                        Self::batch_decrypt(encrypted_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_encrypt(
            masked_data: FxHashMap<String, Secret<serde_json::Value, S>>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]);
            masked_data
                .into_iter()
                .map(|(k, v)| {
                    let data = serde_json::to_vec(v.peek())
                        .change_context(errors::CryptoError::DecodingFailed)?;
                    Ok((
                        k,
                        Self::new(v, crypt_algo.encode_message(key, &data)?.into()),
                    ))
                })
                .collect()
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_decrypt(
            encrypted_data: FxHashMap<String, Encryption>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]);
            encrypted_data
                .into_iter()
                .map(|(k, v)| {
                    let data = crypt_algo.decode_message(key, v.clone().into_inner().clone())?;

                    let value: serde_json::Value = serde_json::from_slice(&data)
                        .change_context(errors::CryptoError::DecodingFailed)?;
                    Ok((k, Self::new(value.into(), v.into_inner())))
                })
                .collect()
        }
    }

    impl<T> EncryptedJsonType<T>
    where
        T: std::fmt::Debug + Clone + serde::Serialize + serde::de::DeserializeOwned,
    {
        fn serialize_json_bytes(&self) -> CustomResult<Secret<Vec<u8>>, errors::CryptoError> {
            common_utils::ext_traits::Encode::encode_to_vec(self.inner())
                .change_context(errors::CryptoError::EncodingFailed)
                .attach_printable("Failed to JSON serialize data before encryption")
                .map(Secret::new)
        }

        fn deserialize_json_bytes<S>(
            bytes: Secret<Vec<u8>>,
        ) -> CustomResult<Secret<Self, S>, errors::ParsingError>
        where
            S: masking::Strategy<Self>,
        {
            bytes
                .peek()
                .as_slice()
                .parse_struct::<T>(std::any::type_name::<T>())
                .map(|result| Secret::new(Self::from(result)))
                .attach_printable("Failed to JSON deserialize data after decryption")
        }
    }

    #[async_trait]
    impl<
            T: std::fmt::Debug + Clone + serde::Serialize + serde::de::DeserializeOwned + Send,
            V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static,
            S: masking::Strategy<EncryptedJsonType<T>> + Send + Sync,
        > TypeEncryption<EncryptedJsonType<T>, V, S>
        for crypto::Encryptable<Secret<EncryptedJsonType<T>, S>>
    {
        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn encrypt_via_api(
            state: &KeyManagerState,
            masked_data: Secret<EncryptedJsonType<T>, S>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            let data_bytes = EncryptedJsonType::serialize_json_bytes(masked_data.peek())?;
            let result: crypto::Encryptable<Secret<Vec<u8>>> =
                TypeEncryption::encrypt_via_api(state, data_bytes, identifier, key, crypt_algo)
                    .await?;
            Ok(Self::new(masked_data, result.into_encrypted()))
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: Encryption,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            let result: crypto::Encryptable<Secret<Vec<u8>>> =
                TypeEncryption::decrypt_via_api(state, encrypted_data, identifier, key, crypt_algo)
                    .await?;
            result
                .deserialize_inner_value(EncryptedJsonType::deserialize_json_bytes)
                .change_context(errors::CryptoError::DecodingFailed)
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn encrypt(
            masked_data: Secret<EncryptedJsonType<T>, S>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            let data_bytes = EncryptedJsonType::serialize_json_bytes(masked_data.peek())?;
            let result: crypto::Encryptable<Secret<Vec<u8>>> =
                TypeEncryption::encrypt(data_bytes, key, crypt_algo).await?;
            Ok(Self::new(masked_data, result.into_encrypted()))
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn decrypt(
            encrypted_data: Encryption,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            let result: crypto::Encryptable<Secret<Vec<u8>>> =
                TypeEncryption::decrypt(encrypted_data, key, crypt_algo).await?;
            result
                .deserialize_inner_value(EncryptedJsonType::deserialize_json_bytes)
                .change_context(errors::CryptoError::DecodingFailed)
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_encrypt_via_api(
            state: &KeyManagerState,
            masked_data: FxHashMap<String, Secret<EncryptedJsonType<T>, S>>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            let hashmap_capacity = masked_data.len();
            let data_bytes = masked_data.iter().try_fold(
                FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()),
                |mut map, (key, value)| {
                    let value_bytes = EncryptedJsonType::serialize_json_bytes(value.peek())?;
                    map.insert(key.to_owned(), value_bytes);
                    Ok::<_, error_stack::Report<errors::CryptoError>>(map)
                },
            )?;

            let result: FxHashMap<String, crypto::Encryptable<Secret<Vec<u8>>>> =
                TypeEncryption::batch_encrypt_via_api(
                    state, data_bytes, identifier, key, crypt_algo,
                )
                .await?;
            let result_hashmap = result.into_iter().try_fold(
                FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()),
                |mut map, (key, value)| {
                    let original_value = masked_data
                        .get(&key)
                        .ok_or(errors::CryptoError::EncodingFailed)
                        .attach_printable_lazy(|| {
                            format!("Failed to find {key} in input hashmap")
                        })?;
                    map.insert(
                        key,
                        Self::new(original_value.clone(), value.into_encrypted()),
                    );
                    Ok::<_, error_stack::Report<errors::CryptoError>>(map)
                },
            )?;

            Ok(result_hashmap)
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: FxHashMap<String, Encryption>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            let result: FxHashMap<String, crypto::Encryptable<Secret<Vec<u8>>>> =
                TypeEncryption::batch_decrypt_via_api(
                    state,
                    encrypted_data,
                    identifier,
                    key,
                    crypt_algo,
                )
                .await?;

            let hashmap_capacity = result.len();
            let result_hashmap = result.into_iter().try_fold(
                FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()),
                |mut map, (key, value)| {
                    let deserialized_value = value
                        .deserialize_inner_value(EncryptedJsonType::deserialize_json_bytes)
                        .change_context(errors::CryptoError::DecodingFailed)?;
                    map.insert(key, deserialized_value);
                    Ok::<_, error_stack::Report<errors::CryptoError>>(map)
                },
            )?;

            Ok(result_hashmap)
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_encrypt(
            masked_data: FxHashMap<String, Secret<EncryptedJsonType<T>, S>>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            let hashmap_capacity = masked_data.len();
            let data_bytes = masked_data.iter().try_fold(
                FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()),
                |mut map, (key, value)| {
                    let value_bytes = EncryptedJsonType::serialize_json_bytes(value.peek())?;
                    map.insert(key.to_owned(), value_bytes);
                    Ok::<_, error_stack::Report<errors::CryptoError>>(map)
                },
            )?;

            let result: FxHashMap<String, crypto::Encryptable<Secret<Vec<u8>>>> =
                TypeEncryption::batch_encrypt(data_bytes, key, crypt_algo).await?;
            let result_hashmap = result.into_iter().try_fold(
                FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()),
                |mut map, (key, value)| {
                    let original_value = masked_data
                        .get(&key)
                        .ok_or(errors::CryptoError::EncodingFailed)
                        .attach_printable_lazy(|| {
                            format!("Failed to find {key} in input hashmap")
                        })?;
                    map.insert(
                        key,
                        Self::new(original_value.clone(), value.into_encrypted()),
                    );
                    Ok::<_, error_stack::Report<errors::CryptoError>>(map)
                },
            )?;

            Ok(result_hashmap)
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_decrypt(
            encrypted_data: FxHashMap<String, Encryption>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            let result: FxHashMap<String, crypto::Encryptable<Secret<Vec<u8>>>> =
                TypeEncryption::batch_decrypt(encrypted_data, key, crypt_algo).await?;

            let hashmap_capacity = result.len();
            let result_hashmap = result.into_iter().try_fold(
                FxHashMap::with_capacity_and_hasher(hashmap_capacity, Default::default()),
                |mut map, (key, value)| {
                    let deserialized_value = value
                        .deserialize_inner_value(EncryptedJsonType::deserialize_json_bytes)
                        .change_context(errors::CryptoError::DecodingFailed)?;
                    map.insert(key, deserialized_value);
                    Ok::<_, error_stack::Report<errors::CryptoError>>(map)
                },
            )?;

            Ok(result_hashmap)
        }
    }

    #[async_trait]
    impl<
            V: crypto::DecodeMessage + crypto::EncodeMessage + Send + 'static,
            S: masking::Strategy<Vec<u8>> + Send + Sync,
        > TypeEncryption<Vec<u8>, V, S> for crypto::Encryptable<Secret<Vec<u8>, S>>
    {
        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn encrypt_via_api(
            state: &KeyManagerState,
            masked_data: Secret<Vec<u8>, S>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::encrypt(masked_data, key, crypt_algo).await
            } else {
                let result: Result<
                    EncryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/encrypt",
                    EncryptDataRequest::from((masked_data.clone(), identifier)),
                )
                .await;
                match result {
                    Ok(response) => Ok(ForeignFrom::foreign_from((masked_data.clone(), response))),
                    Err(err) => {
                        logger::error!("Encryption error {:?}", err);
                        metrics::ENCRYPTION_API_FAILURES.add(1, &[]);
                        logger::info!("Fall back to Application Encryption");
                        Self::encrypt(masked_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: Encryption,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::decrypt(encrypted_data, key, crypt_algo).await
            } else {
                let result: Result<
                    DecryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/decrypt",
                    TransientDecryptDataRequest::from((encrypted_data.clone(), identifier)),
                )
                .await;
                let decrypted = match result {
                    Ok(decrypted_data) => {
                        ForeignTryFrom::foreign_try_from((encrypted_data.clone(), decrypted_data))
                    }
                    Err(err) => {
                        logger::error!("Decryption error {:?}", err);
                        Err(err.change_context(errors::CryptoError::DecodingFailed))
                    }
                };
                match decrypted {
                    Ok(de) => Ok(de),
                    Err(_) => {
                        metrics::DECRYPTION_API_FAILURES.add(1, &[]);
                        logger::info!("Fall back to Application Decryption");
                        Self::decrypt(encrypted_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn encrypt(
            masked_data: Secret<Vec<u8>, S>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]);
            let encrypted_data = crypt_algo.encode_message(key, masked_data.peek())?;
            Ok(Self::new(masked_data, encrypted_data.into()))
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn decrypt(
            encrypted_data: Encryption,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<Self, errors::CryptoError> {
            metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]);
            let encrypted = encrypted_data.into_inner();
            let data = crypt_algo.decode_message(key, encrypted.clone())?;
            Ok(Self::new(data.into(), encrypted))
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_encrypt_via_api(
            state: &KeyManagerState,
            masked_data: FxHashMap<String, Secret<Vec<u8>, S>>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::batch_encrypt(masked_data, key, crypt_algo).await
            } else {
                let result: Result<
                    BatchEncryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/encrypt",
                    BatchEncryptDataRequest::from((masked_data.clone(), identifier)),
                )
                .await;
                match result {
                    Ok(response) => Ok(ForeignFrom::foreign_from((masked_data, response))),
                    Err(err) => {
                        metrics::ENCRYPTION_API_FAILURES.add(1, &[]);
                        logger::error!("Encryption error {:?}", err);
                        logger::info!("Fall back to Application Encryption");
                        Self::batch_encrypt(masked_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_decrypt_via_api(
            state: &KeyManagerState,
            encrypted_data: FxHashMap<String, Encryption>,
            identifier: Identifier,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            // If encryption service is not enabled, fall back to application encryption or else call encryption service
            if !is_encryption_service_enabled(state) {
                Self::batch_decrypt(encrypted_data, key, crypt_algo).await
            } else {
                let result: Result<
                    BatchDecryptDataResponse,
                    error_stack::Report<errors::KeyManagerClientError>,
                > = call_encryption_service(
                    state,
                    Method::POST,
                    "data/decrypt",
                    TransientBatchDecryptDataRequest::from((encrypted_data.clone(), identifier)),
                )
                .await;
                let decrypted = match result {
                    Ok(response) => {
                        ForeignTryFrom::foreign_try_from((encrypted_data.clone(), response))
                    }
                    Err(err) => {
                        logger::error!("Decryption error {:?}", err);
                        Err(err.change_context(errors::CryptoError::DecodingFailed))
                    }
                };
                match decrypted {
                    Ok(de) => Ok(de),
                    Err(_) => {
                        metrics::DECRYPTION_API_FAILURES.add(1, &[]);
                        logger::info!("Fall back to Application Decryption");
                        Self::batch_decrypt(encrypted_data, key, crypt_algo).await
                    }
                }
            }
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_encrypt(
            masked_data: FxHashMap<String, Secret<Vec<u8>, S>>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            metrics::APPLICATION_ENCRYPTION_COUNT.add(1, &[]);
            masked_data
                .into_iter()
                .map(|(k, v)| {
                    Ok((
                        k,
                        Self::new(v.clone(), crypt_algo.encode_message(key, v.peek())?.into()),
                    ))
                })
                .collect()
        }

        // Do not remove the `skip_all` as the key would be logged otherwise
        #[instrument(skip_all)]
        async fn batch_decrypt(
            encrypted_data: FxHashMap<String, Encryption>,
            key: &[u8],
            crypt_algo: V,
        ) -> CustomResult<FxHashMap<String, Self>, errors::CryptoError> {
            metrics::APPLICATION_DECRYPTION_COUNT.add(1, &[]);
            encrypted_data
                .into_iter()
                .map(|(k, v)| {
                    Ok((
                        k,
                        Self::new(
                            crypt_algo
                                .decode_message(key, v.clone().into_inner().clone())?
                                .into(),
                            v.into_inner(),
                        ),
                    ))
                })
                .collect()
        }
    }
}

#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct EncryptedJsonType<T>(T);

impl<T> EncryptedJsonType<T> {
    pub fn inner(&self) -> &T {
        &self.0
    }

    pub fn into_inner(self) -> T {
        self.0
    }
}

impl<T> From<T> for EncryptedJsonType<T> {
    fn from(value: T) -> Self {
        Self(value)
    }
}

impl<T> std::ops::Deref for EncryptedJsonType<T> {
    type Target = T;

    fn deref(&self) -> &Self::Target {
        self.inner()
    }
}

/// Type alias for `Option<Encryptable<Secret<EncryptedJsonType<T>>>>`
pub type OptionalEncryptableJsonType<T> = Option<crypto::Encryptable<Secret<EncryptedJsonType<T>>>>;

pub trait Lift<U> {
    type SelfWrapper<T>;
    type OtherWrapper<T, E>;

    fn lift<Func, E, V>(self, func: Func) -> Self::OtherWrapper<V, E>
    where
        Func: Fn(Self::SelfWrapper<U>) -> Self::OtherWrapper<V, E>;
}

impl<U> Lift<U> for Option<U> {
    type SelfWrapper<T> = Option<T>;
    type OtherWrapper<T, E> = CustomResult<Option<T>, E>;

    fn lift<Func, E, V>(self, func: Func) -> Self::OtherWrapper<V, E>
    where
        Func: Fn(Self::SelfWrapper<U>) -> Self::OtherWrapper<V, E>,
    {
        func(self)
    }
}

#[async_trait]
pub trait AsyncLift<U> {
    type SelfWrapper<T>;
    type OtherWrapper<T, E>;

    async fn async_lift<Func, F, E, V>(self, func: Func) -> Self::OtherWrapper<V, E>
    where
        Func: Fn(Self::SelfWrapper<U>) -> F + Send + Sync,
        F: futures::Future<Output = Self::OtherWrapper<V, E>> + Send;
}

#[async_trait]
impl<U, V: Lift<U> + Lift<U, SelfWrapper<U> = V> + Send> AsyncLift<U> for V {
    type SelfWrapper<T> = <V as Lift<U>>::SelfWrapper<T>;
    type OtherWrapper<T, E> = <V as Lift<U>>::OtherWrapper<T, E>;

    async fn async_lift<Func, F, E, W>(self, func: Func) -> Self::OtherWrapper<W, E>
    where
        Func: Fn(Self::SelfWrapper<U>) -> F + Send + Sync,
        F: futures::Future<Output = Self::OtherWrapper<W, E>> + Send,
    {
        func(self).await
    }
}

#[inline]
async fn encrypt<E: Clone, S>(
    state: &KeyManagerState,
    inner: Secret<E, S>,
    identifier: Identifier,
    key: &[u8],
) -> CustomResult<crypto::Encryptable<Secret<E, S>>, CryptoError>
where
    S: masking::Strategy<E>,
    crypto::Encryptable<Secret<E, S>>: TypeEncryption<E, crypto::GcmAes256, S>,
{
    record_operation_time(
        crypto::Encryptable::encrypt_via_api(state, inner, identifier, key, crypto::GcmAes256),
        &metrics::ENCRYPTION_TIME,
        &[],
    )
    .await
}

#[inline]
async fn batch_encrypt<E: Clone, S>(
    state: &KeyManagerState,
    inner: FxHashMap<String, Secret<E, S>>,
    identifier: Identifier,
    key: &[u8],
) -> CustomResult<FxHashMap<String, crypto::Encryptable<Secret<E, S>>>, CryptoError>
where
    S: masking::Strategy<E>,
    crypto::Encryptable<Secret<E, S>>: TypeEncryption<E, crypto::GcmAes256, S>,
{
    if !inner.is_empty() {
        record_operation_time(
            crypto::Encryptable::batch_encrypt_via_api(
                state,
                inner,
                identifier,
                key,
                crypto::GcmAes256,
            ),
            &metrics::ENCRYPTION_TIME,
            &[],
        )
        .await
    } else {
        Ok(FxHashMap::default())
    }
}

#[inline]
async fn encrypt_optional<E: Clone, S>(
    state: &KeyManagerState,
    inner: Option<Secret<E, S>>,
    identifier: Identifier,
    key: &[u8],
) -> CustomResult<Option<crypto::Encryptable<Secret<E, S>>>, CryptoError>
where
    Secret<E, S>: Send,
    S: masking::Strategy<E>,
    crypto::Encryptable<Secret<E, S>>: TypeEncryption<E, crypto::GcmAes256, S>,
{
    inner
        .async_map(|f| encrypt(state, f, identifier, key))
        .await
        .transpose()
}

#[inline]
async fn decrypt_optional<T: Clone, S: masking::Strategy<T>>(
    state: &KeyManagerState,
    inner: Option<Encryption>,
    identifier: Identifier,
    key: &[u8],
) -> CustomResult<Option<crypto::Encryptable<Secret<T, S>>>, CryptoError>
where
    crypto::Encryptable<Secret<T, S>>: TypeEncryption<T, crypto::GcmAes256, S>,
{
    inner
        .async_map(|item| decrypt(state, item, identifier, key))
        .await
        .transpose()
}

#[inline]
async fn decrypt<T: Clone, S: masking::Strategy<T>>(
    state: &KeyManagerState,
    inner: Encryption,
    identifier: Identifier,
    key: &[u8],
) -> CustomResult<crypto::Encryptable<Secret<T, S>>, CryptoError>
where
    crypto::Encryptable<Secret<T, S>>: TypeEncryption<T, crypto::GcmAes256, S>,
{
    record_operation_time(
        crypto::Encryptable::decrypt_via_api(state, inner, identifier, key, crypto::GcmAes256),
        &metrics::DECRYPTION_TIME,
        &[],
    )
    .await
}

#[inline]
async fn batch_decrypt<E: Clone, S>(
    state: &KeyManagerState,
    inner: FxHashMap<String, Encryption>,
    identifier: Identifier,
    key: &[u8],
) -> CustomResult<FxHashMap<String, crypto::Encryptable<Secret<E, S>>>, CryptoError>
where
    S: masking::Strategy<E>,
    crypto::Encryptable<Secret<E, S>>: TypeEncryption<E, crypto::GcmAes256, S>,
{
    if !inner.is_empty() {
        record_operation_time(
            crypto::Encryptable::batch_decrypt_via_api(
                state,
                inner,
                identifier,
                key,
                crypto::GcmAes256,
            ),
            &metrics::ENCRYPTION_TIME,
            &[],
        )
        .await
    } else {
        Ok(FxHashMap::default())
    }
}

pub enum CryptoOperation<T: Clone, S: masking::Strategy<T>> {
    Encrypt(Secret<T, S>),
    EncryptOptional(Option<Secret<T, S>>),
    Decrypt(Encryption),
    DecryptOptional(Option<Encryption>),
    BatchEncrypt(FxHashMap<String, Secret<T, S>>),
    BatchDecrypt(FxHashMap<String, Encryption>),
}

use errors::CryptoError;

#[derive(router_derive::TryGetEnumVariant)]
#[error(CryptoError::EncodingFailed)]
pub enum CryptoOutput<T: Clone, S: masking::Strategy<T>> {
    Operation(crypto::Encryptable<Secret<T, S>>),
    OptionalOperation(Option<crypto::Encryptable<Secret<T, S>>>),
    BatchOperation(FxHashMap<String, crypto::Encryptable<Secret<T, S>>>),
}

// Do not remove the `skip_all` as the key would be logged otherwise
#[instrument(skip_all, fields(table = table_name))]
pub async fn crypto_operation<T: Clone + Send, S: masking::Strategy<T>>(
    state: &KeyManagerState,
    table_name: &str,
    operation: CryptoOperation<T, S>,
    identifier: Identifier,
    key: &[u8],
) -> CustomResult<CryptoOutput<T, S>, CryptoError>
where
    Secret<T, S>: Send,
    crypto::Encryptable<Secret<T, S>>: TypeEncryption<T, crypto::GcmAes256, S>,
{
    match operation {
        CryptoOperation::Encrypt(data) => {
            let data = encrypt(state, data, identifier, key).await?;
            Ok(CryptoOutput::Operation(data))
        }
        CryptoOperation::EncryptOptional(data) => {
            let data = encrypt_optional(state, data, identifier, key).await?;
            Ok(CryptoOutput::OptionalOperation(data))
        }
        CryptoOperation::Decrypt(data) => {
            let data = decrypt(state, data, identifier, key).await?;
            Ok(CryptoOutput::Operation(data))
        }
        CryptoOperation::DecryptOptional(data) => {
            let data = decrypt_optional(state, data, identifier, key).await?;
            Ok(CryptoOutput::OptionalOperation(data))
        }
        CryptoOperation::BatchEncrypt(data) => {
            let data = batch_encrypt(state, data, identifier, key).await?;
            Ok(CryptoOutput::BatchOperation(data))
        }
        CryptoOperation::BatchDecrypt(data) => {
            let data = batch_decrypt(state, data, identifier, key).await?;
            Ok(CryptoOutput::BatchOperation(data))
        }
    }
}

pub(crate) mod metrics {
    use router_env::{counter_metric, global_meter, histogram_metric_f64};

    global_meter!(GLOBAL_METER, "ROUTER_API");

    // Encryption and Decryption metrics
    histogram_metric_f64!(ENCRYPTION_TIME, GLOBAL_METER);
    histogram_metric_f64!(DECRYPTION_TIME, GLOBAL_METER);
    counter_metric!(ENCRYPTION_API_FAILURES, GLOBAL_METER);
    counter_metric!(DECRYPTION_API_FAILURES, GLOBAL_METER);
    counter_metric!(APPLICATION_ENCRYPTION_COUNT, GLOBAL_METER);
    counter_metric!(APPLICATION_DECRYPTION_COUNT, GLOBAL_METER);
}