// File: crates/router/src/core/errors/customers_error_response.rs

use http::StatusCode;

#[derive(Debug, thiserror::Error)]
pub enum CustomersErrorResponse {
    #[error("Customer has already been redacted")]
    CustomerRedacted,

    #[error("Something went wrong")]
    InternalServerError,

    #[error("Invalid request data: {message}")]
    InvalidRequestData { message: String },

    #[error("Customer has already been redacted")]
    MandateActive,

    #[error("Customer does not exist in our records")]
    CustomerNotFound,

    #[error("Customer with the given customer id already exists")]
    CustomerAlreadyExists,
}

impl actix_web::ResponseError for CustomersErrorResponse {
    fn status_code(&self) -> StatusCode {
        common_utils::errors::ErrorSwitch::<api_models::errors::types::ApiErrorResponse>::switch(
            self,
        )
        .status_code()
    }

    fn error_response(&self) -> actix_web::HttpResponse {
        common_utils::errors::ErrorSwitch::<api_models::errors::types::ApiErrorResponse>::switch(
            self,
        )
        .error_response()
    }
}

// should be removed hola bola


// File: crates/router/src/core/errors/transformers.rs

use common_utils::errors::ErrorSwitch;
use hyperswitch_domain_models::errors::api_error_response::ApiErrorResponse;

use super::{CustomersErrorResponse, StorageError};

impl ErrorSwitch<api_models::errors::types::ApiErrorResponse> for CustomersErrorResponse {
    fn switch(&self) -> api_models::errors::types::ApiErrorResponse {
        use api_models::errors::types::{ApiError, ApiErrorResponse as AER};
        match self {
            Self::CustomerRedacted => AER::BadRequest(ApiError::new(
                "IR",
                11,
                "Customer has already been redacted",
                None,
            )),
            Self::InternalServerError => {
                AER::InternalServerError(ApiError::new("HE", 0, "Something went wrong", None))
            }
            Self::InvalidRequestData { message } => AER::BadRequest(ApiError::new(
                "IR",
                7,
                format!("Invalid value provided:{}", message),
                None,
            )),
            Self::MandateActive => AER::BadRequest(ApiError::new(
                "IR",
                10,
                "Customer has active mandate/subsciption",
                None,
            )),
            Self::CustomerNotFound => AER::NotFound(ApiError::new(
                "HE",
                2,
                "Customer does not exist in our records",
                None,
            )),
            Self::CustomerAlreadyExists => AER::BadRequest(ApiError::new(
                "IR",
                12,
                "Customer with the given `customer_id` already exists",
                None,
            )),
        }
    }
}

impl ErrorSwitch<CustomersErrorResponse> for StorageError {
    fn switch(&self) -> CustomersErrorResponse {
        use CustomersErrorResponse as CER;
        match self {
            err if err.is_db_not_found() => CER::CustomerNotFound,
            Self::CustomerRedacted => CER::CustomerRedacted,
            _ => CER::InternalServerError,
        }
    }
}

impl ErrorSwitch<CustomersErrorResponse> for common_utils::errors::CryptoError {
    fn switch(&self) -> CustomersErrorResponse {
        CustomersErrorResponse::InternalServerError
    }
}

impl ErrorSwitch<CustomersErrorResponse> for ApiErrorResponse {
    fn switch(&self) -> CustomersErrorResponse {
        use CustomersErrorResponse as CER;
        match self {
            Self::InternalServerError => CER::InternalServerError,
            Self::MandateActive => CER::MandateActive,
            Self::CustomerNotFound => CER::CustomerNotFound,
            _ => CER::InternalServerError,
        }
    }
}


// File: crates/router/src/core/errors/user.rs

use common_utils::errors::CustomResult;

use crate::services::ApplicationResponse;

pub type UserResult<T> = CustomResult<T, UserErrors>;
pub type UserResponse<T> = CustomResult<ApplicationResponse<T>, UserErrors>;
pub mod sample_data;

#[derive(Debug, thiserror::Error)]
pub enum UserErrors {
    #[error("User InternalServerError")]
    InternalServerError,
    #[error("InvalidCredentials")]
    InvalidCredentials,
    #[error("UserNotFound")]
    UserNotFound,
    #[error("UserExists")]
    UserExists,
    #[error("LinkInvalid")]
    LinkInvalid,
    #[error("UnverifiedUser")]
    UnverifiedUser,
    #[error("InvalidOldPassword")]
    InvalidOldPassword,
    #[error("EmailParsingError")]
    EmailParsingError,
    #[error("NameParsingError")]
    NameParsingError,
    #[error("PasswordParsingError")]
    PasswordParsingError,
    #[error("UserAlreadyVerified")]
    UserAlreadyVerified,
    #[error("CompanyNameParsingError")]
    CompanyNameParsingError,
    #[error("MerchantAccountCreationError: {0}")]
    MerchantAccountCreationError(String),
    #[error("InvalidEmailError")]
    InvalidEmailError,
    #[error("DuplicateOrganizationId")]
    DuplicateOrganizationId,
    #[error("MerchantIdNotFound")]
    MerchantIdNotFound,
    #[error("MetadataAlreadySet")]
    MetadataAlreadySet,
    #[error("InvalidRoleId")]
    InvalidRoleId,
    #[error("InvalidRoleOperation")]
    InvalidRoleOperation,
    #[error("IpAddressParsingFailed")]
    IpAddressParsingFailed,
    #[error("InvalidMetadataRequest")]
    InvalidMetadataRequest,
    #[error("MerchantIdParsingError")]
    MerchantIdParsingError,
    #[error("ChangePasswordError")]
    ChangePasswordError,
    #[error("InvalidDeleteOperation")]
    InvalidDeleteOperation,
    #[error("MaxInvitationsError")]
    MaxInvitationsError,
    #[error("RoleNotFound")]
    RoleNotFound,
    #[error("InvalidRoleOperationWithMessage")]
    InvalidRoleOperationWithMessage(String),
    #[error("RoleNameParsingError")]
    RoleNameParsingError,
    #[error("RoleNameAlreadyExists")]
    RoleNameAlreadyExists,
    #[error("TotpNotSetup")]
    TotpNotSetup,
    #[error("InvalidTotp")]
    InvalidTotp,
    #[error("TotpRequired")]
    TotpRequired,
    #[error("InvalidRecoveryCode")]
    InvalidRecoveryCode,
    #[error("TwoFactorAuthRequired")]
    TwoFactorAuthRequired,
    #[error("TwoFactorAuthNotSetup")]
    TwoFactorAuthNotSetup,
    #[error("TOTP secret not found")]
    TotpSecretNotFound,
    #[error("User auth method already exists")]
    UserAuthMethodAlreadyExists,
    #[error("Invalid user auth method operation")]
    InvalidUserAuthMethodOperation,
    #[error("Auth config parsing error")]
    AuthConfigParsingError,
    #[error("Invalid SSO request")]
    SSOFailed,
    #[error("profile_id missing in JWT")]
    JwtProfileIdMissing,
    #[error("Maximum attempts reached for TOTP")]
    MaxTotpAttemptsReached,
    #[error("Maximum attempts reached for Recovery Code")]
    MaxRecoveryCodeAttemptsReached,
    #[error("Forbidden tenant id")]
    ForbiddenTenantId,
    #[error("Error Uploading file to Theme Storage")]
    ErrorUploadingFile,
    #[error("Error Retrieving file from Theme Storage")]
    ErrorRetrievingFile,
    #[error("Theme not found")]
    ThemeNotFound,
    #[error("Theme with lineage already exists")]
    ThemeAlreadyExists,
    #[error("Invalid field: {0} in lineage")]
    InvalidThemeLineage(String),
    #[error("Missing required field: email_config")]
    MissingEmailConfig,
    #[error("Invalid Auth Method Operation: {0}")]
    InvalidAuthMethodOperationWithMessage(String),
    #[error("Invalid Clone Connector Operation: {0}")]
    InvalidCloneConnectorOperation(String),
    #[error("Error cloning connector: {0}")]
    ErrorCloningConnector(String),
}

impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for UserErrors {
    fn switch(&self) -> api_models::errors::types::ApiErrorResponse {
        use api_models::errors::types::{ApiError, ApiErrorResponse as AER};
        let sub_code = "UR";
        match self {
            Self::InternalServerError => {
                AER::InternalServerError(ApiError::new("HE", 0, self.get_error_message(), None))
            }
            Self::InvalidCredentials => {
                AER::Unauthorized(ApiError::new(sub_code, 1, self.get_error_message(), None))
            }
            Self::UserNotFound => {
                AER::Unauthorized(ApiError::new(sub_code, 2, self.get_error_message(), None))
            }
            Self::UserExists => {
                AER::BadRequest(ApiError::new(sub_code, 3, self.get_error_message(), None))
            }
            Self::LinkInvalid => {
                AER::Unauthorized(ApiError::new(sub_code, 4, self.get_error_message(), None))
            }
            Self::UnverifiedUser => {
                AER::Unauthorized(ApiError::new(sub_code, 5, self.get_error_message(), None))
            }
            Self::InvalidOldPassword => {
                AER::BadRequest(ApiError::new(sub_code, 6, self.get_error_message(), None))
            }
            Self::EmailParsingError => {
                AER::BadRequest(ApiError::new(sub_code, 7, self.get_error_message(), None))
            }
            Self::NameParsingError => {
                AER::BadRequest(ApiError::new(sub_code, 8, self.get_error_message(), None))
            }
            Self::PasswordParsingError => {
                AER::BadRequest(ApiError::new(sub_code, 9, self.get_error_message(), None))
            }
            Self::UserAlreadyVerified => {
                AER::Unauthorized(ApiError::new(sub_code, 11, self.get_error_message(), None))
            }
            Self::CompanyNameParsingError => {
                AER::BadRequest(ApiError::new(sub_code, 14, self.get_error_message(), None))
            }
            Self::MerchantAccountCreationError(_) => AER::InternalServerError(ApiError::new(
                sub_code,
                15,
                self.get_error_message(),
                None,
            )),
            Self::InvalidEmailError => {
                AER::BadRequest(ApiError::new(sub_code, 16, self.get_error_message(), None))
            }
            Self::MerchantIdNotFound => {
                AER::BadRequest(ApiError::new(sub_code, 18, self.get_error_message(), None))
            }
            Self::MetadataAlreadySet => {
                AER::BadRequest(ApiError::new(sub_code, 19, self.get_error_message(), None))
            }
            Self::DuplicateOrganizationId => AER::InternalServerError(ApiError::new(
                sub_code,
                21,
                self.get_error_message(),
                None,
            )),
            Self::InvalidRoleId => {
                AER::BadRequest(ApiError::new(sub_code, 22, self.get_error_message(), None))
            }
            Self::InvalidRoleOperation => {
                AER::BadRequest(ApiError::new(sub_code, 23, self.get_error_message(), None))
            }
            Self::IpAddressParsingFailed => AER::InternalServerError(ApiError::new(
                sub_code,
                24,
                self.get_error_message(),
                None,
            )),
            Self::InvalidMetadataRequest => {
                AER::BadRequest(ApiError::new(sub_code, 26, self.get_error_message(), None))
            }
            Self::MerchantIdParsingError => {
                AER::BadRequest(ApiError::new(sub_code, 28, self.get_error_message(), None))
            }
            Self::ChangePasswordError => {
                AER::BadRequest(ApiError::new(sub_code, 29, self.get_error_message(), None))
            }
            Self::InvalidDeleteOperation => {
                AER::BadRequest(ApiError::new(sub_code, 30, self.get_error_message(), None))
            }
            Self::MaxInvitationsError => {
                AER::BadRequest(ApiError::new(sub_code, 31, self.get_error_message(), None))
            }
            Self::RoleNotFound => {
                AER::BadRequest(ApiError::new(sub_code, 32, self.get_error_message(), None))
            }
            Self::InvalidRoleOperationWithMessage(_) => {
                AER::BadRequest(ApiError::new(sub_code, 33, self.get_error_message(), None))
            }
            Self::RoleNameParsingError => {
                AER::BadRequest(ApiError::new(sub_code, 34, self.get_error_message(), None))
            }
            Self::RoleNameAlreadyExists => {
                AER::BadRequest(ApiError::new(sub_code, 35, self.get_error_message(), None))
            }
            Self::TotpNotSetup => {
                AER::BadRequest(ApiError::new(sub_code, 36, self.get_error_message(), None))
            }
            Self::InvalidTotp => {
                AER::BadRequest(ApiError::new(sub_code, 37, self.get_error_message(), None))
            }
            Self::TotpRequired => {
                AER::BadRequest(ApiError::new(sub_code, 38, self.get_error_message(), None))
            }
            Self::InvalidRecoveryCode => {
                AER::BadRequest(ApiError::new(sub_code, 39, self.get_error_message(), None))
            }
            Self::TwoFactorAuthRequired => {
                AER::BadRequest(ApiError::new(sub_code, 40, self.get_error_message(), None))
            }
            Self::TwoFactorAuthNotSetup => {
                AER::BadRequest(ApiError::new(sub_code, 41, self.get_error_message(), None))
            }
            Self::TotpSecretNotFound => {
                AER::BadRequest(ApiError::new(sub_code, 42, self.get_error_message(), None))
            }
            Self::UserAuthMethodAlreadyExists => {
                AER::BadRequest(ApiError::new(sub_code, 43, self.get_error_message(), None))
            }
            Self::InvalidUserAuthMethodOperation => {
                AER::BadRequest(ApiError::new(sub_code, 44, self.get_error_message(), None))
            }
            Self::AuthConfigParsingError => {
                AER::BadRequest(ApiError::new(sub_code, 45, self.get_error_message(), None))
            }
            Self::SSOFailed => {
                AER::BadRequest(ApiError::new(sub_code, 46, self.get_error_message(), None))
            }
            Self::JwtProfileIdMissing => {
                AER::Unauthorized(ApiError::new(sub_code, 47, self.get_error_message(), None))
            }
            Self::MaxTotpAttemptsReached => {
                AER::BadRequest(ApiError::new(sub_code, 48, self.get_error_message(), None))
            }
            Self::MaxRecoveryCodeAttemptsReached => {
                AER::BadRequest(ApiError::new(sub_code, 49, self.get_error_message(), None))
            }
            Self::ForbiddenTenantId => {
                AER::BadRequest(ApiError::new(sub_code, 50, self.get_error_message(), None))
            }
            Self::ErrorUploadingFile => AER::InternalServerError(ApiError::new(
                sub_code,
                51,
                self.get_error_message(),
                None,
            )),
            Self::ErrorRetrievingFile => AER::InternalServerError(ApiError::new(
                sub_code,
                52,
                self.get_error_message(),
                None,
            )),
            Self::ThemeNotFound => {
                AER::NotFound(ApiError::new(sub_code, 53, self.get_error_message(), None))
            }
            Self::ThemeAlreadyExists => {
                AER::BadRequest(ApiError::new(sub_code, 54, self.get_error_message(), None))
            }
            Self::InvalidThemeLineage(_) => {
                AER::BadRequest(ApiError::new(sub_code, 55, self.get_error_message(), None))
            }
            Self::MissingEmailConfig => {
                AER::BadRequest(ApiError::new(sub_code, 56, self.get_error_message(), None))
            }
            Self::InvalidAuthMethodOperationWithMessage(_) => {
                AER::BadRequest(ApiError::new(sub_code, 57, self.get_error_message(), None))
            }
            Self::InvalidCloneConnectorOperation(_) => {
                AER::BadRequest(ApiError::new(sub_code, 58, self.get_error_message(), None))
            }
            Self::ErrorCloningConnector(_) => AER::InternalServerError(ApiError::new(
                sub_code,
                59,
                self.get_error_message(),
                None,
            )),
        }
    }
}

impl UserErrors {
    pub fn get_error_message(&self) -> String {
        match self {
            Self::InternalServerError => "Something went wrong".to_string(),
            Self::InvalidCredentials => "Incorrect email or password".to_string(),
            Self::UserNotFound => "Email doesn’t exist. Register".to_string(),
            Self::UserExists => "An account already exists with this email".to_string(),
            Self::LinkInvalid => "Invalid or expired link".to_string(),
            Self::UnverifiedUser => "Kindly verify your account".to_string(),
            Self::InvalidOldPassword => {
                "Old password incorrect. Please enter the correct password".to_string()
            }
            Self::EmailParsingError => "Invalid Email".to_string(),
            Self::NameParsingError => "Invalid Name".to_string(),
            Self::PasswordParsingError => "Invalid Password".to_string(),
            Self::UserAlreadyVerified => "User already verified".to_string(),
            Self::CompanyNameParsingError => "Invalid Company Name".to_string(),
            Self::MerchantAccountCreationError(error_message) => error_message.to_string(),
            Self::InvalidEmailError => "Invalid Email".to_string(),
            Self::MerchantIdNotFound => "Invalid Merchant ID".to_string(),
            Self::MetadataAlreadySet => "Metadata already set".to_string(),
            Self::DuplicateOrganizationId => {
                "An Organization with the id already exists".to_string()
            }
            Self::InvalidRoleId => "Invalid Role ID".to_string(),
            Self::InvalidRoleOperation => "User Role Operation Not Supported".to_string(),
            Self::IpAddressParsingFailed => "Something went wrong".to_string(),
            Self::InvalidMetadataRequest => "Invalid Metadata Request".to_string(),
            Self::MerchantIdParsingError => "Invalid Merchant Id".to_string(),
            Self::ChangePasswordError => "Old and new password cannot be same".to_string(),
            Self::InvalidDeleteOperation => "Delete Operation Not Supported".to_string(),
            Self::MaxInvitationsError => "Maximum invite count per request exceeded".to_string(),
            Self::RoleNotFound => "Role Not Found".to_string(),
            Self::InvalidRoleOperationWithMessage(error_message) => error_message.to_string(),
            Self::RoleNameParsingError => "Invalid Role Name".to_string(),
            Self::RoleNameAlreadyExists => "Role name already exists".to_string(),
            Self::TotpNotSetup => "TOTP not setup".to_string(),
            Self::InvalidTotp => "Invalid TOTP".to_string(),
            Self::TotpRequired => "TOTP required".to_string(),
            Self::InvalidRecoveryCode => "Invalid Recovery Code".to_string(),
            Self::MaxTotpAttemptsReached => "Maximum attempts reached for TOTP".to_string(),
            Self::MaxRecoveryCodeAttemptsReached => {
                "Maximum attempts reached for Recovery Code".to_string()
            }
            Self::TwoFactorAuthRequired => "Two factor auth required".to_string(),
            Self::TwoFactorAuthNotSetup => "Two factor auth not setup".to_string(),
            Self::TotpSecretNotFound => "TOTP secret not found".to_string(),
            Self::UserAuthMethodAlreadyExists => "User auth method already exists".to_string(),
            Self::InvalidUserAuthMethodOperation => {
                "Invalid user auth method operation".to_string()
            }
            Self::AuthConfigParsingError => "Auth config parsing error".to_string(),
            Self::SSOFailed => "Invalid SSO request".to_string(),
            Self::JwtProfileIdMissing => "profile_id missing in JWT".to_string(),
            Self::ForbiddenTenantId => "Forbidden tenant id".to_string(),
            Self::ErrorUploadingFile => "Error Uploading file to Theme Storage".to_string(),
            Self::ErrorRetrievingFile => "Error Retrieving file from Theme Storage".to_string(),
            Self::ThemeNotFound => "Theme not found".to_string(),
            Self::ThemeAlreadyExists => "Theme with lineage already exists".to_string(),
            Self::InvalidThemeLineage(field_name) => {
                format!("Invalid field: {field_name} in lineage")
            }
            Self::MissingEmailConfig => "Missing required field: email_config".to_string(),
            Self::InvalidAuthMethodOperationWithMessage(operation) => {
                format!("Invalid Auth Method Operation: {operation}")
            }
            Self::InvalidCloneConnectorOperation(operation) => {
                format!("Invalid Clone Connector Operation: {operation}")
            }
            Self::ErrorCloningConnector(error_message) => {
                format!("Error cloning connector: {error_message}")
            }
        }
    }
}


// File: crates/router/src/core/errors/error_handlers.rs

use actix_web::{body, dev::ServiceResponse, middleware::ErrorHandlerResponse, ResponseError};
use http::StatusCode;

use super::ApiErrorResponse;
use crate::logger;
pub fn custom_error_handlers<B: body::MessageBody + 'static>(
    res: ServiceResponse<B>,
) -> actix_web::Result<ErrorHandlerResponse<B>> {
    let error_response = match res.status() {
        StatusCode::NOT_FOUND => ApiErrorResponse::InvalidRequestUrl,
        StatusCode::METHOD_NOT_ALLOWED => ApiErrorResponse::InvalidHttpMethod,
        _ => ApiErrorResponse::InternalServerError,
    };

    let (req, res) = res.into_parts();
    logger::warn!(error_response=?res);
    let res = match res.error() {
        Some(_) => res.map_into_boxed_body(),
        None => error_response.error_response(),
    };
    let res = ServiceResponse::new(req, res)
        .map_into_boxed_body()
        .map_into_right_body();
    Ok(ErrorHandlerResponse::Response(res))
}

