// File: crates/router/src/core/customers.rs
// Module: router
// Public functions: 8

use common_utils::{
    crypto::Encryptable,
    errors::ReportSwitchExt,
    ext_traits::AsyncExt,
    id_type, pii, type_name,
    types::{
        keymanager::{Identifier, KeyManagerState, ToEncryptable},
        Description,
    },
};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::payment_methods as payment_methods_domain;
use masking::{ExposeInterface, Secret, SwitchStrategy};
use payment_methods::controller::PaymentMethodsController;
use router_env::{instrument, tracing};

#[cfg(feature = "v2")]
use crate::core::payment_methods::cards::create_encrypted_data;
#[cfg(feature = "v1")]
use crate::utils::CustomerAddress;
use crate::{
    core::{
        errors::{self, StorageErrorExt},
        payment_methods::{cards, network_tokenization},
    },
    db::StorageInterface,
    pii::PeekInterface,
    routes::{metrics, SessionState},
    services,
    types::{
        api::customers,
        domain::{self, types},
        storage::{self, enums},
        transformers::ForeignFrom,
    },
};

pub const REDACTED: &str = "Redacted";

#[instrument(skip(state))]
pub async fn create_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    customer_data: customers::CustomerRequest,
    connector_customer_details: Option<Vec<payment_methods_domain::ConnectorCustomerDetails>>,
) -> errors::CustomerResponse<customers::CustomerResponse> {
    let db: &dyn StorageInterface = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let merchant_reference_id = customer_data.get_merchant_reference_id();

    let merchant_id = merchant_context.get_merchant_account().get_id();

    let merchant_reference_id_customer = MerchantReferenceIdForCustomer {
        merchant_reference_id: merchant_reference_id.as_ref(),
        merchant_id,
        merchant_account: merchant_context.get_merchant_account(),
        key_store: merchant_context.get_merchant_key_store(),
        key_manager_state,
    };

    // We first need to validate whether the customer with the given customer id already exists
    // this may seem like a redundant db call, as the insert_customer will anyway return this error
    //
    // Consider a scenario where the address is inserted and then when inserting the customer,
    // it errors out, now the address that was inserted is not deleted

    merchant_reference_id_customer
        .verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(db)
        .await?;

    let domain_customer = customer_data
        .create_domain_model_from_request(
            &connector_customer_details,
            db,
            &merchant_reference_id,
            &merchant_context,
            key_manager_state,
            &state,
        )
        .await?;

    let customer = db
        .insert_customer(
            domain_customer,
            key_manager_state,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_duplicate_response(errors::CustomersErrorResponse::CustomerAlreadyExists)?;

    customer_data.generate_response(&customer)
}

#[async_trait::async_trait]
trait CustomerCreateBridge {
    async fn create_domain_model_from_request<'a>(
        &'a self,
        connector_customer_details: &'a Option<
            Vec<payment_methods_domain::ConnectorCustomerDetails>,
        >,
        db: &'a dyn StorageInterface,
        merchant_reference_id: &'a Option<id_type::CustomerId>,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse>;

    fn generate_response<'a>(
        &'a self,
        customer: &'a domain::Customer,
    ) -> errors::CustomerResponse<customers::CustomerResponse>;
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl CustomerCreateBridge for customers::CustomerRequest {
    async fn create_domain_model_from_request<'a>(
        &'a self,
        connector_customer_details: &'a Option<
            Vec<payment_methods_domain::ConnectorCustomerDetails>,
        >,
        db: &'a dyn StorageInterface,
        merchant_reference_id: &'a Option<id_type::CustomerId>,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse> {
        // Setting default billing address to Db
        let address = self.get_address();
        let merchant_id = merchant_context.get_merchant_account().get_id();
        let key = merchant_context
            .get_merchant_key_store()
            .key
            .get_inner()
            .peek();

        let customer_billing_address_struct = AddressStructForDbEntry {
            address: address.as_ref(),
            customer_data: self,
            merchant_id,
            customer_id: merchant_reference_id.as_ref(),
            storage_scheme: merchant_context.get_merchant_account().storage_scheme,
            key_store: merchant_context.get_merchant_key_store(),
            key_manager_state,
            state,
        };

        let address_from_db = customer_billing_address_struct
            .encrypt_customer_address_and_set_to_db(db)
            .await?;

        let encrypted_data = types::crypto_operation(
            key_manager_state,
            type_name!(domain::Customer),
            types::CryptoOperation::BatchEncrypt(
                domain::FromRequestEncryptableCustomer::to_encryptable(
                    domain::FromRequestEncryptableCustomer {
                        name: self.name.clone(),
                        email: self.email.clone().map(|a| a.expose().switch_strategy()),
                        phone: self.phone.clone(),
                        tax_registration_id: self.tax_registration_id.clone(),
                    },
                ),
            ),
            Identifier::Merchant(
                merchant_context
                    .get_merchant_key_store()
                    .merchant_id
                    .clone(),
            ),
            key,
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .switch()
        .attach_printable("Failed while encrypting Customer")?;

        let encryptable_customer =
            domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
                .change_context(errors::CustomersErrorResponse::InternalServerError)?;

        let connector_customer = connector_customer_details.as_ref().map(|details_vec| {
            let mut map = serde_json::Map::new();
            for details in details_vec {
                let merchant_connector_id =
                    details.merchant_connector_id.get_string_repr().to_string();
                let connector_customer_id = details.connector_customer_id.clone();
                map.insert(merchant_connector_id, connector_customer_id.into());
            }
            pii::SecretSerdeValue::new(serde_json::Value::Object(map))
        });

        Ok(domain::Customer {
            customer_id: merchant_reference_id
                .to_owned()
                .ok_or(errors::CustomersErrorResponse::InternalServerError)?,
            merchant_id: merchant_id.to_owned(),
            name: encryptable_customer.name,
            email: encryptable_customer.email.map(|email| {
                let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> = Encryptable::new(
                    email.clone().into_inner().switch_strategy(),
                    email.into_encrypted(),
                );
                encryptable
            }),
            phone: encryptable_customer.phone,
            description: self.description.clone(),
            phone_country_code: self.phone_country_code.clone(),
            metadata: self.metadata.clone(),
            connector_customer,
            address_id: address_from_db.clone().map(|addr| addr.address_id),
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            default_payment_method_id: None,
            updated_by: None,
            version: common_types::consts::API_VERSION,
            tax_registration_id: encryptable_customer.tax_registration_id,
        })
    }

    fn generate_response<'a>(
        &'a self,
        customer: &'a domain::Customer,
    ) -> errors::CustomerResponse<customers::CustomerResponse> {
        let address = self.get_address();
        Ok(services::ApplicationResponse::Json(
            customers::CustomerResponse::foreign_from((customer.clone(), address)),
        ))
    }
}

#[cfg(feature = "v2")]
#[async_trait::async_trait]
impl CustomerCreateBridge for customers::CustomerRequest {
    async fn create_domain_model_from_request<'a>(
        &'a self,
        connector_customer_details: &'a Option<
            Vec<payment_methods_domain::ConnectorCustomerDetails>,
        >,
        _db: &'a dyn StorageInterface,
        merchant_reference_id: &'a Option<id_type::CustomerId>,
        merchant_context: &'a domain::MerchantContext,
        key_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse> {
        let default_customer_billing_address = self.get_default_customer_billing_address();
        let encrypted_customer_billing_address = default_customer_billing_address
            .async_map(|billing_address| {
                create_encrypted_data(
                    key_state,
                    merchant_context.get_merchant_key_store(),
                    billing_address,
                )
            })
            .await
            .transpose()
            .change_context(errors::CustomersErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt default customer billing address")?;
        let default_customer_shipping_address = self.get_default_customer_shipping_address();
        let encrypted_customer_shipping_address = default_customer_shipping_address
            .async_map(|shipping_address| {
                create_encrypted_data(
                    key_state,
                    merchant_context.get_merchant_key_store(),
                    shipping_address,
                )
            })
            .await
            .transpose()
            .change_context(errors::CustomersErrorResponse::InternalServerError)
            .attach_printable("Unable to encrypt default customer shipping address")?;

        let merchant_id = merchant_context.get_merchant_account().get_id().clone();
        let key = merchant_context
            .get_merchant_key_store()
            .key
            .get_inner()
            .peek();

        let encrypted_data = types::crypto_operation(
            key_state,
            type_name!(domain::Customer),
            types::CryptoOperation::BatchEncrypt(
                domain::FromRequestEncryptableCustomer::to_encryptable(
                    domain::FromRequestEncryptableCustomer {
                        name: Some(self.name.clone()),
                        email: Some(self.email.clone().expose().switch_strategy()),
                        phone: self.phone.clone(),
                        tax_registration_id: self.tax_registration_id.clone(),
                    },
                ),
            ),
            Identifier::Merchant(
                merchant_context
                    .get_merchant_key_store()
                    .merchant_id
                    .clone(),
            ),
            key,
        )
        .await
        .and_then(|val| val.try_into_batchoperation())
        .switch()
        .attach_printable("Failed while encrypting Customer")?;

        let encryptable_customer =
            domain::FromRequestEncryptableCustomer::from_encryptable(encrypted_data)
                .change_context(errors::CustomersErrorResponse::InternalServerError)?;

        let connector_customer = connector_customer_details.as_ref().map(|details_vec| {
            let map: std::collections::HashMap<_, _> = details_vec
                .iter()
                .map(|details| {
                    (
                        details.merchant_connector_id.clone(),
                        details.connector_customer_id.to_string(),
                    )
                })
                .collect();
            common_types::customers::ConnectorCustomerMap::new(map)
        });

        Ok(domain::Customer {
            id: id_type::GlobalCustomerId::generate(&state.conf.cell_information.id),
            merchant_reference_id: merchant_reference_id.to_owned(),
            merchant_id,
            name: encryptable_customer.name,
            email: encryptable_customer.email.map(|email| {
                let encryptable: Encryptable<Secret<String, pii::EmailStrategy>> = Encryptable::new(
                    email.clone().into_inner().switch_strategy(),
                    email.into_encrypted(),
                );
                encryptable
            }),
            phone: encryptable_customer.phone,
            description: self.description.clone(),
            phone_country_code: self.phone_country_code.clone(),
            metadata: self.metadata.clone(),
            connector_customer,
            created_at: common_utils::date_time::now(),
            modified_at: common_utils::date_time::now(),
            default_payment_method_id: None,
            updated_by: None,
            default_billing_address: encrypted_customer_billing_address.map(Into::into),
            default_shipping_address: encrypted_customer_shipping_address.map(Into::into),
            version: common_types::consts::API_VERSION,
            status: common_enums::DeleteStatus::Active,
            tax_registration_id: encryptable_customer.tax_registration_id,
        })
    }

    fn generate_response<'a>(
        &'a self,
        customer: &'a domain::Customer,
    ) -> errors::CustomerResponse<customers::CustomerResponse> {
        Ok(services::ApplicationResponse::Json(
            customers::CustomerResponse::foreign_from(customer.clone()),
        ))
    }
}

