// File: crates/router/src/services/api/generic_link_response.rs
// Module: router
// Public functions: 6

use common_utils::errors::CustomResult;
use error_stack::ResultExt;
use hyperswitch_domain_models::api::{
    GenericExpiredLinkData, GenericLinkFormData, GenericLinkStatusData, GenericLinksData,
};
use tera::{Context, Tera};

use super::build_secure_payment_link_html;
use crate::core::errors;
pub mod context;

pub fn build_generic_link_html(
    boxed_generic_link_data: GenericLinksData,
    locale: String,
) -> CustomResult<String, errors::ApiErrorResponse> {
    match boxed_generic_link_data {
        GenericLinksData::ExpiredLink(link_data) => build_generic_expired_link_html(&link_data),

        GenericLinksData::PaymentMethodCollect(pm_collect_data) => {
            build_pm_collect_link_html(&pm_collect_data)
        }
        GenericLinksData::PaymentMethodCollectStatus(pm_collect_data) => {
            build_pm_collect_link_status_html(&pm_collect_data)
        }
        GenericLinksData::PayoutLink(payout_link_data) => {
            build_payout_link_html(&payout_link_data, locale.as_str())
        }

        GenericLinksData::PayoutLinkStatus(pm_collect_data) => {
            build_payout_link_status_html(&pm_collect_data, locale.as_str())
        }
        GenericLinksData::SecurePaymentLink(payment_link_data) => {
            build_secure_payment_link_html(payment_link_data)
        }
    }
}

pub fn build_generic_expired_link_html(
    link_data: &GenericExpiredLinkData,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let mut tera = Tera::default();
    let mut context = Context::new();

    // Build HTML
    let html_template = include_str!("../../core/generic_link/expired_link/index.html").to_string();
    let _ = tera.add_raw_template("generic_expired_link", &html_template);
    context.insert("title", &link_data.title);
    context.insert("message", &link_data.message);
    context.insert("theme", &link_data.theme);

    tera.render("generic_expired_link", &context)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render expired link HTML template")
}