// can be used as .default_service for web::resource to modify the default behavior of method_not_found error i.e. raised
// use actix_web::dev::ServiceRequest
// pub async fn default_service_405<E>(req: ServiceRequest) -> Result<ServiceResponse, E> {
//     Ok(req.into_response(ApiErrorResponse::InvalidHttpMethod.error_response()))
// }


// File: crates/router/src/core/errors/utils.rs

use common_utils::errors::CustomResult;

use crate::{core::errors, logger};

pub trait StorageErrorExt<T, E> {
    #[track_caller]
    fn to_not_found_response(self, not_found_response: E) -> error_stack::Result<T, E>;

    #[track_caller]
    fn to_duplicate_response(self, duplicate_response: E) -> error_stack::Result<T, E>;
}

impl<T> StorageErrorExt<T, errors::CustomersErrorResponse>
    for error_stack::Result<T, errors::StorageError>
{
    #[track_caller]
    fn to_not_found_response(
        self,
        not_found_response: errors::CustomersErrorResponse,
    ) -> error_stack::Result<T, errors::CustomersErrorResponse> {
        self.map_err(|err| match err.current_context() {
            error if error.is_db_not_found() => err.change_context(not_found_response),
            errors::StorageError::CustomerRedacted => {
                err.change_context(errors::CustomersErrorResponse::CustomerRedacted)
            }
            _ => err.change_context(errors::CustomersErrorResponse::InternalServerError),
        })
    }

    fn to_duplicate_response(
        self,
        duplicate_response: errors::CustomersErrorResponse,
    ) -> error_stack::Result<T, errors::CustomersErrorResponse> {
        self.map_err(|err| {
            if err.current_context().is_db_unique_violation() {
                err.change_context(duplicate_response)
            } else {
                err.change_context(errors::CustomersErrorResponse::InternalServerError)
            }
        })
    }
}

impl<T> StorageErrorExt<T, errors::ApiErrorResponse>
    for error_stack::Result<T, errors::StorageError>
{
    #[track_caller]
    fn to_not_found_response(
        self,
        not_found_response: errors::ApiErrorResponse,
    ) -> error_stack::Result<T, errors::ApiErrorResponse> {
        self.map_err(|err| {
            let new_err = match err.current_context() {
                errors::StorageError::ValueNotFound(_) => not_found_response,
                errors::StorageError::CustomerRedacted => {
                    errors::ApiErrorResponse::CustomerRedacted
                }
                _ => errors::ApiErrorResponse::InternalServerError,
            };
            err.change_context(new_err)
        })
    }

    #[track_caller]
    fn to_duplicate_response(
        self,
        duplicate_response: errors::ApiErrorResponse,
    ) -> error_stack::Result<T, errors::ApiErrorResponse> {
        self.map_err(|err| {
            let new_err = match err.current_context() {
                errors::StorageError::DuplicateValue { .. } => duplicate_response,
                _ => errors::ApiErrorResponse::InternalServerError,
            };
            err.change_context(new_err)
        })
    }
}

pub trait ConnectorErrorExt<T> {
    #[track_caller]
    fn to_refund_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse>;
    #[track_caller]
    fn to_payment_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse>;
    #[track_caller]
    fn to_setup_mandate_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse>;
    #[track_caller]
    fn to_dispute_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse>;
    #[track_caller]
    fn to_files_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse>;
    #[cfg(feature = "payouts")]
    #[track_caller]
    fn to_payout_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse>;
    #[track_caller]
    fn to_vault_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse>;

    // Validates if the result, is Ok(..) or WebhookEventTypeNotFound all the other error variants
    // are cascaded while these two event types are handled via `Option`
    #[track_caller]
    fn allow_webhook_event_type_not_found(
        self,
        enabled: bool,
    ) -> error_stack::Result<Option<T>, errors::ConnectorError>;
}

impl<T> ConnectorErrorExt<T> for error_stack::Result<T, errors::ConnectorError> {
    fn to_refund_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse> {
        self.map_err(|err| match err.current_context() {
            errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => {
                let response_str = std::str::from_utf8(bytes);
                let data = match response_str {
                    Ok(s) => serde_json::from_str(s)
                        .map_err(
                            |error| logger::error!(%error,"Failed to convert response to JSON"),
                        )
                        .ok(),
                    Err(error) => {
                        logger::error!(%error,"Failed to convert response to UTF8 string");
                        None
                    }
                };
                err.change_context(errors::ApiErrorResponse::RefundFailed { data })
            }
            errors::ConnectorError::NotImplemented(reason) => {
                errors::ApiErrorResponse::NotImplemented {
                    message: errors::NotImplementedMessage::Reason(reason.to_string()),
                }
                .into()
            }
            errors::ConnectorError::NotSupported { message, connector } => {
                errors::ApiErrorResponse::NotSupported {
                    message: format!("{message} is not supported by {connector}"),
                }
                .into()
            }
            errors::ConnectorError::CaptureMethodNotSupported => {
                errors::ApiErrorResponse::NotSupported {
                    message: "Capture Method Not Supported".to_owned(),
                }
                .into()
            }
            errors::ConnectorError::FailedToObtainIntegrationUrl
            | errors::ConnectorError::RequestEncodingFailed
            | errors::ConnectorError::RequestEncodingFailedWithReason(_)
            | errors::ConnectorError::ParsingFailed
            | errors::ConnectorError::ResponseDeserializationFailed
            | errors::ConnectorError::UnexpectedResponseError(_)
            | errors::ConnectorError::RoutingRulesParsingError
            | errors::ConnectorError::FailedToObtainPreferredConnector
            | errors::ConnectorError::ProcessingStepFailed(_)
            | errors::ConnectorError::InvalidConnectorName
            | errors::ConnectorError::InvalidWallet
            | errors::ConnectorError::ResponseHandlingFailed
            | errors::ConnectorError::MissingRequiredField { .. }
            | errors::ConnectorError::MissingRequiredFields { .. }
            | errors::ConnectorError::FailedToObtainAuthType
            | errors::ConnectorError::FailedToObtainCertificate
            | errors::ConnectorError::NoConnectorMetaData
            | errors::ConnectorError::NoConnectorWalletDetails
            | errors::ConnectorError::FailedToObtainCertificateKey
            | errors::ConnectorError::MaxFieldLengthViolated { .. }
            | errors::ConnectorError::FlowNotSupported { .. }
            | errors::ConnectorError::MissingConnectorMandateID
            | errors::ConnectorError::MissingConnectorMandateMetadata
            | errors::ConnectorError::MissingConnectorTransactionID
            | errors::ConnectorError::MissingConnectorRefundID
            | errors::ConnectorError::MissingApplePayTokenData
            | errors::ConnectorError::WebhooksNotImplemented
            | errors::ConnectorError::WebhookBodyDecodingFailed
            | errors::ConnectorError::WebhookSignatureNotFound
            | errors::ConnectorError::WebhookSourceVerificationFailed
            | errors::ConnectorError::WebhookVerificationSecretNotFound
            | errors::ConnectorError::WebhookVerificationSecretInvalid
            | errors::ConnectorError::WebhookReferenceIdNotFound
            | errors::ConnectorError::WebhookEventTypeNotFound
            | errors::ConnectorError::WebhookResourceObjectNotFound
            | errors::ConnectorError::WebhookResponseEncodingFailed
            | errors::ConnectorError::InvalidDateFormat
            | errors::ConnectorError::DateFormattingFailed
            | errors::ConnectorError::InvalidDataFormat { .. }
            | errors::ConnectorError::MismatchedPaymentData
            | errors::ConnectorError::MandatePaymentDataMismatch { .. }
            | errors::ConnectorError::InvalidWalletToken { .. }
            | errors::ConnectorError::MissingConnectorRelatedTransactionID { .. }
            | errors::ConnectorError::FileValidationFailed { .. }
            | errors::ConnectorError::MissingConnectorRedirectionPayload { .. }
            | errors::ConnectorError::FailedAtConnector { .. }
            | errors::ConnectorError::MissingPaymentMethodType
            | errors::ConnectorError::InSufficientBalanceInPaymentMethod
            | errors::ConnectorError::RequestTimeoutReceived
            | errors::ConnectorError::CurrencyNotSupported { .. }
            | errors::ConnectorError::InvalidConnectorConfig { .. }
            | errors::ConnectorError::AmountConversionFailed
            | errors::ConnectorError::GenericError { .. } => {
                err.change_context(errors::ApiErrorResponse::RefundFailed { data: None })
            }
        })
    }

