// File: crates/router/src/services/kafka/authentication_event.rs

use diesel_models::{authentication::Authentication, enums as storage_enums};
use time::OffsetDateTime;

#[serde_with::skip_serializing_none]
#[derive(serde::Serialize, Debug)]
pub struct KafkaAuthenticationEvent<'a> {
    pub authentication_id: &'a common_utils::id_type::AuthenticationId,
    pub merchant_id: &'a common_utils::id_type::MerchantId,
    pub authentication_connector: Option<&'a String>,
    pub connector_authentication_id: Option<&'a String>,
    pub authentication_data: Option<serde_json::Value>,
    pub payment_method_id: &'a String,
    pub authentication_type: Option<storage_enums::DecoupledAuthenticationType>,
    pub authentication_status: storage_enums::AuthenticationStatus,
    pub authentication_lifecycle_status: storage_enums::AuthenticationLifecycleStatus,
    #[serde(default, with = "time::serde::timestamp::milliseconds")]
    pub created_at: OffsetDateTime,
    #[serde(default, with = "time::serde::timestamp::milliseconds")]
    pub modified_at: OffsetDateTime,
    pub error_message: Option<&'a String>,
    pub error_code: Option<&'a String>,
    pub connector_metadata: Option<serde_json::Value>,
    pub maximum_supported_version: Option<common_utils::types::SemanticVersion>,
    pub threeds_server_transaction_id: Option<&'a String>,
    pub cavv: Option<&'a String>,
    pub authentication_flow_type: Option<&'a String>,
    pub message_version: Option<common_utils::types::SemanticVersion>,
    pub eci: Option<&'a String>,
    pub trans_status: Option<storage_enums::TransactionStatus>,
    pub acquirer_bin: Option<&'a String>,
    pub acquirer_merchant_id: Option<&'a String>,
    pub three_ds_method_data: Option<&'a String>,
    pub three_ds_method_url: Option<&'a String>,
    pub acs_url: Option<&'a String>,
    pub challenge_request: Option<&'a String>,
    pub acs_reference_number: Option<&'a String>,
    pub acs_trans_id: Option<&'a String>,
    pub acs_signed_content: Option<&'a String>,
    pub profile_id: &'a common_utils::id_type::ProfileId,
    pub payment_id: Option<&'a common_utils::id_type::PaymentId>,
    pub merchant_connector_id: Option<&'a common_utils::id_type::MerchantConnectorAccountId>,
    pub ds_trans_id: Option<&'a String>,
    pub directory_server_id: Option<&'a String>,
    pub acquirer_country_code: Option<&'a String>,
    pub organization_id: &'a common_utils::id_type::OrganizationId,
}

impl<'a> KafkaAuthenticationEvent<'a> {
    pub fn from_storage(authentication: &'a Authentication) -> Self {
        Self {
            created_at: authentication.created_at.assume_utc(),
            modified_at: authentication.modified_at.assume_utc(),
            authentication_id: &authentication.authentication_id,
            merchant_id: &authentication.merchant_id,
            authentication_status: authentication.authentication_status,
            authentication_connector: authentication.authentication_connector.as_ref(),
            connector_authentication_id: authentication.connector_authentication_id.as_ref(),
            authentication_data: authentication.authentication_data.clone(),
            payment_method_id: &authentication.payment_method_id,
            authentication_type: authentication.authentication_type,
            authentication_lifecycle_status: authentication.authentication_lifecycle_status,
            error_code: authentication.error_code.as_ref(),
            error_message: authentication.error_message.as_ref(),
            connector_metadata: authentication.connector_metadata.clone(),
            maximum_supported_version: authentication.maximum_supported_version.clone(),
            threeds_server_transaction_id: authentication.threeds_server_transaction_id.as_ref(),
            cavv: authentication.cavv.as_ref(),
            authentication_flow_type: authentication.authentication_flow_type.as_ref(),
            message_version: authentication.message_version.clone(),
            eci: authentication.eci.as_ref(),
            trans_status: authentication.trans_status.clone(),
            acquirer_bin: authentication.acquirer_bin.as_ref(),
            acquirer_merchant_id: authentication.acquirer_merchant_id.as_ref(),
            three_ds_method_data: authentication.three_ds_method_data.as_ref(),
            three_ds_method_url: authentication.three_ds_method_url.as_ref(),
            acs_url: authentication.acs_url.as_ref(),
            challenge_request: authentication.challenge_request.as_ref(),
            acs_reference_number: authentication.acs_reference_number.as_ref(),
            acs_trans_id: authentication.acs_trans_id.as_ref(),
            acs_signed_content: authentication.acs_signed_content.as_ref(),
            profile_id: &authentication.profile_id,
            payment_id: authentication.payment_id.as_ref(),
            merchant_connector_id: authentication.merchant_connector_id.as_ref(),
            ds_trans_id: authentication.ds_trans_id.as_ref(),
            directory_server_id: authentication.directory_server_id.as_ref(),
            acquirer_country_code: authentication.acquirer_country_code.as_ref(),
            organization_id: &authentication.organization_id,
        }
    }
}

impl super::KafkaMessage for KafkaAuthenticationEvent<'_> {
    fn key(&self) -> String {
        format!(
            "{}_{}",
            self.merchant_id.get_string_repr(),
            self.authentication_id.get_string_repr()
        )
    }

    fn event_type(&self) -> crate::events::EventType {
        crate::events::EventType::Authentication
    }
}


// File: crates/router/src/services/kafka/revenue_recovery.rs

use common_utils::{id_type, types::MinorUnit};
use masking::Secret;
use time::OffsetDateTime;
#[derive(serde::Serialize, Debug)]
pub struct RevenueRecovery<'a> {
    pub merchant_id: &'a id_type::MerchantId,
    pub invoice_amount: MinorUnit,
    pub invoice_currency: &'a common_enums::Currency,
    #[serde(default, with = "time::serde::timestamp::nanoseconds::option")]
    pub invoice_due_date: Option<OffsetDateTime>,
    #[serde(with = "time::serde::timestamp::nanoseconds::option")]
    pub invoice_date: Option<OffsetDateTime>,
    pub billing_country: Option<&'a common_enums::CountryAlpha2>,
    pub billing_state: Option<Secret<String>>,
    pub billing_city: Option<Secret<String>>,
    pub attempt_amount: MinorUnit,
    pub attempt_currency: &'a common_enums::Currency,
    pub attempt_status: &'a common_enums::AttemptStatus,
    pub pg_error_code: Option<String>,
    pub network_advice_code: Option<String>,
    pub network_error_code: Option<String>,
    pub first_pg_error_code: Option<String>,
    pub first_network_advice_code: Option<String>,
    pub first_network_error_code: Option<String>,
    #[serde(default, with = "time::serde::timestamp::nanoseconds")]
    pub attempt_created_at: OffsetDateTime,
    pub payment_method_type: Option<&'a common_enums::PaymentMethod>,
    pub payment_method_subtype: Option<&'a common_enums::PaymentMethodType>,
    pub card_network: Option<&'a common_enums::CardNetwork>,
    pub card_issuer: Option<String>,
    pub retry_count: Option<i32>,
    pub payment_gateway: Option<common_enums::connector_enums::Connector>,
}

impl super::KafkaMessage for RevenueRecovery<'_> {
    fn key(&self) -> String {
        self.merchant_id.get_string_repr().to_string()
    }

    fn event_type(&self) -> crate::events::EventType {
        crate::events::EventType::RevenueRecovery
    }
}


// File: crates/router/src/services/kafka/authentication.rs

use diesel_models::{authentication::Authentication, enums as storage_enums};
use time::OffsetDateTime;

#[derive(serde::Serialize, Debug)]
pub struct KafkaAuthentication<'a> {
    pub authentication_id: &'a common_utils::id_type::AuthenticationId,
    pub merchant_id: &'a common_utils::id_type::MerchantId,
    pub authentication_connector: Option<&'a String>,
    pub connector_authentication_id: Option<&'a String>,
    pub authentication_data: Option<serde_json::Value>,
    pub payment_method_id: &'a String,
    pub authentication_type: Option<storage_enums::DecoupledAuthenticationType>,
    pub authentication_status: storage_enums::AuthenticationStatus,
    pub authentication_lifecycle_status: storage_enums::AuthenticationLifecycleStatus,
    #[serde(default, with = "time::serde::timestamp::milliseconds")]
    pub created_at: OffsetDateTime,
    #[serde(default, with = "time::serde::timestamp::milliseconds")]
    pub modified_at: OffsetDateTime,
    pub error_message: Option<&'a String>,
    pub error_code: Option<&'a String>,
    pub connector_metadata: Option<serde_json::Value>,
    pub maximum_supported_version: Option<common_utils::types::SemanticVersion>,
    pub threeds_server_transaction_id: Option<&'a String>,
    pub cavv: Option<&'a String>,
    pub authentication_flow_type: Option<&'a String>,
    pub message_version: Option<common_utils::types::SemanticVersion>,
    pub eci: Option<&'a String>,
    pub trans_status: Option<storage_enums::TransactionStatus>,
    pub acquirer_bin: Option<&'a String>,
    pub acquirer_merchant_id: Option<&'a String>,
    pub three_ds_method_data: Option<&'a String>,
    pub three_ds_method_url: Option<&'a String>,
    pub acs_url: Option<&'a String>,
    pub challenge_request: Option<&'a String>,
    pub acs_reference_number: Option<&'a String>,
    pub acs_trans_id: Option<&'a String>,
    pub acs_signed_content: Option<&'a String>,
    pub profile_id: &'a common_utils::id_type::ProfileId,
    pub payment_id: Option<&'a common_utils::id_type::PaymentId>,
    pub merchant_connector_id: Option<&'a common_utils::id_type::MerchantConnectorAccountId>,
    pub ds_trans_id: Option<&'a String>,
    pub directory_server_id: Option<&'a String>,
    pub acquirer_country_code: Option<&'a String>,
    pub organization_id: &'a common_utils::id_type::OrganizationId,
}