fn build_html_template(
    link_data: &GenericLinkFormData,
    document: &'static str,
    styles: &'static str,
) -> CustomResult<(Tera, Context), errors::ApiErrorResponse> {
    let mut tera: Tera = Tera::default();
    let mut context = Context::new();

    // Insert dynamic context in CSS
    let css_dynamic_context = "{{ color_scheme }}";
    let css_template = styles.to_string();
    let final_css = format!("{css_dynamic_context}\n{css_template}");
    let _ = tera.add_raw_template("document_styles", &final_css);
    context.insert("color_scheme", &link_data.css_data);

    let css_style_tag = tera
        .render("document_styles", &context)
        .map(|css| format!("<style>{css}</style>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render CSS template")?;

    // Insert HTML context
    let html_template = document.to_string();
    let _ = tera.add_raw_template("html_template", &html_template);
    context.insert("css_style_tag", &css_style_tag);

    Ok((tera, context))
}

pub fn build_payout_link_html(
    link_data: &GenericLinkFormData,
    locale: &str,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let document = include_str!("../../core/generic_link/payout_link/initiate/index.html");
    let styles = include_str!("../../core/generic_link/payout_link/initiate/styles.css");
    let (mut tera, mut context) = build_html_template(link_data, document, styles)
        .attach_printable("Failed to build context for payout link's HTML template")?;

    // Insert dynamic context in JS
    let script = include_str!("../../core/generic_link/payout_link/initiate/script.js");
    let js_template = script.to_string();
    let js_dynamic_context = "{{ script_data }}";
    let final_js = format!("{js_dynamic_context}\n{js_template}");
    let _ = tera.add_raw_template("document_scripts", &final_js);
    context.insert("script_data", &link_data.js_data);
    context::insert_locales_in_context_for_payout_link(&mut context, locale);
    let js_script_tag = tera
        .render("document_scripts", &context)
        .map(|js| format!("<script>{js}</script>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render JS template")?;
    context.insert("js_script_tag", &js_script_tag);
    context.insert(
        "hyper_sdk_loader_script_tag",
        &format!(
            r#"<script src="{}" onload="initializePayoutSDK()"></script>"#,
            link_data.sdk_url
        ),
    );

    // Render HTML template
    tera.render("html_template", &context)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payout link's HTML template")
}

pub fn build_pm_collect_link_html(
    link_data: &GenericLinkFormData,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let document =
        include_str!("../../core/generic_link/payment_method_collect/initiate/index.html");
    let styles = include_str!("../../core/generic_link/payment_method_collect/initiate/styles.css");
    let (mut tera, mut context) = build_html_template(link_data, document, styles)
        .attach_printable(
            "Failed to build context for payment method collect link's HTML template",
        )?;

    // Insert dynamic context in JS
    let script = include_str!("../../core/generic_link/payment_method_collect/initiate/script.js");
    let js_template = script.to_string();
    let js_dynamic_context = "{{ script_data }}";
    let final_js = format!("{js_dynamic_context}\n{js_template}");
    let _ = tera.add_raw_template("document_scripts", &final_js);
    context.insert("script_data", &link_data.js_data);
    let js_script_tag = tera
        .render("document_scripts", &context)
        .map(|js| format!("<script>{js}</script>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render JS template")?;
    context.insert("js_script_tag", &js_script_tag);
    context.insert(
        "hyper_sdk_loader_script_tag",
        &format!(
            r#"<script src="{}" onload="initializeCollectSDK()"></script>"#,
            link_data.sdk_url
        ),
    );

    // Render HTML template
    tera.render("html_template", &context)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payment method collect link's HTML template")
}

pub fn build_payout_link_status_html(
    link_data: &GenericLinkStatusData,
    locale: &str,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let mut tera = Tera::default();
    let mut context = Context::new();

    // Insert dynamic context in CSS
    let css_dynamic_context = "{{ color_scheme }}";
    let css_template =
        include_str!("../../core/generic_link/payout_link/status/styles.css").to_string();
    let final_css = format!("{css_dynamic_context}\n{css_template}");
    let _ = tera.add_raw_template("payout_link_status_styles", &final_css);
    context.insert("color_scheme", &link_data.css_data);

    let css_style_tag = tera
        .render("payout_link_status_styles", &context)
        .map(|css| format!("<style>{css}</style>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payout link status CSS template")?;

    // Insert dynamic context in JS
    let js_dynamic_context = "{{ script_data }}";
    let js_template =
        include_str!("../../core/generic_link/payout_link/status/script.js").to_string();
    let final_js = format!("{js_dynamic_context}\n{js_template}");
    let _ = tera.add_raw_template("payout_link_status_script", &final_js);
    context.insert("script_data", &link_data.js_data);
    context::insert_locales_in_context_for_payout_link_status(&mut context, locale);
    let js_script_tag = tera
        .render("payout_link_status_script", &context)
        .map(|js| format!("<script>{js}</script>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payout link status JS template")?;

    // Build HTML
    let html_template =
        include_str!("../../core/generic_link/payout_link/status/index.html").to_string();
    let _ = tera.add_raw_template("payout_status_link", &html_template);
    context.insert("css_style_tag", &css_style_tag);
    context.insert("js_script_tag", &js_script_tag);

    tera.render("payout_status_link", &context)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payout link status HTML template")
}

pub fn build_pm_collect_link_status_html(
    link_data: &GenericLinkStatusData,
) -> CustomResult<String, errors::ApiErrorResponse> {
    let mut tera = Tera::default();
    let mut context = Context::new();

    // Insert dynamic context in CSS
    let css_dynamic_context = "{{ color_scheme }}";
    let css_template =
        include_str!("../../core/generic_link/payment_method_collect/status/styles.css")
            .to_string();
    let final_css = format!("{css_dynamic_context}\n{css_template}");
    let _ = tera.add_raw_template("pm_collect_link_status_styles", &final_css);
    context.insert("color_scheme", &link_data.css_data);

    let css_style_tag = tera
        .render("pm_collect_link_status_styles", &context)
        .map(|css| format!("<style>{css}</style>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payment method collect link status CSS template")?;

    // Insert dynamic context in JS
    let js_dynamic_context = "{{ collect_link_status_context }}";
    let js_template =
        include_str!("../../core/generic_link/payment_method_collect/status/script.js").to_string();
    let final_js = format!("{js_dynamic_context}\n{js_template}");
    let _ = tera.add_raw_template("pm_collect_link_status_script", &final_js);
    context.insert("collect_link_status_context", &link_data.js_data);

    let js_script_tag = tera
        .render("pm_collect_link_status_script", &context)
        .map(|js| format!("<script>{js}</script>"))
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payment method collect link status JS template")?;

    // Build HTML
    let html_template =
        include_str!("../../core/generic_link/payment_method_collect/status/index.html")
            .to_string();
    let _ = tera.add_raw_template("payment_method_collect_status_link", &html_template);
    context.insert("css_style_tag", &css_style_tag);
    context.insert("js_script_tag", &js_script_tag);

    tera.render("payment_method_collect_status_link", &context)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Failed to render payment method collect link status HTML template")
}


// Function: payments_create
// File: crates/router/src/routes/payments.rs
// Module: router

pub fn payments_create(
    state: web::Data<app::AppState>,
    req: actix_web::HttpRequest,
    json_payload: web::Json<payment_types::PaymentsRequest>,
) -> impl Responder


// Struct: ApplePayCertificatesMigration
// File: crates/router/src/routes/app.rs
// Module: router
// Implementations: 1

pub struct ApplePayCertificatesMigration


// Implementation: impl behaviour::Conversion for for PaymentAddress
// File: crates/router/src/types/domain/address.rs
// Module: router
// Methods: 3 total (0 public)

impl behaviour::Conversion for for PaymentAddress


// Implementation: impl ConnectorCommon for for Getnet
// File: crates/hyperswitch_connectors/src/connectors/getnet.rs
// Module: hyperswitch_connectors
// Methods: 6 total (0 public)

impl ConnectorCommon for for Getnet


// Struct: UpdateSuccessRateWindowEventRequest
// File: crates/router/src/core/payments/routing/utils.rs
// Module: router
// Implementations: 0

pub struct UpdateSuccessRateWindowEventRequest


// Function: list_users_in_lineage
// File: crates/router/src/routes/user_role.rs
// Module: router

pub fn list_users_in_lineage(
    state: web::Data<AppState>,
    req: HttpRequest,
    query: web::Query<user_role_api::ListUsersInEntityRequest>,
) -> HttpResponse


// Function: new
// File: crates/api_models/src/analytics/active_payments.rs
// Module: api_models

pub fn new(time_bucket: Option<String>) -> Self


// File: crates/redis_interface/src/types.rs
// Module: redis_interface
// Public functions: 10
// Public structs: 3

//! Data types and type conversions
//! from `fred`'s internal data-types to custom data-types

use common_utils::errors::CustomResult;
use fred::types::RedisValue as FredRedisValue;

use crate::{errors, RedisConnectionPool};

pub struct RedisValue {
    inner: FredRedisValue,
}

impl std::ops::Deref for RedisValue {
    type Target = FredRedisValue;

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

impl RedisValue {
    pub fn new(value: FredRedisValue) -> Self {
        Self { inner: value }
    }
    pub fn into_inner(self) -> FredRedisValue {
        self.inner
    }

    pub fn from_bytes(val: Vec<u8>) -> Self {
        Self {
            inner: FredRedisValue::Bytes(val.into()),
        }
    }
    pub fn from_string(value: String) -> Self {
        Self {
            inner: FredRedisValue::String(value.into()),
        }
    }
}

impl From<RedisValue> for FredRedisValue {
    fn from(v: RedisValue) -> Self {
        v.inner
    }
}

#[derive(Debug, serde::Deserialize, Clone)]
#[serde(default)]
pub struct RedisSettings {
    pub host: String,
    pub port: u16,
    pub cluster_enabled: bool,
    pub cluster_urls: Vec<String>,
    pub use_legacy_version: bool,
    pub pool_size: usize,
    pub reconnect_max_attempts: u32,
    /// Reconnect delay in milliseconds
    pub reconnect_delay: u32,
    /// TTL in seconds
    pub default_ttl: u32,
    /// TTL for hash-tables in seconds
    pub default_hash_ttl: u32,
    pub stream_read_count: u64,
    pub auto_pipeline: bool,
    pub disable_auto_backpressure: bool,
    pub max_in_flight_commands: u64,
    pub default_command_timeout: u64,
    pub max_feed_count: u64,
    pub unresponsive_timeout: u64,
}

impl RedisSettings {
    /// Validates the Redis configuration provided.
    pub fn validate(&self) -> CustomResult<(), errors::RedisError> {
        use common_utils::{ext_traits::ConfigExt, fp_utils::when};

        when(self.host.is_default_or_empty(), || {
            Err(errors::RedisError::InvalidConfiguration(
                "Redis `host` must be specified".into(),
            ))
        })?;

        when(self.cluster_enabled && self.cluster_urls.is_empty(), || {
            Err(errors::RedisError::InvalidConfiguration(
                "Redis `cluster_urls` must be specified if `cluster_enabled` is `true`".into(),
            ))
        })?;

        when(
            self.default_command_timeout < self.unresponsive_timeout,
            || {
                Err(errors::RedisError::InvalidConfiguration(
                    "Unresponsive timeout cannot be greater than the command timeout".into(),
                )
                .into())
            },
        )
    }
}

impl Default for RedisSettings {
    fn default() -> Self {
        Self {
            host: "127.0.0.1".to_string(),
            port: 6379,
            cluster_enabled: false,
            cluster_urls: vec![],
            use_legacy_version: false,
            pool_size: 5,
            reconnect_max_attempts: 5,
            reconnect_delay: 5,
            default_ttl: 300,
            stream_read_count: 1,
            default_hash_ttl: 900,
            auto_pipeline: true,
            disable_auto_backpressure: false,
            max_in_flight_commands: 5000,
            default_command_timeout: 30,
            max_feed_count: 200,
            unresponsive_timeout: 10,
        }
    }
}

#[derive(Debug)]
pub enum RedisEntryId {
    UserSpecifiedID {
        milliseconds: String,
        sequence_number: String,
    },
    AutoGeneratedID,
    AfterLastID,
    /// Applicable only with consumer groups
    UndeliveredEntryID,
}

impl From<RedisEntryId> for fred::types::XID {
    fn from(id: RedisEntryId) -> Self {
        match id {
            RedisEntryId::UserSpecifiedID {
                milliseconds,
                sequence_number,
            } => Self::Manual(fred::bytes_utils::format_bytes!(
                "{milliseconds}-{sequence_number}"
            )),
            RedisEntryId::AutoGeneratedID => Self::Auto,
            RedisEntryId::AfterLastID => Self::Max,
            RedisEntryId::UndeliveredEntryID => Self::NewInGroup,
        }
    }
}

impl From<&RedisEntryId> for fred::types::XID {
    fn from(id: &RedisEntryId) -> Self {
        match id {
            RedisEntryId::UserSpecifiedID {
                milliseconds,
                sequence_number,
            } => Self::Manual(fred::bytes_utils::format_bytes!(
                "{milliseconds}-{sequence_number}"
            )),
            RedisEntryId::AutoGeneratedID => Self::Auto,
            RedisEntryId::AfterLastID => Self::Max,
            RedisEntryId::UndeliveredEntryID => Self::NewInGroup,
        }
    }
}

#[derive(Eq, PartialEq)]
pub enum SetnxReply {
    KeySet,
    KeyNotSet, // Existing key
}

impl fred::types::FromRedis for SetnxReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            // Returns String ( "OK" ) in case of success
            fred::types::RedisValue::String(_) => Ok(Self::KeySet),
            // Return Null in case of failure
            fred::types::RedisValue::Null => Ok(Self::KeyNotSet),
            // Unexpected behaviour
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected SETNX command reply",
            )),
        }
    }
}

#[derive(Eq, PartialEq)]
pub enum HsetnxReply {
    KeySet,
    KeyNotSet, // Existing key
}

impl fred::types::FromRedis for HsetnxReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            fred::types::RedisValue::Integer(1) => Ok(Self::KeySet),
            fred::types::RedisValue::Integer(0) => Ok(Self::KeyNotSet),
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected HSETNX command reply",
            )),
        }
    }
}

