use std::collections::HashSet;

use api_models::webhooks::IncomingWebhookEvent;
use common_enums;
use external_services::superposition;
use scheduler::consumer::types::process_data::RetryMapping;

use super::{dimension_state, fetch_db_config_for_dimensions, ConfigContext, DatabaseBackedConfig};
use crate::{consts::superposition as superposition_consts, db::StorageInterface, utils::id_type};

/// Macro to generate config struct and superposition::Config trait implementation.
/// Note: Manually implement `DatabaseBackedConfig` for the config struct:
/// The `fetch_db` method is provided by the default implementation in DatabaseBackedConfig.
///
/// # Targeting Key
///
/// In Superposition, a **targeting key** is the identifier used to assign a user/entity
/// to a specific experiment variant during traffic splitting. When an experiment is running
/// (e.g., testing a new config value vs the old one), Superposition uses the targeting key
/// to deterministically decide which variant a given entity sees.
///
/// The same targeting key value will always resolve to the same variant, ensuring
/// consistent behavior for a given entity across requests.
///
/// ## How to Select a Targeting Key
///
/// The targeting key should be the **most granular entity that should have a consistent
/// experiment experience**:
///
/// - Use **`CustomerId`** when the config affects customer-facing behavior.
///   This ensures the same customer always sees the same variant
///   across multiple payments and sessions.
///
/// - Use **`PaymentId`** when there is no customer context involved, or when the config
///   decision is per-payment (e.g., proxy payment flows). Each payment will independently
///   resolve to a variant.
///
/// As a rule of thumb: pick the entity whose experience should remain stable throughout
/// the experiment.
macro_rules! config {
    // Object config variant (with object = true)
    (
        superposition_key = $key:ident,
        output = $output:ty,
        default = $default:expr,
        object = true,
        requires = $requirement:ty,
        targeting_key = $targeting_type:ty
    ) => {
        paste::paste! {
            pub struct [<$key:camel>];

            impl superposition::Config for [<$key:camel>] {
                type Output = serde_json::Value;
                type TargetingKey = $targeting_type;
                const SUPERPOSITION_KEY: &'static str = superposition_consts::$key;
                fn default_value() -> Self::Output {
                    serde_json::to_value(&$default).expect("Failed to serialize default")
                }
            }

            impl $requirement {
                pub async fn [<get_ $key:lower>](
                    &self,
                    storage: &dyn StorageInterface,
                    superposition_client: &superposition::SuperpositionClient,
                    targeting_key: Option<&$targeting_type>,
                ) -> $output {
                    // Fetch JSON and convert to $output using the conversion function
                    crate::core::configs::fetch_db_config_for_objects::<[<$key:camel>], $output>(
                        storage, superposition_client, self, targeting_key
                    ).await
                }
            }
        }
    };

    // String-enum config variant
    (
        superposition_key = $key:ident,
        output = $output:ty,
        default = $default:expr,
        string_enum = true,
        requires = $requirement:ty,
        targeting_key = $targeting_type:ty
    ) => {
        paste::paste! {
            pub struct [<$key:camel>];

            impl superposition::Config for [<$key:camel>] {
                type Output = String;
                type TargetingKey = $targeting_type;
                const SUPERPOSITION_KEY: &'static str = superposition_consts::$key;
                fn default_value() -> Self::Output {
                    $default.to_string()
                }
            }

            impl $requirement {
                pub async fn [<get_ $key:lower>](
                    &self,
                    storage: &dyn StorageInterface,
                    superposition_client: &superposition::SuperpositionClient,
                    targeting_key: Option<&$targeting_type>,
                ) -> $output {
                    crate::core::configs::fetch_db_config_for_string_enum::<[<$key:camel>], $output>(
                        storage, superposition_client, self, targeting_key
                    )
                    .await
                    .unwrap_or($default)
                }
            }
        }
    };

    // Primitive config variant (no helper function - use get_{{key_name}}() directly on Dimensions)
    (
        superposition_key = $key:ident,
        output = $output:ty,
        default = $default:expr,
        requires = $requirement:ty,
        targeting_key = $targeting_type:ty
    ) => {
        paste::paste! {
            pub struct [<$key:camel>];

            impl superposition::Config for [<$key:camel>] {
                type Output = $output;
                type TargetingKey = $targeting_type;
                const SUPERPOSITION_KEY: &'static str = superposition_consts::$key;
                fn default_value() -> Self::Output {
                    $default
                }
            }

            impl $requirement {
                pub async fn [<get_ $key:lower>](
                    &self,
                    storage: &dyn StorageInterface,
                    superposition_client: &superposition::SuperpositionClient,
                    targeting_key: Option<&$targeting_type>,
                ) -> $output {
                    fetch_db_config_for_dimensions::<[<$key:camel>]>(storage, superposition_client, self, targeting_key).await
                }
            }
        }
    };
}

