/// Created by Haoyi on 2022/8/24

import 'dart:collection';
import 'dart:core';
import 'dart:io';

import 'package:adjust_sdk/adjust_third_party_sharing.dart';
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
import 'package:firebase_remote_config/firebase_remote_config.dart';
import 'package:flutter/foundation.dart';
import 'package:guru_analytics_flutter/event_logger.dart';
import 'package:guru_analytics_flutter/event_logger_common.dart';
import 'package:guru_analytics_flutter/events_constants.dart';
import 'package:guru_analytics_flutter/guru/guru_event_logger.dart';
import 'package:guru_analytics_flutter/guru/guru_statistic.dart';
import 'package:guru_app/account/account_data_store.dart';
import 'package:guru_app/ads/ads_manager.dart';
import 'package:guru_app/ads/core/ads_config.dart';
import 'package:guru_app/aigc/bi/ai_bi.dart';
import 'package:guru_app/analytics/abtest/abtest_model.dart';
import 'package:guru_app/analytics/data/analytics_model.dart';
import 'package:guru_app/analytics/strategy/guru_analytics_strategy.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:guru_app/firebase/remoteconfig/remote_config_manager.dart';
import 'package:guru_app/guru_app.dart';
import 'package:guru_app/property/app_property.dart';
import 'package:guru_app/property/property_keys.dart';
import 'package:guru_app/property/runtime_property.dart';
import 'package:guru_app/property/settings/guru_settings.dart';
import 'package:guru_platform_data/guru_platform_data.dart';
import 'package:guru_utils/collection/collectionutils.dart';
import 'package:guru_utils/datetime/datetime_utils.dart';
import 'package:guru_utils/device/device_info.dart';
import 'package:guru_utils/device/device_utils.dart';
import 'package:guru_utils/analytics/analytics.dart';
import 'package:guru_utils/network/network_utils.dart';
import 'package:intl/intl.dart';
import 'package:guru_utils/extensions/extensions.dart';
import 'package:adjust_sdk/adjust_ad_revenue.dart';
import 'package:adjust_sdk/adjust_config.dart';
export 'package:adjust_sdk/adjust.dart';

part 'modules/ads_analytics.dart';

part 'modules/adjust_aware.dart';

class GuruAnalytics extends Analytics with AdjustAware {
  bool get release => !_mock && (_enabledAnalytics || kReleaseMode);

  String appInstanceId = "";

  static bool _mock = false;

  static bool _enabledAnalytics = true;

  static GuruAnalytics instance = GuruAnalytics._();

  /// Name of virtual currency type.
  static bool initialized = false;

  static final Map<String, String> facebookEventMapping = {};

  static String currentScreen = "";

  static final RegExp _consentPurposeRegExp = RegExp(r"^[01]+$");

  static String? mockCountryCode;

  static const errorEventCodes = {
    14, // 上报事件失败
    22, // 网络状态不可用
    101, // 调用api出错
    102, // api返回结果错误
    103, // 设置cacheControl出错
    104, // 删除过期事件出错
    105, // 从数据库取事件以及更改事件状态为正在上报出错
    106, // dns 错误
  };

  int latestFetchStatisticTs = 0;

  final BehaviorSubject<GuruStatistic> guruEventStatistic =
      BehaviorSubject.seeded(GuruStatistic.invalid);

  final BehaviorSubject<Map<String, String>> abTestExperimentVariant = BehaviorSubject.seeded({});

  Stream<GuruStatistic> get observableGuruEventStatistic => guruEventStatistic.stream;

  Stream<Map<String, String>> get observableABTestExperimentVariant =>
      abTestExperimentVariant.stream;

  final BehaviorSubject<UserIdentification> userIdentificationSubject =
      BehaviorSubject.seeded(UserIdentification());

  UserIdentification get userIdentification => userIdentificationSubject.value;

  AppEventCapabilities get currentAppEventCapabilities => EventLogger.getCapabilities();

  static void setMock() {
    _mock = true;
  }

  static void disableAnalytics() {
    _enabledAnalytics = false;
  }

  static void enableAnalytics() {
    _enabledAnalytics = true;
  }