impl<'a> KafkaAuthentication<'a> {
    pub fn from_storage(authentication: &'a Authentication) -> Self {
        Self {
            created_at: authentication.created_at.assume_utc(),
            modified_at: authentication.modified_at.assume_utc(),
            authentication_id: &authentication.authentication_id,
            merchant_id: &authentication.merchant_id,
            authentication_status: authentication.authentication_status,
            authentication_connector: authentication.authentication_connector.as_ref(),
            connector_authentication_id: authentication.connector_authentication_id.as_ref(),
            authentication_data: authentication.authentication_data.clone(),
            payment_method_id: &authentication.payment_method_id,
            authentication_type: authentication.authentication_type,
            authentication_lifecycle_status: authentication.authentication_lifecycle_status,
            error_code: authentication.error_code.as_ref(),
            error_message: authentication.error_message.as_ref(),
            connector_metadata: authentication.connector_metadata.clone(),
            maximum_supported_version: authentication.maximum_supported_version.clone(),
            threeds_server_transaction_id: authentication.threeds_server_transaction_id.as_ref(),
            cavv: authentication.cavv.as_ref(),
            authentication_flow_type: authentication.authentication_flow_type.as_ref(),
            message_version: authentication.message_version.clone(),
            eci: authentication.eci.as_ref(),
            trans_status: authentication.trans_status.clone(),
            acquirer_bin: authentication.acquirer_bin.as_ref(),
            acquirer_merchant_id: authentication.acquirer_merchant_id.as_ref(),
            three_ds_method_data: authentication.three_ds_method_data.as_ref(),
            three_ds_method_url: authentication.three_ds_method_url.as_ref(),
            acs_url: authentication.acs_url.as_ref(),
            challenge_request: authentication.challenge_request.as_ref(),
            acs_reference_number: authentication.acs_reference_number.as_ref(),
            acs_trans_id: authentication.acs_trans_id.as_ref(),
            acs_signed_content: authentication.acs_signed_content.as_ref(),
            profile_id: &authentication.profile_id,
            payment_id: authentication.payment_id.as_ref(),
            merchant_connector_id: authentication.merchant_connector_id.as_ref(),
            ds_trans_id: authentication.ds_trans_id.as_ref(),
            directory_server_id: authentication.directory_server_id.as_ref(),
            acquirer_country_code: authentication.acquirer_country_code.as_ref(),
            organization_id: &authentication.organization_id,
        }
    }
}

impl super::KafkaMessage for KafkaAuthentication<'_> {
    fn key(&self) -> String {
        format!(
            "{}_{}",
            self.merchant_id.get_string_repr(),
            self.authentication_id.get_string_repr()
        )
    }

    fn event_type(&self) -> crate::events::EventType {
        crate::events::EventType::Authentication
    }
}


// File: crates/router/src/services/kafka/payment_intent.rs

#[cfg(feature = "v2")]
use ::common_types::{
    payments,
    primitive_wrappers::{EnablePartialAuthorizationBool, RequestExtendedAuthorizationBool},
};
#[cfg(feature = "v2")]
use common_enums::RequestIncrementalAuthorization;
use common_utils::{
    crypto::Encryptable, hashing::HashedString, id_type, pii, types as common_types,
};
use diesel_models::enums as storage_enums;
#[cfg(feature = "v2")]
use diesel_models::{types as diesel_types, PaymentLinkConfigRequestForPayments};
#[cfg(feature = "v2")]
use diesel_models::{types::OrderDetailsWithAmount, TaxDetails};
use hyperswitch_domain_models::payments::PaymentIntent;
#[cfg(feature = "v2")]
use hyperswitch_domain_models::{address, routing};
use masking::{PeekInterface, Secret};
use serde_json::Value;
use time::OffsetDateTime;

#[cfg(feature = "v1")]
#[derive(serde::Serialize, Debug)]
pub struct KafkaPaymentIntent<'a> {
    pub payment_id: &'a id_type::PaymentId,
    pub merchant_id: &'a id_type::MerchantId,
    pub status: storage_enums::IntentStatus,
    pub amount: common_types::MinorUnit,
    pub currency: Option<storage_enums::Currency>,
    pub amount_captured: Option<common_types::MinorUnit>,
    pub customer_id: Option<&'a id_type::CustomerId>,
    pub description: Option<&'a String>,
    pub return_url: Option<&'a String>,
    pub metadata: Option<String>,
    pub connector_id: Option<&'a String>,
    pub statement_descriptor_name: Option<&'a String>,
    pub statement_descriptor_suffix: Option<&'a String>,
    #[serde(with = "time::serde::timestamp")]
    pub created_at: OffsetDateTime,
    #[serde(with = "time::serde::timestamp")]
    pub modified_at: OffsetDateTime,
    #[serde(default, with = "time::serde::timestamp::option")]
    pub last_synced: Option<OffsetDateTime>,
    pub setup_future_usage: Option<storage_enums::FutureUsage>,
    pub off_session: Option<bool>,
    pub client_secret: Option<&'a String>,
    pub active_attempt_id: String,
    pub business_country: Option<storage_enums::CountryAlpha2>,
    pub business_label: Option<&'a String>,
    pub attempt_count: i16,
    pub profile_id: Option<&'a id_type::ProfileId>,
    pub payment_confirm_source: Option<storage_enums::PaymentSource>,
    pub billing_details: Option<Encryptable<Secret<Value>>>,
    pub shipping_details: Option<Encryptable<Secret<Value>>>,
    pub customer_email: Option<HashedString<pii::EmailStrategy>>,
    pub feature_metadata: Option<&'a Value>,
    pub merchant_order_reference_id: Option<&'a String>,
    pub organization_id: &'a id_type::OrganizationId,
    #[serde(flatten)]
    infra_values: Option<Value>,
}

#[cfg(feature = "v1")]
impl<'a> KafkaPaymentIntent<'a> {
    pub fn from_storage(intent: &'a PaymentIntent, infra_values: Option<Value>) -> Self {
        Self {
            payment_id: &intent.payment_id,
            merchant_id: &intent.merchant_id,
            status: intent.status,
            amount: intent.amount,
            currency: intent.currency,
            amount_captured: intent.amount_captured,
            customer_id: intent.customer_id.as_ref(),
            description: intent.description.as_ref(),
            return_url: intent.return_url.as_ref(),
            metadata: intent.metadata.as_ref().map(|x| x.to_string()),
            connector_id: intent.connector_id.as_ref(),
            statement_descriptor_name: intent.statement_descriptor_name.as_ref(),
            statement_descriptor_suffix: intent.statement_descriptor_suffix.as_ref(),
            created_at: intent.created_at.assume_utc(),
            modified_at: intent.modified_at.assume_utc(),
            last_synced: intent.last_synced.map(|i| i.assume_utc()),
            setup_future_usage: intent.setup_future_usage,
            off_session: intent.off_session,
            client_secret: intent.client_secret.as_ref(),
            active_attempt_id: intent.active_attempt.get_id(),
            business_country: intent.business_country,
            business_label: intent.business_label.as_ref(),
            attempt_count: intent.attempt_count,
            profile_id: intent.profile_id.as_ref(),
            payment_confirm_source: intent.payment_confirm_source,
            // TODO: use typed information here to avoid PII logging
            billing_details: None,
            shipping_details: None,
            customer_email: intent
                .customer_details
                .as_ref()
                .and_then(|value| value.get_inner().peek().as_object())
                .and_then(|obj| obj.get("email"))
                .and_then(|email| email.as_str())
                .map(|email| HashedString::from(Secret::new(email.to_string()))),
            feature_metadata: intent.feature_metadata.as_ref(),
            merchant_order_reference_id: intent.merchant_order_reference_id.as_ref(),
            organization_id: &intent.organization_id,
            infra_values,
        }
    }
}