    fn to_payment_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse> {
        self.map_err(|err| {
            let error = match err.current_context() {
                errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => {
                    let response_str = std::str::from_utf8(bytes);
                    let data = match response_str {
                        Ok(s) => serde_json::from_str(s)
                            .map_err(
                                |error| logger::error!(%error,"Failed to convert response to JSON"),
                            )
                            .ok(),
                        Err(error) => {
                            logger::error!(%error,"Failed to convert response to UTF8 string");
                            None
                        }
                    };
                    errors::ApiErrorResponse::PaymentAuthorizationFailed { data }
                }
                errors::ConnectorError::MissingRequiredField { field_name } => {
                    errors::ApiErrorResponse::MissingRequiredField { field_name }
                }
                errors::ConnectorError::MissingRequiredFields { field_names } => {
                    errors::ApiErrorResponse::MissingRequiredFields { field_names: field_names.to_vec() }
                }
                errors::ConnectorError::NotImplemented(reason) => {
                    errors::ApiErrorResponse::NotImplemented {
                        message: errors::NotImplementedMessage::Reason(
                            reason.to_string(),
                        ),
                    }
                }
                errors::ConnectorError::MismatchedPaymentData => {
                    errors::ApiErrorResponse::InvalidDataValue {
                        field_name:
                            "payment_method_data, payment_method_type and payment_experience does not match",
                    }
                },
                errors::ConnectorError::MandatePaymentDataMismatch {fields}=> {
                    errors::ApiErrorResponse::MandatePaymentDataMismatch {
                        fields: fields.to_owned(),
                    }
                },
                errors::ConnectorError::NotSupported { message, connector } => {
                    errors::ApiErrorResponse::NotSupported { message: format!("{message} is not supported by {connector}") }
                },
                errors::ConnectorError::FlowNotSupported{ flow, connector } => {
                    errors::ApiErrorResponse::FlowNotSupported { flow: flow.to_owned(), connector: connector.to_owned() }
                },
                errors::ConnectorError::MaxFieldLengthViolated{ connector, field_name,  max_length, received_length} => {
                    errors::ApiErrorResponse::MaxFieldLengthViolated { connector: connector.to_string(), field_name: field_name.to_string(), max_length: *max_length, received_length: *received_length }
                },
                errors::ConnectorError::InvalidDataFormat { field_name } => {
                    errors::ApiErrorResponse::InvalidDataValue { field_name }
                },
                errors::ConnectorError::CaptureMethodNotSupported => {
                    errors::ApiErrorResponse::NotSupported {
                        message: "Capture Method Not Supported".to_owned(),
                    }
                }
                errors::ConnectorError::InvalidWalletToken {wallet_name} => errors::ApiErrorResponse::InvalidWalletToken {wallet_name: wallet_name.to_string()},
                errors::ConnectorError::CurrencyNotSupported { message, connector} => errors::ApiErrorResponse::CurrencyNotSupported { message: format!("Credentials for the currency {message} are not configured with the connector {connector}/hyperswitch") },
                errors::ConnectorError::FailedToObtainAuthType =>  errors::ApiErrorResponse::InvalidConnectorConfiguration {config: "connector_account_details".to_string()},
                errors::ConnectorError::InvalidConnectorConfig { config }  => errors::ApiErrorResponse::InvalidConnectorConfiguration { config: config.to_string() },
                errors::ConnectorError::FailedToObtainIntegrationUrl |
                errors::ConnectorError::RequestEncodingFailed |
                errors::ConnectorError::RequestEncodingFailedWithReason(_) |
                errors::ConnectorError::ParsingFailed |
                errors::ConnectorError::ResponseDeserializationFailed |
                errors::ConnectorError::UnexpectedResponseError(_) |
                errors::ConnectorError::RoutingRulesParsingError |
                errors::ConnectorError::FailedToObtainPreferredConnector |
                errors::ConnectorError::InvalidConnectorName |
                errors::ConnectorError::InvalidWallet |
                errors::ConnectorError::ResponseHandlingFailed |
                errors::ConnectorError::FailedToObtainCertificate |
                errors::ConnectorError::NoConnectorMetaData | errors::ConnectorError::NoConnectorWalletDetails |
                errors::ConnectorError::FailedToObtainCertificateKey |
                errors::ConnectorError::MissingConnectorMandateID |
                errors::ConnectorError::MissingConnectorMandateMetadata |
                errors::ConnectorError::MissingConnectorTransactionID |
                errors::ConnectorError::MissingConnectorRefundID |
                errors::ConnectorError::MissingApplePayTokenData |
                errors::ConnectorError::WebhooksNotImplemented |
                errors::ConnectorError::WebhookBodyDecodingFailed |
                errors::ConnectorError::WebhookSignatureNotFound |
                errors::ConnectorError::WebhookSourceVerificationFailed |
                errors::ConnectorError::WebhookVerificationSecretNotFound |
                errors::ConnectorError::WebhookVerificationSecretInvalid |
                errors::ConnectorError::WebhookReferenceIdNotFound |
                errors::ConnectorError::WebhookEventTypeNotFound |
                errors::ConnectorError::WebhookResourceObjectNotFound |
                errors::ConnectorError::WebhookResponseEncodingFailed |
                errors::ConnectorError::InvalidDateFormat |
                errors::ConnectorError::DateFormattingFailed |
                errors::ConnectorError::MissingConnectorRelatedTransactionID { .. } |
                errors::ConnectorError::FileValidationFailed { .. } |
                errors::ConnectorError::MissingConnectorRedirectionPayload { .. } |
                errors::ConnectorError::FailedAtConnector { .. } |
                errors::ConnectorError::MissingPaymentMethodType |
                errors::ConnectorError::InSufficientBalanceInPaymentMethod |
                errors::ConnectorError::RequestTimeoutReceived |
                errors::ConnectorError::ProcessingStepFailed(None)|
                errors::ConnectorError::GenericError {..} |
                errors::ConnectorError::AmountConversionFailed => errors::ApiErrorResponse::InternalServerError
            };
            err.change_context(error)
        })
    }

    fn to_setup_mandate_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse> {
        self.map_err(|err| {
            let error = err.current_context();
            let data = match error {
                errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => {
                    let response_str = std::str::from_utf8(bytes);
                    let error_response = match response_str {
                        Ok(s) => serde_json::from_str(s)
                            .map_err(
                                |err| logger::error!(%err, "Failed to convert response to JSON"),
                            )
                            .ok(),
                        Err(err) => {
                            logger::error!(%err, "Failed to convert response to UTF8 string");
                            None
                        }
                    };
                    errors::ApiErrorResponse::PaymentAuthorizationFailed {
                        data: error_response,
                    }
                }
                errors::ConnectorError::MissingRequiredField { field_name } => {
                    errors::ApiErrorResponse::MissingRequiredField { field_name }
                }
                errors::ConnectorError::FailedToObtainIntegrationUrl => {
                    errors::ApiErrorResponse::InvalidConnectorConfiguration {
                        config: "connector_account_details".to_string(),
                    }
                }
                errors::ConnectorError::InvalidConnectorConfig { config: field_name } => {
                    errors::ApiErrorResponse::InvalidConnectorConfiguration {
                        config: field_name.to_string(),
                    }
                }
                errors::ConnectorError::InvalidWalletToken { wallet_name } => {
                    errors::ApiErrorResponse::InvalidWalletToken {
                        wallet_name: wallet_name.to_string(),
                    }
                }
                errors::ConnectorError::CaptureMethodNotSupported => {
                    errors::ApiErrorResponse::NotSupported {
                        message: "Capture Method Not Supported".to_owned(),
                    }
                }
                errors::ConnectorError::RequestEncodingFailed
                | errors::ConnectorError::RequestEncodingFailedWithReason(_)
                | errors::ConnectorError::ParsingFailed
                | errors::ConnectorError::ResponseDeserializationFailed
                | errors::ConnectorError::UnexpectedResponseError(_)
                | errors::ConnectorError::RoutingRulesParsingError
                | errors::ConnectorError::FailedToObtainPreferredConnector
                | errors::ConnectorError::InvalidConnectorName
                | errors::ConnectorError::InvalidWallet
                | errors::ConnectorError::ResponseHandlingFailed
                | errors::ConnectorError::MissingRequiredFields { .. }
                | errors::ConnectorError::FailedToObtainAuthType
                | errors::ConnectorError::FailedToObtainCertificate
                | errors::ConnectorError::NoConnectorMetaData
                | errors::ConnectorError::NoConnectorWalletDetails
                | errors::ConnectorError::FailedToObtainCertificateKey
                | errors::ConnectorError::NotImplemented(_)
                | errors::ConnectorError::NotSupported { .. }
                | errors::ConnectorError::MaxFieldLengthViolated { .. }
                | errors::ConnectorError::FlowNotSupported { .. }
                | errors::ConnectorError::MissingConnectorMandateID
                | errors::ConnectorError::MissingConnectorMandateMetadata
                | errors::ConnectorError::MissingConnectorTransactionID
                | errors::ConnectorError::MissingConnectorRefundID
                | errors::ConnectorError::MissingApplePayTokenData
                | errors::ConnectorError::WebhooksNotImplemented
                | errors::ConnectorError::WebhookBodyDecodingFailed
                | errors::ConnectorError::WebhookSignatureNotFound
                | errors::ConnectorError::WebhookSourceVerificationFailed
                | errors::ConnectorError::WebhookVerificationSecretNotFound
                | errors::ConnectorError::WebhookVerificationSecretInvalid
                | errors::ConnectorError::WebhookReferenceIdNotFound
                | errors::ConnectorError::WebhookEventTypeNotFound
                | errors::ConnectorError::WebhookResourceObjectNotFound
                | errors::ConnectorError::WebhookResponseEncodingFailed
                | errors::ConnectorError::InvalidDateFormat
                | errors::ConnectorError::DateFormattingFailed
                | errors::ConnectorError::InvalidDataFormat { .. }
                | errors::ConnectorError::MismatchedPaymentData
                | errors::ConnectorError::MandatePaymentDataMismatch { .. }
                | errors::ConnectorError::MissingConnectorRelatedTransactionID { .. }
                | errors::ConnectorError::FileValidationFailed { .. }
                | errors::ConnectorError::MissingConnectorRedirectionPayload { .. }
                | errors::ConnectorError::FailedAtConnector { .. }
                | errors::ConnectorError::MissingPaymentMethodType
                | errors::ConnectorError::InSufficientBalanceInPaymentMethod
                | errors::ConnectorError::RequestTimeoutReceived
                | errors::ConnectorError::CurrencyNotSupported { .. }
                | errors::ConnectorError::ProcessingStepFailed(None)
                | errors::ConnectorError::AmountConversionFailed
                | errors::ConnectorError::GenericError { .. } => {
                    logger::error!(%error,"Setup Mandate flow failed");
                    errors::ApiErrorResponse::PaymentAuthorizationFailed { data: None }
                }
            };
            err.change_context(data)
        })
    }

    fn to_dispute_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse> {
        self.map_err(|err| {
            let error = match err.current_context() {
                errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => {
                    let response_str = std::str::from_utf8(bytes);
                    let data = match response_str {
                        Ok(s) => serde_json::from_str(s)
                            .map_err(
                                |error| logger::error!(%error,"Failed to convert response to JSON"),
                            )
                            .ok(),
                        Err(error) => {
                            logger::error!(%error,"Failed to convert response to UTF8 string");
                            None
                        }
                    };
                    errors::ApiErrorResponse::DisputeFailed { data }
                }
                errors::ConnectorError::MissingRequiredField { field_name } => {
                    errors::ApiErrorResponse::MissingRequiredField { field_name }
                }
                errors::ConnectorError::MissingRequiredFields { field_names } => {
                    errors::ApiErrorResponse::MissingRequiredFields {
                        field_names: field_names.to_vec(),
                    }
                }
                _ => errors::ApiErrorResponse::InternalServerError,
            };
            err.change_context(error)
        })
    }

    fn to_files_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse> {
        self.map_err(|err| {
            let error = match err.current_context() {
                errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => {
                    let response_str = std::str::from_utf8(bytes);
                    let data = match response_str {
                        Ok(s) => serde_json::from_str(s)
                            .map_err(
                                |error| logger::error!(%error,"Failed to convert response to JSON"),
                            )
                            .ok(),
                        Err(error) => {
                            logger::error!(%error,"Failed to convert response to UTF8 string");
                            None
                        }
                    };
                    errors::ApiErrorResponse::DisputeFailed { data }
                }
                errors::ConnectorError::MissingRequiredField { field_name } => {
                    errors::ApiErrorResponse::MissingRequiredField { field_name }
                }
                errors::ConnectorError::MissingRequiredFields { field_names } => {
                    errors::ApiErrorResponse::MissingRequiredFields {
                        field_names: field_names.to_vec(),
                    }
                }
                _ => errors::ApiErrorResponse::InternalServerError,
            };
            err.change_context(error)
        })
    }