#[derive(Eq, PartialEq)]
pub enum MsetnxReply {
    KeysSet,
    KeysNotSet, // At least one existing key
}

impl fred::types::FromRedis for MsetnxReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            fred::types::RedisValue::Integer(1) => Ok(Self::KeysSet),
            fred::types::RedisValue::Integer(0) => Ok(Self::KeysNotSet),
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected MSETNX command reply",
            )),
        }
    }
}

#[derive(Debug)]
pub enum StreamCapKind {
    MinID,
    MaxLen,
}

impl From<StreamCapKind> for fred::types::XCapKind {
    fn from(item: StreamCapKind) -> Self {
        match item {
            StreamCapKind::MaxLen => Self::MaxLen,
            StreamCapKind::MinID => Self::MinID,
        }
    }
}

#[derive(Debug)]
pub enum StreamCapTrim {
    Exact,
    AlmostExact,
}

impl From<StreamCapTrim> for fred::types::XCapTrim {
    fn from(item: StreamCapTrim) -> Self {
        match item {
            StreamCapTrim::Exact => Self::Exact,
            StreamCapTrim::AlmostExact => Self::AlmostExact,
        }
    }
}

#[derive(Debug)]
pub enum DelReply {
    KeyDeleted,
    KeyNotDeleted, // Key not found
}

impl DelReply {
    pub fn is_key_deleted(&self) -> bool {
        matches!(self, Self::KeyDeleted)
    }

    pub fn is_key_not_deleted(&self) -> bool {
        matches!(self, Self::KeyNotDeleted)
    }
}

impl fred::types::FromRedis for DelReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            fred::types::RedisValue::Integer(1) => Ok(Self::KeyDeleted),
            fred::types::RedisValue::Integer(0) => Ok(Self::KeyNotDeleted),
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected del command reply",
            )),
        }
    }
}

#[derive(Debug)]
pub enum SaddReply {
    KeySet,
    KeyNotSet,
}

impl fred::types::FromRedis for SaddReply {
    fn from_value(value: fred::types::RedisValue) -> Result<Self, fred::error::RedisError> {
        match value {
            fred::types::RedisValue::Integer(1) => Ok(Self::KeySet),
            fred::types::RedisValue::Integer(0) => Ok(Self::KeyNotSet),
            _ => Err(fred::error::RedisError::new(
                fred::error::RedisErrorKind::Unknown,
                "Unexpected sadd command reply",
            )),
        }
    }
}

#[derive(Debug)]
pub enum SetGetReply<T> {
    ValueSet(T),    // Value was set and this is the value that was set
    ValueExists(T), // Value already existed and this is the existing value
}

impl<T> SetGetReply<T> {
    pub fn get_value(&self) -> &T {
        match self {
            Self::ValueSet(value) => value,
            Self::ValueExists(value) => value,
        }
    }
}

#[derive(Debug)]
pub struct RedisKey(String);

impl RedisKey {
    pub fn tenant_aware_key(&self, pool: &RedisConnectionPool) -> String {
        pool.add_prefix(&self.0)
    }

    pub fn tenant_unaware_key(&self, _pool: &RedisConnectionPool) -> String {
        self.0.clone()
    }
}

impl<T: AsRef<str>> From<T> for RedisKey {
    fn from(value: T) -> Self {
        let value = value.as_ref();

        Self(value.to_string())
    }
}


// Struct: AuthorizationIsoFields
// File: crates/hyperswitch_connectors/src/connectors/silverflow/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct AuthorizationIsoFields


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

// Public submodules:
pub mod transformers;


// Struct: HelcimVoidRequest
// File: crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct HelcimVoidRequest


// Function: get_skrill_account_id
// File: crates/hyperswitch_connectors/src/connectors/paysafe/transformers.rs
// Module: hyperswitch_connectors

pub fn get_skrill_account_id(
        &self,
        currency: Currency,
    ) -> Result<Secret<String>, errors::ConnectorError>


// Implementation: impl EmailData for for ProFeatureRequest
// File: crates/router/src/services/email/types.rs
// Module: router
// Methods: 1 total (0 public)

impl EmailData for for ProFeatureRequest


// Function: retry_refund_sync_task
// File: crates/router/src/core/refunds.rs
// Module: router

// Documentation: Schedule the task for refund retry  Returns bool which indicates whether this was the last refund retry or not

pub fn retry_refund_sync_task(
    db: &dyn db::StorageInterface,
    connector: String,
    merchant_id: common_utils::id_type::MerchantId,
    pt: storage::ProcessTracker,
) -> Result<bool, sch_errors::ProcessTrackerError>


// Struct: SearchData
// File: crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct SearchData


// Struct: RetrieveCardResp
// File: crates/router/src/core/payment_methods/transformers.rs
// Module: router
// Implementations: 0

pub struct RetrieveCardResp


// Struct: RefundResponse
// File: crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct RefundResponse


// Struct: DecisionManagerRecord
// File: crates/api_models/src/conditional_configs.rs
// Module: api_models
// Implementations: 1
// Traits: events::ApiEventMetric

pub struct DecisionManagerRecord


// File: crates/router/src/core/fraud_check/operation/fraud_check_pre.rs
// Module: router
// Public structs: 1

use async_trait::async_trait;
use common_enums::FrmSuggestion;
use common_utils::ext_traits::Encode;
use diesel_models::enums::FraudCheckLastStep;
use router_env::{instrument, tracing};
use uuid::Uuid;

use super::{Domain, FraudCheckOperation, GetTracker, UpdateTracker};
use crate::{
    core::{
        errors::RouterResult,
        fraud_check::{
            self as frm_core,
            types::{FrmData, PaymentDetails, PaymentToFrmData},
            ConnectorDetailsCore,
        },
        payments,
    },
    errors,
    routes::app::ReqState,
    types::{
        api::fraud_check as frm_api,
        domain,
        fraud_check::{
            FraudCheckCheckoutData, FraudCheckResponseData, FraudCheckTransactionData, FrmRequest,
            FrmResponse, FrmRouterData,
        },
        storage::{
            enums::{FraudCheckStatus, FraudCheckType},
            fraud_check::{FraudCheckNew, FraudCheckUpdate},
        },
        ResponseId,
    },
    SessionState,
};

#[derive(Debug, Clone, Copy)]
pub struct FraudCheckPre;