#[cfg(feature = "v2")]
#[derive(serde::Serialize, Debug)]
pub struct KafkaPaymentIntent<'a> {
    pub payment_id: &'a id_type::GlobalPaymentId,
    pub merchant_id: &'a id_type::MerchantId,
    pub status: storage_enums::IntentStatus,
    pub amount: common_types::MinorUnit,
    pub currency: storage_enums::Currency,
    pub amount_captured: Option<common_types::MinorUnit>,
    pub customer_id: Option<&'a id_type::GlobalCustomerId>,
    pub description: Option<&'a common_types::Description>,
    pub return_url: Option<&'a common_types::Url>,
    pub metadata: Option<&'a Secret<Value>>,
    pub statement_descriptor: Option<&'a common_types::StatementDescriptor>,
    #[serde(with = "time::serde::timestamp")]
    pub created_at: OffsetDateTime,
    #[serde(with = "time::serde::timestamp")]
    pub modified_at: OffsetDateTime,
    #[serde(default, with = "time::serde::timestamp::option")]
    pub last_synced: Option<OffsetDateTime>,
    pub setup_future_usage: storage_enums::FutureUsage,
    pub off_session: bool,
    pub active_attempt_id: Option<&'a id_type::GlobalAttemptId>,
    pub active_attempt_id_type: common_enums::ActiveAttemptIDType,
    pub active_attempts_group_id: Option<&'a id_type::GlobalAttemptGroupId>,
    pub attempt_count: i16,
    pub profile_id: &'a id_type::ProfileId,
    pub customer_email: Option<HashedString<pii::EmailStrategy>>,
    pub feature_metadata: Option<&'a diesel_types::FeatureMetadata>,
    pub organization_id: &'a id_type::OrganizationId,
    pub order_details: Option<&'a Vec<Secret<OrderDetailsWithAmount>>>,

    pub allowed_payment_method_types: Option<&'a Vec<common_enums::PaymentMethodType>>,
    pub connector_metadata: Option<&'a api_models::payments::ConnectorMetadata>,
    pub payment_link_id: Option<&'a String>,
    pub updated_by: &'a String,
    pub surcharge_applicable: Option<bool>,
    pub request_incremental_authorization: RequestIncrementalAuthorization,
    pub split_txns_enabled: common_enums::SplitTxnsEnabled,
    pub authorization_count: Option<i32>,
    #[serde(with = "time::serde::timestamp")]
    pub session_expiry: OffsetDateTime,
    pub request_external_three_ds_authentication: common_enums::External3dsAuthenticationRequest,
    pub frm_metadata: Option<Secret<&'a Value>>,
    pub customer_details: Option<Secret<&'a Value>>,
    pub shipping_cost: Option<common_types::MinorUnit>,
    pub tax_details: Option<TaxDetails>,
    pub skip_external_tax_calculation: bool,
    pub request_extended_authorization: Option<RequestExtendedAuthorizationBool>,
    pub psd2_sca_exemption_type: Option<storage_enums::ScaExemptionType>,
    pub split_payments: Option<&'a payments::SplitPaymentsRequest>,
    pub platform_merchant_id: Option<&'a id_type::MerchantId>,
    pub force_3ds_challenge: Option<bool>,
    pub force_3ds_challenge_trigger: Option<bool>,
    pub processor_merchant_id: &'a id_type::MerchantId,
    pub created_by: Option<&'a common_types::CreatedBy>,
    pub is_iframe_redirection_enabled: Option<bool>,
    pub merchant_reference_id: Option<&'a id_type::PaymentReferenceId>,
    pub capture_method: storage_enums::CaptureMethod,
    pub authentication_type: Option<common_enums::AuthenticationType>,
    pub prerouting_algorithm: Option<&'a routing::PaymentRoutingInfo>,
    pub surcharge_amount: Option<common_types::MinorUnit>,
    pub billing_address: Option<Secret<&'a address::Address>>,
    pub shipping_address: Option<Secret<&'a address::Address>>,
    pub tax_on_surcharge: Option<common_types::MinorUnit>,
    pub frm_merchant_decision: Option<common_enums::MerchantDecision>,
    pub enable_payment_link: common_enums::EnablePaymentLinkRequest,
    pub apply_mit_exemption: common_enums::MitExemptionRequest,
    pub customer_present: common_enums::PresenceOfCustomerDuringPayment,
    pub routing_algorithm_id: Option<&'a id_type::RoutingId>,
    pub payment_link_config: Option<&'a PaymentLinkConfigRequestForPayments>,
    pub enable_partial_authorization: Option<EnablePartialAuthorizationBool>,

    #[serde(flatten)]
    infra_values: Option<Value>,
}

#[cfg(feature = "v2")]
impl<'a> KafkaPaymentIntent<'a> {
    pub fn from_storage(intent: &'a PaymentIntent, infra_values: Option<Value>) -> Self {
        let PaymentIntent {
            id,
            merchant_id,
            status,
            amount_details,
            amount_captured,
            customer_id,
            description,
            return_url,
            metadata,
            statement_descriptor,
            created_at,
            modified_at,
            last_synced,
            setup_future_usage,
            active_attempt_id,
            active_attempt_id_type,
            active_attempts_group_id,
            order_details,
            allowed_payment_method_types,
            connector_metadata,
            feature_metadata,
            attempt_count,
            profile_id,
            payment_link_id,
            frm_merchant_decision,
            updated_by,
            request_incremental_authorization,
            split_txns_enabled,
            authorization_count,
            session_expiry,
            request_external_three_ds_authentication,
            frm_metadata,
            customer_details,
            merchant_reference_id,
            billing_address,
            shipping_address,
            capture_method,
            authentication_type,
            prerouting_algorithm,
            organization_id,
            enable_payment_link,
            apply_mit_exemption,
            customer_present,
            payment_link_config,
            routing_algorithm_id,
            split_payments,
            force_3ds_challenge,
            force_3ds_challenge_trigger,
            processor_merchant_id,
            created_by,
            is_iframe_redirection_enabled,
            is_payment_id_from_merchant,
            enable_partial_authorization,
        } = intent;

        Self {
            payment_id: id,
            merchant_id,
            status: *status,
            amount: amount_details.order_amount,
            currency: amount_details.currency,
            amount_captured: *amount_captured,
            customer_id: customer_id.as_ref(),
            description: description.as_ref(),
            return_url: return_url.as_ref(),
            metadata: metadata.as_ref(),
            statement_descriptor: statement_descriptor.as_ref(),
            created_at: created_at.assume_utc(),
            modified_at: modified_at.assume_utc(),
            last_synced: last_synced.map(|t| t.assume_utc()),
            setup_future_usage: *setup_future_usage,
            off_session: setup_future_usage.is_off_session(),
            active_attempt_id: active_attempt_id.as_ref(),
            active_attempt_id_type: *active_attempt_id_type,
            active_attempts_group_id: active_attempts_group_id.as_ref(),
            attempt_count: *attempt_count,
            profile_id,
            customer_email: None,
            feature_metadata: feature_metadata.as_ref(),
            organization_id,
            order_details: order_details.as_ref(),
            allowed_payment_method_types: allowed_payment_method_types.as_ref(),
            connector_metadata: connector_metadata.as_ref(),
            payment_link_id: payment_link_id.as_ref(),
            updated_by,
            surcharge_applicable: None,
            request_incremental_authorization: *request_incremental_authorization,
            split_txns_enabled: *split_txns_enabled,
            authorization_count: *authorization_count,
            session_expiry: session_expiry.assume_utc(),
            request_external_three_ds_authentication: *request_external_three_ds_authentication,
            frm_metadata: frm_metadata
                .as_ref()
                .map(|frm_metadata| frm_metadata.as_ref()),
            customer_details: customer_details
                .as_ref()
                .map(|customer_details| customer_details.get_inner().as_ref()),
            shipping_cost: amount_details.shipping_cost,
            tax_details: amount_details.tax_details.clone(),
            skip_external_tax_calculation: amount_details.get_external_tax_action_as_bool(),
            request_extended_authorization: None,
            psd2_sca_exemption_type: None,
            split_payments: split_payments.as_ref(),
            platform_merchant_id: None,
            force_3ds_challenge: *force_3ds_challenge,
            force_3ds_challenge_trigger: *force_3ds_challenge_trigger,
            processor_merchant_id,
            created_by: created_by.as_ref(),
            is_iframe_redirection_enabled: *is_iframe_redirection_enabled,
            merchant_reference_id: merchant_reference_id.as_ref(),
            billing_address: billing_address
                .as_ref()
                .map(|billing_address| Secret::new(billing_address.get_inner())),
            shipping_address: shipping_address
                .as_ref()
                .map(|shipping_address| Secret::new(shipping_address.get_inner())),
            capture_method: *capture_method,
            authentication_type: *authentication_type,
            prerouting_algorithm: prerouting_algorithm.as_ref(),
            surcharge_amount: amount_details.surcharge_amount,
            tax_on_surcharge: amount_details.tax_on_surcharge,
            frm_merchant_decision: *frm_merchant_decision,
            enable_payment_link: *enable_payment_link,
            apply_mit_exemption: *apply_mit_exemption,
            customer_present: *customer_present,
            routing_algorithm_id: routing_algorithm_id.as_ref(),
            payment_link_config: payment_link_config.as_ref(),
            infra_values,
            enable_partial_authorization: *enable_partial_authorization,
        }
    }
}

impl KafkaPaymentIntent<'_> {
    #[cfg(feature = "v1")]
    fn get_id(&self) -> &id_type::PaymentId {
        self.payment_id
    }

    #[cfg(feature = "v2")]
    fn get_id(&self) -> &id_type::GlobalPaymentId {
        self.payment_id
    }
}

impl super::KafkaMessage for KafkaPaymentIntent<'_> {
    fn key(&self) -> String {
        format!(
            "{}_{}",
            self.merchant_id.get_string_repr(),
            self.get_id().get_string_repr(),
        )
    }

    fn event_type(&self) -> crate::events::EventType {
        crate::events::EventType::PaymentIntent
    }
}


// File: crates/router/src/services/api/request.rs

pub use common_utils::request::ContentType;
pub use masking::{Mask, Maskable};


// File: crates/router/src/services/api/client.rs

use std::time::Duration;

use common_utils::errors::ReportSwitchExt;
use error_stack::ResultExt;
pub use external_services::http_client::{self, client};
use http::{HeaderValue, Method};
pub use hyperswitch_interfaces::{
    api_client::{ApiClient, ApiClientWrapper, RequestBuilder},
    types::Proxy,
};
use masking::PeekInterface;
use reqwest::multipart::Form;
use router_env::RequestId;