  GuruAnalytics._();

  String? getProperty(String key) {
    return Analytics.userProperties[key];
  }

  Future prepare() async {
    if (GuruApp.instance.appSpec.localABTestExperiments.isNotEmpty) {
      await initLocalExperiments();
    }
    RemoteConfigManager.instance.observeConfig().listen((config) {
      Log.i(
          "GuruAnalytics observeConfig changed: ${config.lastFetchStatus} ${config.lastFetchTime}");
      if (config.lastFetchStatus == RemoteConfigFetchStatus.success) {
        refreshABProperties();
      }
    });
  }

  void init() async {
    Log.d(
        "AnalyticsUtil init### Platform.localeName :${Platform.localeName} ${Intl.getCurrentLocale()}");
    if (!_mock && !initialized) {
      final analyticsConfig = RemoteConfigManager.instance.getAnalyticsConfig();
      EventLogger.setCapabilities(analyticsConfig.toAppEventCapabilities());
      EventLogger.registerTransmitter(EventTransmitter({}, defaultHook: (name, parameters) {
        recordEvents(name, parameters);
        final fbEvent = facebookEventMapping[name];
        if (fbEvent == null) {
          return;
        }
        Log.d("transmit EVENT [$name] => [$fbEvent]");
        EventLogger.facebookLogEvent(name: fbEvent);
      }));
      EventLogger.setGuruPriorityGetter((name, parameters) =>
          GuruApp.instance.conversionEvents.contains(name)
              ? EventPriority.EMERGENCE
              : EventPriority.DEFAULT);
      String xDeviceInfo = '';
      try {
        final deviceId = await AppProperty.getInstance().getDeviceId();
        final deviceInfo = await DeviceUtils.buildDeviceInfo(deviceId: deviceId);
        xDeviceInfo = deviceInfo.toXDeviceInfo();
      } catch (error, stacktrace) {
        Log.e("init deviceInfo error: $error, $stacktrace");
      }

      await GuruAnalyticsStrategy.instance.load();
      EventLogger.initialize(
        appId: GuruApp.instance.appSpec.details.saasAppId,
        deviceInfo: xDeviceInfo,
        delayedInSeconds: analyticsConfig.delayedInSeconds,
        eventExpiredInDays: analyticsConfig.expiredInDays,
        callback: processAnalyticsCallback,
        debug: true,
      );
      _initEnvProperties();
      _logLocale();
      _logDeviceType();
      _logFirstOpen();
      Future.delayed(const Duration(seconds: 1), () {
        initAdjust();
        initFbEventMapping();
        refreshConsents();
        Log.d("register transmitter");
      });
      initialized = true;
      // if (Platform.isAndroid) {
      //   _logPeerApps();
      // }
    }
  }

  Future switchSession(String oldToken, String newToken) async {
    _initEnvProperties();
    _logLocale();
    _logDeviceType();
  }

  Future initLocalExperiments() async {
    final runningExperiments = await AppProperty.getInstance().loadRunningExperiments();
    final experiments = GuruApp.instance.appSpec.localABTestExperiments;
    final validRunningExperimentKeys =
        runningExperiments.keys.toSet().intersection(experiments.keys.toSet());
    for (var experiment in experiments.values) {
      // 如果在已经开始的实验中，但是不在当前的实验列表中，需要删除
      final needRemove = runningExperiments.containsKey(experiment.name) &&
          !validRunningExperimentKeys.contains(experiment.name);
      if (needRemove) {
        await removeExperiment(experiment.name);
      } else {
        await _applyExperiment(experiment);
      }
    }
  }