#[cfg(feature = "v1")]
struct AddressStructForDbEntry<'a> {
    address: Option<&'a api_models::payments::AddressDetails>,
    customer_data: &'a customers::CustomerRequest,
    merchant_id: &'a id_type::MerchantId,
    customer_id: Option<&'a id_type::CustomerId>,
    storage_scheme: common_enums::MerchantStorageScheme,
    key_store: &'a domain::MerchantKeyStore,
    key_manager_state: &'a KeyManagerState,
    state: &'a SessionState,
}

#[cfg(feature = "v1")]
impl AddressStructForDbEntry<'_> {
    async fn encrypt_customer_address_and_set_to_db(
        &self,
        db: &dyn StorageInterface,
    ) -> errors::CustomResult<Option<domain::Address>, errors::CustomersErrorResponse> {
        let encrypted_customer_address = self
            .address
            .async_map(|addr| async {
                self.customer_data
                    .get_domain_address(
                        self.state,
                        addr.clone(),
                        self.merchant_id,
                        self.customer_id
                            .ok_or(errors::CustomersErrorResponse::InternalServerError)?, // should we raise error since in v1 appilcation is supposed to have this id or generate it at this point.
                        self.key_store.key.get_inner().peek(),
                        self.storage_scheme,
                    )
                    .await
                    .switch()
                    .attach_printable("Failed while encrypting address")
            })
            .await
            .transpose()?;

        encrypted_customer_address
            .async_map(|encrypt_add| async {
                db.insert_address_for_customers(self.key_manager_state, encrypt_add, self.key_store)
                    .await
                    .switch()
                    .attach_printable("Failed while inserting new address")
            })
            .await
            .transpose()
    }
}

struct MerchantReferenceIdForCustomer<'a> {
    merchant_reference_id: Option<&'a id_type::CustomerId>,
    merchant_id: &'a id_type::MerchantId,
    merchant_account: &'a domain::MerchantAccount,
    key_store: &'a domain::MerchantKeyStore,
    key_manager_state: &'a KeyManagerState,
}

#[cfg(feature = "v1")]
impl<'a> MerchantReferenceIdForCustomer<'a> {
    async fn verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(
        &self,
        db: &dyn StorageInterface,
    ) -> Result<Option<()>, error_stack::Report<errors::CustomersErrorResponse>> {
        self.merchant_reference_id
            .async_map(|cust| async {
                self.verify_if_merchant_reference_not_present_by_merchant_reference_id(cust, db)
                    .await
            })
            .await
            .transpose()
    }

    async fn verify_if_merchant_reference_not_present_by_merchant_reference_id(
        &self,
        cus: &'a id_type::CustomerId,
        db: &dyn StorageInterface,
    ) -> Result<(), error_stack::Report<errors::CustomersErrorResponse>> {
        match db
            .find_customer_by_customer_id_merchant_id(
                self.key_manager_state,
                cus,
                self.merchant_id,
                self.key_store,
                self.merchant_account.storage_scheme,
            )
            .await
        {
            Err(err) => {
                if !err.current_context().is_db_not_found() {
                    Err(err).switch()
                } else {
                    Ok(())
                }
            }
            Ok(_) => Err(report!(
                errors::CustomersErrorResponse::CustomerAlreadyExists
            )),
        }
    }
}

#[cfg(feature = "v2")]
impl<'a> MerchantReferenceIdForCustomer<'a> {
    async fn verify_if_merchant_reference_not_present_by_optional_merchant_reference_id(
        &self,
        db: &dyn StorageInterface,
    ) -> Result<Option<()>, error_stack::Report<errors::CustomersErrorResponse>> {
        self.merchant_reference_id
            .async_map(|merchant_ref| async {
                self.verify_if_merchant_reference_not_present_by_merchant_reference(
                    merchant_ref,
                    db,
                )
                .await
            })
            .await
            .transpose()
    }

    async fn verify_if_merchant_reference_not_present_by_merchant_reference(
        &self,
        merchant_ref: &'a id_type::CustomerId,
        db: &dyn StorageInterface,
    ) -> Result<(), error_stack::Report<errors::CustomersErrorResponse>> {
        match db
            .find_customer_by_merchant_reference_id_merchant_id(
                self.key_manager_state,
                merchant_ref,
                self.merchant_id,
                self.key_store,
                self.merchant_account.storage_scheme,
            )
            .await
        {
            Err(err) => {
                if !err.current_context().is_db_not_found() {
                    Err(err).switch()
                } else {
                    Ok(())
                }
            }
            Ok(_) => Err(report!(
                errors::CustomersErrorResponse::CustomerAlreadyExists
            )),
        }
    }
}

#[cfg(feature = "v1")]
#[instrument(skip(state))]
pub async fn retrieve_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    _profile_id: Option<id_type::ProfileId>,
    customer_id: id_type::CustomerId,
) -> errors::CustomerResponse<customers::CustomerResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let response = db
        .find_customer_optional_with_redacted_customer_details_by_customer_id_merchant_id(
            key_manager_state,
            &customer_id,
            merchant_context.get_merchant_account().get_id(),
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .switch()?
        .ok_or(errors::CustomersErrorResponse::CustomerNotFound)?;

    let address = match &response.address_id {
        Some(address_id) => Some(api_models::payments::AddressDetails::from(
            db.find_address_by_address_id(
                key_manager_state,
                address_id,
                merchant_context.get_merchant_key_store(),
            )
            .await
            .switch()?,
        )),
        None => None,
    };
    Ok(services::ApplicationResponse::Json(
        customers::CustomerResponse::foreign_from((response, address)),
    ))
}

#[cfg(feature = "v2")]
#[instrument(skip(state))]
pub async fn retrieve_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    id: id_type::GlobalCustomerId,
) -> errors::CustomerResponse<customers::CustomerResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();

    let response = db
        .find_customer_by_global_id(
            key_manager_state,
            &id,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .switch()?;

    Ok(services::ApplicationResponse::Json(
        customers::CustomerResponse::foreign_from(response),
    ))
}

#[instrument(skip(state))]
pub async fn list_customers(
    state: SessionState,
    merchant_id: id_type::MerchantId,
    _profile_id_list: Option<Vec<id_type::ProfileId>>,
    key_store: domain::MerchantKeyStore,
    request: customers::CustomerListRequest,
) -> errors::CustomerResponse<Vec<customers::CustomerResponse>> {
    let db = state.store.as_ref();

    let customer_list_constraints = crate::db::customers::CustomerListConstraints {
        limit: request
            .limit
            .unwrap_or(crate::consts::DEFAULT_LIST_API_LIMIT),
        offset: request.offset,
    };

    let domain_customers = db
        .list_customers_by_merchant_id(
            &(&state).into(),
            &merchant_id,
            &key_store,
            customer_list_constraints,
        )
        .await
        .switch()?;

    #[cfg(feature = "v1")]
    let customers = domain_customers
        .into_iter()
        .map(|domain_customer| customers::CustomerResponse::foreign_from((domain_customer, None)))
        .collect();

    #[cfg(feature = "v2")]
    let customers = domain_customers
        .into_iter()
        .map(customers::CustomerResponse::foreign_from)
        .collect();

    Ok(services::ApplicationResponse::Json(customers))
}

#[cfg(feature = "v2")]
#[instrument(skip_all)]
pub async fn delete_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    id: id_type::GlobalCustomerId,
) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
    let db = &*state.store;
    let key_manager_state = &(&state).into();
    id.redact_customer_details_and_generate_response(
        db,
        &merchant_context,
        key_manager_state,
        &state,
    )
    .await
}