use super::{request::Maskable, Request};
use crate::core::errors::{ApiClientError, CustomResult};

#[derive(Clone)]
pub struct ProxyClient {
    proxy_config: Proxy,
    client: reqwest::Client,
    request_id: Option<RequestId>,
}

impl ProxyClient {
    pub fn new(proxy_config: &Proxy) -> CustomResult<Self, ApiClientError> {
        let client = client::get_client_builder(proxy_config)
            .switch()?
            .build()
            .change_context(ApiClientError::InvalidProxyConfiguration)?;
        Ok(Self {
            proxy_config: proxy_config.clone(),
            client,
            request_id: None,
        })
    }

    pub fn get_reqwest_client(
        &self,
        client_certificate: Option<masking::Secret<String>>,
        client_certificate_key: Option<masking::Secret<String>>,
    ) -> CustomResult<reqwest::Client, ApiClientError> {
        match (client_certificate, client_certificate_key) {
            (Some(certificate), Some(certificate_key)) => {
                let client_builder = client::get_client_builder(&self.proxy_config).switch()?;
                let identity =
                    client::create_identity_from_certificate_and_key(certificate, certificate_key)
                        .switch()?;
                Ok(client_builder
                    .identity(identity)
                    .build()
                    .change_context(ApiClientError::ClientConstructionFailed)
                    .attach_printable(
                        "Failed to construct client with certificate and certificate key",
                    )?)
            }
            (_, _) => Ok(self.client.clone()),
        }
    }
}

pub struct RouterRequestBuilder {
    // Using option here to get around the reinitialization problem
    // request builder follows a chain pattern where the value is consumed and a newer requestbuilder is returned
    // Since for this brief period of time between the value being consumed & newer request builder
    // since requestbuilder does not allow moving the value
    // leaves our struct in an inconsistent state, we are using option to get around rust semantics
    inner: Option<reqwest::RequestBuilder>,
}

impl RequestBuilder for RouterRequestBuilder {
    fn json(&mut self, body: serde_json::Value) {
        self.inner = self.inner.take().map(|r| r.json(&body));
    }
    fn url_encoded_form(&mut self, body: serde_json::Value) {
        self.inner = self.inner.take().map(|r| r.form(&body));
    }

    fn timeout(&mut self, timeout: Duration) {
        self.inner = self.inner.take().map(|r| r.timeout(timeout));
    }

    fn multipart(&mut self, form: Form) {
        self.inner = self.inner.take().map(|r| r.multipart(form));
    }

    fn header(&mut self, key: String, value: Maskable<String>) -> CustomResult<(), ApiClientError> {
        let header_value = match value {
            Maskable::Masked(hvalue) => HeaderValue::from_str(hvalue.peek()).map(|mut h| {
                h.set_sensitive(true);
                h
            }),
            Maskable::Normal(hvalue) => HeaderValue::from_str(&hvalue),
        }
        .change_context(ApiClientError::HeaderMapConstructionFailed)?;

        self.inner = self.inner.take().map(|r| r.header(key, header_value));
        Ok(())
    }

    fn send(
        self,
    ) -> CustomResult<
        Box<dyn core::future::Future<Output = Result<reqwest::Response, reqwest::Error>> + 'static>,
        ApiClientError,
    > {
        Ok(Box::new(
            self.inner.ok_or(ApiClientError::UnexpectedState)?.send(),
        ))
    }
}

#[async_trait::async_trait]
impl ApiClient for ProxyClient {
    fn request(
        &self,
        method: Method,
        url: String,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        self.request_with_certificate(method, url, None, None)
    }

    fn request_with_certificate(
        &self,
        method: Method,
        url: String,
        certificate: Option<masking::Secret<String>>,
        certificate_key: Option<masking::Secret<String>>,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        let client_builder = self
            .get_reqwest_client(certificate, certificate_key)
            .change_context(ApiClientError::ClientConstructionFailed)?;
        Ok(Box::new(RouterRequestBuilder {
            inner: Some(client_builder.request(method, url)),
        }))
    }
    async fn send_request(
        &self,
        api_client: &dyn ApiClientWrapper,
        request: Request,
        option_timeout_secs: Option<u64>,
        _forward_to_kafka: bool,
    ) -> CustomResult<reqwest::Response, ApiClientError> {
        http_client::send_request(&api_client.get_proxy(), request, option_timeout_secs)
            .await
            .switch()
    }

    fn add_request_id(&mut self, request_id: RequestId) {
        self.request_id = Some(request_id);
    }

    fn get_request_id(&self) -> Option<RequestId> {
        self.request_id.clone()
    }

    fn get_request_id_str(&self) -> Option<String> {
        self.request_id.as_ref().map(|id| id.to_string())
    }

    fn add_flow_name(&mut self, _flow_name: String) {}
}

/// Api client for testing sending request
#[derive(Clone)]
pub struct MockApiClient;

#[async_trait::async_trait]
impl ApiClient for MockApiClient {
    fn request(
        &self,
        _method: Method,
        _url: String,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        // [#2066]: Add Mock implementation for ApiClient
        Err(ApiClientError::UnexpectedState.into())
    }

    fn request_with_certificate(
        &self,
        _method: Method,
        _url: String,
        _certificate: Option<masking::Secret<String>>,
        _certificate_key: Option<masking::Secret<String>>,
    ) -> CustomResult<Box<dyn RequestBuilder>, ApiClientError> {
        // [#2066]: Add Mock implementation for ApiClient
        Err(ApiClientError::UnexpectedState.into())
    }

    async fn send_request(
        &self,
        _state: &dyn ApiClientWrapper,
        _request: Request,
        _option_timeout_secs: Option<u64>,
        _forward_to_kafka: bool,
    ) -> CustomResult<reqwest::Response, ApiClientError> {
        // [#2066]: Add Mock implementation for ApiClient
        Err(ApiClientError::UnexpectedState.into())
    }

    fn add_request_id(&mut self, _request_id: RequestId) {
        // [#2066]: Add Mock implementation for ApiClient
    }

    fn get_request_id(&self) -> Option<RequestId> {
        // [#2066]: Add Mock implementation for ApiClient
        None
    }

    fn get_request_id_str(&self) -> Option<String> {
        // [#2066]: Add Mock implementation for ApiClient
        None
    }

    fn add_flow_name(&mut self, _flow_name: String) {}
}


// File: crates/router/src/services/api/generic_link_response.rs

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")
}


// File: crates/router/src/services/api/generic_link_response/context.rs

use common_utils::consts::DEFAULT_LOCALE;
use rust_i18n::t;
use tera::Context;

fn get_language(locale_str: &str) -> String {
    let lowercase_str = locale_str.to_lowercase();
    let primary_locale = lowercase_str.split(',').next().unwrap_or("").trim();

    if primary_locale.is_empty() {
        return DEFAULT_LOCALE.to_string();
    }

    let parts = primary_locale.split('-').collect::<Vec<&str>>();
    let language = *parts.first().unwrap_or(&DEFAULT_LOCALE);
    let country = parts.get(1).copied();

    match (language, country) {
        ("en", Some("gb")) => "en_gb".to_string(),
        ("fr", Some("be")) => "fr_be".to_string(),
        (lang, _) => lang.to_string(),
    }
}

pub fn insert_locales_in_context_for_payout_link(context: &mut Context, locale: &str) {
    let language = get_language(locale);
    let locale = language.as_str();
    let i18n_payout_link_title = t!("payout_link.initiate.title", locale = locale);
    let i18n_january = t!("months.january", locale = locale);
    let i18n_february = t!("months.february", locale = locale);
    let i18n_march = t!("months.march", locale = locale);
    let i18n_april = t!("months.april", locale = locale);
    let i18n_may = t!("months.may", locale = locale);
    let i18n_june = t!("months.june", locale = locale);
    let i18n_july = t!("months.july", locale = locale);
    let i18n_august = t!("months.august", locale = locale);
    let i18n_september = t!("months.september", locale = locale);
    let i18n_october = t!("months.october", locale = locale);
    let i18n_november = t!("months.november", locale = locale);
    let i18n_december = t!("months.december", locale = locale);
    let i18n_not_allowed = t!("payout_link.initiate.not_allowed", locale = locale);
    let i18n_am = t!("time.am", locale = locale);
    let i18n_pm = t!("time.pm", locale = locale);

    context.insert("i18n_payout_link_title", &i18n_payout_link_title);
    context.insert("i18n_january", &i18n_january);
    context.insert("i18n_february", &i18n_february);
    context.insert("i18n_march", &i18n_march);
    context.insert("i18n_april", &i18n_april);
    context.insert("i18n_may", &i18n_may);
    context.insert("i18n_june", &i18n_june);
    context.insert("i18n_july", &i18n_july);
    context.insert("i18n_august", &i18n_august);
    context.insert("i18n_september", &i18n_september);
    context.insert("i18n_october", &i18n_october);
    context.insert("i18n_november", &i18n_november);
    context.insert("i18n_december", &i18n_december);
    context.insert("i18n_not_allowed", &i18n_not_allowed);
    context.insert("i18n_am", &i18n_am);
    context.insert("i18n_pm", &i18n_pm);
}

