package com.suno.android.gating.statsig import android.app.Application import android.content.Context import com.statsig.androidsdk.DynamicConfig import com.statsig.androidsdk.ParameterStore import com.statsig.androidsdk.Statsig import com.statsig.androidsdk.StatsigOptions import com.statsig.androidsdk.StatsigUser import com.statsig.androidsdk.Tier import com.suno.android.common_core_utils.environment.EnvironmentConstantsProvider import com.suno.android.common_core_utils.extensions.xGetVersionName import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import javax.inject.Inject import javax.inject.Singleton interface StatsigFeatureDataSource { suspend fun updateUser( userId: String, email: String?, custom: Map?, customIds: Map?, ) suspend fun resetUser() fun checkGate( gate: FeatureGate, ): Boolean fun shutdownStatsig() suspend fun initializeStatsig( application: Application, ) fun setUserId( userId: String, ) fun statsigInitStatusFlow(): Flow fun fetchParamStore( storeName: ParameterStoreName, ): ParameterStore fun fetchParamStoreValue( parameterStoreAccessor: ParameterStoreAccessor, ): S fun fetchDynamicConfig( configName: DynamicConfigName, ): DynamicConfig fun fetchExperiment( experimentName: String, persistExperimentalValue: Boolean = true, ): DynamicConfig fun fetchExperimentParam( experiment: Experiment, persistExperimentalValue: Boolean = true, ): T } @Singleton internal class StatsigFeatureDataSourceImpl @Inject constructor( @ApplicationContext private val context: Context, private val statsig: Statsig, private val environmentConstantsProvider: EnvironmentConstantsProvider, ) : StatsigFeatureDataSource { private val _statsigStatusFlow = MutableSharedFlow() override fun statsigInitStatusFlow(): Flow = _statsigStatusFlow.asSharedFlow() override suspend fun initializeStatsig( application: Application, ) = coroutineScope { // need to fix this val environmentConstants = environmentConstantsProvider.environmentConstants val initDetails = statsig.initialize( application = application, sdkKey = environmentConstants.statsigClientKey, user = StatsigUser().also { it.appVersion = context.xGetVersionName() }, options = StatsigOptions().also { it.setTier( if (environmentConstants.statsigTier == "staging") { Tier.STAGING } else { Tier.PRODUCTION }, ) }, ) if (initDetails?.success == true) { _statsigStatusFlow.emit(true) } } override suspend fun updateUser( userId: String, email: String?, custom: Map?, customIds: Map?, ) { statsig.updateUser( StatsigUser(userID = userId).also { it.email = email it.appVersion = context.xGetVersionName() it.custom = custom it.customIDs = customIds }, ) } override suspend fun resetUser() { statsig.updateUser( StatsigUser().also { it.appVersion = context.xGetVersionName() }, ) } override fun fetchParamStore( storeName: ParameterStoreName, ): ParameterStore { val parameterStore: ParameterStore = statsig.getParameterStore( parameterStoreName = storeName.value, ) return parameterStore } override fun fetchParamStoreValue( parameterStoreAccessor: ParameterStoreAccessor, ): S { val parameterStore = fetchParamStore(parameterStoreAccessor.parameterStoreName) val value = when (parameterStoreAccessor.fallbackValue) { is FeatureValue.Boolean -> FeatureValue.Boolean( parameterStore.getBoolean( paramName = parameterStoreAccessor.parameterName, fallback = parameterStoreAccessor.fallbackValue.value, ), ) is FeatureValue.Double -> FeatureValue.Double( parameterStore.getDouble( paramName = parameterStoreAccessor.parameterName, fallback = parameterStoreAccessor.fallbackValue.value, ), ) is FeatureValue.String -> parameterStore.getString( paramName = parameterStoreAccessor.parameterName, fallback = parameterStoreAccessor.fallbackValue.value, )?.let(FeatureValue::String) ?: parameterStoreAccessor.fallbackValue } // the compiler doesn't acknowledge that each input type maps to the same output type // unfortunately the only way to handle this atm is to force cast @Suppress("UNCHECKED_CAST") return value as S } override fun fetchDynamicConfig( configName: DynamicConfigName, ): DynamicConfig { val dynamicConfig: DynamicConfig = statsig.getConfig(configName.serverName) return dynamicConfig } override fun fetchExperiment( experimentName: String, persistExperimentalValue: Boolean, ): DynamicConfig = statsig.getExperiment( experimentName = experimentName, keepDeviceValue = persistExperimentalValue, ) override fun fetchExperimentParam( experiment: Experiment, persistExperimentalValue: Boolean, ): T { val experimentConfig = this.fetchExperiment( experimentName = experiment.experimentName, persistExperimentalValue = persistExperimentalValue, ) @Suppress("UNCHECKED_CAST") return experimentConfig.getValue()[experiment.paramName] as? T? ?: experiment.fallbackValue } override fun setUserId( userId: String, ) { statsig.updateUserAsync( user = StatsigUser( userID = userId, ), ) } override fun checkGate( gate: FeatureGate, ): Boolean = statsig.checkGate( gateName = gate.value, ) override fun shutdownStatsig() { statsig.shutdown() } }