  Future<String> refreshConsents({AnalyticsConfig? analyticsConfig}) async {
    final config = analyticsConfig ?? RemoteConfigManager.instance.getAnalyticsConfig();
    final purposeConsents = await GuruPlatformData.getPurposeConsents();
    Log.i("refreshConsents: '$purposeConsents'");
    if (purposeConsents.isEmpty) {
      return "";
    }

    /// 如果他不是完全使用 1，0 组成的字符串
    if (!_consentPurposeRegExp.hasMatch(purposeConsents)) {
      Log.i("invalid consents $purposeConsents");
      return "";
    }

    /// 获取当前的 countryCode, 判断是否在 dma country的范围内
    if (config.dmaCountry.isNotEmpty) {
      final countryCode = getCountryCode();
      if (!config.dmaCountry.contains(countryCode)) {
        Log.i("invalid country $countryCode");
        return "";
      }
    }

    final length = min(purposeConsents.length, 32);
    int flags = 0;
    for (var i = 0; i < length; i++) {
      flags |= (((purposeConsents[i] == "1") ? 1 : 0) << i);
    }

    final consentsData = {
      ConsentFieldName.adStorage: config.googleDmaGranted(ConsentType.adStorage, flags),
      ConsentFieldName.analyticsStorage:
          config.googleDmaGranted(ConsentType.analyticsStorage, flags),
      ConsentFieldName.adPersonalization:
          config.googleDmaGranted(ConsentType.adPersonalization, flags),
      ConsentFieldName.adUserData: config.googleDmaGranted(ConsentType.adUserData, flags),
    };

    String _flag(String key) {
      return consentsData[key] == true ? "1" : "0";
    }

    Log.d("setConsents consentsData: $consentsData");

    try {
      final result = await EventLogger.guru.setConsents(consentsData);
      Log.d("setConsents result: $result");
    } catch (error, stacktrace) {
      Log.e("setConsents error! $error, $stacktrace");
    }

    if (enabledAdjust) {
      AdjustThirdPartySharing adjustThirdPartySharing = AdjustThirdPartySharing(null);
      adjustThirdPartySharing.addGranularOption("google_dma", "eea", "1");
      adjustThirdPartySharing.addGranularOption(
          "google_dma", "ad_personalization", _flag(ConsentFieldName.adPersonalization));
      adjustThirdPartySharing.addGranularOption(
          "google_dma", "ad_user_data", _flag(ConsentFieldName.adUserData));
      Adjust.trackThirdPartySharing(adjustThirdPartySharing);
      Log.d("setAdjust complete!");
    }

    final result =
        "${_flag(ConsentFieldName.adStorage)}${_flag(ConsentFieldName.analyticsStorage)}${_flag(ConsentFieldName.adPersonalization)}${_flag(ConsentFieldName.adUserData)}";
    final changed = await AppProperty.getInstance().refreshGoogleDma(result);
    if (changed || GuruSettings.instance.debugMode.get()) {
      logEventEx("dma_gg", parameters: {"purpose": purposeConsents, "result": result});
    }
    return result;
  }

  void processAnalyticsCallback(int code, String? errorInfo) {
    if (!errorEventCodes.contains(code)) {
      return;
    }
    final parameters = {
      "item_category": "error_event",
      "item_name": code.toString(),
      "country": AccountDataStore.instance.countryCode,
      "network": AdsManager.instance.connectivityStatus.toString(),
    };

    if (errorInfo != null) {
      parameters["err"] = errorInfo.length > 32 ? errorInfo.substring(0, 32) : errorInfo;
    }
    logFirebaseEvent("dev_audit", parameters);
    // Guru Analytics Event(GAE)
    Log.d("[GAE]($code)=>$errorInfo $parameters", tag: "Analytics");
  }

  void updateUserIdentification(
      {String? firebaseAppInstanceId, String? idfa, String? adId, String? gpsAdId}) {
    final latestUserIdentification = userIdentificationSubject.value;
    bool changed = false;
    String? changedFirebaseInstanceId = latestUserIdentification.firebaseAppInstanceId;
    String? changedIdfa = latestUserIdentification.idfa;
    String? changedAdId = latestUserIdentification.adId;
    String? changedGpsAdId = latestUserIdentification.gpsAdId;

    if (firebaseAppInstanceId != null &&
        latestUserIdentification.firebaseAppInstanceId != firebaseAppInstanceId) {
      changedFirebaseInstanceId = firebaseAppInstanceId;
      changed = true;
    }
    if (idfa != null && latestUserIdentification.idfa != idfa) {
      changedIdfa = idfa;
      changed = true;
    }
    if (adId != null && latestUserIdentification.adId != adId) {
      changedAdId = adId;
      changed = true;
    }
    if (gpsAdId != null && latestUserIdentification.gpsAdId != gpsAdId) {
      changedGpsAdId = gpsAdId;
      changed = true;
    }
    if (changed) {
      final newUserIdentification = UserIdentification(
          firebaseAppInstanceId: changedFirebaseInstanceId ?? '',
          idfa: changedIdfa,
          adId: changedAdId,
          gpsAdId: changedGpsAdId);
      userIdentificationSubject.add(newUserIdentification);
      Log.d("updateUserIdentification: $newUserIdentification");
    }
  }