impl<F, D> FraudCheckOperation<F, D> for &FraudCheckPre
where
    F: Clone + Send,
    D: payments::OperationSessionGetters<F> + Send + Sync + Clone,
{
    fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker<PaymentToFrmData> + Send + Sync)> {
        Ok(*self)
    }
    fn to_domain(&self) -> RouterResult<&dyn Domain<F, D>> {
        Ok(*self)
    }
    fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker<FrmData, F, D> + Send + Sync)> {
        Ok(*self)
    }
}

impl<F, D> FraudCheckOperation<F, D> for FraudCheckPre
where
    F: Clone + Send,
    D: payments::OperationSessionGetters<F> + Send + Sync + Clone,
{
    fn to_get_tracker(&self) -> RouterResult<&(dyn GetTracker<PaymentToFrmData> + Send + Sync)> {
        Ok(self)
    }
    fn to_domain(&self) -> RouterResult<&dyn Domain<F, D>> {
        Ok(self)
    }
    fn to_update_tracker(&self) -> RouterResult<&(dyn UpdateTracker<FrmData, F, D> + Send + Sync)> {
        Ok(self)
    }
}

#[async_trait]
impl GetTracker<PaymentToFrmData> for FraudCheckPre {
    #[cfg(feature = "v2")]
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_data: PaymentToFrmData,
        frm_connector_details: ConnectorDetailsCore,
    ) -> RouterResult<Option<FrmData>> {
        todo!()
    }

    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    async fn get_trackers<'a>(
        &'a self,
        state: &'a SessionState,
        payment_data: PaymentToFrmData,
        frm_connector_details: ConnectorDetailsCore,
    ) -> RouterResult<Option<FrmData>> {
        let db = &*state.store;

        let payment_details: Option<serde_json::Value> = PaymentDetails::from(payment_data.clone())
            .encode_to_value()
            .ok();

        let existing_fraud_check = db
            .find_fraud_check_by_payment_id_if_present(
                payment_data.payment_intent.get_id().to_owned(),
                payment_data.merchant_account.get_id().clone(),
            )
            .await
            .ok();

        let fraud_check = match existing_fraud_check {
            Some(Some(fraud_check)) => Ok(fraud_check),
            _ => {
                db.insert_fraud_check_response(FraudCheckNew {
                    frm_id: Uuid::new_v4().simple().to_string(),
                    payment_id: payment_data.payment_intent.get_id().to_owned(),
                    merchant_id: payment_data.merchant_account.get_id().clone(),
                    attempt_id: payment_data.payment_attempt.attempt_id.clone(),
                    created_at: common_utils::date_time::now(),
                    frm_name: frm_connector_details.connector_name,
                    frm_transaction_id: None,
                    frm_transaction_type: FraudCheckType::PreFrm,
                    frm_status: FraudCheckStatus::Pending,
                    frm_score: None,
                    frm_reason: None,
                    frm_error: None,
                    payment_details,
                    metadata: None,
                    modified_at: common_utils::date_time::now(),
                    last_step: FraudCheckLastStep::Processing,
                    payment_capture_method: payment_data.payment_attempt.capture_method,
                })
                .await
            }
        };

        match fraud_check {
            Ok(fraud_check_value) => {
                let frm_data = FrmData {
                    payment_intent: payment_data.payment_intent,
                    payment_attempt: payment_data.payment_attempt,
                    merchant_account: payment_data.merchant_account,
                    address: payment_data.address,
                    fraud_check: fraud_check_value,
                    connector_details: payment_data.connector_details,
                    order_details: payment_data.order_details,
                    refund: None,
                    frm_metadata: payment_data.frm_metadata,
                };
                Ok(Some(frm_data))
            }
            Err(error) => {
                router_env::logger::error!("inserting into fraud_check table failed {error:?}");
                Ok(None)
            }
        }
    }
}

#[async_trait]
impl<F, D> Domain<F, D> for FraudCheckPre
where
    F: Clone + Send,
    D: payments::OperationSessionGetters<F> + Send + Sync + Clone,
{
    #[cfg(feature = "v2")]
    #[instrument(skip_all)]
    async fn post_payment_frm<'a>(
        &'a self,
        _state: &'a SessionState,
        _req_state: ReqState,
        _payment_data: &mut D,
        _frm_data: &mut FrmData,
        _merchant_context: &domain::MerchantContext,
        _customer: &Option<domain::Customer>,
    ) -> RouterResult<Option<FrmRouterData>> {
        todo!()
    }

    #[cfg(feature = "v1")]
    #[instrument(skip_all)]
    async fn post_payment_frm<'a>(
        &'a self,
        state: &'a SessionState,
        _req_state: ReqState,
        payment_data: &mut D,
        frm_data: &mut FrmData,
        merchant_context: &domain::MerchantContext,
        customer: &Option<domain::Customer>,
    ) -> RouterResult<Option<FrmRouterData>> {
        let router_data = frm_core::call_frm_service::<F, frm_api::Transaction, _, D>(
            state,
            payment_data,
            &mut frm_data.to_owned(),
            merchant_context,
            customer,
        )
        .await?;
        frm_data.fraud_check.last_step = FraudCheckLastStep::TransactionOrRecordRefund;
        Ok(Some(FrmRouterData {
            merchant_id: router_data.merchant_id,
            connector: router_data.connector,
            payment_id: router_data.payment_id.clone(),
            attempt_id: router_data.attempt_id,
            request: FrmRequest::Transaction(FraudCheckTransactionData {
                amount: router_data.request.amount,
                order_details: router_data.request.order_details,
                currency: router_data.request.currency,
                payment_method: Some(router_data.payment_method),
                error_code: router_data.request.error_code,
                error_message: router_data.request.error_message,
                connector_transaction_id: router_data.request.connector_transaction_id,
                connector: router_data.request.connector,
            }),
            response: FrmResponse::Transaction(router_data.response),
        }))
    }

    async fn pre_payment_frm<'a>(
        &'a self,
        state: &'a SessionState,
        payment_data: &mut D,
        frm_data: &mut FrmData,
        merchant_context: &domain::MerchantContext,
        customer: &Option<domain::Customer>,
    ) -> RouterResult<FrmRouterData> {
        let router_data = frm_core::call_frm_service::<F, frm_api::Checkout, _, D>(
            state,
            payment_data,
            &mut frm_data.to_owned(),
            merchant_context,
            customer,
        )
        .await?;
        frm_data.fraud_check.last_step = FraudCheckLastStep::CheckoutOrSale;
        Ok(FrmRouterData {
            merchant_id: router_data.merchant_id,
            connector: router_data.connector,
            payment_id: router_data.payment_id.clone(),
            attempt_id: router_data.attempt_id,
            request: FrmRequest::Checkout(Box::new(FraudCheckCheckoutData {
                amount: router_data.request.amount,
                order_details: router_data.request.order_details,
                currency: router_data.request.currency,
                browser_info: router_data.request.browser_info,
                payment_method_data: router_data.request.payment_method_data,
                email: router_data.request.email,
                gateway: router_data.request.gateway,
            })),
            response: FrmResponse::Checkout(router_data.response),
        })
    }
}