config! {
    superposition_key = REQUIRES_CVV,
    output = bool,
    default = true,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for RequiresCvv {
    const KEY: &'static str = "requires_cvv";
    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .map(|id| format!("{}_{}", id.get_string_repr(), Self::KEY))
    }
}

config! {
    superposition_key = IMPLICIT_CUSTOMER_UPDATE,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndProfileId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ImplicitCustomerUpdate {
    const KEY: &'static str = "implicit_customer_update";
    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_provider_merchant_id()
            .map(|id| format!("{}_{}", id.get_string_repr(), Self::KEY))
    }
}

// Retained temporarily so merchants without a database value can fall back to
// their existing Superposition fingerprint secret during migration.
config! {
    superposition_key = FINGERPRINT_SECRET,
    output = String,
    default = String::new(),
    requires = dimension_state::DimensionsWithProcessorMerchantId,
    targeting_key = id_type::MerchantId
}

impl DatabaseBackedConfig for FingerprintSecret {
    const KEY: &'static str = "fingerprint_secret";
    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .map(|id| format!("{}_{}", id.get_string_repr(), Self::KEY))
    }
}

config! {
    superposition_key = SHOULD_CALL_GSM,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndProfileId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ShouldCallGsm {
    const KEY: &'static str = "should_call_gsm";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = SHOULD_PERFORM_ELIGIBILITY,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndProfileId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ShouldPerformEligibility {
    const KEY: &'static str = "should_perform_eligibility";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = SHOULD_ENABLE_MIT_WITH_LIMITED_CARD_DATA,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndProfileId,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for ShouldEnableMitWithLimitedCardData {
    const KEY: &'static str = "should_enable_mit_with_limited_card_data";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = SHOULD_STORE_ELIGIBILITY_CHECK_DATA_FOR_AUTHENTICATION,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantId,
    targeting_key = id_type::AuthenticationId
}

impl DatabaseBackedConfig for ShouldStoreEligibilityCheckDataForAuthentication {
    const KEY: &'static str = "should_store_eligibility_check_data_for_authentication";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = ENABLE_EXTENDED_CARD_BIN,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndProfileId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for EnableExtendedCardBin {
    const KEY: &'static str = "enable_extended_card_bin";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_profile_id()
            .map(|id| format!("{}_{}", id.get_string_repr(), Self::KEY))
    }
}

config! {
    superposition_key = GSM_PAYOUT_CALL,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndPayoutRetryType,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for GsmPayoutCall {
    const KEY: &'static str = "gsm_payout_call";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .and_then(|merchant_id| {
                dimensions
                    .get_payout_retry_type()
                    .map(|retry_type| match retry_type {
                        common_enums::PayoutRetryType::SingleConnector => format!(
                            "should_call_gsm_single_connector_payout_{}",
                            merchant_id.get_string_repr()
                        ),
                        common_enums::PayoutRetryType::MultiConnector => format!(
                            "should_call_gsm_multiple_connector_payout_{}",
                            merchant_id.get_string_repr()
                        ),
                    })
            })
    }
}

config! {
    superposition_key = SHOULD_DISABLE_VAULT_TOKENIZATION,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndOrgId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ShouldDisableVaultTokenization {
    const KEY: &'static str = "should_disable_vault_tokenization";

    fn db_keys(dimensions: &impl dimension_state::DimensionsBase) -> Vec<Option<String>> {
        vec![
            dimensions
                .get_organization_id()
                .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr())),
            dimensions
                .get_processor_merchant_id()
                .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr())),
        ]
    }
}