    #[cfg(feature = "payouts")]
    fn to_payout_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse> {
        self.map_err(|err| {
            let error = match err.current_context() {
                errors::ConnectorError::ProcessingStepFailed(Some(bytes)) => {
                    let response_str = std::str::from_utf8(bytes);
                    let data = match response_str {
                        Ok(s) => serde_json::from_str(s)
                            .map_err(
                                |error| logger::error!(%error,"Failed to convert response to JSON"),
                            )
                            .ok(),
                        Err(error) => {
                            logger::error!(%error,"Failed to convert response to UTF8 string");
                            None
                        }
                    };
                    errors::ApiErrorResponse::PayoutFailed { data }
                }
                errors::ConnectorError::MissingRequiredField { field_name } => {
                    errors::ApiErrorResponse::MissingRequiredField { field_name }
                }
                errors::ConnectorError::MissingRequiredFields { field_names } => {
                    errors::ApiErrorResponse::MissingRequiredFields {
                        field_names: field_names.to_vec(),
                    }
                }
                errors::ConnectorError::NotSupported { message, connector } => {
                    errors::ApiErrorResponse::NotSupported {
                        message: format!("{message} by {connector}"),
                    }
                }
                errors::ConnectorError::NotImplemented(reason) => {
                    errors::ApiErrorResponse::NotImplemented {
                        message: errors::NotImplementedMessage::Reason(reason.to_string()),
                    }
                }
                errors::ConnectorError::InvalidConnectorConfig { config } => {
                    errors::ApiErrorResponse::InvalidConnectorConfiguration {
                        config: config.to_string(),
                    }
                }
                _ => errors::ApiErrorResponse::InternalServerError,
            };
            err.change_context(error)
        })
    }

    fn to_vault_failed_response(self) -> error_stack::Result<T, errors::ApiErrorResponse> {
        self.map_err(|err| {
            let error = match err.current_context() {
                errors::ConnectorError::ProcessingStepFailed(_) => {
                    errors::ApiErrorResponse::ExternalVaultFailed
                }
                errors::ConnectorError::MissingRequiredField { field_name } => {
                    errors::ApiErrorResponse::MissingRequiredField { field_name }
                }
                errors::ConnectorError::MissingRequiredFields { field_names } => {
                    errors::ApiErrorResponse::MissingRequiredFields {
                        field_names: field_names.to_vec(),
                    }
                }
                errors::ConnectorError::NotSupported { message, connector } => {
                    errors::ApiErrorResponse::NotSupported {
                        message: format!("{message} by {connector}"),
                    }
                }
                errors::ConnectorError::NotImplemented(reason) => {
                    errors::ApiErrorResponse::NotImplemented {
                        message: errors::NotImplementedMessage::Reason(reason.to_string()),
                    }
                }
                errors::ConnectorError::InvalidConnectorConfig { config } => {
                    errors::ApiErrorResponse::InvalidConnectorConfiguration {
                        config: config.to_string(),
                    }
                }
                _ => errors::ApiErrorResponse::InternalServerError,
            };
            err.change_context(error)
        })
    }

    fn allow_webhook_event_type_not_found(
        self,
        enabled: bool,
    ) -> CustomResult<Option<T>, errors::ConnectorError> {
        match self {
            Ok(event_type) => Ok(Some(event_type)),
            Err(error) => match error.current_context() {
                errors::ConnectorError::WebhookEventTypeNotFound if enabled => Ok(None),
                _ => Err(error),
            },
        }
    }
}

pub trait RedisErrorExt {
    #[track_caller]
    fn to_redis_failed_response(self, key: &str) -> error_stack::Report<errors::StorageError>;
}

impl RedisErrorExt for error_stack::Report<errors::RedisError> {
    fn to_redis_failed_response(self, key: &str) -> error_stack::Report<errors::StorageError> {
        match self.current_context() {
            errors::RedisError::NotFound => self.change_context(
                errors::StorageError::ValueNotFound(format!("Data does not exist for key {key}")),
            ),
            errors::RedisError::SetNxFailed => {
                self.change_context(errors::StorageError::DuplicateValue {
                    entity: "redis",
                    key: Some(key.to_string()),
                })
            }
            _ => self.change_context(errors::StorageError::KVError),
        }
    }
}

#[cfg(feature = "olap")]
impl<T> StorageErrorExt<T, errors::UserErrors> for error_stack::Result<T, errors::StorageError> {
    #[track_caller]
    fn to_not_found_response(
        self,
        not_found_response: errors::UserErrors,
    ) -> error_stack::Result<T, errors::UserErrors> {
        self.map_err(|e| {
            if e.current_context().is_db_not_found() {
                e.change_context(not_found_response)
            } else {
                e.change_context(errors::UserErrors::InternalServerError)
            }
        })
    }

    #[track_caller]
    fn to_duplicate_response(
        self,
        duplicate_response: errors::UserErrors,
    ) -> error_stack::Result<T, errors::UserErrors> {
        self.map_err(|e| {
            if e.current_context().is_db_unique_violation() {
                e.change_context(duplicate_response)
            } else {
                e.change_context(errors::UserErrors::InternalServerError)
            }
        })
    }
}


// File: crates/router/src/core/errors/chat.rs

#[derive(Debug, thiserror::Error)]
pub enum ChatErrors {
    #[error("User InternalServerError")]
    InternalServerError,
    #[error("Missing Config error")]
    MissingConfigError,
    #[error("Chat response deserialization failed")]
    ChatResponseDeserializationFailed,
    #[error("Unauthorized access")]
    UnauthorizedAccess,
}

impl common_utils::errors::ErrorSwitch<api_models::errors::types::ApiErrorResponse> for ChatErrors {
    fn switch(&self) -> api_models::errors::types::ApiErrorResponse {
        use api_models::errors::types::{ApiError, ApiErrorResponse as AER};
        let sub_code = "AI";
        match self {
            Self::InternalServerError => {
                AER::InternalServerError(ApiError::new("HE", 0, self.get_error_message(), None))
            }
            Self::MissingConfigError => {
                AER::InternalServerError(ApiError::new(sub_code, 1, self.get_error_message(), None))
            }
            Self::ChatResponseDeserializationFailed => {
                AER::BadRequest(ApiError::new(sub_code, 2, self.get_error_message(), None))
            }
            Self::UnauthorizedAccess => {
                AER::Unauthorized(ApiError::new(sub_code, 3, self.get_error_message(), None))
            }
        }
    }
}

impl ChatErrors {
    pub fn get_error_message(&self) -> String {
        match self {
            Self::InternalServerError => "Something went wrong".to_string(),
            Self::MissingConfigError => "Missing webhook url".to_string(),
            Self::ChatResponseDeserializationFailed => "Failed to parse chat response".to_string(),
            Self::UnauthorizedAccess => "Not authorized to access the resource".to_string(),
        }
    }
}


// File: crates/router/src/core/errors/user/sample_data.rs

use api_models::errors::types::{ApiError, ApiErrorResponse};
use common_utils::errors::{CustomResult, ErrorSwitch, ErrorSwitchFrom};
use storage_impl::errors::StorageError;

pub type SampleDataResult<T> = CustomResult<T, SampleDataError>;

#[derive(Debug, Clone, serde::Serialize, thiserror::Error)]
pub enum SampleDataError {
    #[error["Internal Server Error"]]
    InternalServerError,
    #[error("Data Does Not Exist")]
    DataDoesNotExist,
    #[error("Invalid Parameters")]
    InvalidParameters,
    #[error["Invalid Records"]]
    InvalidRange,
}

impl ErrorSwitch<ApiErrorResponse> for SampleDataError {
    fn switch(&self) -> ApiErrorResponse {
        match self {
            Self::InternalServerError => ApiErrorResponse::InternalServerError(ApiError::new(
                "SD",
                0,
                "Something went wrong",
                None,
            )),
            Self::DataDoesNotExist => ApiErrorResponse::NotFound(ApiError::new(
                "SD",
                1,
                "Sample Data not present for given request",
                None,
            )),
            Self::InvalidParameters => ApiErrorResponse::BadRequest(ApiError::new(
                "SD",
                2,
                "Invalid parameters to generate Sample Data",
                None,
            )),
            Self::InvalidRange => ApiErrorResponse::BadRequest(ApiError::new(
                "SD",
                3,
                "Records to be generated should be between range 10 and 100",
                None,
            )),
        }
    }
}

impl ErrorSwitchFrom<StorageError> for SampleDataError {
    fn switch_from(error: &StorageError) -> Self {
        match matches!(error, StorageError::ValueNotFound(_)) {
            true => Self::DataDoesNotExist,
            false => Self::InternalServerError,
        }
    }
}


// File: crates/router/src/core/relay/utils.rs

use std::str::FromStr;

use common_utils::{ext_traits::OptionExt, id_type};
use error_stack::ResultExt;
use hyperswitch_domain_models::{router_data::ErrorResponse, types};

use crate::{
    core::payments,
    db::{
        domain,
        errors::{self, RouterResult},
    },
    routes::SessionState,
};

const IRRELEVANT_PAYMENT_INTENT_ID: &str = "irrelevant_payment_intent_id";

const IRRELEVANT_PAYMENT_ATTEMPT_ID: &str = "irrelevant_payment_attempt_id";