#[async_trait]
impl<F, D> UpdateTracker<FrmData, F, D> for FraudCheckPre
where
    F: Clone + Send,
    D: payments::OperationSessionGetters<F> + Send + Sync + Clone,
{
    async fn update_tracker<'b>(
        &'b self,
        state: &SessionState,
        _key_store: &domain::MerchantKeyStore,
        mut frm_data: FrmData,
        payment_data: &mut D,
        _frm_suggestion: Option<FrmSuggestion>,
        frm_router_data: FrmRouterData,
    ) -> RouterResult<FrmData> {
        let frm_check_update = match frm_router_data.response {
            FrmResponse::Checkout(response) => match response {
                Err(err) => Some(FraudCheckUpdate::ErrorUpdate {
                    status: FraudCheckStatus::TransactionFailure,
                    error_message: Some(Some(err.message)),
                }),
                Ok(payments_response) => match payments_response {
                    FraudCheckResponseData::TransactionResponse {
                        resource_id,
                        connector_metadata,
                        status,
                        reason,
                        score,
                    } => {
                        let connector_transaction_id = match resource_id {
                            ResponseId::NoResponseId => None,
                            ResponseId::ConnectorTransactionId(id) => Some(id),
                            ResponseId::EncodedData(id) => Some(id),
                        };

                        let fraud_check_update = FraudCheckUpdate::ResponseUpdate {
                            frm_status: status,
                            frm_transaction_id: connector_transaction_id,
                            frm_reason: reason,
                            frm_score: score,
                            metadata: connector_metadata,
                            modified_at: common_utils::date_time::now(),
                            last_step: frm_data.fraud_check.last_step,
                            payment_capture_method: frm_data.fraud_check.payment_capture_method,
                        };
                        Some(fraud_check_update)
                    }
                    FraudCheckResponseData::FulfillmentResponse {
                        order_id: _,
                        shipment_ids: _,
                    } => None,
                    FraudCheckResponseData::RecordReturnResponse {
                        resource_id: _,
                        connector_metadata: _,
                        return_id: _,
                    } => Some(FraudCheckUpdate::ErrorUpdate {
                        status: FraudCheckStatus::TransactionFailure,
                        error_message: Some(Some(
                            "Error: Got Record Return Response response in current Checkout flow"
                                .to_string(),
                        )),
                    }),
                },
            },
            FrmResponse::Transaction(response) => match response {
                Err(err) => Some(FraudCheckUpdate::ErrorUpdate {
                    status: FraudCheckStatus::TransactionFailure,
                    error_message: Some(Some(err.message)),
                }),
                Ok(payments_response) => match payments_response {
                    FraudCheckResponseData::TransactionResponse {
                        resource_id,
                        connector_metadata,
                        status,
                        reason,
                        score,
                    } => {
                        let connector_transaction_id = match resource_id {
                            ResponseId::NoResponseId => None,
                            ResponseId::ConnectorTransactionId(id) => Some(id),
                            ResponseId::EncodedData(id) => Some(id),
                        };

                        let frm_status = payment_data
                            .get_frm_message()
                            .as_ref()
                            .map_or(status, |frm_data| frm_data.frm_status);

                        let fraud_check_update = FraudCheckUpdate::ResponseUpdate {
                            frm_status,
                            frm_transaction_id: connector_transaction_id,
                            frm_reason: reason,
                            frm_score: score,
                            metadata: connector_metadata,
                            modified_at: common_utils::date_time::now(),
                            last_step: frm_data.fraud_check.last_step,
                            payment_capture_method: None,
                        };
                        Some(fraud_check_update)
                    }
                    FraudCheckResponseData::FulfillmentResponse {
                        order_id: _,
                        shipment_ids: _,
                    } => None,
                    FraudCheckResponseData::RecordReturnResponse {
                        resource_id: _,
                        connector_metadata: _,
                        return_id: _,
                    } => Some(FraudCheckUpdate::ErrorUpdate {
                        status: FraudCheckStatus::TransactionFailure,
                        error_message: Some(Some(
                            "Error: Got Record Return Response response in current Checkout flow"
                                .to_string(),
                        )),
                    }),
                },
            },
            FrmResponse::Sale(_response)
            | FrmResponse::Fulfillment(_response)
            | FrmResponse::RecordReturn(_response) => Some(FraudCheckUpdate::ErrorUpdate {
                status: FraudCheckStatus::TransactionFailure,
                error_message: Some(Some(
                    "Error: Got Pre(Sale) flow response in current post flow".to_string(),
                )),
            }),
        };

        let db = &*state.store;
        frm_data.fraud_check = match frm_check_update {
            Some(fraud_check_update) => db
                .update_fraud_check_response_with_attempt_id(
                    frm_data.clone().fraud_check,
                    fraud_check_update,
                )
                .await
                .map_err(|error| error.change_context(errors::ApiErrorResponse::PaymentNotFound))?,
            None => frm_data.clone().fraud_check,
        };

        Ok(frm_data)
    }
}


// Function: set_three_d_s_comp_ind
// File: crates/hyperswitch_connectors/src/connectors/redsys/transformers.rs
// Module: hyperswitch_connectors

pub fn set_three_d_s_comp_ind(mut self, three_d_s_comp_ind: ThreeDSCompInd) -> Self


// Struct: TrustpayWebhookResponse
// File: crates/hyperswitch_connectors/src/connectors/trustpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct TrustpayWebhookResponse


// Implementation: impl Responder
// File: crates/router/src/routes/payments.rs
// Module: router
// Methods: 0 total (0 public)

impl Responder


// File: crates/router/src/core/webhooks/incoming_v2.rs
// Module: router
// Public functions: 1

use std::{marker::PhantomData, str::FromStr, time::Instant};

use actix_web::FromRequest;
use api_models::webhooks::{self, WebhookResponseTracker};
use common_utils::{
    errors::ReportSwitchExt, events::ApiEventsType, types::keymanager::KeyManagerState,
};
use error_stack::ResultExt;
use hyperswitch_domain_models::{
    payments::{HeaderPayload, PaymentStatusData},
    router_request_types::VerifyWebhookSourceRequestData,
    router_response_types::{VerifyWebhookSourceResponseData, VerifyWebhookStatus},
};
use hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails;
use router_env::{instrument, tracing, tracing_actix_web::RequestId};

use super::{types, utils, MERCHANT_ID};
#[cfg(feature = "revenue_recovery")]
use crate::core::webhooks::recovery_incoming;
use crate::{
    core::{
        api_locking,
        errors::{self, ConnectorErrorExt, CustomResult, RouterResponse, StorageErrorExt},
        metrics,
        payments::{
            self,
            transformers::{GenerateResponse, ToResponse},
        },
        webhooks::{
            create_event_and_trigger_outgoing_webhook, utils::construct_webhook_router_data,
        },
    },
    db::StorageInterface,
    events::api_logs::ApiEvent,
    logger,
    routes::{
        app::{ReqState, SessionStateInfo},
        lock_utils, SessionState,
    },
    services::{
        self, authentication as auth, connector_integration_interface::ConnectorEnum,
        ConnectorValidation,
    },
    types::{
        api::{self, ConnectorData, GetToken, IncomingWebhook},
        domain,
        storage::enums,
        transformers::ForeignInto,
    },
};