  void parseFbEventMapping() {
    final fbEventMappingString =
        RemoteConfigManager.instance.getString(RemoteConfigReservedConstants.fbEventMapping);
    Log.d("parseFbEventMapping first: $fbEventMappingString");
    if (fbEventMappingString == null) {
      return;
    }
    Map<String, String> result = {};
    final eventEntries = fbEventMappingString.split(";");
    for (String eventEntryString in eventEntries) {
      final eventEntry = eventEntryString.split(":");
      if (eventEntry.length == 2) {
        result[eventEntry.first] = eventEntry.last;
      }
    }
    facebookEventMapping.clear();
    facebookEventMapping.addAll(result);
    Log.d("parseFbEventMapping: $result");
  }

  void initFbEventMapping() {
    RemoteConfigManager.instance.observeConfig().listen((config) {
      parseFbEventMapping();
    });
    parseFbEventMapping();
  }

  @override
  Future<String> getAppInstanceId() async {
    if (appInstanceId.isNotEmpty != true) {
      appInstanceId = await EventLogger.getAppInstanceId();
      RuntimeProperty.instance.setString(PropertyKeys.appInstanceId, appInstanceId);
    }
    return appInstanceId;
  }

  void _initEnvProperties() async {
    final bundle = await AppProperty.getInstance().loadValuesByTag(PropertyTags.analytics);

    final userId = bundle.getString(PropertyKeys.analyticsUserId);
    if (userId != null) {
      setUserId(userId);
    }

    final adjustId = bundle.getString(PropertyKeys.analyticsAdjustId);
    if (adjustId != null) {
      setAdjustId(adjustId);
    }

    final adId = bundle.getString(PropertyKeys.analyticsAdId);
    if (adId != null) {
      setAdId(adId);
    }
    refreshEventStatistic();

    String? firebaseId = await getAppInstanceId();
    if (firebaseId.isEmpty) {
      firebaseId = bundle.getString(PropertyKeys.analyticsFirebaseId);
    }
    if (firebaseId?.isNotEmpty == true) {
      setFirebaseId(firebaseId!);
    }
    refreshABProperties();
  }

  void refreshABProperties() {
    final abProperties = RemoteConfigManager.instance.getABProperties();

    final PropertyBundle propertyBundle = PropertyBundle();
    if (abProperties.isNotEmpty) {
      for (var entry in abProperties.entries) {
        setGuruUserProperty(entry.key, entry.value);
        propertyBundle.setString(PropertyKeys.buildABTestProperty(entry.key), entry.value);
        Log.d("setGuruUserProperty: ${entry.key} = ${entry.value}");
      }
    }
    AppProperty.getInstance().setProperties(propertyBundle);
  }

  void _logFirstOpen() async {
    int firstInstallTime =
        RuntimeProperty.instance.getInt(PropertyKeys.firstInstallTime, defValue: -1);
    if (firstInstallTime == -1) {
      firstInstallTime = await AppProperty.getInstance()
          .getOrCreateInt(PropertyKeys.firstInstallTime, DateTimeUtils.currentTimeInMillis());
    }
    setUserProperty("first_open_time", firstInstallTime.toString());
  }

  String getCountryCode() {
    if (mockCountryCode != null) {
      return mockCountryCode!;
    }
    final currentLocale = Platform.localeName.split('_');
    if (currentLocale.length > 1) {
      return currentLocale.last.toLowerCase();
    }
    return "";
  }