pub async fn construct_relay_refund_router_data<F>(
    state: &SessionState,
    merchant_id: &id_type::MerchantId,
    connector_account: &domain::MerchantConnectorAccount,
    relay_record: &hyperswitch_domain_models::relay::Relay,
) -> RouterResult<types::RefundsRouterData<F>> {
    let connector_auth_type = connector_account
        .get_connector_account_details()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed while parsing value for ConnectorAuthType")?;

    #[cfg(feature = "v2")]
    let connector_name = &connector_account.connector_name.to_string();

    #[cfg(feature = "v1")]
    let connector_name = &connector_account.connector_name;

    let webhook_url = Some(payments::helpers::create_webhook_url(
        &state.base_url.clone(),
        merchant_id,
        connector_account.get_id().get_string_repr(),
    ));

    let supported_connector = &state
        .conf
        .multiple_api_version_supported_connectors
        .supported_connectors;

    let connector_enum = api_models::enums::Connector::from_str(connector_name)
        .change_context(errors::ConnectorError::InvalidConnectorName)
        .change_context(errors::ApiErrorResponse::InvalidDataValue {
            field_name: "connector",
        })
        .attach_printable_lazy(|| format!("unable to parse connector name {connector_name:?}"))?;

    let connector_api_version = if supported_connector.contains(&connector_enum) {
        state
            .store
            .find_config_by_key(&format!("connector_api_version_{connector_name}"))
            .await
            .map(|value| value.config)
            .ok()
    } else {
        None
    };

    let hyperswitch_domain_models::relay::RelayData::Refund(relay_refund_data) = relay_record
        .request_data
        .clone()
        .get_required_value("refund relay data")
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to obtain relay data to construct relay refund data")?;

    let relay_id_string = relay_record.id.get_string_repr().to_string();

    let router_data = hyperswitch_domain_models::router_data::RouterData {
        flow: std::marker::PhantomData,
        merchant_id: merchant_id.clone(),
        customer_id: None,
        tenant_id: state.tenant.tenant_id.clone(),
        connector: connector_name.to_string(),
        payment_id: IRRELEVANT_PAYMENT_INTENT_ID.to_string(),
        attempt_id: IRRELEVANT_PAYMENT_ATTEMPT_ID.to_string(),
        status: common_enums::AttemptStatus::Charged,
        payment_method: common_enums::PaymentMethod::default(),
        payment_method_type: None,
        connector_auth_type,
        description: None,
        address: hyperswitch_domain_models::payment_address::PaymentAddress::default(),
        auth_type: common_enums::AuthenticationType::default(),
        connector_meta_data: connector_account.metadata.clone(),
        connector_wallets_details: None,
        amount_captured: None,
        payment_method_status: None,
        minor_amount_captured: None,
        request: hyperswitch_domain_models::router_request_types::RefundsData {
            refund_id: relay_id_string.clone(),
            connector_transaction_id: relay_record.connector_resource_id.clone(),
            refund_amount: relay_refund_data.amount.get_amount_as_i64(),
            minor_refund_amount: relay_refund_data.amount,
            currency: relay_refund_data.currency,
            payment_amount: relay_refund_data.amount.get_amount_as_i64(),
            minor_payment_amount: relay_refund_data.amount,
            webhook_url,
            connector_metadata: None,
            refund_connector_metadata: None,
            reason: relay_refund_data.reason,
            connector_refund_id: relay_record.connector_reference_id.clone(),
            browser_info: None,
            split_refunds: None,
            integrity_object: None,
            refund_status: common_enums::RefundStatus::from(relay_record.status),
            merchant_account_id: None,
            merchant_config_currency: None,
            capture_method: None,
            additional_payment_method_data: None,
        },

        response: Err(ErrorResponse::default()),
        access_token: None,
        session_token: None,
        reference_id: None,
        payment_method_token: None,
        connector_customer: None,
        recurring_mandate_payment_data: None,
        preprocessing_id: None,
        connector_request_reference_id: relay_id_string.clone(),
        #[cfg(feature = "payouts")]
        payout_method_data: None,
        #[cfg(feature = "payouts")]
        quote_id: None,
        test_mode: connector_account.get_connector_test_mode(),
        payment_method_balance: None,
        connector_api_version,
        connector_http_status_code: None,
        external_latency: None,
        apple_pay_flow: None,
        frm_metadata: None,
        refund_id: Some(relay_id_string),
        dispute_id: None,
        connector_response: None,
        integrity_check: Ok(()),
        additional_merchant_data: None,
        header_payload: None,
        connector_mandate_request_reference_id: None,
        authentication_id: None,
        psd2_sca_exemption_type: None,
        raw_connector_response: None,
        is_payment_id_from_merchant: None,
        l2_l3_data: None,
        minor_amount_capturable: None,
        authorized_amount: None,
    };

    Ok(router_data)
}


// File: crates/router/src/core/webhooks/outgoing_v2.rs

use std::collections::HashMap;

use api_models::{webhook_events, webhooks};
use common_utils::{ext_traits, request, type_name, types::keymanager};
use diesel_models::process_tracker::business_status;
use error_stack::{report, Report, ResultExt};
use hyperswitch_domain_models::type_encryption::{crypto_operation, CryptoOperation};
use hyperswitch_interfaces::consts;
use router_env::{
    instrument,
    tracing::{self, Instrument},
};

use super::{
    types,
    utils::{self, increment_webhook_outgoing_received_count},
    MERCHANT_ID,
};
use crate::{
    core::{
        errors::{self, CustomResult},
        metrics,
    },
    events::outgoing_webhook_logs,
    logger,
    routes::{app::SessionStateInfo, SessionState},
    services,
    types::{
        api, domain,
        storage::{self, enums},
        transformers::ForeignFrom,
    },
};

#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub(crate) async fn create_event_and_trigger_outgoing_webhook(
    state: SessionState,
    business_profile: domain::Profile,
    merchant_key_store: &domain::MerchantKeyStore,
    event_type: enums::EventType,
    event_class: enums::EventClass,
    primary_object_id: String,
    primary_object_type: enums::EventObjectType,
    content: api::OutgoingWebhookContent,
    primary_object_created_at: time::PrimitiveDateTime,
) -> CustomResult<(), errors::ApiErrorResponse> {
    let delivery_attempt = enums::WebhookDeliveryAttempt::InitialAttempt;
    let idempotent_event_id =
        utils::get_idempotent_event_id(&primary_object_id, event_type, delivery_attempt)
            .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
            .attach_printable("Failed to generate idempotent event ID")?;
    let webhook_url_result = business_profile
        .get_webhook_url_from_profile()
        .change_context(errors::WebhooksFlowError::MerchantWebhookUrlNotConfigured);

    if utils::is_outgoing_webhook_disabled(
        &state,
        &webhook_url_result,
        &business_profile,
        &idempotent_event_id,
    ) {
        return Ok(());
    }

    let event_id = utils::generate_event_id();
    let merchant_id = business_profile.merchant_id.clone();
    let now = common_utils::date_time::now();

    let outgoing_webhook = api::OutgoingWebhook {
        merchant_id: merchant_id.clone(),
        event_id: event_id.clone(),
        event_type,
        content: content.clone(),
        timestamp: now,
    };

    let request_content = get_outgoing_webhook_request(outgoing_webhook, &business_profile)
        .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
        .attach_printable("Failed to construct outgoing webhook request content")?;

    let event_metadata = storage::EventMetadata::foreign_from(&content);
    let key_manager_state = &(&state).into();
    let new_event = domain::Event {
        event_id: event_id.clone(),
        event_type,
        event_class,
        is_webhook_notified: false,
        primary_object_id,
        primary_object_type,
        created_at: now,
        merchant_id: Some(business_profile.merchant_id.clone()),
        business_profile_id: Some(business_profile.get_id().to_owned()),
        primary_object_created_at: Some(primary_object_created_at),
        idempotent_event_id: Some(idempotent_event_id.clone()),
        initial_attempt_id: Some(event_id.clone()),
        request: Some(
            crypto_operation(
                key_manager_state,
                type_name!(domain::Event),
                CryptoOperation::Encrypt(
                    ext_traits::Encode::encode_to_string_of_json(&request_content)
                        .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
                        .attach_printable("Failed to encode outgoing webhook request content")
                        .map(masking::Secret::new)?,
                ),
                keymanager::Identifier::Merchant(merchant_key_store.merchant_id.clone()),
                masking::PeekInterface::peek(merchant_key_store.key.get_inner()),
            )
            .await
            .and_then(|val| val.try_into_operation())
            .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
            .attach_printable("Failed to encrypt outgoing webhook request content")?,
        ),
        response: None,
        delivery_attempt: Some(delivery_attempt),
        metadata: Some(event_metadata),
        is_overall_delivery_successful: Some(false),
    };

    let event_insert_result = state
        .store
        .insert_event(new_event, merchant_key_store)
        .await;

    let event = match event_insert_result {
        Ok(event) => Ok(event),
        Err(error) => {
            if error.current_context().is_db_unique_violation() {
                // If the event_id already exists in the database, it indicates that the event for the resource has already been sent, so we skip the flow
                logger::debug!("Event with idempotent ID `{idempotent_event_id}` already exists in the database");
                return Ok(());
            } else {
                logger::error!(event_insertion_failure=?error);
                Err(error
                    .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
                    .attach_printable("Failed to insert event in events table"))
            }
        }
    }?;

    let cloned_key_store = merchant_key_store.clone();
    // Using a tokio spawn here and not arbiter because not all caller of this function
    // may have an actix arbiter
    tokio::spawn(
        async move {
            Box::pin(trigger_webhook_and_raise_event(
                state,
                business_profile,
                &cloned_key_store,
                event,
                request_content,
                delivery_attempt,
                Some(content),
            ))
            .await;
        }
        .in_current_span(),
    );

    Ok(())
}

#[allow(clippy::too_many_arguments)]
#[instrument(skip_all)]
pub(crate) async fn trigger_webhook_and_raise_event(
    state: SessionState,
    business_profile: domain::Profile,
    merchant_key_store: &domain::MerchantKeyStore,
    event: domain::Event,
    request_content: webhook_events::OutgoingWebhookRequestContent,
    delivery_attempt: enums::WebhookDeliveryAttempt,
    content: Option<api::OutgoingWebhookContent>,
) {
    logger::debug!(
        event_id=%event.event_id,
        idempotent_event_id=?event.idempotent_event_id,
        initial_attempt_id=?event.initial_attempt_id,
        "Attempting to send webhook"
    );

    let merchant_id = business_profile.merchant_id.clone();
    let trigger_webhook_result = trigger_webhook_to_merchant(
        state.clone(),
        business_profile,
        merchant_key_store,
        event.clone(),
        request_content,
        delivery_attempt,
    )
    .await;

    let _ =
        raise_webhooks_analytics_event(state, trigger_webhook_result, content, merchant_id, event)
            .await;
}

async fn trigger_webhook_to_merchant(
    state: SessionState,
    business_profile: domain::Profile,
    merchant_key_store: &domain::MerchantKeyStore,
    event: domain::Event,
    request_content: webhook_events::OutgoingWebhookRequestContent,
    delivery_attempt: enums::WebhookDeliveryAttempt,
) -> CustomResult<
    (domain::Event, Option<Report<errors::WebhooksFlowError>>),
    errors::WebhooksFlowError,
> {
    let webhook_url = business_profile
        .get_webhook_url_from_profile()
        .change_context(errors::WebhooksFlowError::MerchantWebhookUrlNotConfigured)?;

    let response = build_and_send_request(&state, request_content, webhook_url).await;

    metrics::WEBHOOK_OUTGOING_COUNT.add(
        1,
        router_env::metric_attributes!((MERCHANT_ID, business_profile.merchant_id.clone())),
    );
    logger::debug!(outgoing_webhook_response=?response);

    match response {
        Ok(response) => {
            delivery_attempt
                .handle_success_response(
                    state,
                    merchant_key_store.clone(),
                    &business_profile.merchant_id,
                    &event.event_id,
                    None,
                    response,
                )
                .await
        }
        Err(client_error) => {
            delivery_attempt
                .handle_error_response(
                    state,
                    merchant_key_store.clone(),
                    &business_profile.merchant_id,
                    &event.event_id,
                    client_error,
                )
                .await
        }
    }
}