pub fn insert_locales_in_context_for_payout_link_status(context: &mut Context, locale: &str) {
    let language = get_language(locale);
    let locale = language.as_str();
    let i18n_payout_link_status_title = t!("payout_link.status.title", locale = locale);
    let i18n_success_text = t!("payout_link.status.text.success", locale = locale);
    let i18n_success_message = t!("payout_link.status.message.success", locale = locale);
    let i18n_pending_text = t!("payout_link.status.text.processing", locale = locale);
    let i18n_pending_message = t!("payout_link.status.message.processing", locale = locale);
    let i18n_failed_text = t!("payout_link.status.text.failed", locale = locale);
    let i18n_failed_message = t!("payout_link.status.message.failed", locale = locale);
    let i18n_ref_id_text = t!("payout_link.status.info.ref_id", locale = locale);
    let i18n_error_code_text = t!("payout_link.status.info.error_code", locale = locale);
    let i18n_error_message = t!("payout_link.status.info.error_message", locale = locale);
    let i18n_redirecting_text = t!(
        "payout_link.status.redirection_text.redirecting",
        locale = locale
    );
    let i18n_redirecting_in_text = t!(
        "payout_link.status.redirection_text.redirecting_in",
        locale = locale
    );
    let i18n_seconds_text = t!(
        "payout_link.status.redirection_text.seconds",
        locale = locale
    );

    context.insert(
        "i18n_payout_link_status_title",
        &i18n_payout_link_status_title,
    );
    context.insert("i18n_success_text", &i18n_success_text);
    context.insert("i18n_success_message", &i18n_success_message);
    context.insert("i18n_pending_text", &i18n_pending_text);
    context.insert("i18n_pending_message", &i18n_pending_message);
    context.insert("i18n_failed_text", &i18n_failed_text);
    context.insert("i18n_failed_message", &i18n_failed_message);
    context.insert("i18n_ref_id_text", &i18n_ref_id_text);
    context.insert("i18n_error_code_text", &i18n_error_code_text);
    context.insert("i18n_error_message", &i18n_error_message);
    context.insert("i18n_redirecting_text", &i18n_redirecting_text);
    context.insert("i18n_redirecting_in_text", &i18n_redirecting_in_text);
    context.insert("i18n_seconds_text", &i18n_seconds_text);
}


// File: crates/router/src/services/authorization/roles.rs

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

#[cfg(feature = "recon")]
use api_models::enums::ReconPermissionScope;
use common_enums::{EntityType, PermissionGroup, Resource, RoleScope};
use common_utils::{errors::CustomResult, id_type};

#[cfg(feature = "recon")]
use super::permission_groups::{RECON_OPS, RECON_REPORTS};
use super::{permission_groups::PermissionGroupExt, permissions::Permission};
use crate::{core::errors, routes::SessionState};

pub mod predefined_roles;

#[derive(Clone, serde::Serialize, serde::Deserialize, Debug)]
pub struct RoleInfo {
    role_id: String,
    role_name: String,
    groups: Vec<PermissionGroup>,
    scope: RoleScope,
    entity_type: EntityType,
    is_invitable: bool,
    is_deletable: bool,
    is_updatable: bool,
    is_internal: bool,
}

impl RoleInfo {
    pub fn get_role_id(&self) -> &str {
        &self.role_id
    }

    pub fn get_role_name(&self) -> &str {
        &self.role_name
    }

    pub fn get_permission_groups(&self) -> Vec<PermissionGroup> {
        self.groups
            .iter()
            .flat_map(|group| group.accessible_groups())
            .collect::<HashSet<_>>()
            .into_iter()
            .collect()
    }

    pub fn get_scope(&self) -> RoleScope {
        self.scope
    }

    pub fn get_entity_type(&self) -> EntityType {
        self.entity_type
    }

    pub fn is_invitable(&self) -> bool {
        self.is_invitable
    }

    pub fn is_deletable(&self) -> bool {
        self.is_deletable
    }

    pub fn is_internal(&self) -> bool {
        self.is_internal
    }

    pub fn is_updatable(&self) -> bool {
        self.is_updatable
    }

    pub fn get_resources_set(&self) -> HashSet<Resource> {
        self.get_permission_groups()
            .iter()
            .flat_map(|group| group.resources())
            .collect()
    }

    pub fn check_permission_exists(&self, required_permission: Permission) -> bool {
        required_permission.entity_type() <= self.entity_type
            && self.get_permission_groups().iter().any(|group| {
                required_permission.scope() <= group.scope()
                    && group.resources().contains(&required_permission.resource())
            })
    }

    #[cfg(feature = "recon")]
    pub fn get_recon_acl(&self) -> HashMap<Resource, ReconPermissionScope> {
        let mut acl: HashMap<Resource, ReconPermissionScope> = HashMap::new();
        let mut recon_resources = RECON_OPS.to_vec();
        recon_resources.extend(RECON_REPORTS);
        let recon_internal_resources = [Resource::ReconToken];
        self.get_permission_groups()
            .iter()
            .for_each(|permission_group| {
                permission_group.resources().iter().for_each(|resource| {
                    if recon_resources.contains(resource)
                        && !recon_internal_resources.contains(resource)
                    {
                        let scope = match resource {
                            Resource::ReconAndSettlementAnalytics => ReconPermissionScope::Read,
                            _ => ReconPermissionScope::from(permission_group.scope()),
                        };
                        acl.entry(*resource)
                            .and_modify(|curr_scope| {
                                *curr_scope = if (*curr_scope) < scope {
                                    scope
                                } else {
                                    *curr_scope
                                }
                            })
                            .or_insert(scope);
                    }
                })
            });
        acl
    }

    pub fn from_predefined_roles(role_id: &str) -> Option<Self> {
        predefined_roles::PREDEFINED_ROLES.get(role_id).cloned()
    }

    pub async fn from_role_id_in_lineage(
        state: &SessionState,
        role_id: &str,
        merchant_id: &id_type::MerchantId,
        org_id: &id_type::OrganizationId,
        profile_id: &id_type::ProfileId,
        tenant_id: &id_type::TenantId,
    ) -> CustomResult<Self, errors::StorageError> {
        if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
            Ok(role.clone())
        } else {
            state
                .global_store
                .find_role_by_role_id_in_lineage(
                    role_id,
                    merchant_id,
                    org_id,
                    profile_id,
                    tenant_id,
                )
                .await
                .map(Self::from)
        }
    }

    // TODO: To evaluate whether we can omit org_id and tenant_id for this function
    pub async fn from_role_id_org_id_tenant_id(
        state: &SessionState,
        role_id: &str,
        org_id: &id_type::OrganizationId,
        tenant_id: &id_type::TenantId,
    ) -> CustomResult<Self, errors::StorageError> {
        if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) {
            Ok(role.clone())
        } else {
            state
                .global_store
                .find_by_role_id_org_id_tenant_id(role_id, org_id, tenant_id)
                .await
                .map(Self::from)
        }
    }
}

impl From<diesel_models::role::Role> for RoleInfo {
    fn from(role: diesel_models::role::Role) -> Self {
        Self {
            role_id: role.role_id,
            role_name: role.role_name,
            groups: role.groups,
            scope: role.scope,
            entity_type: role.entity_type,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        }
    }
}


// File: crates/router/src/services/authorization/permission_groups.rs

use std::{collections::HashMap, ops::Not};

use common_enums::{EntityType, ParentGroup, PermissionGroup, PermissionScope, Resource};
use strum::IntoEnumIterator;

use super::permissions;

pub trait PermissionGroupExt {
    fn scope(&self) -> PermissionScope;
    fn parent(&self) -> ParentGroup;
    fn resources(&self) -> Vec<Resource>;
    fn accessible_groups(&self) -> Vec<PermissionGroup>;
}

impl PermissionGroupExt for PermissionGroup {
    fn scope(&self) -> PermissionScope {
        match self {
            Self::OperationsView
            | Self::ConnectorsView
            | Self::WorkflowsView
            | Self::AnalyticsView
            | Self::UsersView
            | Self::AccountView
            | Self::ReconOpsView
            | Self::ReconReportsView
            | Self::ThemeView => PermissionScope::Read,

            Self::OperationsManage
            | Self::ConnectorsManage
            | Self::WorkflowsManage
            | Self::UsersManage
            | Self::AccountManage
            | Self::ReconOpsManage
            | Self::ReconReportsManage
            | Self::InternalManage
            | Self::ThemeManage => PermissionScope::Write,
        }
    }

    fn parent(&self) -> ParentGroup {
        match self {
            Self::OperationsView | Self::OperationsManage => ParentGroup::Operations,
            Self::ConnectorsView | Self::ConnectorsManage => ParentGroup::Connectors,
            Self::WorkflowsView | Self::WorkflowsManage => ParentGroup::Workflows,
            Self::AnalyticsView => ParentGroup::Analytics,
            Self::UsersView | Self::UsersManage => ParentGroup::Users,
            Self::AccountView | Self::AccountManage => ParentGroup::Account,

            Self::ThemeView | Self::ThemeManage => ParentGroup::Theme,
            Self::ReconOpsView | Self::ReconOpsManage => ParentGroup::ReconOps,
            Self::ReconReportsView | Self::ReconReportsManage => ParentGroup::ReconReports,
            Self::InternalManage => ParentGroup::Internal,
        }
    }

    fn resources(&self) -> Vec<Resource> {
        self.parent().resources()
    }