#[cfg(feature = "v2")]
#[async_trait::async_trait]
impl CustomerDeleteBridge for id_type::GlobalCustomerId {
    async fn redact_customer_details_and_generate_response<'a>(
        &'a self,
        db: &'a dyn StorageInterface,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
        let customer_orig = db
            .find_customer_by_global_id(
                key_manager_state,
                self,
                merchant_context.get_merchant_key_store(),
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .switch()?;

        let merchant_reference_id = customer_orig.merchant_reference_id.clone();

        let customer_mandates = db.find_mandate_by_global_customer_id(self).await.switch()?;

        for mandate in customer_mandates.into_iter() {
            if mandate.mandate_status == enums::MandateStatus::Active {
                Err(errors::CustomersErrorResponse::MandateActive)?
            }
        }

        match db
            .find_payment_method_list_by_global_customer_id(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                self,
                None,
            )
            .await
        {
            // check this in review
            Ok(customer_payment_methods) => {
                for pm in customer_payment_methods.into_iter() {
                    if pm.get_payment_method_type() == Some(enums::PaymentMethod::Card) {
                        cards::delete_card_by_locker_id(
                            state,
                            self,
                            merchant_context.get_merchant_account().get_id(),
                        )
                        .await
                        .switch()?;
                    }
                    // No solution as of now, need to discuss this further with payment_method_v2

                    // db.delete_payment_method(
                    //     key_manager_state,
                    //     key_store,
                    //     pm,
                    // )
                    // .await
                    // .switch()?;
                }
            }
            Err(error) => {
                if error.current_context().is_db_not_found() {
                    Ok(())
                } else {
                    Err(error)
                        .change_context(errors::CustomersErrorResponse::InternalServerError)
                        .attach_printable(
                            "failed find_payment_method_by_customer_id_merchant_id_list",
                        )
                }?
            }
        };

        let key = merchant_context
            .get_merchant_key_store()
            .key
            .get_inner()
            .peek();

        let identifier = Identifier::Merchant(
            merchant_context
                .get_merchant_key_store()
                .merchant_id
                .clone(),
        );
        let redacted_encrypted_value: Encryptable<Secret<_>> = types::crypto_operation(
            key_manager_state,
            type_name!(storage::Address),
            types::CryptoOperation::Encrypt(REDACTED.to_string().into()),
            identifier.clone(),
            key,
        )
        .await
        .and_then(|val| val.try_into_operation())
        .switch()?;

        let redacted_encrypted_email = Encryptable::new(
            redacted_encrypted_value
                .clone()
                .into_inner()
                .switch_strategy(),
            redacted_encrypted_value.clone().into_encrypted(),
        );

        let updated_customer =
            storage::CustomerUpdate::Update(Box::new(storage::CustomerGeneralUpdate {
                name: Some(redacted_encrypted_value.clone()),
                email: Box::new(Some(redacted_encrypted_email)),
                phone: Box::new(Some(redacted_encrypted_value.clone())),
                description: Some(Description::from_str_unchecked(REDACTED)),
                phone_country_code: Some(REDACTED.to_string()),
                metadata: None,
                connector_customer: Box::new(None),
                default_billing_address: None,
                default_shipping_address: None,
                default_payment_method_id: None,
                status: Some(common_enums::DeleteStatus::Redacted),
                tax_registration_id: Some(redacted_encrypted_value),
            }));

        db.update_customer_by_global_id(
            key_manager_state,
            self,
            customer_orig,
            updated_customer,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .switch()?;

        let response = customers::CustomerDeleteResponse {
            id: self.clone(),
            merchant_reference_id,
            customer_deleted: true,
            address_deleted: true,
            payment_methods_deleted: true,
        };
        metrics::CUSTOMER_REDACTED.add(1, &[]);
        Ok(services::ApplicationResponse::Json(response))
    }
}

#[async_trait::async_trait]
trait CustomerDeleteBridge {
    async fn redact_customer_details_and_generate_response<'a>(
        &'a self,
        db: &'a dyn StorageInterface,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomerResponse<customers::CustomerDeleteResponse>;
}

#[cfg(feature = "v1")]
#[instrument(skip_all)]
pub async fn delete_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    customer_id: id_type::CustomerId,
) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
    let db = &*state.store;
    let key_manager_state = &(&state).into();
    customer_id
        .redact_customer_details_and_generate_response(
            db,
            &merchant_context,
            key_manager_state,
            &state,
        )
        .await
}

#[cfg(feature = "v1")]
#[async_trait::async_trait]
impl CustomerDeleteBridge for id_type::CustomerId {
    async fn redact_customer_details_and_generate_response<'a>(
        &'a self,
        db: &'a dyn StorageInterface,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
    ) -> errors::CustomerResponse<customers::CustomerDeleteResponse> {
        let customer_orig = db
            .find_customer_by_customer_id_merchant_id(
                key_manager_state,
                self,
                merchant_context.get_merchant_account().get_id(),
                merchant_context.get_merchant_key_store(),
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await
            .switch()?;

        let customer_mandates = db
            .find_mandate_by_merchant_id_customer_id(
                merchant_context.get_merchant_account().get_id(),
                self,
            )
            .await
            .switch()?;

        for mandate in customer_mandates.into_iter() {
            if mandate.mandate_status == enums::MandateStatus::Active {
                Err(errors::CustomersErrorResponse::MandateActive)?
            }
        }

        match db
            .find_payment_method_by_customer_id_merchant_id_list(
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                self,
                merchant_context.get_merchant_account().get_id(),
                None,
            )
            .await
        {
            // check this in review
            Ok(customer_payment_methods) => {
                for pm in customer_payment_methods.into_iter() {
                    if pm.get_payment_method_type() == Some(enums::PaymentMethod::Card) {
                        cards::PmCards {
                            state,
                            merchant_context,
                        }
                        .delete_card_from_locker(
                            self,
                            merchant_context.get_merchant_account().get_id(),
                            pm.locker_id.as_ref().unwrap_or(&pm.payment_method_id),
                        )
                        .await
                        .switch()?;

                        if let Some(network_token_ref_id) = pm.network_token_requestor_reference_id
                        {
                            network_tokenization::delete_network_token_from_locker_and_token_service(
                            state,
                            self,
                            merchant_context.get_merchant_account().get_id(),
                            pm.payment_method_id.clone(),
                            pm.network_token_locker_id,
                            network_token_ref_id,
                            merchant_context,
                        )
                        .await
                        .switch()?;
                        }
                    }

                    db.delete_payment_method_by_merchant_id_payment_method_id(
                        key_manager_state,
                        merchant_context.get_merchant_key_store(),
                        merchant_context.get_merchant_account().get_id(),
                        &pm.payment_method_id,
                    )
                    .await
                    .change_context(errors::CustomersErrorResponse::InternalServerError)
                    .attach_printable(
                        "failed to delete payment method while redacting customer details",
                    )?;
                }
            }
            Err(error) => {
                if error.current_context().is_db_not_found() {
                    Ok(())
                } else {
                    Err(error)
                        .change_context(errors::CustomersErrorResponse::InternalServerError)
                        .attach_printable(
                            "failed find_payment_method_by_customer_id_merchant_id_list",
                        )
                }?
            }
        };

        let key = merchant_context
            .get_merchant_key_store()
            .key
            .get_inner()
            .peek();
        let identifier = Identifier::Merchant(
            merchant_context
                .get_merchant_key_store()
                .merchant_id
                .clone(),
        );
        let redacted_encrypted_value: Encryptable<Secret<_>> = types::crypto_operation(
            key_manager_state,
            type_name!(storage::Address),
            types::CryptoOperation::Encrypt(REDACTED.to_string().into()),
            identifier.clone(),
            key,
        )
        .await
        .and_then(|val| val.try_into_operation())
        .switch()?;

        let redacted_encrypted_email = Encryptable::new(
            redacted_encrypted_value
                .clone()
                .into_inner()
                .switch_strategy(),
            redacted_encrypted_value.clone().into_encrypted(),
        );

        let update_address = storage::AddressUpdate::Update {
            city: Some(REDACTED.to_string()),
            country: None,
            line1: Some(redacted_encrypted_value.clone()),
            line2: Some(redacted_encrypted_value.clone()),
            line3: Some(redacted_encrypted_value.clone()),
            state: Some(redacted_encrypted_value.clone()),
            zip: Some(redacted_encrypted_value.clone()),
            first_name: Some(redacted_encrypted_value.clone()),
            last_name: Some(redacted_encrypted_value.clone()),
            phone_number: Some(redacted_encrypted_value.clone()),
            country_code: Some(REDACTED.to_string()),
            updated_by: merchant_context
                .get_merchant_account()
                .storage_scheme
                .to_string(),
            email: Some(redacted_encrypted_email),
            origin_zip: Some(redacted_encrypted_value.clone()),
        };

        match db
            .update_address_by_merchant_id_customer_id(
                key_manager_state,
                self,
                merchant_context.get_merchant_account().get_id(),
                update_address,
                merchant_context.get_merchant_key_store(),
            )
            .await
        {
            Ok(_) => Ok(()),
            Err(error) => {
                if error.current_context().is_db_not_found() {
                    Ok(())
                } else {
                    Err(error)
                        .change_context(errors::CustomersErrorResponse::InternalServerError)
                        .attach_printable("failed update_address_by_merchant_id_customer_id")
                }
            }
        }?;

        let updated_customer = storage::CustomerUpdate::Update {
            name: Some(redacted_encrypted_value.clone()),
            email: Some(
                types::crypto_operation(
                    key_manager_state,
                    type_name!(storage::Customer),
                    types::CryptoOperation::Encrypt(REDACTED.to_string().into()),
                    identifier,
                    key,
                )
                .await
                .and_then(|val| val.try_into_operation())
                .switch()?,
            ),
            phone: Box::new(Some(redacted_encrypted_value.clone())),
            description: Some(Description::from_str_unchecked(REDACTED)),
            phone_country_code: Some(REDACTED.to_string()),
            metadata: Box::new(None),
            connector_customer: Box::new(None),
            address_id: None,
            tax_registration_id: Some(redacted_encrypted_value.clone()),
        };

        db.update_customer_by_customer_id_merchant_id(
            key_manager_state,
            self.clone(),
            merchant_context.get_merchant_account().get_id().to_owned(),
            customer_orig,
            updated_customer,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .switch()?;

        let response = customers::CustomerDeleteResponse {
            customer_id: self.clone(),
            customer_deleted: true,
            address_deleted: true,
            payment_methods_deleted: true,
        };
        metrics::CUSTOMER_REDACTED.add(1, &[]);
        Ok(services::ApplicationResponse::Json(response))
    }
}

#[instrument(skip(state))]
pub async fn update_customer(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    update_customer: customers::CustomerUpdateRequestInternal,
) -> errors::CustomerResponse<customers::CustomerResponse> {
    let db = state.store.as_ref();
    let key_manager_state = &(&state).into();
    //Add this in update call if customer can be updated anywhere else

    #[cfg(feature = "v1")]
    let verify_id_for_update_customer = VerifyIdForUpdateCustomer {
        merchant_reference_id: &update_customer.customer_id,
        merchant_account: merchant_context.get_merchant_account(),
        key_store: merchant_context.get_merchant_key_store(),
        key_manager_state,
    };

    #[cfg(feature = "v2")]
    let verify_id_for_update_customer = VerifyIdForUpdateCustomer {
        id: &update_customer.id,
        merchant_account: merchant_context.get_merchant_account(),
        key_store: merchant_context.get_merchant_key_store(),
        key_manager_state,
    };

    let customer = verify_id_for_update_customer
        .verify_id_and_get_customer_object(db)
        .await?;

    let updated_customer = update_customer
        .request
        .create_domain_model_from_request(
            &None,
            db,
            &merchant_context,
            key_manager_state,
            &state,
            &customer,
        )
        .await?;

    update_customer.request.generate_response(&updated_customer)
}

#[async_trait::async_trait]
trait CustomerUpdateBridge {
    async fn create_domain_model_from_request<'a>(
        &'a self,
        connector_customer_details: &'a Option<
            Vec<payment_methods_domain::ConnectorCustomerDetails>,
        >,
        db: &'a dyn StorageInterface,
        merchant_context: &'a domain::MerchantContext,
        key_manager_state: &'a KeyManagerState,
        state: &'a SessionState,
        domain_customer: &'a domain::Customer,
    ) -> errors::CustomResult<domain::Customer, errors::CustomersErrorResponse>;

    fn generate_response<'a>(
        &'a self,
        customer: &'a domain::Customer,
    ) -> errors::CustomerResponse<customers::CustomerResponse>;
}