#[allow(clippy::too_many_arguments)]
pub async fn incoming_webhooks_wrapper<W: types::OutgoingWebhookType>(
    flow: &impl router_env::types::FlowMetric,
    state: SessionState,
    req_state: ReqState,
    req: &actix_web::HttpRequest,
    merchant_context: domain::MerchantContext,
    profile: domain::Profile,
    connector_id: &common_utils::id_type::MerchantConnectorAccountId,
    body: actix_web::web::Bytes,
    is_relay_webhook: bool,
) -> RouterResponse<serde_json::Value> {
    let start_instant = Instant::now();
    let (application_response, webhooks_response_tracker, serialized_req) =
        Box::pin(incoming_webhooks_core::<W>(
            state.clone(),
            req_state,
            req,
            merchant_context.clone(),
            profile,
            connector_id,
            body.clone(),
            is_relay_webhook,
        ))
        .await?;

    logger::info!(incoming_webhook_payload = ?serialized_req);

    let request_duration = Instant::now()
        .saturating_duration_since(start_instant)
        .as_millis();

    let request_id = RequestId::extract(req)
        .await
        .attach_printable("Unable to extract request id from request")
        .change_context(errors::ApiErrorResponse::InternalServerError)?;
    let auth_type = auth::AuthenticationType::WebhookAuth {
        merchant_id: merchant_context.get_merchant_account().get_id().clone(),
    };
    let status_code = 200;
    let api_event = ApiEventsType::Webhooks {
        connector: connector_id.clone(),
        payment_id: webhooks_response_tracker.get_payment_id(),
    };
    let response_value = serde_json::to_value(&webhooks_response_tracker)
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Could not convert webhook effect to string")?;

    let infra = state.infra_components.clone();

    let api_event = ApiEvent::new(
        state.tenant.tenant_id.clone(),
        Some(merchant_context.get_merchant_account().get_id().clone()),
        flow,
        &request_id,
        request_duration,
        status_code,
        serialized_req,
        Some(response_value),
        None,
        auth_type,
        None,
        api_event,
        req,
        req.method(),
        infra,
    );
    state.event_handler().log_event(&api_event);
    Ok(application_response)
}