async fn raise_webhooks_analytics_event(
    state: SessionState,
    trigger_webhook_result: CustomResult<
        (domain::Event, Option<Report<errors::WebhooksFlowError>>),
        errors::WebhooksFlowError,
    >,
    content: Option<api::OutgoingWebhookContent>,
    merchant_id: common_utils::id_type::MerchantId,
    fallback_event: domain::Event,
) {
    let (updated_event, optional_error) = match trigger_webhook_result {
        Ok((updated_event, error)) => (updated_event, error),
        Err(error) => (fallback_event, Some(error)),
    };
    let error = optional_error.and_then(|error| {
        logger::error!(?error, "Failed to send webhook to merchant");

        serde_json::to_value(error.current_context())
            .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
            .inspect_err(|error| {
                logger::error!(?error, "Failed to serialize outgoing webhook error as JSON");
            })
            .ok()
    });

    let outgoing_webhook_event_content = content
        .as_ref()
        .and_then(
            outgoing_webhook_logs::OutgoingWebhookEventMetric::get_outgoing_webhook_event_content,
        )
        .or_else(|| {
            updated_event
                .metadata
                .map(outgoing_webhook_logs::OutgoingWebhookEventContent::foreign_from)
        });

    // Get status_code from webhook response
    let status_code = {
        let webhook_response: Option<webhook_events::OutgoingWebhookResponseContent> =
            updated_event.response.and_then(|res| {
                ext_traits::StringExt::parse_struct(
                    masking::PeekInterface::peek(res.get_inner()),
                    "OutgoingWebhookResponseContent",
                )
                .map_err(|error| {
                    logger::error!(?error, "Error deserializing webhook response");
                    error
                })
                .ok()
            });
        webhook_response.and_then(|res| res.status_code)
    };

    let webhook_event = outgoing_webhook_logs::OutgoingWebhookEvent::new(
        state.tenant.tenant_id.clone(),
        merchant_id,
        updated_event.event_id,
        updated_event.event_type,
        outgoing_webhook_event_content,
        error,
        updated_event.initial_attempt_id,
        status_code,
        updated_event.delivery_attempt,
    );
    state.event_handler().log_event(&webhook_event);
}

pub(crate) fn get_outgoing_webhook_request(
    outgoing_webhook: api::OutgoingWebhook,
    business_profile: &domain::Profile,
) -> CustomResult<webhook_events::OutgoingWebhookRequestContent, errors::WebhooksFlowError> {
    #[inline]
    fn get_outgoing_webhook_request_inner<WebhookType: types::OutgoingWebhookType>(
        outgoing_webhook: api::OutgoingWebhook,
        business_profile: &domain::Profile,
    ) -> CustomResult<webhook_events::OutgoingWebhookRequestContent, errors::WebhooksFlowError>
    {
        let mut headers = vec![
            (
                reqwest::header::CONTENT_TYPE.to_string(),
                mime::APPLICATION_JSON.essence_str().into(),
            ),
            (
                reqwest::header::USER_AGENT.to_string(),
                consts::USER_AGENT.to_string().into(),
            ),
        ];

        let transformed_outgoing_webhook = WebhookType::from(outgoing_webhook);
        let payment_response_hash_key = business_profile.payment_response_hash_key.clone();
        let custom_headers = business_profile
            .outgoing_webhook_custom_http_headers
            .clone()
            .map(|headers| {
                ext_traits::ValueExt::parse_value::<HashMap<String, String>>(
                    masking::ExposeInterface::expose(headers.into_inner()),
                    "HashMap<String,String>",
                )
                .change_context(errors::WebhooksFlowError::OutgoingWebhookEncodingFailed)
                .attach_printable("Failed to deserialize outgoing webhook custom HTTP headers")
            })
            .transpose()?;
        if let Some(ref map) = custom_headers {
            headers.extend(
                map.iter()
                    .map(|(key, value)| (key.clone(), masking::Mask::into_masked(value.clone()))),
            );
        };
        let outgoing_webhooks_signature = transformed_outgoing_webhook
            .get_outgoing_webhooks_signature(payment_response_hash_key)?;

        if let Some(signature) = outgoing_webhooks_signature.signature {
            WebhookType::add_webhook_header(&mut headers, signature)
        }

        Ok(webhook_events::OutgoingWebhookRequestContent {
            body: outgoing_webhooks_signature.payload,
            headers: headers
                .into_iter()
                .map(|(name, value)| (name, masking::Secret::new(value.into_inner())))
                .collect(),
        })
    }

    get_outgoing_webhook_request_inner::<webhooks::OutgoingWebhook>(
        outgoing_webhook,
        business_profile,
    )
}

async fn build_and_send_request(
    state: &SessionState,
    request_content: webhook_events::OutgoingWebhookRequestContent,
    webhook_url: String,
) -> Result<reqwest::Response, Report<common_enums::ApiClientError>> {
    let headers = request_content
        .headers
        .into_iter()
        .map(|(name, value)| (name, masking::Mask::into_masked(value)))
        .collect();
    let request = services::RequestBuilder::new()
        .method(services::Method::Post)
        .url(&webhook_url)
        .attach_default_headers()
        .headers(headers)
        .set_body(request::RequestContent::RawBytes(
            masking::ExposeInterface::expose(request_content.body).into_bytes(),
        ))
        .build();

    state
        .api_client
        .send_request(state, request, None, false)
        .await
}

async fn api_client_error_handler(
    state: SessionState,
    merchant_key_store: domain::MerchantKeyStore,
    merchant_id: &common_utils::id_type::MerchantId,
    event_id: &str,
    client_error: Report<errors::ApiClientError>,
    delivery_attempt: enums::WebhookDeliveryAttempt,
    _schedule_webhook_retry: types::ScheduleWebhookRetry,
) -> CustomResult<
    (domain::Event, Option<Report<errors::WebhooksFlowError>>),
    errors::WebhooksFlowError,
> {
    // Not including detailed error message in response information since it contains too
    // much of diagnostic information to be exposed to the merchant.
    let is_webhook_notified = false;
    let response_to_store = webhook_events::OutgoingWebhookResponseContent {
        body: None,
        headers: None,
        status_code: None,
        error_message: Some("Unable to send request to merchant server".to_string()),
    };
    let updated_event = update_event_in_storage(
        state,
        is_webhook_notified,
        response_to_store,
        merchant_key_store,
        merchant_id,
        event_id,
    )
    .await?;

    let error = client_error.change_context(errors::WebhooksFlowError::CallToMerchantFailed);
    logger::error!(
        ?error,
        ?delivery_attempt,
        "An error occurred when sending webhook to merchant"
    );

    //TODO: add outgoing webhook retries support
    // if let ScheduleWebhookRetry::WithProcessTracker(process_tracker) = schedule_webhook_retry {
    //     // Schedule a retry attempt for webhook delivery
    //     outgoing_webhook_retry::retry_webhook_delivery_task(
    //         &*state.store,
    //         merchant_id,
    //         *process_tracker,
    //     )
    //     .await
    //     .change_context(errors::WebhooksFlowError::OutgoingWebhookRetrySchedulingFailed)?;
    // }

    Ok((updated_event, Some(error)))
}

async fn update_event_in_storage(
    state: SessionState,
    is_webhook_notified: bool,
    outgoing_webhook_response: webhook_events::OutgoingWebhookResponseContent,
    merchant_key_store: domain::MerchantKeyStore,
    merchant_id: &common_utils::id_type::MerchantId,
    event_id: &str,
) -> CustomResult<domain::Event, errors::WebhooksFlowError> {
    let key_manager_state = &(&state).into();
    let event_update = domain::EventUpdate::UpdateResponse {
        is_webhook_notified,
        response: Some(
            crypto_operation(
                key_manager_state,
                type_name!(domain::Event),
                CryptoOperation::Encrypt(
                    ext_traits::Encode::encode_to_string_of_json(&outgoing_webhook_response)
                        .change_context(
                            errors::WebhooksFlowError::OutgoingWebhookResponseEncodingFailed,
                        )
                        .map(masking::Secret::new)?,
                ),
                keymanager::Identifier::Merchant(merchant_key_store.merchant_id.clone()),
                masking::PeekInterface::peek(merchant_key_store.key.get_inner()),
            )
            .await
            .and_then(|val| val.try_into_operation())
            .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)
            .attach_printable("Failed to encrypt outgoing webhook response content")?,
        ),
    };
    state
        .store
        .update_event_by_merchant_id_event_id(
            merchant_id,
            event_id,
            event_update,
            &merchant_key_store,
        )
        .await
        .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)
}

async fn update_overall_delivery_status_in_storage(
    state: SessionState,
    merchant_key_store: domain::MerchantKeyStore,
    merchant_id: &common_utils::id_type::MerchantId,
    updated_event: &domain::Event,
) -> CustomResult<(), errors::WebhooksFlowError> {
    let update_overall_delivery_status = domain::EventUpdate::OverallDeliveryStatusUpdate {
        is_overall_delivery_successful: true,
    };

    let initial_attempt_id = updated_event.initial_attempt_id.as_ref();
    let delivery_attempt = updated_event.delivery_attempt;

    if let Some((
        initial_attempt_id,
        enums::WebhookDeliveryAttempt::InitialAttempt
        | enums::WebhookDeliveryAttempt::AutomaticRetry,
    )) = initial_attempt_id.zip(delivery_attempt)
    {
        state
            .store
            .update_event_by_merchant_id_event_id(
                merchant_id,
                initial_attempt_id.as_str(),
                update_overall_delivery_status,
                &merchant_key_store,
            )
            .await
            .change_context(errors::WebhooksFlowError::WebhookEventUpdationFailed)
            .attach_printable("Failed to update initial delivery attempt")?;
    }

    Ok(())
}

async fn handle_successful_delivery(
    state: SessionState,
    merchant_key_store: domain::MerchantKeyStore,
    updated_event: &domain::Event,
    merchant_id: &common_utils::id_type::MerchantId,
    process_tracker: Option<storage::ProcessTracker>,
    business_status: &'static str,
) -> CustomResult<(), errors::WebhooksFlowError> {
    update_overall_delivery_status_in_storage(
        state.clone(),
        merchant_key_store.clone(),
        merchant_id,
        updated_event,
    )
    .await?;

    increment_webhook_outgoing_received_count(merchant_id);

    match process_tracker {
        Some(process_tracker) => state
            .store
            .as_scheduler()
            .finish_process_with_business_status(process_tracker, business_status)
            .await
            .change_context(
                errors::WebhooksFlowError::OutgoingWebhookProcessTrackerTaskUpdateFailed,
            ),
        None => Ok(()),
    }
}