#[cfg(feature = "v1")]
struct AddressStructForDbUpdate<'a> {
    update_customer: &'a customers::CustomerUpdateRequest,
    merchant_account: &'a domain::MerchantAccount,
    key_store: &'a domain::MerchantKeyStore,
    key_manager_state: &'a KeyManagerState,
    state: &'a SessionState,
    domain_customer: &'a domain::Customer,
}

#[cfg(feature = "v1")]
impl AddressStructForDbUpdate<'_> {
    async fn update_address_if_sent(
        &self,
        db: &dyn StorageInterface,
    ) -> errors::CustomResult<Option<domain::Address>, errors::CustomersErrorResponse> {
        let address = if let Some(addr) = &self.update_customer.address {
            match self.domain_customer.address_id.clone() {
                Some(address_id) => {
                    let customer_address: api_models::payments::AddressDetails = addr.clone();
                    let update_address = self
                        .update_customer
                        .get_address_update(
                            self.state,
                            customer_address,
                            self.key_store.key.get_inner().peek(),
                            self.merchant_account.storage_scheme,
                            self.merchant_account.get_id().clone(),
                        )
                        .await
                        .switch()
                        .attach_printable("Failed while encrypting Address while Update")?;
                    Some(
                        db.update_address(
                            self.key_manager_state,
                            address_id,
                            update_address,
                            self.key_store,
                        )
                        .await
                        .switch()
                        .attach_printable(format!(
                            "Failed while updating address: merchant_id: {:?}, customer_id: {:?}",
                            self.merchant_account.get_id(),
                            self.domain_customer.customer_id
                        ))?,
                    )
                }
                None => {
                    let customer_address: api_models::payments::AddressDetails = addr.clone();

                    let address = self
                        .update_customer
                        .get_domain_address(
                            self.state,
                            customer_address,
                            self.merchant_account.get_id(),
                            &self.domain_customer.customer_id,
                            self.key_store.key.get_inner().peek(),
                            self.merchant_account.storage_scheme,
                        )
                        .await
                        .switch()
                        .attach_printable("Failed while encrypting address")?;
                    Some(
                        db.insert_address_for_customers(
                            self.key_manager_state,
                            address,
                            self.key_store,
                        )
                        .await
                        .switch()
                        .attach_printable("Failed while inserting new address")?,
                    )
                }


// Function: get_two_digit_expiry_year
// File: crates/common_types/src/payments.rs
// Module: common_types

// Documentation: Get the 2-digit expiration year from the Google Pay pre-decrypt data

pub fn get_two_digit_expiry_year(&self) -> Result<Secret<String>, errors::ValidationError>


// File: crates/diesel_models/src/fraud_check.rs
// Module: diesel_models
// Public structs: 3

use common_enums as storage_enums;
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use masking::{Deserialize, Serialize};
use time::PrimitiveDateTime;

use crate::{
    enums::{FraudCheckLastStep, FraudCheckStatus, FraudCheckType},
    schema::fraud_check,
};
#[derive(Clone, Debug, Identifiable, Queryable, Selectable, Serialize, Deserialize)]
#[diesel(table_name = fraud_check,  primary_key(payment_id, merchant_id), check_for_backend(diesel::pg::Pg))]
pub struct FraudCheck {
    pub frm_id: String,
    pub payment_id: common_utils::id_type::PaymentId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub attempt_id: String,
    pub created_at: PrimitiveDateTime,
    pub frm_name: String,
    pub frm_transaction_id: Option<String>,
    pub frm_transaction_type: FraudCheckType,
    pub frm_status: FraudCheckStatus,
    pub frm_score: Option<i32>,
    pub frm_reason: Option<serde_json::Value>,
    pub frm_error: Option<String>,
    pub payment_details: Option<serde_json::Value>,
    pub metadata: Option<serde_json::Value>,
    pub modified_at: PrimitiveDateTime,
    pub last_step: FraudCheckLastStep,
    pub payment_capture_method: Option<storage_enums::CaptureMethod>, // In postFrm, we are updating capture method from automatic to manual. To store the merchant actual capture method, we are storing the actual capture method in payment_capture_method. It will be useful while approving the FRM decision.
}

#[derive(router_derive::Setter, Clone, Debug, Insertable, router_derive::DebugAsDisplay)]
#[diesel(table_name = fraud_check)]
pub struct FraudCheckNew {
    pub frm_id: String,
    pub payment_id: common_utils::id_type::PaymentId,
    pub merchant_id: common_utils::id_type::MerchantId,
    pub attempt_id: String,
    pub created_at: PrimitiveDateTime,
    pub frm_name: String,
    pub frm_transaction_id: Option<String>,
    pub frm_transaction_type: FraudCheckType,
    pub frm_status: FraudCheckStatus,
    pub frm_score: Option<i32>,
    pub frm_reason: Option<serde_json::Value>,
    pub frm_error: Option<String>,
    pub payment_details: Option<serde_json::Value>,
    pub metadata: Option<serde_json::Value>,
    pub modified_at: PrimitiveDateTime,
    pub last_step: FraudCheckLastStep,
    pub payment_capture_method: Option<storage_enums::CaptureMethod>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum FraudCheckUpdate {
    //Refer PaymentAttemptUpdate for other variants implementations
    ResponseUpdate {
        frm_status: FraudCheckStatus,
        frm_transaction_id: Option<String>,
        frm_reason: Option<serde_json::Value>,
        frm_score: Option<i32>,
        metadata: Option<serde_json::Value>,
        modified_at: PrimitiveDateTime,
        last_step: FraudCheckLastStep,
        payment_capture_method: Option<storage_enums::CaptureMethod>,
    },
    ErrorUpdate {
        status: FraudCheckStatus,
        error_message: Option<Option<String>>,
    },
}

#[derive(Clone, Debug, Default, AsChangeset, router_derive::DebugAsDisplay)]
#[diesel(table_name = fraud_check)]
pub struct FraudCheckUpdateInternal {
    frm_status: Option<FraudCheckStatus>,
    frm_transaction_id: Option<String>,
    frm_reason: Option<serde_json::Value>,
    frm_score: Option<i32>,
    frm_error: Option<Option<String>>,
    metadata: Option<serde_json::Value>,
    last_step: FraudCheckLastStep,
    payment_capture_method: Option<storage_enums::CaptureMethod>,
}

impl From<FraudCheckUpdate> for FraudCheckUpdateInternal {
    fn from(fraud_check_update: FraudCheckUpdate) -> Self {
        match fraud_check_update {
            FraudCheckUpdate::ResponseUpdate {
                frm_status,
                frm_transaction_id,
                frm_reason,
                frm_score,
                metadata,
                modified_at: _,
                last_step,
                payment_capture_method,
            } => Self {
                frm_status: Some(frm_status),
                frm_transaction_id,
                frm_reason,
                frm_score,
                metadata,
                last_step,
                payment_capture_method,
                ..Default::default()
            },
            FraudCheckUpdate::ErrorUpdate {
                status,
                error_message,
            } => Self {
                frm_status: Some(status),
                frm_error: error_message,
                ..Default::default()
            },
        }
    }
}


// File: crates/router/src/core/refunds.rs
// Module: router
// Public functions: 24

#[cfg(feature = "olap")]
use std::collections::HashMap;

#[cfg(feature = "olap")]
use api_models::admin::MerchantConnectorInfo;
use common_utils::{
    ext_traits::{AsyncExt, StringExt},
    types::{ConnectorTransactionId, MinorUnit},
};
use diesel_models::{process_tracker::business_status, refund as diesel_refund};
use error_stack::{report, ResultExt};
use hyperswitch_domain_models::{
    router_data::ErrorResponse, router_request_types::SplitRefundsRequest,
};
use hyperswitch_interfaces::integrity::{CheckIntegrity, FlowIntegrity, GetIntegrityObject};
use router_env::{instrument, tracing};
use scheduler::{
    consumer::types::process_data, errors as sch_errors, utils as process_tracker_utils,
};
#[cfg(feature = "olap")]
use strum::IntoEnumIterator;

use crate::{
    consts,
    core::{
        errors::{self, ConnectorErrorExt, RouterResponse, RouterResult, StorageErrorExt},
        payments::{self, access_token, helpers},
        refunds::transformers::SplitRefundInput,
        utils::{
            self as core_utils, refunds_transformers as transformers,
            refunds_validator as validator,
        },
    },
    db, logger,
    routes::{metrics, SessionState},
    services,
    types::{
        self,
        api::{self, refunds},
        domain,
        storage::{self, enums},
        transformers::{ForeignFrom, ForeignInto},
    },
    utils::{self, OptionExt},
};

// ********************************************** REFUND EXECUTE **********************************************

#[instrument(skip_all)]
pub async fn refund_create_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    _profile_id: Option<common_utils::id_type::ProfileId>,
    req: refunds::RefundRequest,
) -> RouterResponse<refunds::RefundResponse> {
    let db = &*state.store;
    let (merchant_id, payment_intent, payment_attempt, amount);

    merchant_id = merchant_context.get_merchant_account().get_id();

    payment_intent = db
        .find_payment_intent_by_payment_id_merchant_id(
            &(&state).into(),
            &req.payment_id,
            merchant_id,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

    utils::when(
        !(payment_intent.status == enums::IntentStatus::Succeeded
            || payment_intent.status == enums::IntentStatus::PartiallyCaptured),
        || {
            Err(report!(errors::ApiErrorResponse::PaymentUnexpectedState {
                current_flow: "refund".into(),
                field_name: "status".into(),
                current_value: payment_intent.status.to_string(),
                states: "succeeded, partially_captured".to_string()
            })
            .attach_printable("unable to refund for a unsuccessful payment intent"))
        },
    )?;

    // Amount is not passed in request refer from payment intent.
    amount = req
        .amount
        .or(payment_intent.amount_captured)
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("amount captured is none in a successful payment")?;

    //[#299]: Can we change the flow based on some workflow idea
    utils::when(amount <= MinorUnit::new(0), || {
        Err(report!(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "amount".to_string(),
            expected_format: "positive integer".to_string()
        })
        .attach_printable("amount less than or equal to zero"))
    })?;

    payment_attempt = db
        .find_payment_attempt_last_successful_or_partially_captured_attempt_by_payment_id_merchant_id(
            &req.payment_id,
            merchant_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::SuccessfulPaymentNotFound)?;

    let creds_identifier = req
        .merchant_connector_details
        .as_ref()
        .map(|mcd| mcd.creds_identifier.to_owned());
    req.merchant_connector_details
        .to_owned()
        .async_map(|mcd| async {
            helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await
        })
        .await
        .transpose()?;

    Box::pin(validate_and_create_refund(
        &state,
        &merchant_context,
        &payment_attempt,
        &payment_intent,
        amount,
        req,
        creds_identifier,
    ))
    .await
    .map(services::ApplicationResponse::Json)
}

#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn trigger_refund_to_gateway(
    state: &SessionState,
    refund: &diesel_refund::Refund,
    merchant_context: &domain::MerchantContext,
    payment_attempt: &storage::PaymentAttempt,
    payment_intent: &storage::PaymentIntent,
    creds_identifier: Option<String>,
    split_refunds: Option<SplitRefundsRequest>,
) -> RouterResult<diesel_refund::Refund> {
    let routed_through = payment_attempt
        .connector
        .clone()
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to retrieve connector from payment attempt")?;

    let storage_scheme = merchant_context.get_merchant_account().storage_scheme;
    metrics::REFUND_COUNT.add(
        1,
        router_env::metric_attributes!(("connector", routed_through.clone())),
    );

    let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        &routed_through,
        api::GetToken::Connector,
        payment_attempt.merchant_connector_id.clone(),
    )?;

    let currency = payment_attempt.currency.ok_or_else(|| {
        report!(errors::ApiErrorResponse::InternalServerError).attach_printable(
            "Transaction in invalid. Missing field \"currency\" in payment_attempt.",
        )
    })?;

    validator::validate_for_valid_refunds(payment_attempt, connector.connector_name)?;

    let mut router_data = core_utils::construct_refund_router_data(
        state,
        &routed_through,
        merchant_context,
        (payment_attempt.get_total_amount(), currency),
        payment_intent,
        payment_attempt,
        refund,
        creds_identifier.clone(),
        split_refunds,
    )
    .await?;

    let add_access_token_result = Box::pin(access_token::add_access_token(
        state,
        &connector,
        merchant_context,
        &router_data,
        creds_identifier.as_deref(),
    ))
    .await?;

    logger::debug!(refund_router_data=?router_data);

    access_token::update_router_data_with_access_token_result(
        &add_access_token_result,
        &mut router_data,
        &payments::CallConnectorAction::Trigger,
    );

    let router_data_res = if !(add_access_token_result.connector_supports_access_token
        && router_data.access_token.is_none())
    {
        let connector_integration: services::BoxedRefundConnectorIntegrationInterface<
            api::Execute,
            types::RefundsData,
            types::RefundsResponseData,
        > = connector.connector.get_connector_integration();
        let router_data_res = services::execute_connector_processing_step(
            state,
            connector_integration,
            &router_data,
            payments::CallConnectorAction::Trigger,
            None,
            None,
        )
        .await;
        let option_refund_error_update =
            router_data_res
                .as_ref()
                .err()
                .and_then(|error| match error.current_context() {
                    errors::ConnectorError::NotImplemented(message) => {
                        Some(diesel_refund::RefundUpdate::ErrorUpdate {
                            refund_status: Some(enums::RefundStatus::Failure),
                            refund_error_message: Some(
                                errors::ConnectorError::NotImplemented(message.to_owned())
                                    .to_string(),
                            ),
                            refund_error_code: Some("NOT_IMPLEMENTED".to_string()),
                            updated_by: storage_scheme.to_string(),
                            connector_refund_id: None,
                            processor_refund_data: None,
                            unified_code: None,
                            unified_message: None,
                            issuer_error_code: None,
                            issuer_error_message: None,
                        })
                    }
                    errors::ConnectorError::NotSupported { message, connector } => {
                        Some(diesel_refund::RefundUpdate::ErrorUpdate {
                            refund_status: Some(enums::RefundStatus::Failure),
                            refund_error_message: Some(format!(
                                "{message} is not supported by {connector}"
                            )),
                            refund_error_code: Some("NOT_SUPPORTED".to_string()),
                            updated_by: storage_scheme.to_string(),
                            connector_refund_id: None,
                            processor_refund_data: None,
                            unified_code: None,
                            unified_message: None,
                            issuer_error_code: None,
                            issuer_error_message: None,
                        })
                    }
                    _ => None,
                });
        // Update the refund status as failure if connector_error is NotImplemented
        if let Some(refund_error_update) = option_refund_error_update {
            state
                .store
                .update_refund(
                    refund.to_owned(),
                    refund_error_update,
                    merchant_context.get_merchant_account().storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::InternalServerError)
                .attach_printable_lazy(|| {
                    format!(
                        "Failed while updating refund: refund_id: {}",
                        refund.refund_id
                    )
                })?;
        }
        let mut refund_router_data_res = router_data_res.to_refund_failed_response()?;
        // Initiating Integrity check
        let integrity_result = check_refund_integrity(
            &refund_router_data_res.request,
            &refund_router_data_res.response,
        );
        refund_router_data_res.integrity_check = integrity_result;
        refund_router_data_res
    } else {
        router_data
    };

    let refund_update = match router_data_res.response {
        Err(err) => {
            let option_gsm = helpers::get_gsm_record(
                state,
                Some(err.code.clone()),
                Some(err.message.clone()),
                connector.connector_name.to_string(),
                consts::REFUND_FLOW_STR.to_string(),
            )
            .await;
            // Note: Some connectors do not have a separate list of refund errors
            // In such cases, the error codes and messages are stored under "Authorize" flow in GSM table
            // So we will have to fetch the GSM using Authorize flow in case GSM is not found using "refund_flow"
            let option_gsm = if option_gsm.is_none() {
                helpers::get_gsm_record(
                    state,
                    Some(err.code.clone()),
                    Some(err.message.clone()),
                    connector.connector_name.to_string(),
                    consts::AUTHORIZE_FLOW_STR.to_string(),
                )
                .await
            } else {
                option_gsm
            };

            let gsm_unified_code = option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone());
            let gsm_unified_message = option_gsm.and_then(|gsm| gsm.unified_message);

            let (unified_code, unified_message) = if let Some((code, message)) =
                gsm_unified_code.as_ref().zip(gsm_unified_message.as_ref())
            {
                (code.to_owned(), message.to_owned())
            } else {
                (
                    consts::DEFAULT_UNIFIED_ERROR_CODE.to_owned(),
                    consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_owned(),
                )
            };

            diesel_refund::RefundUpdate::ErrorUpdate {
                refund_status: Some(enums::RefundStatus::Failure),
                refund_error_message: err.reason.or(Some(err.message)),
                refund_error_code: Some(err.code),
                updated_by: storage_scheme.to_string(),
                connector_refund_id: None,
                processor_refund_data: None,
                unified_code: Some(unified_code),
                unified_message: Some(unified_message),
                issuer_error_code: err.network_decline_code,
                issuer_error_message: err.network_error_message,
            }
        }
        Ok(response) => {
            // match on connector integrity checks
            match router_data_res.integrity_check.clone() {
                Err(err) => {
                    let (refund_connector_transaction_id, processor_refund_data) =
                        err.connector_transaction_id.map_or((None, None), |txn_id| {
                            let (refund_id, refund_data) =
                                ConnectorTransactionId::form_id_and_data(txn_id);
                            (Some(refund_id), refund_data)
                        });
                    metrics::INTEGRITY_CHECK_FAILED.add(
                        1,
                        router_env::metric_attributes!(
                            ("connector", connector.connector_name.to_string()),
                            (
                                "merchant_id",
                                merchant_context.get_merchant_account().get_id().clone()
                            ),
                        ),
                    );
                    diesel_refund::RefundUpdate::ErrorUpdate {
                        refund_status: Some(enums::RefundStatus::ManualReview),
                        refund_error_message: Some(format!(
                            "Integrity Check Failed! as data mismatched for fields {}",
                            err.field_names
                        )),
                        refund_error_code: Some("IE".to_string()),
                        updated_by: storage_scheme.to_string(),
                        connector_refund_id: refund_connector_transaction_id,
                        processor_refund_data,
                        unified_code: None,
                        unified_message: None,
                        issuer_error_code: None,
                        issuer_error_message: None,
                    }
                }
                Ok(()) => {
                    if response.refund_status == diesel_models::enums::RefundStatus::Success {
                        metrics::SUCCESSFUL_REFUND.add(
                            1,
                            router_env::metric_attributes!((
                                "connector",
                                connector.connector_name.to_string(),
                            )),
                        )
                    }
                    let (connector_refund_id, processor_refund_data) =
                        ConnectorTransactionId::form_id_and_data(response.connector_refund_id);
                    diesel_refund::RefundUpdate::Update {
                        connector_refund_id,
                        refund_status: response.refund_status,
                        sent_to_gateway: true,
                        refund_error_message: None,
                        refund_arn: "".to_string(),
                        updated_by: storage_scheme.to_string(),
                        processor_refund_data,
                    }
                }
            }
        }
    };

    let response = state
        .store
        .update_refund(
            refund.to_owned(),
            refund_update,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| {
            format!(
                "Failed while updating refund: refund_id: {}",
                refund.refund_id
            )
        })?;
    utils::trigger_refund_outgoing_webhook(
        state,
        merchant_context,
        &response,
        payment_attempt.profile_id.clone(),
    )
    .await
    .map_err(|error| logger::warn!(refunds_outgoing_webhook_error=?error))
    .ok();
    Ok(response)
}