#[instrument(skip_all)]
#[allow(clippy::too_many_arguments)]
async fn incoming_webhooks_core<W: types::OutgoingWebhookType>(
    state: SessionState,
    req_state: ReqState,
    req: &actix_web::HttpRequest,
    merchant_context: domain::MerchantContext,
    profile: domain::Profile,
    connector_id: &common_utils::id_type::MerchantConnectorAccountId,
    body: actix_web::web::Bytes,
    _is_relay_webhook: bool,
) -> errors::RouterResult<(
    services::ApplicationResponse<serde_json::Value>,
    WebhookResponseTracker,
    serde_json::Value,
)> {
    metrics::WEBHOOK_INCOMING_COUNT.add(
        1,
        router_env::metric_attributes!((
            MERCHANT_ID,
            merchant_context.get_merchant_account().get_id().clone()
        )),
    );
    let mut request_details = IncomingWebhookRequestDetails {
        method: req.method().clone(),
        uri: req.uri().clone(),
        headers: req.headers(),
        query_params: req.query_string().to_string(),
        body: &body,
    };

    // Fetch the merchant connector account to get the webhooks source secret
    // `webhooks source secret` is a secret shared between the merchant and connector
    // This is used for source verification and webhooks integrity
    let (merchant_connector_account, connector, connector_name) = fetch_mca_and_connector(
        &state,
        connector_id,
        merchant_context.get_merchant_key_store(),
    )
    .await?;

    let decoded_body = connector
        .decode_webhook_body(
            &request_details,
            merchant_context.get_merchant_account().get_id(),
            merchant_connector_account.connector_webhook_details.clone(),
            connector_name.as_str(),
        )
        .await
        .switch()
        .attach_printable("There was an error in incoming webhook body decoding")?;

    request_details.body = &decoded_body;

    let event_type = match connector
        .get_webhook_event_type(&request_details)
        .allow_webhook_event_type_not_found(
            state
                .clone()
                .conf
                .webhooks
                .ignore_error
                .event_type
                .unwrap_or(true),
        )
        .switch()
        .attach_printable("Could not find event type in incoming webhook body")?
    {
        Some(event_type) => event_type,
        // Early return allows us to acknowledge the webhooks that we do not support
        None => {
            logger::error!(
                webhook_payload =? request_details.body,
                "Failed while identifying the event type",
            );

            metrics::WEBHOOK_EVENT_TYPE_IDENTIFICATION_FAILURE_COUNT.add(
                1,
                router_env::metric_attributes!(
                    (
                        MERCHANT_ID,
                        merchant_context.get_merchant_account().get_id().clone()
                    ),
                    ("connector", connector_name)
                ),
            );

            let response = connector
                .get_webhook_api_response(&request_details, None)
                .switch()
                .attach_printable("Failed while early return in case of event type parsing")?;

            return Ok((
                response,
                WebhookResponseTracker::NoEffect,
                serde_json::Value::Null,
            ));
        }
    };
    logger::info!(event_type=?event_type);

    // if it is a setup webhook event, return ok status
    if event_type == webhooks::IncomingWebhookEvent::SetupWebhook {
        return Ok((
            services::ApplicationResponse::StatusOk,
            WebhookResponseTracker::NoEffect,
            serde_json::Value::default(),
        ));
    }

    let is_webhook_event_supported = !matches!(
        event_type,
        webhooks::IncomingWebhookEvent::EventNotSupported
    );
    let is_webhook_event_enabled = !utils::is_webhook_event_disabled(
        &*state.clone().store,
        connector_name.as_str(),
        merchant_context.get_merchant_account().get_id(),
        &event_type,
    )
    .await;

    //process webhook further only if webhook event is enabled and is not event_not_supported
    let process_webhook_further = is_webhook_event_enabled && is_webhook_event_supported;

    logger::info!(process_webhook=?process_webhook_further);

    let flow_type: api::WebhookFlow = event_type.into();
    let mut event_object: Box<dyn masking::ErasedMaskSerialize> = Box::new(serde_json::Value::Null);
    let webhook_effect = if process_webhook_further
        && !matches!(flow_type, api::WebhookFlow::ReturnResponse)
    {
        let object_ref_id = connector
            .get_webhook_object_reference_id(&request_details)
            .switch()
            .attach_printable("Could not find object reference id in incoming webhook body")?;
        let connector_enum = api_models::enums::Connector::from_str(&connector_name)
            .change_context(errors::ApiErrorResponse::InvalidDataValue {
                field_name: "connector",
            })
            .attach_printable_lazy(|| {
                format!("unable to parse connector name {connector_name:?}")
            })?;
        let connectors_with_source_verification_call = &state.conf.webhook_source_verification_call;

        let source_verified = if connectors_with_source_verification_call
            .connectors_with_webhook_source_verification_call
            .contains(&connector_enum)
        {
            verify_webhook_source_verification_call(
                connector.clone(),
                &state,
                &merchant_context,
                merchant_connector_account.clone(),
                &connector_name,
                &request_details,
            )
            .await
            .or_else(|error| match error.current_context() {
                errors::ConnectorError::WebhookSourceVerificationFailed => {
                    logger::error!(?error, "Source Verification Failed");
                    Ok(false)
                }
                _ => Err(error),
            })
            .switch()
            .attach_printable("There was an issue in incoming webhook source verification")?
        } else {
            connector
                .clone()
                .verify_webhook_source(
                    &request_details,
                    merchant_context.get_merchant_account().get_id(),
                    merchant_connector_account.connector_webhook_details.clone(),
                    merchant_connector_account.connector_account_details.clone(),
                    connector_name.as_str(),
                )
                .await
                .or_else(|error| match error.current_context() {
                    errors::ConnectorError::WebhookSourceVerificationFailed => {
                        logger::error!(?error, "Source Verification Failed");
                        Ok(false)
                    }
                    _ => Err(error),
                })
                .switch()
                .attach_printable("There was an issue in incoming webhook source verification")?
        };

        logger::info!(source_verified=?source_verified);

        if source_verified {
            metrics::WEBHOOK_SOURCE_VERIFIED_COUNT.add(
                1,
                router_env::metric_attributes!((
                    MERCHANT_ID,
                    merchant_context.get_merchant_account().get_id().clone()
                )),
            );
        }

        // If source verification is mandatory and source is not verified, fail with webhook authentication error
        // else continue the flow
        match (
            connector.is_webhook_source_verification_mandatory(),
            source_verified,
        ) {
            (true, false) => Err(errors::ApiErrorResponse::WebhookAuthenticationFailed)?,
            _ => {
                event_object = connector
                    .get_webhook_resource_object(&request_details)
                    .switch()
                    .attach_printable("Could not find resource object in incoming webhook body")?;

                let webhook_details = api::IncomingWebhookDetails {
                    object_reference_id: object_ref_id.clone(),
                    resource_object: serde_json::to_vec(&event_object)
                        .change_context(errors::ParsingError::EncodeError("byte-vec"))
                        .attach_printable("Unable to convert webhook payload to a value")
                        .change_context(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable(
                            "There was an issue when encoding the incoming webhook body to bytes",
                        )?,
                };

                match flow_type {
                    api::WebhookFlow::Payment => Box::pin(payments_incoming_webhook_flow(
                        state.clone(),
                        req_state,
                        merchant_context,
                        profile,
                        webhook_details,
                        source_verified,
                    ))
                    .await
                    .attach_printable("Incoming webhook flow for payments failed")?,

                    api::WebhookFlow::Refund => todo!(),

                    api::WebhookFlow::Dispute => todo!(),

                    api::WebhookFlow::BankTransfer => todo!(),

                    api::WebhookFlow::ReturnResponse => WebhookResponseTracker::NoEffect,

                    api::WebhookFlow::Mandate => todo!(),

                    api::WebhookFlow::ExternalAuthentication => todo!(),
                    api::WebhookFlow::FraudCheck => todo!(),
                    api::WebhookFlow::Setup => WebhookResponseTracker::NoEffect,

                    #[cfg(feature = "payouts")]
                    api::WebhookFlow::Payout => todo!(),

                    api::WebhookFlow::Subscription => todo!(),
                    #[cfg(all(feature = "revenue_recovery", feature = "v2"))]
                    api::WebhookFlow::Recovery => {
                        Box::pin(recovery_incoming::recovery_incoming_webhook_flow(
                            state.clone(),
                            merchant_context,
                            profile,
                            source_verified,
                            &connector,
                            merchant_connector_account,
                            &connector_name,
                            &request_details,
                            event_type,
                            req_state,
                            &object_ref_id,
                        ))
                        .await
                        .change_context(errors::ApiErrorResponse::WebhookProcessingFailure)
                        .attach_printable("Failed to process recovery incoming webhook")?
                    }
                }
            }
        }
    } else {
        metrics::WEBHOOK_INCOMING_FILTERED_COUNT.add(
            1,
            router_env::metric_attributes!((
                MERCHANT_ID,
                merchant_context.get_merchant_account().get_id().clone()
            )),
        );
        WebhookResponseTracker::NoEffect
    };

    let response = connector
        .get_webhook_api_response(&request_details, None)
        .switch()
        .attach_printable("Could not get incoming webhook api response from connector")?;

    let serialized_request = event_object
        .masked_serialize()
        .change_context(errors::ApiErrorResponse::InternalServerError)
        .attach_printable("Could not convert webhook effect to string")?;
    Ok((response, webhook_effect, serialized_request))
}

#[instrument(skip_all)]
async fn payments_incoming_webhook_flow(
    state: SessionState,
    req_state: ReqState,
    merchant_context: domain::MerchantContext,
    profile: domain::Profile,
    webhook_details: api::IncomingWebhookDetails,
    source_verified: bool,
) -> CustomResult<WebhookResponseTracker, errors::ApiErrorResponse> {
    let consume_or_trigger_flow = if source_verified {
        payments::CallConnectorAction::HandleResponse(webhook_details.resource_object)
    } else {
        payments::CallConnectorAction::Trigger
    };
    let key_manager_state = &(&state).into();
    let payments_response = match webhook_details.object_reference_id {
        webhooks::ObjectReferenceId::PaymentId(id) => {
            let get_trackers_response = get_trackers_response_for_payment_get_operation(
                state.store.as_ref(),
                &id,
                profile.get_id(),
                key_manager_state,
                merchant_context.get_merchant_key_store(),
                merchant_context.get_merchant_account().storage_scheme,
            )
            .await?;

            let payment_id = get_trackers_response.payment_data.get_payment_id();

            let lock_action = api_locking::LockAction::Hold {
                input: api_locking::LockingInput {
                    unique_locking_key: payment_id.get_string_repr().to_owned(),
                    api_identifier: lock_utils::ApiIdentifier::Payments,
                    override_lock_retries: None,
                },
            };

            lock_action
                .clone()
                .perform_locking_action(
                    &state,
                    merchant_context.get_merchant_account().get_id().to_owned(),
                )
                .await?;

            let (
                payment_data,
                _req,
                customer,
                connector_http_status_code,
                external_latency,
                connector_response_data,
            ) = Box::pin(payments::payments_operation_core::<
                api::PSync,
                _,
                _,
                _,
                PaymentStatusData<api::PSync>,
            >(
                &state,
                req_state,
                merchant_context.clone(),
                &profile,
                payments::operations::PaymentGet,
                api::PaymentsRetrieveRequest {
                    force_sync: true,
                    expand_attempts: false,
                    param: None,
                    return_raw_connector_response: None,
                    merchant_connector_details: None,
                },
                get_trackers_response,
                consume_or_trigger_flow,
                HeaderPayload::default(),
            ))
            .await?;

            let response = payment_data.generate_response(
                &state,
                connector_http_status_code,
                external_latency,
                None,
                &merchant_context,
                &profile,
                Some(connector_response_data),
            );

            lock_action
                .free_lock_action(
                    &state,
                    merchant_context.get_merchant_account().get_id().to_owned(),
                )
                .await?;

            match response {
                Ok(value) => value,
                Err(err)
                    if matches!(
                        err.current_context(),
                        &errors::ApiErrorResponse::PaymentNotFound
                    ) && state
                        .clone()
                        .conf
                        .webhooks
                        .ignore_error
                        .payment_not_found
                        .unwrap_or(true) =>
                {
                    metrics::WEBHOOK_PAYMENT_NOT_FOUND.add(
                        1,
                        router_env::metric_attributes!((
                            "merchant_id",
                            merchant_context.get_merchant_account().get_id().clone()
                        )),
                    );
                    return Ok(WebhookResponseTracker::NoEffect);
                }
                error @ Err(_) => error?,
            }
        }
        _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure).attach_printable(
            "Did not get payment id as object reference id in webhook payments flow",
        )?,
    };

    match payments_response {
        services::ApplicationResponse::JsonWithHeaders((payments_response, _)) => {
            let payment_id = payments_response.id.clone();

            let status = payments_response.status;

            let event_type: Option<enums::EventType> = payments_response.status.into();

            // If event is NOT an UnsupportedEvent, trigger Outgoing Webhook
            if let Some(outgoing_event_type) = event_type {
                let primary_object_created_at = payments_response.created;
                // TODO: trigger an outgoing webhook to merchant
                Box::pin(create_event_and_trigger_outgoing_webhook(
                    state,
                    profile,
                    merchant_context.get_merchant_key_store(),
                    outgoing_event_type,
                    enums::EventClass::Payments,
                    payment_id.get_string_repr().to_owned(),
                    enums::EventObjectType::PaymentDetails,
                    api::OutgoingWebhookContent::PaymentDetails(Box::new(payments_response)),
                    primary_object_created_at,
                ))
                .await?;
            };

            let response = WebhookResponseTracker::Payment { payment_id, status };

            Ok(response)
        }

        _ => Err(errors::ApiErrorResponse::WebhookProcessingFailure)
            .attach_printable("received non-json response from payments core")?,
    }
}