    fn accessible_groups(&self) -> Vec<Self> {
        match self {
            Self::OperationsView => vec![Self::OperationsView, Self::ConnectorsView],
            Self::OperationsManage => vec![
                Self::OperationsView,
                Self::OperationsManage,
                Self::ConnectorsView,
            ],

            Self::ConnectorsView => vec![Self::ConnectorsView],
            Self::ConnectorsManage => vec![Self::ConnectorsView, Self::ConnectorsManage],

            Self::WorkflowsView => vec![Self::WorkflowsView, Self::ConnectorsView],
            Self::WorkflowsManage => vec![
                Self::WorkflowsView,
                Self::WorkflowsManage,
                Self::ConnectorsView,
            ],

            Self::AnalyticsView => vec![Self::AnalyticsView, Self::OperationsView],

            Self::UsersView => vec![Self::UsersView],
            Self::UsersManage => {
                vec![Self::UsersView, Self::UsersManage]
            }

            Self::ReconOpsView => vec![Self::ReconOpsView],
            Self::ReconOpsManage => vec![Self::ReconOpsView, Self::ReconOpsManage],

            Self::ReconReportsView => vec![Self::ReconReportsView],
            Self::ReconReportsManage => vec![Self::ReconReportsView, Self::ReconReportsManage],

            Self::AccountView => vec![Self::AccountView],
            Self::AccountManage => vec![Self::AccountView, Self::AccountManage],

            Self::InternalManage => vec![Self::InternalManage],
            Self::ThemeView => vec![Self::ThemeView, Self::AccountView],
            Self::ThemeManage => vec![Self::ThemeManage, Self::AccountView],
        }
    }
}

pub trait ParentGroupExt {
    fn resources(&self) -> Vec<Resource>;
    fn get_descriptions_for_groups(
        entity_type: EntityType,
        groups: Vec<PermissionGroup>,
    ) -> Option<HashMap<ParentGroup, String>>;
    fn get_available_scopes(&self) -> Vec<PermissionScope>;
}

impl ParentGroupExt for ParentGroup {
    fn resources(&self) -> Vec<Resource> {
        match self {
            Self::Operations => OPERATIONS.to_vec(),
            Self::Connectors => CONNECTORS.to_vec(),
            Self::Workflows => WORKFLOWS.to_vec(),
            Self::Analytics => ANALYTICS.to_vec(),
            Self::Users => USERS.to_vec(),
            Self::Account => ACCOUNT.to_vec(),
            Self::ReconOps => RECON_OPS.to_vec(),
            Self::ReconReports => RECON_REPORTS.to_vec(),
            Self::Internal => INTERNAL.to_vec(),
            Self::Theme => THEME.to_vec(),
        }
    }

    fn get_descriptions_for_groups(
        entity_type: EntityType,
        groups: Vec<PermissionGroup>,
    ) -> Option<HashMap<Self, String>> {
        let descriptions_map = Self::iter()
            .filter_map(|parent| {
                if !groups.iter().any(|group| group.parent() == parent) {
                    return None;
                }
                let filtered_resources =
                    permissions::filter_resources_by_entity_type(parent.resources(), entity_type)?;

                let description = filtered_resources
                    .iter()
                    .map(|res| permissions::get_resource_name(*res, entity_type))
                    .collect::<Option<Vec<_>>>()?
                    .join(", ");

                Some((parent, description))
            })
            .collect::<HashMap<_, _>>();

        descriptions_map
            .is_empty()
            .not()
            .then_some(descriptions_map)
    }

    fn get_available_scopes(&self) -> Vec<PermissionScope> {
        PermissionGroup::iter()
            .filter(|group| group.parent() == *self)
            .map(|group| group.scope())
            .collect()
    }
}

pub static OPERATIONS: [Resource; 8] = [
    Resource::Payment,
    Resource::Refund,
    Resource::Mandate,
    Resource::Dispute,
    Resource::Customer,
    Resource::Payout,
    Resource::Report,
    Resource::Account,
];

pub static CONNECTORS: [Resource; 2] = [Resource::Connector, Resource::Account];

pub static WORKFLOWS: [Resource; 5] = [
    Resource::Routing,
    Resource::ThreeDsDecisionManager,
    Resource::SurchargeDecisionManager,
    Resource::Account,
    Resource::RevenueRecovery,
];

pub static ANALYTICS: [Resource; 3] = [Resource::Analytics, Resource::Report, Resource::Account];

pub static USERS: [Resource; 2] = [Resource::User, Resource::Account];

pub static ACCOUNT: [Resource; 3] = [Resource::Account, Resource::ApiKey, Resource::WebhookEvent];

pub static RECON_OPS: [Resource; 8] = [
    Resource::ReconToken,
    Resource::ReconFiles,
    Resource::ReconUpload,
    Resource::RunRecon,
    Resource::ReconConfig,
    Resource::ReconAndSettlementAnalytics,
    Resource::ReconReports,
    Resource::Account,
];

pub static INTERNAL: [Resource; 1] = [Resource::InternalConnector];

pub static RECON_REPORTS: [Resource; 4] = [
    Resource::ReconToken,
    Resource::ReconAndSettlementAnalytics,
    Resource::ReconReports,
    Resource::Account,
];

pub static THEME: [Resource; 1] = [Resource::Theme];


// File: crates/router/src/services/authorization/info.rs

use std::ops::Not;

use api_models::user_role::GroupInfo;
use common_enums::{ParentGroup, PermissionGroup};
use strum::IntoEnumIterator;

// TODO: To be deprecated
pub fn get_group_authorization_info() -> Option<Vec<GroupInfo>> {
    let groups = PermissionGroup::iter()
        .filter_map(get_group_info_from_permission_group)
        .collect::<Vec<_>>();

    groups.is_empty().not().then_some(groups)
}

// TODO: To be deprecated
fn get_group_info_from_permission_group(group: PermissionGroup) -> Option<GroupInfo> {
    let description = get_group_description(group)?;
    Some(GroupInfo { group, description })
}

// TODO: To be deprecated
fn get_group_description(group: PermissionGroup) -> Option<&'static str> {
    match group {
        PermissionGroup::OperationsView => {
            Some("View Payments, Refunds, Payouts, Mandates, Disputes and Customers")
        }
        PermissionGroup::OperationsManage => {
            Some("Create, modify and delete Payments, Refunds, Payouts, Mandates, Disputes and Customers")
        }
        PermissionGroup::ConnectorsView => {
            Some("View connected Payment Processors, Payout Processors and Fraud & Risk Manager details")
        }
        PermissionGroup::ConnectorsManage => Some("Create, modify and delete connectors like Payment Processors, Payout Processors and Fraud & Risk Manager"),
        PermissionGroup::WorkflowsView => {
            Some("View Routing, 3DS Decision Manager, Surcharge Decision Manager")
        }
        PermissionGroup::WorkflowsManage => {
            Some("Create, modify and delete Routing, 3DS Decision Manager, Surcharge Decision Manager")
        }
        PermissionGroup::AnalyticsView => Some("View Analytics"),
        PermissionGroup::UsersView => Some("View Users"),
        PermissionGroup::UsersManage => Some("Manage and invite Users to the Team"),
        PermissionGroup::AccountView => Some("View Merchant Details"),
        PermissionGroup::AccountManage => Some("Create, modify and delete Merchant Details like api keys, webhooks, etc"),
        PermissionGroup::ReconReportsView => Some("View reconciliation reports and analytics"),
        PermissionGroup::ReconReportsManage => Some("Manage reconciliation reports"),
        PermissionGroup::ReconOpsView => Some("View and access all reconciliation operations including reports and analytics"),
        PermissionGroup::ReconOpsManage => Some("Manage all reconciliation operations including reports and analytics"),
        PermissionGroup::ThemeView => Some("View Themes"),
        PermissionGroup::ThemeManage => Some("Manage Themes"),
        PermissionGroup::InternalManage => None, // Internal group, no user-facing description
    }
}

pub fn get_parent_group_description(group: ParentGroup) -> Option<&'static str> {
    match group {
        ParentGroup::Operations => Some("Payments, Refunds, Payouts, Mandates, Disputes and Customers"),
        ParentGroup::Connectors => Some("Create, modify and delete connectors like Payment Processors, Payout Processors and Fraud & Risk Manager"),
        ParentGroup::Workflows => Some("Create, modify and delete Routing, 3DS Decision Manager, Surcharge Decision Manager"),
        ParentGroup::Analytics => Some("View Analytics"),
        ParentGroup::Users =>  Some("Manage and invite Users to the Team"),
        ParentGroup::Account => Some("Create, modify and delete Merchant Details like api keys, webhooks, etc"),
        ParentGroup::ReconOps => Some("View, manage reconciliation operations like upload and process files, run reconciliation etc"),
        ParentGroup::ReconReports => Some("View, manage reconciliation reports and analytics"),
        ParentGroup::Theme => Some("Manage and view themes for the organization"),
        ParentGroup::Internal => None, // Internal group, no user-facing description
    }
}


// File: crates/router/src/services/authorization/permissions.rs

use common_enums::{EntityType, PermissionScope, Resource};
use router_derive::generate_permissions;