async fn handle_failed_delivery(
    _state: SessionState,
    merchant_id: &common_utils::id_type::MerchantId,
    delivery_attempt: enums::WebhookDeliveryAttempt,
    status_code: u16,
    log_message: &'static str,
    _schedule_webhook_retry: types::ScheduleWebhookRetry,
) -> CustomResult<(), errors::WebhooksFlowError> {
    utils::increment_webhook_outgoing_not_received_count(merchant_id);

    let error = report!(errors::WebhooksFlowError::NotReceivedByMerchant);
    logger::warn!(?error, ?delivery_attempt, status_code, %log_message);

    //TODO: add outgoing webhook retries support
    // if let ScheduleWebhookRetry::WithProcessTracker(process_tracker) = schedule_webhook_retry {
    //     // Schedule a retry attempt for webhook delivery
    //     outgoing_webhook_retry::retry_webhook_delivery_task(
    //         &*state.store,
    //         merchant_id,
    //         *process_tracker,
    //     )
    //     .await
    //     .change_context(errors::WebhooksFlowError::OutgoingWebhookRetrySchedulingFailed)?;
    // }

    Err(error)
}

impl ForeignFrom<&api::OutgoingWebhookContent> for storage::EventMetadata {
    fn foreign_from(content: &api::OutgoingWebhookContent) -> Self {
        match content {
            webhooks::OutgoingWebhookContent::PaymentDetails(payments_response) => Self::Payment {
                payment_id: payments_response.id.clone(),
            },
            webhooks::OutgoingWebhookContent::RefundDetails(refund_response) => Self::Refund {
                payment_id: refund_response.payment_id.clone(),
                refund_id: refund_response.id.clone(),
            },
            webhooks::OutgoingWebhookContent::DisputeDetails(dispute_response) => {
                //TODO: add support for dispute outgoing webhook
                todo!()
            }
            webhooks::OutgoingWebhookContent::MandateDetails(mandate_response) => Self::Mandate {
                payment_method_id: mandate_response.payment_method_id.clone(),
                mandate_id: mandate_response.mandate_id.clone(),
            },
            #[cfg(feature = "payouts")]
            webhooks::OutgoingWebhookContent::PayoutDetails(payout_response) => Self::Payout {
                payout_id: payout_response.payout_id.clone(),
            },
        }
    }
}

impl ForeignFrom<storage::EventMetadata> for outgoing_webhook_logs::OutgoingWebhookEventContent {
    fn foreign_from(event_metadata: storage::EventMetadata) -> Self {
        match event_metadata {
            diesel_models::EventMetadata::Payment { payment_id } => Self::Payment {
                payment_id,
                content: serde_json::Value::Null,
            },
            diesel_models::EventMetadata::Payout { payout_id } => Self::Payout {
                payout_id,
                content: serde_json::Value::Null,
            },
            diesel_models::EventMetadata::Refund {
                payment_id,
                refund_id,
            } => Self::Refund {
                payment_id,
                refund_id,
                content: serde_json::Value::Null,
            },
            diesel_models::EventMetadata::Dispute {
                payment_id,
                attempt_id,
                dispute_id,
            } => Self::Dispute {
                payment_id,
                attempt_id,
                dispute_id,
                content: serde_json::Value::Null,
            },
            diesel_models::EventMetadata::Mandate {
                payment_method_id,
                mandate_id,
            } => Self::Mandate {
                payment_method_id,
                mandate_id,
                content: serde_json::Value::Null,
            },
            diesel_models::EventMetadata::Subscription {
                subscription_id,
                invoice_id,
                payment_id,
            } => Self::Subscription {
                subscription_id,
                invoice_id,
                payment_id,
                content: serde_json::Value::Null,
            },
        }
    }
}

trait OutgoingWebhookResponseHandler {
    async fn handle_error_response(
        &self,
        state: SessionState,
        merchant_key_store: domain::MerchantKeyStore,
        merchant_id: &common_utils::id_type::MerchantId,
        event_id: &str,
        client_error: Report<errors::ApiClientError>,
    ) -> CustomResult<
        (domain::Event, Option<Report<errors::WebhooksFlowError>>),
        errors::WebhooksFlowError,
    >;

    async fn handle_success_response(
        &self,
        state: SessionState,
        merchant_key_store: domain::MerchantKeyStore,
        merchant_id: &common_utils::id_type::MerchantId,
        event_id: &str,
        process_tracker: Option<storage::ProcessTracker>,
        response: reqwest::Response,
    ) -> CustomResult<
        (domain::Event, Option<Report<errors::WebhooksFlowError>>),
        errors::WebhooksFlowError,
    >;
}

impl OutgoingWebhookResponseHandler for enums::WebhookDeliveryAttempt {
    async fn handle_error_response(
        &self,
        state: SessionState,
        merchant_key_store: domain::MerchantKeyStore,
        merchant_id: &common_utils::id_type::MerchantId,
        event_id: &str,
        client_error: Report<errors::ApiClientError>,
    ) -> CustomResult<
        (domain::Event, Option<Report<errors::WebhooksFlowError>>),
        errors::WebhooksFlowError,
    > {
        let schedule_webhook_retry = match self {
            Self::InitialAttempt | Self::ManualRetry => types::ScheduleWebhookRetry::NoSchedule,
            Self::AutomaticRetry => {
                // ScheduleWebhookRetry::WithProcessTracker(Box::new(process_tracker))
                todo!()
            }
        };

        api_client_error_handler(
            state,
            merchant_key_store,
            merchant_id,
            event_id,
            client_error,
            *self,
            schedule_webhook_retry,
        )
        .await
    }

    async fn handle_success_response(
        &self,
        state: SessionState,
        merchant_key_store: domain::MerchantKeyStore,
        merchant_id: &common_utils::id_type::MerchantId,
        event_id: &str,
        process_tracker: Option<storage::ProcessTracker>,
        response: reqwest::Response,
    ) -> CustomResult<
        (domain::Event, Option<Report<errors::WebhooksFlowError>>),
        errors::WebhooksFlowError,
    > {
        let status_code = response.status();
        let is_webhook_notified = status_code.is_success();
        let response_struct = types::WebhookResponse { response };
        let outgoing_webhook_response = response_struct
            .get_outgoing_webhook_response_content()
            .await;
        let updated_event = update_event_in_storage(
            state.clone(),
            is_webhook_notified,
            outgoing_webhook_response,
            merchant_key_store.clone(),
            merchant_id,
            event_id,
        )
        .await?;

        let webhook_action_handler = get_action_handler(*self);
        let result = if is_webhook_notified {
            webhook_action_handler
                .notified_action(
                    state,
                    merchant_key_store,
                    &updated_event,
                    merchant_id,
                    process_tracker,
                )
                .await
        } else {
            webhook_action_handler
                .not_notified_action(state, merchant_id, status_code.as_u16())
                .await
        };

        Ok((updated_event, result))
    }
}

#[async_trait::async_trait]
trait WebhookNotificationHandler: Send + Sync {
    async fn notified_action(
        &self,
        state: SessionState,
        merchant_key_store: domain::MerchantKeyStore,
        updated_event: &domain::Event,
        merchant_id: &common_utils::id_type::MerchantId,
        process_tracker: Option<storage::ProcessTracker>,
    ) -> Option<Report<errors::WebhooksFlowError>>;

    async fn not_notified_action(
        &self,
        state: SessionState,
        merchant_id: &common_utils::id_type::MerchantId,
        status_code: u16,
    ) -> Option<Report<errors::WebhooksFlowError>>;
}

struct InitialAttempt;
struct AutomaticRetry;
struct ManualRetry;

#[async_trait::async_trait]
impl WebhookNotificationHandler for InitialAttempt {
    async fn notified_action(
        &self,
        state: SessionState,
        merchant_key_store: domain::MerchantKeyStore,
        updated_event: &domain::Event,
        merchant_id: &common_utils::id_type::MerchantId,
        process_tracker: Option<storage::ProcessTracker>,
    ) -> Option<Report<errors::WebhooksFlowError>> {
        handle_successful_delivery(
            state,
            merchant_key_store,
            updated_event,
            merchant_id,
            process_tracker,
            business_status::INITIAL_DELIVERY_ATTEMPT_SUCCESSFUL,
        )
        .await
        .err()
        .map(|error: Report<errors::WebhooksFlowError>| report!(error))
    }

    async fn not_notified_action(
        &self,
        state: SessionState,
        merchant_id: &common_utils::id_type::MerchantId,
        status_code: u16,
    ) -> Option<Report<errors::WebhooksFlowError>> {
        handle_failed_delivery(
            state.clone(),
            merchant_id,
            enums::WebhookDeliveryAttempt::InitialAttempt,
            status_code,
            "Ignoring error when sending webhook to merchant",
            types::ScheduleWebhookRetry::NoSchedule,
        )
        .await
        .err()
        .map(|error| report!(error))
    }
}

#[async_trait::async_trait]
impl WebhookNotificationHandler for AutomaticRetry {
    async fn notified_action(
        &self,
        _state: SessionState,
        _merchant_key_store: domain::MerchantKeyStore,
        _updated_event: &domain::Event,
        _merchant_id: &common_utils::id_type::MerchantId,
        _process_tracker: Option<storage::ProcessTracker>,
    ) -> Option<Report<errors::WebhooksFlowError>> {
        todo!()
    }

    async fn not_notified_action(
        &self,
        _state: SessionState,
        _merchant_id: &common_utils::id_type::MerchantId,
        _status_code: u16,
    ) -> Option<Report<errors::WebhooksFlowError>> {
        todo!()
    }
}

#[async_trait::async_trait]
impl WebhookNotificationHandler for ManualRetry {
    async fn notified_action(
        &self,
        _state: SessionState,
        _merchant_key_store: domain::MerchantKeyStore,
        _updated_event: &domain::Event,
        merchant_id: &common_utils::id_type::MerchantId,
        _process_tracker: Option<storage::ProcessTracker>,
    ) -> Option<Report<errors::WebhooksFlowError>> {
        increment_webhook_outgoing_received_count(merchant_id);
        None
    }

    async fn not_notified_action(
        &self,
        state: SessionState,
        merchant_id: &common_utils::id_type::MerchantId,
        status_code: u16,
    ) -> Option<Report<errors::WebhooksFlowError>> {
        handle_failed_delivery(
            state.clone(),
            merchant_id,
            enums::WebhookDeliveryAttempt::ManualRetry,
            status_code,
            "Ignoring error when sending webhook to merchant",
            types::ScheduleWebhookRetry::NoSchedule,
        )
        .await
        .err()
        .map(|error| report!(error))
    }
}

fn get_action_handler(
    attempt: enums::WebhookDeliveryAttempt,
) -> Box<dyn WebhookNotificationHandler> {
    match attempt {
        enums::WebhookDeliveryAttempt::InitialAttempt => Box::new(InitialAttempt),
        enums::WebhookDeliveryAttempt::AutomaticRetry => Box::new(AutomaticRetry),
        enums::WebhookDeliveryAttempt::ManualRetry => Box::new(ManualRetry),
    }
}