async fn get_trackers_response_for_payment_get_operation<F>(
    db: &dyn StorageInterface,
    payment_id: &api::PaymentIdType,
    profile_id: &common_utils::id_type::ProfileId,
    key_manager_state: &KeyManagerState,
    merchant_key_store: &domain::MerchantKeyStore,
    storage_scheme: enums::MerchantStorageScheme,
) -> errors::RouterResult<payments::operations::GetTrackerResponse<PaymentStatusData<F>>>
where
    F: Clone,
{
    let (payment_intent, payment_attempt) = match payment_id {
        api_models::payments::PaymentIdType::PaymentIntentId(ref id) => {
            let payment_intent = db
                .find_payment_intent_by_id(
                    key_manager_state,
                    id,
                    merchant_key_store,
                    storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
            let payment_attempt = db
                .find_payment_attempt_by_id(
                    key_manager_state,
                    merchant_key_store,
                    &payment_intent
                        .active_attempt_id
                        .clone()
                        .ok_or(errors::ApiErrorResponse::InternalServerError)
                        .attach_printable("active_attempt_id not present in payment_attempt")?,
                    storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
            (payment_intent, payment_attempt)
        }
        api_models::payments::PaymentIdType::ConnectorTransactionId(ref id) => {
            let payment_attempt = db
                .find_payment_attempt_by_profile_id_connector_transaction_id(
                    key_manager_state,
                    merchant_key_store,
                    profile_id,
                    id,
                    storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
            let payment_intent = db
                .find_payment_intent_by_id(
                    key_manager_state,
                    &payment_attempt.payment_id,
                    merchant_key_store,
                    storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
            (payment_intent, payment_attempt)
        }
        api_models::payments::PaymentIdType::PaymentAttemptId(ref id) => {
            let global_attempt_id = common_utils::id_type::GlobalAttemptId::try_from(
                std::borrow::Cow::Owned(id.to_owned()),
            )
            .change_context(errors::ApiErrorResponse::InternalServerError)
            .attach_printable("Error while getting GlobalAttemptId")?;
            let payment_attempt = db
                .find_payment_attempt_by_id(
                    key_manager_state,
                    merchant_key_store,
                    &global_attempt_id,
                    storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
            let payment_intent = db
                .find_payment_intent_by_id(
                    key_manager_state,
                    &payment_attempt.payment_id,
                    merchant_key_store,
                    storage_scheme,
                )
                .await
                .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?;
            (payment_intent, payment_attempt)
        }
        api_models::payments::PaymentIdType::PreprocessingId(ref _id) => todo!(),
    };

    // We need the address here to send it in the response
    // In case we need to send an outgoing webhook, we might have to send the billing address and shipping address
    let payment_address = hyperswitch_domain_models::payment_address::PaymentAddress::new(
        payment_intent
            .shipping_address
            .clone()
            .map(|address| address.into_inner()),
        payment_intent
            .billing_address
            .clone()
            .map(|address| address.into_inner()),
        payment_attempt
            .payment_method_billing_address
            .clone()
            .map(|address| address.into_inner()),
        Some(true),
    );

    Ok(payments::operations::GetTrackerResponse {
        payment_data: PaymentStatusData {
            flow: PhantomData,
            payment_intent,
            payment_attempt,
            attempts: None,
            should_sync_with_connector: true,
            payment_address,
            merchant_connector_details: None,
        },
    })
}

#[inline]
async fn verify_webhook_source_verification_call(
    connector: ConnectorEnum,
    state: &SessionState,
    merchant_context: &domain::MerchantContext,
    merchant_connector_account: domain::MerchantConnectorAccount,
    connector_name: &str,
    request_details: &IncomingWebhookRequestDetails<'_>,
) -> CustomResult<bool, errors::ConnectorError> {
    let connector_data = ConnectorData::get_connector_by_name(
        &state.conf.connectors,
        connector_name,
        GetToken::Connector,
        None,
    )
    .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)
    .attach_printable("invalid connector name received in payment attempt")?;
    let connector_integration: services::BoxedWebhookSourceVerificationConnectorIntegrationInterface<
        hyperswitch_domain_models::router_flow_types::VerifyWebhookSource,
        VerifyWebhookSourceRequestData,
        VerifyWebhookSourceResponseData,
    > = connector_data.connector.get_connector_integration();
    let connector_webhook_secrets = connector
        .get_webhook_source_verification_merchant_secret(
            merchant_context.get_merchant_account().get_id(),
            connector_name,
            merchant_connector_account.connector_webhook_details.clone(),
        )
        .await
        .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)?;

    let router_data = construct_webhook_router_data(
        state,
        connector_name,
        merchant_connector_account,
        merchant_context,
        &connector_webhook_secrets,
        request_details,
    )
    .await
    .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)
    .attach_printable("Failed while constructing webhook router data")?;

    let response = services::execute_connector_processing_step(
        state,
        connector_integration,
        &router_data,
        payments::CallConnectorAction::Trigger,
        None,
        None,
    )
    .await?;

    let verification_result = response
        .response
        .map(|response| response.verify_webhook_status);
    match verification_result {
        Ok(VerifyWebhookStatus::SourceVerified) => Ok(true),
        _ => Ok(false),
    }
}

fn get_connector_by_connector_name(
    state: &SessionState,
    connector_name: &str,
    merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
) -> CustomResult<(ConnectorEnum, String), errors::ApiErrorResponse> {
    let authentication_connector =
        api_models::enums::convert_authentication_connector(connector_name);
    #[cfg(feature = "frm")]
    {
        let frm_connector = api_models::enums::convert_frm_connector(connector_name);
        if frm_connector.is_some() {
            let frm_connector_data =
                api::FraudCheckConnectorData::get_connector_by_name(connector_name)?;
            return Ok((
                frm_connector_data.connector,
                frm_connector_data.connector_name.to_string(),
            ));
        }
    }

    let (connector, connector_name) = if authentication_connector.is_some() {
        let authentication_connector_data =
            api::AuthenticationConnectorData::get_connector_by_name(connector_name)?;
        (
            authentication_connector_data.connector,
            authentication_connector_data.connector_name.to_string(),
        )
    } else {
        let connector_data = ConnectorData::get_connector_by_name(
            &state.conf.connectors,
            connector_name,
            GetToken::Connector,
            merchant_connector_id,
        )
        .change_context(errors::ApiErrorResponse::InvalidRequestData {
            message: "invalid connector name received".to_string(),
        })
        .attach_printable("Failed construction of ConnectorData")?;
        (
            connector_data.connector,
            connector_data.connector_name.to_string(),
        )
    };
    Ok((connector, connector_name))
}

/// This function fetches the merchant connector account and connector details
async fn fetch_mca_and_connector(
    state: &SessionState,
    connector_id: &common_utils::id_type::MerchantConnectorAccountId,
    key_store: &domain::MerchantKeyStore,
) -> CustomResult<(domain::MerchantConnectorAccount, ConnectorEnum, String), errors::ApiErrorResponse>
{
    let db = &state.store;
    let mca = db
        .find_merchant_connector_account_by_id(&state.into(), connector_id, key_store)
        .await
        .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
            id: connector_id.get_string_repr().to_owned(),
        })
        .attach_printable("error while fetching merchant_connector_account from connector_id")?;

    let (connector, connector_name) = get_connector_by_connector_name(
        state,
        &mca.connector_name.to_string(),
        Some(mca.get_id()),
    )?;

    Ok((mca, connector, connector_name))
}


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

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


// Struct: ProphetpayRefundResponse
// File: crates/hyperswitch_connectors/src/connectors/prophetpay/transformers.rs
// Module: hyperswitch_connectors
// Implementations: 0

pub struct ProphetpayRefundResponse