  void _logLocale() {
    if (Platform.localeName.isNotEmpty == true) {
      String lanCode = "";
      String countryCode = "";
      final currentLocale = Platform.localeName.split('_');
      if (currentLocale.isNotEmpty) {
        setUserProperty("lang_code", currentLocale[0].toLowerCase());
        lanCode = currentLocale[0].toLowerCase();
      }

      if (currentLocale.length > 1) {
        setUserProperty("country_code", currentLocale.last.toLowerCase());
        countryCode = currentLocale.last.toLowerCase();
      }
      Log.d("## locale: [$currentLocale]");

      if (lanCode.isNotEmpty && countryCode.isNotEmpty) {
        // CountryCodes.init(Locale(lanCode, countryCode));
      } else {
        // CountryCodes.init();
      }
    } else {
      // CountryCodes.init();
    }
  }

  void _logDeviceType() async {
    setUserProperty("device_type", DeviceUtils.isTablet() ? "tablet" : "phone");

    final deviceId = await AppProperty.getInstance().getDeviceId();
    setDeviceId(deviceId);
  }

  @override
  Future setUserProperty(String key, String value) async {
    recordEvents("setUserProperty", {key: value});
    recordProperty(key, value);
    if (release) {
      await EventLogger.setUserProperty(key, value);
    }
  }

  static String buildVariantKey(String experimentName) {
    return "ab_$experimentName";
  }

  String getExperimentVariant(String experimentName) {
    return abTestExperimentVariant.value[experimentName] ?? "BASELINE";
  }

  Future<bool> setLocalABTest(ABTestExperiment experiment, {PropertyBundle? bundle}) async {
    Log.d("setLocalABTest: $experiment");
    String experimentName = experiment.name;
    final exp = await AppProperty.getInstance().getExperiment(experimentName, bundle: bundle);
    if (exp != null) {
      Log.w("Experiment already exists!");
      experiment = exp;
    }

    return await _applyExperiment(experiment);
  }

  Future removeExperiment(String experimentName) async {
    await AppProperty.getInstance().removeExperiment(experimentName);
    final data = Map<String, String>.of(abTestExperimentVariant.value);
    data.remove(experimentName);
    abTestExperimentVariant.addIfChanged(data);
  }

  Future<bool> _applyExperiment(ABTestExperiment experiment) async {
    final experimentName = experiment.name;
    if (experiment.isExpired()) {
      Log.w("Experiment($experimentName) is expired");
      await removeExperiment(experimentName);
      return false;
    }

    if (!experiment.isMatchAudience()) {
      Log.i("NOT match audience! $experiment! INTO BASELINE");
      return false;
    }

    String variantName = await AppProperty.getInstance().getExperimentVariant(experimentName);
    if (variantName.isEmpty) {
      variantName = await AppProperty.getInstance().setExperiment(experiment);
    }

    await setGuruUserProperty(buildVariantKey(experimentName), variantName);
    Log.i("==> Setup Local Experiment($experimentName) variantName: $variantName");

    final data = Map<String, String>.of(abTestExperimentVariant.value);
    data[experimentName] = variantName;
    abTestExperimentVariant.addIfChanged(data);
    return true;
  }

  void setDeviceId(String deviceId) {
    Log.d("setDeviceId: $deviceId");
    recordEvents("setDeviceId", {"userId": deviceId});
    recordProperty("deviceId", deviceId);
    if (deviceId.isNotEmpty) {
      AppProperty.getInstance().setAnalyticsDeviceId(deviceId);
      if (release) {
        EventLogger.setUserProperty("device_id", deviceId);
        EventLogger.setDeviceId(deviceId);
      }
    }
  }

  Future setUserId(String userId) async {
    Log.d("setUserId: $userId");
    recordEvents("setUserId", {"userId": userId});
    recordProperty("userId", userId);
    if (userId.isNotEmpty) {
      await AppProperty.getInstance().setUserId(userId);
      if (release) {
        EventLogger.setUserId(userId);
        FirebaseCrashlytics.instance.setUserIdentifier(userId);
      }
    }
  }