generate_permissions! {
    permissions: [
        Payment: {
            scopes: [Read, Write],
            entities: [Profile, Merchant]
        },
        Refund: {
            scopes: [Read, Write],
            entities: [Profile, Merchant]
        },
        Dispute: {
            scopes: [Read, Write],
            entities: [Profile, Merchant]
        },
        Mandate: {
            scopes: [Read, Write],
            entities: [Merchant]
        },
        Customer: {
            scopes: [Read, Write],
            entities: [Merchant]
        },
        Payout: {
            scopes: [Read],
            entities: [Profile, Merchant]
        },
        ApiKey: {
            scopes: [Read, Write],
            entities: [Merchant]
        },
        Account: {
            scopes: [Read, Write],
            entities: [Profile, Merchant, Organization, Tenant]
        },
        Connector: {
            scopes: [Read, Write],
            entities: [Profile, Merchant]
        },
        Routing: {
            scopes: [Read, Write],
            entities: [Profile, Merchant]
        },
        Subscription: {
            scopes: [Read, Write],
            entities: [Profile, Merchant]
        },
        ThreeDsDecisionManager: {
            scopes: [Read, Write],
            entities: [Merchant, Profile]
        },
        SurchargeDecisionManager: {
            scopes: [Read, Write],
            entities: [Merchant]
        },
        Analytics: {
            scopes: [Read],
            entities: [Profile, Merchant, Organization]
        },
        Report: {
            scopes: [Read],
            entities: [Profile, Merchant, Organization]
        },
        User: {
            scopes: [Read, Write],
            entities: [Profile, Merchant]
        },
        WebhookEvent: {
            scopes: [Read, Write],
            entities: [Profile, Merchant]
        },
        ReconToken: {
            scopes: [Read],
            entities: [Merchant]
        },
        ReconFiles: {
            scopes: [Read, Write],
            entities: [Merchant]
        },
        ReconAndSettlementAnalytics: {
            scopes: [Read],
            entities: [Merchant]
        },
        ReconUpload: {
            scopes: [Read, Write],
            entities: [Merchant]
        },
        ReconReports: {
            scopes: [Read, Write],
            entities: [Merchant]
        },
        RunRecon: {
            scopes: [Read, Write],
            entities: [Merchant]
        },
        ReconConfig: {
            scopes: [Read, Write],
            entities: [Merchant]
        },
        RevenueRecovery: {
            scopes: [Read],
            entities: [Profile]
        },
        InternalConnector: {
            scopes: [Write],
            entities: [Merchant]
        },
        Theme: {
            scopes: [Read,Write],
            entities: [Organization]
        }
    ]
}

pub fn get_resource_name(resource: Resource, entity_type: EntityType) -> Option<&'static str> {
    match (resource, entity_type) {
        (Resource::Payment, _) => Some("Payments"),
        (Resource::Refund, _) => Some("Refunds"),
        (Resource::Dispute, _) => Some("Disputes"),
        (Resource::Mandate, _) => Some("Mandates"),
        (Resource::Customer, _) => Some("Customers"),
        (Resource::Payout, _) => Some("Payouts"),
        (Resource::ApiKey, _) => Some("Api Keys"),
        (Resource::Connector, _) => {
            Some("Payment Processors, Payout Processors, Fraud & Risk Managers")
        }
        (Resource::Routing, _) => Some("Routing"),
        (Resource::Subscription, _) => Some("Subscription"),
        (Resource::RevenueRecovery, _) => Some("Revenue Recovery"),
        (Resource::ThreeDsDecisionManager, _) => Some("3DS Decision Manager"),
        (Resource::SurchargeDecisionManager, _) => Some("Surcharge Decision Manager"),
        (Resource::Analytics, _) => Some("Analytics"),
        (Resource::Report, _) => Some("Operation Reports"),
        (Resource::User, _) => Some("Users"),
        (Resource::WebhookEvent, _) => Some("Webhook Events"),
        (Resource::ReconUpload, _) => Some("Reconciliation File Upload"),
        (Resource::RunRecon, _) => Some("Run Reconciliation Process"),
        (Resource::ReconConfig, _) => Some("Reconciliation Configurations"),
        (Resource::ReconToken, _) => Some("Generate & Verify Reconciliation Token"),
        (Resource::ReconFiles, _) => Some("Reconciliation Process Manager"),
        (Resource::ReconReports, _) => Some("Reconciliation Reports"),
        (Resource::ReconAndSettlementAnalytics, _) => Some("Reconciliation Analytics"),
        (Resource::Account, EntityType::Profile) => Some("Business Profile Account"),
        (Resource::Account, EntityType::Merchant) => Some("Merchant Account"),
        (Resource::Account, EntityType::Organization) => Some("Organization Account"),
        (Resource::Account, EntityType::Tenant) => Some("Tenant Account"),
        (Resource::Theme, _) => Some("Themes"),
        (Resource::InternalConnector, _) => None,
    }
}

pub fn get_scope_name(scope: PermissionScope) -> &'static str {
    match scope {
        PermissionScope::Read => "View",
        PermissionScope::Write => "View and Manage",
    }
}

pub fn filter_resources_by_entity_type(
    resources: Vec<Resource>,
    entity_type: EntityType,
) -> Option<Vec<Resource>> {
    let filtered: Vec<Resource> = resources
        .into_iter()
        .filter(|res| res.entities().iter().any(|entity| entity <= &entity_type))
        .collect();

    (!filtered.is_empty()).then_some(filtered)
}


// File: crates/router/src/services/authorization/roles/predefined_roles.rs

use std::{collections::HashMap, sync::LazyLock};

use common_enums::{EntityType, PermissionGroup, RoleScope};

use super::RoleInfo;
use crate::consts;

pub static PREDEFINED_ROLES: LazyLock<HashMap<&'static str, RoleInfo>> = LazyLock::new(|| {
    let mut roles = HashMap::new();

    // Internal Roles
    roles.insert(
        common_utils::consts::ROLE_ID_INTERNAL_ADMIN,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::OperationsManage,
                PermissionGroup::ConnectorsView,
                PermissionGroup::ConnectorsManage,
                PermissionGroup::WorkflowsView,
                PermissionGroup::WorkflowsManage,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::UsersManage,
                PermissionGroup::AccountView,
                PermissionGroup::AccountManage,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconOpsManage,
                PermissionGroup::ReconReportsView,
                PermissionGroup::ReconReportsManage,
            ],
            role_id: common_utils::consts::ROLE_ID_INTERNAL_ADMIN.to_string(),
            role_name: "internal_admin".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Merchant,
            is_invitable: false,
            is_deletable: false,
            is_updatable: false,
            is_internal: true,
        },
    );
    roles.insert(
        common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::ConnectorsView,
                PermissionGroup::WorkflowsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconReportsView,
            ],
            role_id: common_utils::consts::ROLE_ID_INTERNAL_VIEW_ONLY_USER.to_string(),
            role_name: "internal_view_only".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Merchant,
            is_invitable: false,
            is_deletable: false,
            is_updatable: false,
            is_internal: true,
        },
    );
    roles.insert(
        common_utils::consts::ROLE_ID_INTERNAL_DEMO,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::ConnectorsView,
                PermissionGroup::WorkflowsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconReportsView,
                PermissionGroup::InternalManage,
            ],
            role_id: common_utils::consts::ROLE_ID_INTERNAL_DEMO.to_string(),
            role_name: "internal_demo".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Merchant,
            is_invitable: false,
            is_deletable: false,
            is_updatable: false,
            is_internal: true,
        },
    );

    // Tenant Roles
    roles.insert(
        common_utils::consts::ROLE_ID_TENANT_ADMIN,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::OperationsManage,
                PermissionGroup::ConnectorsView,
                PermissionGroup::ConnectorsManage,
                PermissionGroup::WorkflowsView,
                PermissionGroup::WorkflowsManage,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::UsersManage,
                PermissionGroup::AccountView,
                PermissionGroup::AccountManage,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconOpsManage,
                PermissionGroup::ReconReportsView,
                PermissionGroup::ReconReportsManage,
            ],
            role_id: common_utils::consts::ROLE_ID_TENANT_ADMIN.to_string(),
            role_name: "tenant_admin".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Tenant,
            is_invitable: false,
            is_deletable: false,
            is_updatable: false,
            is_internal: false,
        },
    );

    // Organization Roles
    roles.insert(
        common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::OperationsManage,
                PermissionGroup::ConnectorsView,
                PermissionGroup::ConnectorsManage,
                PermissionGroup::WorkflowsView,
                PermissionGroup::WorkflowsManage,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::UsersManage,
                PermissionGroup::AccountView,
                PermissionGroup::AccountManage,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconOpsManage,
                PermissionGroup::ReconReportsView,
                PermissionGroup::ReconReportsManage,
                PermissionGroup::ThemeView,
                PermissionGroup::ThemeManage,
            ],
            role_id: common_utils::consts::ROLE_ID_ORGANIZATION_ADMIN.to_string(),
            role_name: "organization_admin".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Organization,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );

    // MERCHANT ROLES
    roles.insert(
        consts::user_role::ROLE_ID_MERCHANT_ADMIN,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::OperationsManage,
                PermissionGroup::ConnectorsView,
                PermissionGroup::ConnectorsManage,
                PermissionGroup::WorkflowsView,
                PermissionGroup::WorkflowsManage,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::UsersManage,
                PermissionGroup::AccountView,
                PermissionGroup::AccountManage,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconOpsManage,
                PermissionGroup::ReconReportsView,
                PermissionGroup::ReconReportsManage,
            ],
            role_id: consts::user_role::ROLE_ID_MERCHANT_ADMIN.to_string(),
            role_name: "merchant_admin".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Merchant,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::ConnectorsView,
                PermissionGroup::WorkflowsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconReportsView,
            ],
            role_id: consts::user_role::ROLE_ID_MERCHANT_VIEW_ONLY.to_string(),
            role_name: "merchant_view_only".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Merchant,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::UsersManage,
                PermissionGroup::AccountView,
            ],
            role_id: consts::user_role::ROLE_ID_MERCHANT_IAM_ADMIN.to_string(),
            role_name: "merchant_iam".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Merchant,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_MERCHANT_DEVELOPER,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::ConnectorsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
                PermissionGroup::AccountManage,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconReportsView,
            ],
            role_id: consts::user_role::ROLE_ID_MERCHANT_DEVELOPER.to_string(),
            role_name: "merchant_developer".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Merchant,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_MERCHANT_OPERATOR,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::OperationsManage,
                PermissionGroup::ConnectorsView,
                PermissionGroup::WorkflowsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconOpsManage,
                PermissionGroup::ReconReportsView,
            ],
            role_id: consts::user_role::ROLE_ID_MERCHANT_OPERATOR.to_string(),
            role_name: "merchant_operator".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Merchant,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
                PermissionGroup::ReconOpsView,
                PermissionGroup::ReconReportsView,
            ],
            role_id: consts::user_role::ROLE_ID_MERCHANT_CUSTOMER_SUPPORT.to_string(),
            role_name: "customer_support".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Merchant,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );

    // Profile Roles
    roles.insert(
        consts::user_role::ROLE_ID_PROFILE_ADMIN,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::OperationsManage,
                PermissionGroup::ConnectorsView,
                PermissionGroup::ConnectorsManage,
                PermissionGroup::WorkflowsView,
                PermissionGroup::WorkflowsManage,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::UsersManage,
                PermissionGroup::AccountView,
                PermissionGroup::AccountManage,
            ],
            role_id: consts::user_role::ROLE_ID_PROFILE_ADMIN.to_string(),
            role_name: "profile_admin".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Profile,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_PROFILE_VIEW_ONLY,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::ConnectorsView,
                PermissionGroup::WorkflowsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
            ],
            role_id: consts::user_role::ROLE_ID_PROFILE_VIEW_ONLY.to_string(),
            role_name: "profile_view_only".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Profile,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_PROFILE_IAM_ADMIN,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::UsersManage,
                PermissionGroup::AccountView,
            ],
            role_id: consts::user_role::ROLE_ID_PROFILE_IAM_ADMIN.to_string(),
            role_name: "profile_iam".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Profile,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_PROFILE_DEVELOPER,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::ConnectorsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
                PermissionGroup::AccountManage,
            ],
            role_id: consts::user_role::ROLE_ID_PROFILE_DEVELOPER.to_string(),
            role_name: "profile_developer".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Profile,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_PROFILE_OPERATOR,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::OperationsManage,
                PermissionGroup::ConnectorsView,
                PermissionGroup::WorkflowsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
            ],
            role_id: consts::user_role::ROLE_ID_PROFILE_OPERATOR.to_string(),
            role_name: "profile_operator".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Profile,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles.insert(
        consts::user_role::ROLE_ID_PROFILE_CUSTOMER_SUPPORT,
        RoleInfo {
            groups: vec![
                PermissionGroup::OperationsView,
                PermissionGroup::AnalyticsView,
                PermissionGroup::UsersView,
                PermissionGroup::AccountView,
            ],
            role_id: consts::user_role::ROLE_ID_PROFILE_CUSTOMER_SUPPORT.to_string(),
            role_name: "profile_customer_support".to_string(),
            scope: RoleScope::Organization,
            entity_type: EntityType::Profile,
            is_invitable: true,
            is_deletable: true,
            is_updatable: true,
            is_internal: false,
        },
    );
    roles
});