#[cfg(feature = "v2")]
config! {
    superposition_key = SHOULD_RETURN_RAW_PAYMENT_METHOD_DETAILS,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProviderMerchantId,
    targeting_key = id_type::GlobalCustomerId
}

#[cfg(feature = "v2")]
impl DatabaseBackedConfig for ShouldReturnRawPaymentMethodDetails {
    const KEY: &'static str = "should_return_raw_payment_method_details";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_provider_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = SHOULD_CALL_PM_MODULAR_SERVICE,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProviderMerchantIdAndOrgId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ShouldCallPmModularService {
    const KEY: &'static str = "should_call_pm_modular_service";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_organization_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = SHOULD_SCHEDULE_MODULAR_FORWARD_COMPAT,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProviderMerchantIdAndOrgId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ShouldScheduleModularForwardCompat {
    const KEY: &'static str = "should_schedule_modular_forward_compat";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_provider_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = SHOULD_SCHEDULE_MODULAR_BACKWARD_COMPAT,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProviderMerchantIdAndOrgId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ShouldScheduleModularBackwardCompat {
    const KEY: &'static str = "should_schedule_modular_backward_compat";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_provider_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = SHOULD_TRIGGER_BACKWARDS_COMPATIBILITY_INLINE,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProviderMerchantIdAndOrgId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ShouldTriggerBackwardsCompatibilityInline {
    const KEY: &'static str = "should_trigger_backwards_compatibility_inline";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_provider_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = SHOULD_TRIGGER_FINGERPRINT_MIGRATION,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProviderMerchantId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ShouldTriggerFingerprintMigration {
    const KEY: &'static str = "should_trigger_fingerprint_migration";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_provider_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = NETWORK_TOKEN_FETCH_TIMEOUT_IN_SECS,
    output = u32,
    default = 4,
    requires = dimension_state::DimensionsGlobal,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for NetworkTokenFetchTimeoutInSecs {
    const KEY: &'static str = "network_token_fetch_timeout_in_secs";

    // Global config: a single deployment-wide key, not scoped to any merchant.
    fn db_key(_dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        Some(Self::KEY.to_string())
    }
}

config! {
    superposition_key = SHOULD_PERFORM_SDK_VAULTING,
    output = bool,
    default = true,
    requires = dimension_state::DimensionsWithOrgId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for ShouldPerformSdkVaulting {
    const KEY: &'static str = "should_perform_sdk_vaulting";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_organization_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = PAYOUT_TRACKER_MAPPING,
    output = RetryMapping,
    default = RetryMapping::default(),
    object = true,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndConnector,
    targeting_key = id_type::PayoutId
}

impl DatabaseBackedConfig for PayoutTrackerMapping {
    const KEY: &'static str = "payout_tracker_mapping";

    fn db_key(_dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        // The payout tracker mapping config has been directly implemented in superposition. So there would be no db fallback for it. Hence returning none here.
        None
    }
}

config! {
    superposition_key = CLIENT_SESSION_VALIDATION_ENABLED,
    output = bool,
    default = true,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantId,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for ClientSessionValidationEnabled {
    const KEY: &'static str = "client_session_validation_enabled";
    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = MAX_AUTO_PAYOUT_RETRIES,
    output = u32,
    default = 0u32,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndPayoutRetryType,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for MaxAutoPayoutRetries {
    const KEY: &'static str = "max_auto_payout_retries";
    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .and_then(|merchant_id| {
                dimensions
                    .get_payout_retry_type()
                    .map(|retry_type| match retry_type {
                        common_enums::PayoutRetryType::SingleConnector => format!(
                            "max_auto_single_connector_payout_retries_enabled_{}",
                            merchant_id.get_string_repr()
                        ),
                        common_enums::PayoutRetryType::MultiConnector => format!(
                            "max_auto_multiple_connector_payout_retries_enabled_{}",
                            merchant_id.get_string_repr()
                        ),
                    })
            })
    }
}

config! {
    superposition_key = POLL_CONFIG_EXTERNAL_THREE_DS,
    output = crate::types::PollConfig,
    default = crate::types::PollConfig::default(),
    object = true,
    requires = dimension_state::DimensionsWithProcessorMerchantIdAndConnector,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for PollConfigExternalThreeDs {
    const KEY: &'static str = "poll_config_external_three_ds";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_connector()
            .map(|connector| format!("{}_{}", Self::KEY, connector))
    }
}

config! {
    superposition_key = PT_MAPPING_OUTGOING_CONNECTOR_WEBHOOKS,
    output = scheduler::types::process_data::OutgoingWebhookRetryProcessTrackerMapping,
    default = scheduler::types::process_data::OutgoingWebhookRetryProcessTrackerMapping::default(),
    object = true,
    requires = dimension_state::DimensionsWithProcessorMerchantIdAndConnector,
    targeting_key = id_type::MerchantId
}

impl DatabaseBackedConfig for PtMappingOutgoingConnectorWebhooks {
    const KEY: &'static str = "pt_mapping_outgoing_connector_webhooks";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_connector()
            .map(|connector| format!("{}_{}", Self::KEY, connector))
    }
}

config! {
    superposition_key = PT_MAPPING_OUTGOING_WEBHOOKS,
    output = scheduler::types::process_data::OutgoingWebhookRetryProcessTrackerMapping,
    default = scheduler::types::process_data::OutgoingWebhookRetryProcessTrackerMapping::default(),
    object = true,
    requires = dimension_state::DimensionsWithProcessorMerchantId,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for PtMappingOutgoingWebhooks {
    const KEY: &'static str = "pt_mapping_outgoing_webhooks";

    fn db_key(_dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        Some(Self::KEY.to_string())
    }
}

config! {
    superposition_key = PT_MAPPING_PCR_RETRIES,
    output = scheduler::types::process_data::RevenueRecoveryPaymentProcessTrackerMapping,
    default = scheduler::types::process_data::RevenueRecoveryPaymentProcessTrackerMapping::default(),
    object = true,
    requires = dimension_state::DimensionsWithProcessorMerchantIdAndConnector,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for PtMappingPcrRetries {
    const KEY: &'static str = "pt_mapping_pcr_retries";

    fn db_key(_dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        Some(Self::KEY.to_string())
    }
}

config! {
    superposition_key = PT_MAPPING_PAYMENT_SYNC,
    output = scheduler::types::process_data::ConnectorPTMapping,
    default = scheduler::types::process_data::ConnectorPTMapping::default(),
    object = true,
    requires = dimension_state::DimensionsWithProcessorMerchantIdAndConnector,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for PtMappingPaymentSync {
    const KEY: &'static str = "pt_mapping";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_connector()
            .map(|connector| format!("{}_{}", Self::KEY, connector))
    }
}

config! {
    superposition_key = PT_MAPPING_REFUND_SYNC,
    output = scheduler::types::process_data::ConnectorPTMapping,
    default = scheduler::types::process_data::ConnectorPTMapping::refund_default(),
    object = true,
    requires = dimension_state::DimensionsWithProcessorMerchantIdAndConnector,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for PtMappingRefundSync {
    const KEY: &'static str = "pt_mapping_refund_sync";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_connector()
            .map(|connector| format!("{}_{}", Self::KEY, connector))
    }
}

config! {
    superposition_key = PT_MAPPING_DISPUTE_SYNC,
    output = scheduler::types::process_data::ConnectorPTMapping,
    default = scheduler::types::process_data::ConnectorPTMapping::default(),
    object = true,
    requires = dimension_state::DimensionsWithProcessorMerchantIdAndConnector,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for PtMappingDisputeSync {
    const KEY: &'static str = "pt_mapping";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_connector()
            .map(|connector| format!("{}_{}", Self::KEY, connector))
    }
}

config! {
    superposition_key = ROUTING_RESULT_SOURCE,
    output = api_models::routing::RoutingResultSource,
    default = api_models::routing::RoutingResultSource::HyperswitchRouting,
    string_enum = true,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndProfileId,
    targeting_key = id_type::ProfileId
}

impl DatabaseBackedConfig for RoutingResultSource {
    const KEY: &'static str = "routing_result_source";
    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_profile_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = THREEDS_ROUTING_REGION_UAS,
    output = common_enums::RoutingRegion,
    default = common_enums::RoutingRegion::Region1,
    string_enum = true,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndOrgId,
    targeting_key = id_type::MerchantId
}

impl DatabaseBackedConfig for ThreedsRoutingRegionUas {
    const KEY: &'static str = "threeds_routing_region_uas";
    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_organization_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = INCOMING_WEBHOOK_DISABLED_EVENTS,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantIdAndConnectorAndWebhookEvent,
    targeting_key = id_type::MerchantId
}

impl DatabaseBackedConfig for IncomingWebhookDisabledEvents {
    const KEY: &'static str = "incoming_webhook_disabled_events";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        dimensions
            .get_processor_merchant_id()
            .and_then(|merchant_id| {
                dimensions.get_connector().map(|connector| {
                    format!("whconf_{}_{}", merchant_id.get_string_repr(), connector)
                })
            })
    }

    fn parse_db_config(config_str: &str, context: Option<&ConfigContext>) -> Option<Self::Output>
    where
        Self::Output: super::ConfigType,
    {
        let disabled_events: HashSet<IncomingWebhookEvent> = serde_json::from_str(config_str)
            .inspect_err(|err| {
                router_env::logger::error!(
                    ?err,
                    "Failed to parse incoming_webhook_disabled_events list from db config"
                )
            })
            .ok()?;

        context
            .and_then(|ctx| ctx.get("incoming_webhook_events"))
            .and_then(|event_str| {
                serde_json::from_value::<IncomingWebhookEvent>(serde_json::Value::String(
                    event_str.to_string(),
                ))
                .inspect_err(|err| {
                    router_env::logger::error!(
                        ?err,
                        event = %event_str,
                        "Failed to parse incoming_webhook_event from context"
                    )
                })
                .ok()
            })
            .map(|event| disabled_events.contains(&event))
    }
}

config! {
    superposition_key = SAVE_WALLET_DECRYPTED_DATA,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsWithProcessorAndProviderMerchantId,
    targeting_key = id_type::CustomerId
}

impl DatabaseBackedConfig for SaveWalletDecryptedData {
    const KEY: &'static str = "save_wallet_decrypted_data";

    fn db_key(dimensions: &impl dimension_state::DimensionsBase) -> Option<String> {
        // Matches the existing key format: "save_wallet_decrypted_data_{merchant_id}"
        dimensions
            .get_processor_merchant_id()
            .map(|id| format!("{}_{}", Self::KEY, id.get_string_repr()))
    }
}

config! {
    superposition_key = OFFER_ENGINE_ENABLED,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsGlobal,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for OfferEngineEnabled {
    const KEY: &'static str = "offer_engine_enabled";
}

config! {
    superposition_key = OFFER_ENGINE_CREDENTIAL_SOURCE,
    output = crate::core::offer_engine::types::OfferEngineCredentialSource,
    default = crate::core::offer_engine::types::OfferEngineCredentialSource::None,
    string_enum = true,
    requires = dimension_state::DimensionsGlobal,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for OfferEngineCredentialSource {
    const KEY: &'static str = "offer_engine_credential_source";
}

config! {
    superposition_key = ACCOUNT_UPDATER_ENABLED,
    output = bool,
    default = false,
    requires = dimension_state::DimensionsGlobal,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for AccountUpdaterEnabled {
    const KEY: &'static str = "account_updater_enabled";
}

config! {
    superposition_key = ACCOUNT_UPDATER_CREDENTIAL_SOURCE,
    output = crate::core::account_updater::types::AccountUpdaterCredentialSource,
    default = crate::core::account_updater::types::AccountUpdaterCredentialSource::None,
    string_enum = true,
    requires = dimension_state::DimensionsGlobal,
    targeting_key = id_type::PaymentId
}

impl DatabaseBackedConfig for AccountUpdaterCredentialSource {
    const KEY: &'static str = "account_updater_credential_source";
}