  void setAdjustId(String adjustId) {
    Log.d("setAdjustId: $adjustId");
    recordEvents("setAdjustId", {"adjustId": adjustId});
    recordProperty("adjustId", adjustId);
    if (adjustId.isNotEmpty) {
      AppProperty.getInstance().setAdjustId(adjustId);
      updateUserIdentification(adId: adjustId);
      if (release) {
        EventLogger.setAdjustId(adjustId);
      }
    }
  }

  void setFirebaseId(String firebaseId) {
    Log.d("setFirebaseId: $firebaseId");
    recordEvents("setFirebaseId", {"firebaseId": firebaseId});
    recordProperty("firebaseId", firebaseId);
    if (firebaseId.isNotEmpty) {
      AppProperty.getInstance().setFirebaseId(firebaseId);
      updateUserIdentification(firebaseAppInstanceId: firebaseId);
      if (release) {
        EventLogger.setFirebaseId(firebaseId);
      }
    }
  }

  void setAdId(String adId) {
    Log.d("setAdId: $adId");
    recordEvents("setAdId", {"adId": adId});
    recordProperty("adId", adId);
    AppProperty.getInstance().setAdId(adId);
    updateUserIdentification(gpsAdId: adId);
    if (release) {
      EventLogger.setAdId(adId);
    }
  }

  void setIdfa(String idfa) {
    Log.d("setIdfa: $idfa");
    recordEvents("setIdfa", {"idfa": idfa});
    recordProperty("idfa", idfa);
    AppProperty.getInstance().setIdfa(idfa);
    updateUserIdentification(idfa: idfa);
    if (release) {
      // 自打点中。idfa变是adId
      EventLogger.setAdId(idfa);
    }
  }

  void logScreen(String screenName) {
    recordEvents("logScreen", {"name": screenName});
    recordProperty("screen", screenName);
    if (release) {
      FirebaseCrashlytics.instance.log(screenName);
      EventLogger.logScreen(screenName);
    }
  }

  @override
  void setScreen(String screenName) {
    if (currentScreen != screenName) {
      currentScreen = screenName;
      logScreen(screenName);
    }
  }

  @override
  void logFirebase(String msg) async {
    if (release) {
      try {
        FirebaseCrashlytics.instance.log(msg);
        if (EventLogger.dumpLog) {
          Log.d("[Firebase]: $msg");
        }
      } catch (error, stacktrace) {}
    } else {
      Log.d("[Firebase]: $msg");
    }
  }

  AppEventOptions? getOptions(String eventName) {
    return GuruAnalyticsStrategy.instance.getStrategyRule(eventName)?.getAppEventOptions();
  }

  @override
  void logEvent(String eventName, Map<String, dynamic> parameters, {AppEventOptions? options}) {
    refreshEventStatistic();
    // Firebase Facebook log event
    if (release) {
      EventLogger.logEvent(eventName, parameters, options: options ?? getOptions(eventName));
      _logAdjustEvent(eventName, parameters);
    } else {
      Log.d("logEvent: $eventName $parameters");
      EventLogger.transmit(eventName, parameters);
    }
  }

  @override
  void logEventEx(String eventName,
      {String? itemCategory,
      String? itemName,
      double? value,
      Map<String, dynamic> parameters = const {},
      AppEventOptions? options}) async {
    Map<String, dynamic> map = Map<String, dynamic>.from(parameters);
    if (itemCategory != null) {
      map["item_category"] = itemCategory;
    }

    if (itemName != null) {
      map["item_name"] = itemName;
    }

    if (value != null) {
      map["value"] = value;
    }

    logEvent(eventName, map, options: options);
  }

  Future refreshEventStatistic({bool force = false}) async {
    if (!GuruApp.instance.appSpec.deployment.enableAnalyticsStatistic) {
      return;
    }
    final now = DateTimeUtils.currentTimeInMillis();
    if (force || (now - latestFetchStatisticTs > DateTimeUtils.minuteInMillis * 2)) {
      EventLogger.getStatistic().then((statistic) {
        Log.d("Event Statistic:$statistic");
        if (statistic != GuruStatistic.invalid && guruEventStatistic.addIfChanged(statistic)) {
          setUserProperty("lgd", statistic.logged.toString());
          setUserProperty("uld", statistic.uploaded.toString());
        }
      });
      latestFetchStatisticTs = now;
    }
  }

  Future<String> zipGuruLogs() {
    return EventLogger.zipGuruLogs();
  }

  Map<String, dynamic> filterOutNulls(Map<String, dynamic> parameters) {
    final Map<String, dynamic> filtered = <String, dynamic>{};
    parameters.forEach((String key, dynamic value) {
      if (value != null) {
        filtered[key] = value;
      }
    });
    return filtered;
  }

  @override
  void logException(dynamic exception, {StackTrace? stacktrace}) async {
    if (release) {
      Log.d("exception! $exception");
      FirebaseCrashlytics.instance.log(exception.toString());
      FirebaseCrashlytics.instance
          .recordError(exception, stacktrace, printDetails: EventLogger.dumpLog);
    } else {
      Log.w("Occur Error! $exception $stacktrace", stackTrace: stacktrace);
    }
  }

  void logPurchase(double amount,
      {String currency = "",
      String contentId = "",
      String adPlatform = "",
      Map<String, dynamic> parameters = const <String, dynamic>{}}) async {
    Log.i("logPurchase:$amount, $currency, $contentId, $adPlatform, $parameters");
    try {
      await EventLogger.logFbPurchase(amount,
          currency: currency,
          contentId: contentId,
          adPlatform: adPlatform,
          additionParameters: parameters);
    } catch (error, stacktrace) {
      Log.w("logFbPurchase error$error, $stacktrace");
    }
  }

  void logEventShare({String? itemCategory, String? itemName}) {
    logEvent("share", {
      "item_category": itemCategory,
      "item_name": itemName,
      "content_type": itemCategory,
      "item_id": itemName
    });
  }

  void logSpendCredits(String contentId, String contentType, int price,
      {required String virtualCurrencyName, required int balance, String scene = ''}) {
    final levelName = GuruApp.instance.protocol.getLevelName();
    if (release) {
      EventLogger.logSpendCredits(contentId, contentType, price,
          virtualCurrencyName: virtualCurrencyName, balance: balance, scene: scene);
    } else {
      final parameters = <String, dynamic>{
        "item_name": contentId,
        "item_category": contentType,
        "virtual_currency_name": virtualCurrencyName,
        "value": price,
        "balance": balance,
        "scene": scene,
        "level_name": levelName
      };
      Log.d("logEvent: spend_virtual_currency $parameters");
      EventLogger.transmit("spend_virtual_currency", parameters);
    }
    AiBi.instance.spendVirtualCurrency(balance, price.toDouble(), contentType);
  }

  Future<void> logEarnVirtualCurrency(
      {required String virtualCurrencyName,
      required String method,
      required int balance,
      required int value,
      String? specific,
      String? scene}) async {
    final levelName = GuruApp.instance.protocol.getLevelName();
    logEvent(
        "earn_virtual_currency",
        filterOutNulls(<String, dynamic>{
          "virtual_currency_name": virtualCurrencyName,
          "item_category": method,
          "item_name": specific,
          "value": value,
          "balance": balance,
          "level_name": levelName,
          "scene": scene
        }));
    AiBi.instance.earnVirtualCurrency(balance, value.toDouble(), method);
  }

  String? peekUserProperty(String key) {
    return Analytics.userProperties[key];
  }

  Future<void> setGuruUserProperty(String key, String value) async {
    recordProperty(key, value);
    return await EventLogger.setGuruUserProperty(key, value);
  }

  Future<void> logGuruEvent(String eventName, Map<String, dynamic> parameters) async {
    EventLogger.guruLogEvent(name: eventName, parameters: parameters);
  }

  Future<void> logFirebaseEvent(String eventName, Map<String, dynamic> parameters) async {
    if (release) {
      EventLogger.firebaseLogEvent(name: eventName, parameters: parameters);
    } else {
      Log.d("logEvent: $eventName $parameters");
    }
    EventLogger.transmit(eventName, parameters);
  }
}