// File: crates/router/src/services/authentication/decision.rs

use common_utils::{errors::CustomResult, request::RequestContent};
use masking::{ErasedMaskSerialize, Secret};
use serde::Serialize;
use storage_impl::errors::ApiClientError;

use crate::{
    core::metrics,
    routes::{app::settings::DecisionConfig, SessionState},
};

// # Consts
//

const DECISION_ENDPOINT: &str = "/rule";
const RULE_ADD_METHOD: common_utils::request::Method = common_utils::request::Method::Post;
const RULE_DELETE_METHOD: common_utils::request::Method = common_utils::request::Method::Delete;

pub const REVOKE: &str = "REVOKE";
pub const ADD: &str = "ADD";

// # Types
//

/// [`RuleRequest`] is a request body used to register a new authentication method in the proxy.
#[derive(Debug, Serialize)]
pub struct RuleRequest {
    /// [`tag`] similar to a partition key, which can be used by the decision service to tag rules
    /// by partitioning identifiers. (e.g. `tenant_id`)
    pub tag: String,
    /// [`variant`] is the type of authentication method to be registered.
    #[serde(flatten)]
    pub variant: AuthRuleType,
    /// [`expiry`] is the time **in seconds** after which the rule should be removed
    pub expiry: Option<u64>,
}

#[derive(Debug, Serialize)]
pub struct RuleDeleteRequest {
    pub tag: String,
    #[serde(flatten)]
    pub variant: AuthType,
}

#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AuthType {
    /// [`ApiKey`] is an authentication method that uses an API key. This is used with [`ApiKey`]
    ApiKey { api_key: Secret<String> },
}

#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum AuthRuleType {
    /// [`ApiKey`] is an authentication method that uses an API key. This is used with [`ApiKey`]
    /// and [`PublishableKey`] authentication methods.
    ApiKey {
        api_key: Secret<String>,
        identifiers: Identifiers,
    },
}

#[allow(clippy::enum_variant_names)]
#[derive(Debug, Serialize, Clone)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum Identifiers {
    /// [`ApiKey`] is an authentication method that uses an API key. This is used with [`ApiKey`]
    ApiKey {
        merchant_id: common_utils::id_type::MerchantId,
        key_id: common_utils::id_type::ApiKeyId,
    },
    /// [`PublishableKey`] is an authentication method that uses a publishable key. This is used with [`PublishableKey`]
    PublishableKey { merchant_id: String },
}

// # Decision Service
//

pub async fn add_api_key(
    state: &SessionState,
    api_key: Secret<String>,
    merchant_id: common_utils::id_type::MerchantId,
    key_id: common_utils::id_type::ApiKeyId,
    expiry: Option<u64>,
) -> CustomResult<(), ApiClientError> {
    let decision_config = if let Some(config) = &state.conf.decision {
        config
    } else {
        return Ok(());
    };

    let rule = RuleRequest {
        tag: state.tenant.schema.clone(),
        expiry,
        variant: AuthRuleType::ApiKey {
            api_key,
            identifiers: Identifiers::ApiKey {
                merchant_id,
                key_id,
            },
        },
    };

    call_decision_service(state, decision_config, rule, RULE_ADD_METHOD).await
}

pub async fn add_publishable_key(
    state: &SessionState,
    api_key: Secret<String>,
    merchant_id: common_utils::id_type::MerchantId,
    expiry: Option<u64>,
) -> CustomResult<(), ApiClientError> {
    let decision_config = if let Some(config) = &state.conf.decision {
        config
    } else {
        return Ok(());
    };

    let rule = RuleRequest {
        tag: state.tenant.schema.clone(),
        expiry,
        variant: AuthRuleType::ApiKey {
            api_key,
            identifiers: Identifiers::PublishableKey {
                merchant_id: merchant_id.get_string_repr().to_owned(),
            },
        },
    };

    call_decision_service(state, decision_config, rule, RULE_ADD_METHOD).await
}

async fn call_decision_service<T: ErasedMaskSerialize + Send + 'static>(
    state: &SessionState,
    decision_config: &DecisionConfig,
    rule: T,
    method: common_utils::request::Method,
) -> CustomResult<(), ApiClientError> {
    let mut request = common_utils::request::Request::new(
        method,
        &(decision_config.base_url.clone() + DECISION_ENDPOINT),
    );

    request.set_body(RequestContent::Json(Box::new(rule)));
    request.add_default_headers();

    let response = state
        .api_client
        .send_request(state, request, None, false)
        .await;

    match response {
        Err(error) => {
            router_env::error!("Failed while calling the decision service: {:?}", error);
            Err(error)
        }
        Ok(response) => {
            router_env::info!("Decision service response: {:?}", response);
            Ok(())
        }
    }
}

pub async fn revoke_api_key(
    state: &SessionState,
    api_key: Secret<String>,
) -> CustomResult<(), ApiClientError> {
    let decision_config = if let Some(config) = &state.conf.decision {
        config
    } else {
        return Ok(());
    };

    let rule = RuleDeleteRequest {
        tag: state.tenant.schema.clone(),
        variant: AuthType::ApiKey { api_key },
    };

    call_decision_service(state, decision_config, rule, RULE_DELETE_METHOD).await
}

/// Safety: i64::MAX < u64::MAX
#[allow(clippy::as_conversions)]
pub fn convert_expiry(expiry: time::PrimitiveDateTime) -> u64 {
    let now = common_utils::date_time::now();
    let duration = expiry - now;
    let output = duration.whole_seconds();

    match output {
        i64::MIN..=0 => 0,
        _ => output as u64,
    }
}

pub fn spawn_tracked_job<E, F>(future: F, request_type: &'static str)
where
    E: std::fmt::Debug,
    F: futures::Future<Output = Result<(), E>> + Send + 'static,
{
    metrics::API_KEY_REQUEST_INITIATED
        .add(1, router_env::metric_attributes!(("type", request_type)));
    tokio::spawn(async move {
        match future.await {
            Ok(_) => {
                metrics::API_KEY_REQUEST_COMPLETED
                    .add(1, router_env::metric_attributes!(("type", request_type)));
            }
            Err(e) => {
                router_env::error!("Error in tracked job: {:?}", e);
            }
        }
    });
}