pub fn check_refund_integrity<T, Request>(
    request: &Request,
    refund_response_data: &Result<types::RefundsResponseData, ErrorResponse>,
) -> Result<(), common_utils::errors::IntegrityCheckError>
where
    T: FlowIntegrity,
    Request: GetIntegrityObject<T> + CheckIntegrity<Request, T>,
{
    let connector_refund_id = refund_response_data
        .as_ref()
        .map(|resp_data| resp_data.connector_refund_id.clone())
        .ok();

    request.check_integrity(request, connector_refund_id.to_owned())
}

// ********************************************** REFUND SYNC **********************************************

pub async fn refund_response_wrapper<F, Fut, T, Req>(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    request: Req,
    f: F,
) -> RouterResponse<refunds::RefundResponse>
where
    F: Fn(
        SessionState,
        domain::MerchantContext,
        Option<common_utils::id_type::ProfileId>,
        Req,
    ) -> Fut,
    Fut: futures::Future<Output = RouterResult<T>>,
    T: ForeignInto<refunds::RefundResponse>,
{
    Ok(services::ApplicationResponse::Json(
        f(state, merchant_context, profile_id, request)
            .await?
            .foreign_into(),
    ))
}

#[instrument(skip_all)]
pub async fn refund_retrieve_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    request: refunds::RefundsRetrieveRequest,
    refund: diesel_refund::Refund,
) -> RouterResult<diesel_refund::Refund> {
    let db = &*state.store;
    let merchant_id = merchant_context.get_merchant_account().get_id();
    core_utils::validate_profile_id_from_auth_layer(profile_id, &refund)?;
    let payment_id = &refund.payment_id;
    let payment_intent = db
        .find_payment_intent_by_payment_id_merchant_id(
            &(&state).into(),
            payment_id,
            merchant_id,
            merchant_context.get_merchant_key_store(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;

    let payment_attempt = db
        .find_payment_attempt_by_connector_transaction_id_payment_id_merchant_id(
            &refund.connector_transaction_id,
            payment_id,
            merchant_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;

    let creds_identifier = request
        .merchant_connector_details
        .as_ref()
        .map(|mcd| mcd.creds_identifier.to_owned());
    request
        .merchant_connector_details
        .to_owned()
        .async_map(|mcd| async {
            helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await
        })
        .await
        .transpose()?;

    let split_refunds_req = core_utils::get_split_refunds(SplitRefundInput {
        split_payment_request: payment_intent.split_payments.clone(),
        payment_charges: payment_attempt.charges.clone(),
        charge_id: payment_attempt.charge_id.clone(),
        refund_request: refund.split_refunds.clone(),
    })?;

    let unified_translated_message = if let (Some(unified_code), Some(unified_message)) =
        (refund.unified_code.clone(), refund.unified_message.clone())
    {
        helpers::get_unified_translation(
            &state,
            unified_code,
            unified_message.clone(),
            state.locale.to_string(),
        )
        .await
        .or(Some(unified_message))
    } else {
        refund.unified_message
    };

    let refund = diesel_refund::Refund {
        unified_message: unified_translated_message,
        ..refund
    };

    let response = if should_call_refund(&refund, request.force_sync.unwrap_or(false)) {
        Box::pin(sync_refund_with_gateway(
            &state,
            &merchant_context,
            &payment_attempt,
            &payment_intent,
            &refund,
            creds_identifier,
            split_refunds_req,
        ))
        .await
    } else {
        Ok(refund)
    }?;

    Ok(response)
}

fn should_call_refund(refund: &diesel_models::refund::Refund, force_sync: bool) -> bool {
    // This implies, we cannot perform a refund sync & `the connector_refund_id`
    // doesn't exist
    let predicate1 = refund.connector_refund_id.is_some();

    // This allows refund sync at connector level if force_sync is enabled, or
    // checks if the refund has failed
    let predicate2 = force_sync
        || !matches!(
            refund.refund_status,
            diesel_models::enums::RefundStatus::Failure
                | diesel_models::enums::RefundStatus::Success
        );

    predicate1 && predicate2
}

#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub async fn sync_refund_with_gateway(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_attempt: &storage::PaymentAttempt,
    payment_intent: &storage::PaymentIntent,
    refund: &diesel_refund::Refund,
    creds_identifier: Option<String>,
    split_refunds: Option<SplitRefundsRequest>,
) -> RouterResult<diesel_refund::Refund> {
    let connector_id = refund.connector.to_string();
    let connector: api::ConnectorData = api::ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        &connector_id,
        api::GetToken::Connector,
        payment_attempt.merchant_connector_id.clone(),
    )
    .change_context(errors::ApiErrorResponse::InternalServerError)
    .attach_printable("Failed to get the connector")?;

    let storage_scheme = merchant_context.get_merchant_account().storage_scheme;

    let currency = payment_attempt.currency.get_required_value("currency")?;

    let mut router_data = core_utils::construct_refund_router_data::<api::RSync>(
        state,
        &connector_id,
        merchant_context,
        (payment_attempt.get_total_amount(), currency),
        payment_intent,
        payment_attempt,
        refund,
        creds_identifier.clone(),
        split_refunds,
    )
    .await?;

    let add_access_token_result = Box::pin(access_token::add_access_token(
        state,
        &connector,
        merchant_context,
        &router_data,
        creds_identifier.as_deref(),
    ))
    .await?;

    logger::debug!(refund_retrieve_router_data=?router_data);

    access_token::update_router_data_with_access_token_result(
        &add_access_token_result,
        &mut router_data,
        &payments::CallConnectorAction::Trigger,
    );

    let router_data_res = if !(add_access_token_result.connector_supports_access_token
        && router_data.access_token.is_none())
    {
        let connector_integration: services::BoxedRefundConnectorIntegrationInterface<
            api::RSync,
            types::RefundsData,
            types::RefundsResponseData,
        > = connector.connector.get_connector_integration();
        let mut refund_sync_router_data = services::execute_connector_processing_step(
            state,
            connector_integration,
            &router_data,
            payments::CallConnectorAction::Trigger,
            None,
            None,
        )
        .await
        .to_refund_failed_response()?;

        // Initiating connector integrity checks
        let integrity_result = check_refund_integrity(
            &refund_sync_router_data.request,
            &refund_sync_router_data.response,
        );

        refund_sync_router_data.integrity_check = integrity_result;

        refund_sync_router_data
    } else {
        router_data
    };

    let refund_update = match router_data_res.response {
        Err(error_message) => {
            let refund_status = match error_message.status_code {
                // marking failure for 2xx because this is genuine refund failure
                200..=299 => Some(enums::RefundStatus::Failure),
                _ => None,
            };
            diesel_refund::RefundUpdate::ErrorUpdate {
                refund_status,
                refund_error_message: error_message.reason.or(Some(error_message.message)),
                refund_error_code: Some(error_message.code),
                updated_by: storage_scheme.to_string(),
                connector_refund_id: None,
                processor_refund_data: None,
                unified_code: None,
                unified_message: None,
                issuer_error_code: error_message.network_decline_code,
                issuer_error_message: error_message.network_error_message,
            }
        }
        Ok(response) => match router_data_res.integrity_check.clone() {
            Err(err) => {
                metrics::INTEGRITY_CHECK_FAILED.add(
                    1,
                    router_env::metric_attributes!(
                        ("connector", connector.connector_name.to_string()),
                        (
                            "merchant_id",
                            merchant_context.get_merchant_account().get_id().clone()
                        ),
                    ),
                );
                let (refund_connector_transaction_id, processor_refund_data) = err
                    .connector_transaction_id
                    .map_or((None, None), |refund_id| {
                        let (refund_id, refund_data) =
                            ConnectorTransactionId::form_id_and_data(refund_id);
                        (Some(refund_id), refund_data)
                    });
                diesel_refund::RefundUpdate::ErrorUpdate {
                    refund_status: Some(enums::RefundStatus::ManualReview),
                    refund_error_message: Some(format!(
                        "Integrity Check Failed! as data mismatched for fields {}",
                        err.field_names
                    )),
                    refund_error_code: Some("IE".to_string()),
                    updated_by: storage_scheme.to_string(),
                    connector_refund_id: refund_connector_transaction_id,
                    processor_refund_data,
                    unified_code: None,
                    unified_message: None,
                    issuer_error_code: None,
                    issuer_error_message: None,
                }
            }
            Ok(()) => {
                let (connector_refund_id, processor_refund_data) =
                    ConnectorTransactionId::form_id_and_data(response.connector_refund_id);
                diesel_refund::RefundUpdate::Update {
                    connector_refund_id,
                    refund_status: response.refund_status,
                    sent_to_gateway: true,
                    refund_error_message: None,
                    refund_arn: "".to_string(),
                    updated_by: storage_scheme.to_string(),
                    processor_refund_data,
                }
            }
        },
    };

    let response = state
        .store
        .update_refund(
            refund.to_owned(),
            refund_update,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)
        .attach_printable_lazy(|| {
            format!(
                "Unable to update refund with refund_id: {}",
                refund.refund_id
            )
        })?;
    utils::trigger_refund_outgoing_webhook(
        state,
        merchant_context,
        &response,
        payment_attempt.profile_id.clone(),
    )
    .await
    .map_err(|error| logger::warn!(refunds_outgoing_webhook_error=?error))
    .ok();
    Ok(response)
}

// ********************************************** REFUND UPDATE **********************************************

pub async fn refund_update_core(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: refunds::RefundUpdateRequest,
) -> RouterResponse<refunds::RefundResponse> {
    let db = state.store.as_ref();
    let refund = db
        .find_refund_by_merchant_id_refund_id(
            merchant_context.get_merchant_account().get_id(),
            &req.refund_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;

    let response = db
        .update_refund(
            refund,
            diesel_refund::RefundUpdate::MetadataAndReasonUpdate {
                metadata: req.metadata,
                reason: req.reason,
                updated_by: merchant_context
                    .get_merchant_account()
                    .storage_scheme
                    .to_string(),
            },
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable_lazy(|| {
            format!("Unable to update refund with refund_id: {}", req.refund_id)
        })?;

    Ok(services::ApplicationResponse::Json(response.foreign_into()))
}

// ********************************************** VALIDATIONS **********************************************

#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
pub async fn validate_and_create_refund(
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    payment_attempt: &storage::PaymentAttempt,
    payment_intent: &storage::PaymentIntent,
    refund_amount: MinorUnit,
    req: refunds::RefundRequest,
    creds_identifier: Option<String>,
) -> RouterResult<refunds::RefundResponse> {
    let db = &*state.store;
    let split_refunds = core_utils::get_split_refunds(SplitRefundInput {
        split_payment_request: payment_intent.split_payments.clone(),
        payment_charges: payment_attempt.charges.clone(),
        charge_id: payment_attempt.charge_id.clone(),
        refund_request: req.split_refunds.clone(),
    })?;

    // Only for initial dev and testing
    let refund_type = req.refund_type.unwrap_or_default();

    // If Refund Id not passed in request Generate one.

    let refund_id = core_utils::get_or_generate_id("refund_id", &req.refund_id, "ref")?;

    let predicate = req
        .merchant_id
        .as_ref()
        .map(|merchant_id| merchant_id != merchant_context.get_merchant_account().get_id());

    utils::when(predicate.unwrap_or(false), || {
        Err(report!(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "merchant_id".to_string(),
            expected_format: "merchant_id from merchant account".to_string()
        })
        .attach_printable("invalid merchant_id in request"))
    })?;

    let connector_transaction_id = payment_attempt.clone().connector_transaction_id.ok_or_else(|| {
        report!(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Transaction in invalid. Missing field \"connector_transaction_id\" in payment_attempt.")
    })?;

    let all_refunds = db
        .find_refund_by_merchant_id_connector_transaction_id(
            merchant_context.get_merchant_account().get_id(),
            &connector_transaction_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;

    let currency = payment_attempt.currency.get_required_value("currency")?;

    //[#249]: Add Connector Based Validation here.
    validator::validate_payment_order_age(&payment_intent.created_at, state.conf.refund.max_age)
        .change_context(errors::ApiErrorResponse::InvalidDataFormat {
            field_name: "created_at".to_string(),
            expected_format: format!(
                "created_at not older than {} days",
                state.conf.refund.max_age,
            ),
        })?;

    let total_amount_captured = payment_intent
        .amount_captured
        .unwrap_or(payment_attempt.get_total_amount());

    validator::validate_refund_amount(
        total_amount_captured.get_amount_as_i64(),
        &all_refunds,
        refund_amount.get_amount_as_i64(),
    )
    .change_context(errors::ApiErrorResponse::RefundAmountExceedsPaymentAmount)?;

    validator::validate_maximum_refund_against_payment_attempt(
        &all_refunds,
        state.conf.refund.max_attempts,
    )
    .change_context(errors::ApiErrorResponse::MaximumRefundCount)?;

    let connector = payment_attempt
        .connector
        .clone()
        .ok_or(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("No connector populated in payment attempt")?;
    let (connector_transaction_id, processor_transaction_data) =
        ConnectorTransactionId::form_id_and_data(connector_transaction_id);
    let refund_create_req = diesel_refund::RefundNew {
        refund_id: refund_id.to_string(),
        internal_reference_id: utils::generate_id(consts::ID_LENGTH, "refid"),
        external_reference_id: Some(refund_id.clone()),
        payment_id: req.payment_id,
        merchant_id: merchant_context.get_merchant_account().get_id().clone(),
        connector_transaction_id,
        connector,
        refund_type: req.refund_type.unwrap_or_default().foreign_into(),
        total_amount: payment_attempt.get_total_amount(),
        refund_amount,
        currency,
        created_at: common_utils::date_time::now(),
        modified_at: common_utils::date_time::now(),
        refund_status: enums::RefundStatus::Pending,
        metadata: req.metadata,
        description: req.reason.clone(),
        attempt_id: payment_attempt.attempt_id.clone(),
        refund_reason: req.reason,
        profile_id: payment_intent.profile_id.clone(),
        merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
        charges: None,
        split_refunds: req.split_refunds,
        connector_refund_id: None,
        sent_to_gateway: Default::default(),
        refund_arn: None,
        updated_by: Default::default(),
        organization_id: merchant_context
            .get_merchant_account()
            .organization_id
            .clone(),
        processor_transaction_data,
        processor_refund_data: None,
    };

    let refund = match db
        .insert_refund(
            refund_create_req,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
    {
        Ok(refund) => {
            Box::pin(schedule_refund_execution(
                state,
                refund.clone(),
                refund_type,
                merchant_context,
                payment_attempt,
                payment_intent,
                creds_identifier,
                split_refunds,
            ))
            .await?
        }
        Err(err) => {
            if err.current_context().is_db_unique_violation() {
                Err(errors::ApiErrorResponse::DuplicateRefundRequest)?
            } else {
                return Err(err)
                    .change_context(errors::ApiErrorResponse::RefundNotFound)
                    .attach_printable("Inserting Refund failed");
            }
        }
    };
    let unified_translated_message = if let (Some(unified_code), Some(unified_message)) =
        (refund.unified_code.clone(), refund.unified_message.clone())
    {
        helpers::get_unified_translation(
            state,
            unified_code,
            unified_message.clone(),
            state.locale.to_string(),
        )
        .await
        .or(Some(unified_message))
    } else {
        refund.unified_message
    };

    let refund = diesel_refund::Refund {
        unified_message: unified_translated_message,
        ..refund
    };

    Ok(refund.foreign_into())
}

// ********************************************** Refund list **********************************************

///   If payment-id is provided, lists all the refunds associated with that particular payment-id
///   If payment-id is not provided, lists the refunds associated with that particular merchant - to the limit specified,if no limits given, it is 10 by default
#[instrument(skip_all)]
#[cfg(feature = "olap")]
pub async fn refund_list(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
    req: api_models::refunds::RefundListRequest,
) -> RouterResponse<api_models::refunds::RefundListResponse> {
    let db = state.store;
    let limit = validator::validate_refund_list(req.limit)?;
    let offset = req.offset.unwrap_or_default();

    let refund_list = db
        .filter_refund_by_constraints(
            merchant_context.get_merchant_account().get_id(),
            &(req.clone(), profile_id_list.clone()).try_into()?,
            merchant_context.get_merchant_account().storage_scheme,
            limit,
            offset,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;

    let data: Vec<refunds::RefundResponse> = refund_list
        .into_iter()
        .map(ForeignInto::foreign_into)
        .collect();

    let total_count = db
        .get_total_count_of_refunds(
            merchant_context.get_merchant_account().get_id(),
            &(req, profile_id_list).try_into()?,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;

    Ok(services::ApplicationResponse::Json(
        api_models::refunds::RefundListResponse {
            count: data.len(),
            total_count,
            data,
        },
    ))
}

#[instrument(skip_all)]
#[cfg(feature = "olap")]
pub async fn refund_filter_list(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    req: common_utils::types::TimeRange,
) -> RouterResponse<api_models::refunds::RefundListMetaData> {
    let db = state.store;
    let filter_list = db
        .filter_refund_by_meta_constraints(
            merchant_context.get_merchant_account().get_id(),
            &req,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;

    Ok(services::ApplicationResponse::Json(filter_list))
}

#[instrument(skip_all)]
pub async fn refund_retrieve_core_with_internal_reference_id(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    refund_internal_request_id: String,
    force_sync: Option<bool>,
) -> RouterResult<diesel_refund::Refund> {
    let db = &*state.store;
    let merchant_id = merchant_context.get_merchant_account().get_id();

    let refund = db
        .find_refund_by_internal_reference_id_merchant_id(
            &refund_internal_request_id,
            merchant_id,
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;

    let request = refunds::RefundsRetrieveRequest {
        refund_id: refund.refund_id.clone(),
        force_sync,
        merchant_connector_details: None,
    };

    Box::pin(refund_retrieve_core(
        state.clone(),
        merchant_context,
        profile_id,
        request,
        refund,
    ))
    .await
}

#[instrument(skip_all)]
pub async fn refund_retrieve_core_with_refund_id(
    state: SessionState,
    merchant_context: domain::MerchantContext,
    profile_id: Option<common_utils::id_type::ProfileId>,
    request: refunds::RefundsRetrieveRequest,
) -> RouterResult<diesel_refund::Refund> {
    let refund_id = request.refund_id.clone();
    let db = &*state.store;
    let merchant_id = merchant_context.get_merchant_account().get_id();

    let refund = db
        .find_refund_by_merchant_id_refund_id(
            merchant_id,
            refund_id.as_str(),
            merchant_context.get_merchant_account().storage_scheme,
        )
        .await
        .to_not_found_response(errors::ApiErrorResponse::RefundNotFound)?;

    Box::pin(refund_retrieve_core(
        state.clone(),
        merchant_context,
        profile_id,
        request,
        refund,
    ))
    .await
}

#[instrument(skip_all)]
#[cfg(feature = "olap")]
pub async fn refund_manual_update(
    state: SessionState,


// Struct: TokenexAuthType
// File: crates/hyperswitch_connectors/src/connectors/tokenex/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct TokenexAuthType


// Implementation: impl std::ops::Deref for for PayoutsMandateReference
// File: crates/hyperswitch_domain_models/src/mandates.rs
// Module: hyperswitch_domain_models
// Methods: 1 total (0 public)

impl std::ops::Deref for for PayoutsMandateReference


// Struct: FiservReturnUrls
// File: crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct FiservReturnUrls


// Struct: DisputeListWorkflow
// File: crates/router/src/workflows/dispute_list.rs
// Module: router
// Implementations: 0

pub struct DisputeListWorkflow


// Function: msearch_results
// File: crates/analytics/src/search.rs
// Module: analytics

pub fn msearch_results(
    client: &OpenSearchClient,
    req: GetGlobalSearchRequest,
    search_params: Vec<AuthInfo>,
    indexes: Vec<SearchIndex>,
) -> CustomResult<Vec<GetSearchResponse>, OpenSearchError>


// Implementation: impl api::RefundExecute for for Breadpay
// File: crates/hyperswitch_connectors/src/connectors/breadpay.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::RefundExecute for for Breadpay


// Implementation: impl super::sdk_events::filters::SdkEventFilterAnalytics for for ClickhouseClient
// File: crates/analytics/src/clickhouse.rs
// Module: analytics
// Methods: 0 total (0 public)

impl super::sdk_events::filters::SdkEventFilterAnalytics for for ClickhouseClient


// Struct: DigitalvirgoRouterData
// File: crates/hyperswitch_connectors/src/connectors/digitalvirgo/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct DigitalvirgoRouterData<T>


// Struct: BarclaycardAuthType
// File: crates/hyperswitch_connectors/src/connectors/barclaycard/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct BarclaycardAuthType


// Implementation: impl api::MandateSetup for for Coingate
// File: crates/hyperswitch_connectors/src/connectors/coingate.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::MandateSetup for for Coingate


// File: crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs
// Module: analytics

use std::collections::HashSet;

use api_models::analytics::{
    disputes::{DisputeDimensions, DisputeFilters, DisputeMetricsBucketIdentifier},
    Granularity, TimeRange,
};
use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
use time::PrimitiveDateTime;

use super::DisputeMetricRow;
use crate::{
    enums::AuthInfo,
    query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, SeriesBucket, ToSql, Window},
    types::{AnalyticsCollection, AnalyticsDataSource, MetricsError, MetricsResult},
};
#[derive(Default)]
pub(crate) struct TotalDisputeLostAmount {}

#[async_trait::async_trait]
impl<T> super::DisputeMetric<T> for TotalDisputeLostAmount
where
    T: AnalyticsDataSource + super::DisputeMetricAnalytics,
    PrimitiveDateTime: ToSql<T>,
    AnalyticsCollection: ToSql<T>,
    Granularity: GroupByClause<T>,
    Aggregate<&'static str>: ToSql<T>,
    Window<&'static str>: ToSql<T>,
{
    async fn load_metrics(
        &self,
        dimensions: &[DisputeDimensions],
        auth: &AuthInfo,
        filters: &DisputeFilters,
        granularity: Option<Granularity>,
        time_range: &TimeRange,
        pool: &T,
    ) -> MetricsResult<HashSet<(DisputeMetricsBucketIdentifier, DisputeMetricRow)>>
    where
        T: AnalyticsDataSource + super::DisputeMetricAnalytics,
    {
        let mut query_builder: QueryBuilder<T> =
            QueryBuilder::new(AnalyticsCollection::DisputeSessionized);

        for dim in dimensions.iter() {
            query_builder.add_select_column(dim).switch()?;
        }

        query_builder
            .add_select_column(Aggregate::Sum {
                field: "dispute_amount",
                alias: Some("total"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Min {
                field: "created_at",
                alias: Some("start_bucket"),
            })
            .switch()?;
        query_builder
            .add_select_column(Aggregate::Max {
                field: "created_at",
                alias: Some("end_bucket"),
            })
            .switch()?;

        filters.set_filter_clause(&mut query_builder).switch()?;

        auth.set_filter_clause(&mut query_builder).switch()?;

        time_range
            .set_filter_clause(&mut query_builder)
            .attach_printable("Error filtering time range")
            .switch()?;

        for dim in dimensions.iter() {
            query_builder.add_group_by_clause(dim).switch()?;
        }

        if let Some(granularity) = granularity {
            granularity
                .set_group_by_clause(&mut query_builder)
                .switch()?;
        }

        query_builder
            .add_filter_clause("dispute_status", "dispute_lost")
            .switch()?;

        query_builder
            .execute_query::<DisputeMetricRow, _>(pool)
            .await
            .change_context(MetricsError::QueryBuildingError)?
            .change_context(MetricsError::QueryExecutionFailure)?
            .into_iter()
            .map(|i| {
                Ok((
                    DisputeMetricsBucketIdentifier::new(
                        i.dispute_stage.as_ref().map(|i| i.0),
                        i.connector.clone(),
                        i.currency.as_ref().map(|i| i.0),
                        TimeRange {
                            start_time: match (granularity, i.start_bucket) {
                                (Some(g), Some(st)) => g.clip_to_start(st)?,
                                _ => time_range.start_time,
                            },
                            end_time: granularity.as_ref().map_or_else(
                                || Ok(time_range.end_time),
                                |g| i.end_bucket.map(|et| g.clip_to_end(et)).transpose(),
                            )?,
                        },
                    ),
                    i,
                ))
            })
            .collect::<error_stack::Result<HashSet<_>, crate::query::PostProcessingError>>()
            .change_context(MetricsError::PostProcessingFailure)
    }
}


// Module Structure
// File: crates/hyperswitch_connectors/src/connectors/stripe.rs
// Module: hyperswitch_connectors

// Public submodules:
pub mod transformers;


// Function: validate
// File: crates/router/src/configs/validations.rs
// Module: router

pub fn validate(&self) -> Result<(), ApplicationError>


// Implementation: impl api::PaymentVoid for for Fiuu
// File: crates/hyperswitch_connectors/src/connectors/fiuu.rs
// Module: hyperswitch_connectors
// Methods: 0 total (0 public)

impl api::PaymentVoid for for Fiuu


// Struct: TrustpaymentsVoidRequest
// File: crates/hyperswitch_connectors/src/connectors/trustpayments/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct TrustpaymentsVoidRequest