package com.appsflyer.reactnative;


import android.app.Application;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import android.util.Log;

import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import com.appsflyer.attribution.AppsFlyerRequestListener;
import com.appsflyer.deeplink.DeepLinkListener;
import com.appsflyer.deeplink.DeepLinkResult;
import com.appsflyer.*;
import com.appsflyer.AFInAppEventType;
import com.appsflyer.AppsFlyerConversionListener;
import com.appsflyer.AppsFlyerLib;
import com.appsflyer.AppsFlyerProperties.EmailsCryptType;
import com.appsflyer.internal.platform_extension.Plugin;
import com.appsflyer.internal.platform_extension.PluginInfo;
import com.appsflyer.share.CrossPromotionHelper;
import com.appsflyer.share.LinkGenerator;
import com.appsflyer.share.ShareInviteHelper;
import com.appsflyer.AFPurchaseType;
import com.appsflyer.AFPurchaseDetails;
import com.appsflyer.AppsFlyerInAppPurchaseValidationCallback;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.lang.ref.WeakReference;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Iterator;
import java.util.Locale;

import static com.appsflyer.reactnative.RNAppsFlyerConstants.*;
import static com.appsflyer.reactnative.RNAppsFlyerConstants.afOnDeepLinking;

public class RNAppsFlyerModule extends ReactContextBaseJavaModule {

    private ReactApplicationContext reactContext;
    private Application application;
    private String personalDevKey;

    private static class CallbackGuard {
        private static final String TAG = "AppsFlyer_" + RNAppsFlyerConstants.PLUGIN_VERSION;
        private final AtomicBoolean invoked = new AtomicBoolean(false);
        private final WeakReference<Callback> callbackRef;

        public CallbackGuard(Callback callback) {
            this.callbackRef = new WeakReference<>(callback);
        }

        public void invoke(Object... args) {
            if (invoked.compareAndSet(false, true)) {
                // Store in local strong reference to prevent race condition with GC
                final Callback callback = callbackRef.get();
                if (callback != null) {
                    try {
                        callback.invoke(args);
                    } catch (RuntimeException e) {
                        // Log error when bridge is destroyed or context is dead
                        // Don't rethrow - callback failure shouldn't break the SDK
                        Log.e(TAG, "Failed to invoke callback - bridge may be destroyed", e);
                    } catch (Exception e) {
                        // Catch any other unexpected exceptions
                        Log.e(TAG, "Unexpected error invoking callback", e);
                    }
                }
            }
        }
    }

    public RNAppsFlyerModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
        this.application = (Application) reactContext.getApplicationContext();
        this.personalDevKey = "";
    }

    @Override
    public String getName() {
        return "RNAppsFlyer";
    }

    @Override
    public Map<String, Object> getConstants() {
        HashMap<String, Object> constants = new HashMap<String, Object>();
        constants.put("ACHIEVEMENT_UNLOCKED", AFInAppEventType.ACHIEVEMENT_UNLOCKED);
        constants.put("ADD_PAYMENT_INFO", AFInAppEventType.ADD_PAYMENT_INFO);
        constants.put("ADD_TO_CART", AFInAppEventType.ADD_TO_CART);
        constants.put("ADD_TO_WISH_LIST", AFInAppEventType.ADD_TO_WISH_LIST);
        constants.put("COMPLETE_REGISTRATION", AFInAppEventType.COMPLETE_REGISTRATION);
        constants.put("CONTENT_VIEW", AFInAppEventType.CONTENT_VIEW);
        constants.put("INITIATED_CHECKOUT", AFInAppEventType.INITIATED_CHECKOUT);
        constants.put("INVITE", AFInAppEventType.INVITE);
        constants.put("LEVEL_ACHIEVED", AFInAppEventType.LEVEL_ACHIEVED);
        constants.put("LOCATION_CHANGED", AFInAppEventType.LOCATION_CHANGED);
        constants.put("LOCATION_COORDINATES", AFInAppEventType.LOCATION_COORDINATES);
        constants.put("LOGIN", AFInAppEventType.LOGIN);
        constants.put("OPENED_FROM_PUSH_NOTIFICATION", AFInAppEventType.OPENED_FROM_PUSH_NOTIFICATION);
        constants.put("ORDER_ID", AFInAppEventType.ORDER_ID);
        constants.put("PURCHASE", AFInAppEventType.PURCHASE);
        constants.put("RATE", AFInAppEventType.RATE);
        constants.put("RE_ENGAGE", AFInAppEventType.RE_ENGAGE);
        constants.put("SEARCH", AFInAppEventType.SEARCH);
        constants.put("SHARE", AFInAppEventType.SHARE);
        constants.put("SPENT_CREDIT", AFInAppEventType.SPENT_CREDIT);
        constants.put("TRAVEL_BOOKING", AFInAppEventType.TRAVEL_BOOKING);
        constants.put("TUTORIAL_COMPLETION", AFInAppEventType.TUTORIAL_COMPLETION);
        constants.put("UPDATE", AFInAppEventType.UPDATE);
        return constants;
    }

    @ReactMethod
    public void initSdkWithCallBack(ReadableMap _options, Callback successCallback, Callback errorCallback) {
        CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);
        try {
            final String errorReason = callSdkInternal(_options);
            if (errorReason == null) {
                //TODO: callback should come from SDK
                guardedSuccessCallback.invoke(SUCCESS);
            } else {
                guardedErrorCallback.invoke(new Exception(errorReason).getMessage());
            }
        } catch (Exception e) {
            guardedErrorCallback.invoke(e.getMessage());
        }
    }

    @ReactMethod
    public void initSdkWithPromise(ReadableMap _options, Promise promise) {
        try {
            final String errorReason = callSdkInternal(_options);
            if (errorReason == null) {
                //TODO: callback should come from SDK
                promise.resolve(SUCCESS);
            } else {
                promise.reject(errorReason, new Exception(errorReason).getMessage());
            }
        } catch (Exception e) {
            promise.reject(UNKNOWN_ERROR, e);
        }
    }


    private String callSdkInternal(ReadableMap _options) {

        String devKey;
        boolean isDebug;
        boolean isConversionData;
        boolean isDeepLinking;
        boolean isManualStartMode;

        AppsFlyerLib instance = AppsFlyerLib.getInstance();
        JSONObject options = RNUtil.readableMapToJson(_options);
        devKey = options.optString(afDevKey, "");
        if (devKey.trim().equals("")) {
            return NO_DEVKEY_FOUND;
        }
        this.personalDevKey = devKey;

        isDebug = options.optBoolean(afIsDebug, false);
        instance.setDebugLog(isDebug);

        isConversionData = options.optBoolean(afConversionData, true);
        if (isDebug == true) {
            Log.d("AppsFlyer", "Starting SDK");
        }
        isDeepLinking = options.optBoolean(afDeepLink, false);
        isManualStartMode = options.optBoolean("manualStart", false);

        boolean isExpoApp = this.isExpoApp();
        PluginInfo pluginInfo = new PluginInfo(isExpoApp ? Plugin.EXPO : Plugin.REACT_NATIVE, PLUGIN_VERSION);
        instance.setPluginInfo(pluginInfo);

        instance.init(devKey, (isConversionData == true) ? registerConversionListener() : null, application.getApplicationContext());
        if (isDeepLinking) {
            instance.subscribeForDeepLink(registerDeepLinkListener());
        }

        if (!isManualStartMode) {
            startSdk();
        }
        return null;
    }

    /**
     * We try to load this class which is associated with Expo. If the class is loaded we return true
     * so we handle this app as an Expo app. If not its just a React Native app.
     * @return true if its an Expo app, false is its React Native app
     */
    private boolean isExpoApp() {
        try {
            Class.forName("expo.modules.devmenu.react.DevMenuAwareReactActivity");
            return true;
        } catch (ClassNotFoundException e) {
            return false;
        } catch (Exception e) {
            return false;
        }
    }

    private DeepLinkListener registerDeepLinkListener() {
        return new DeepLinkListener() {
            @Override
            public void onDeepLinking(@NonNull DeepLinkResult deepLinkResult) {
                JSONObject deepLinkObj = new JSONObject();
                DeepLinkResult.Error dlError = deepLinkResult.getError();
                try {
                    deepLinkObj.put("deepLinkStatus", deepLinkResult.getStatus());
                    deepLinkObj.put("status", afSuccess);
                    deepLinkObj.put("type", afOnDeepLinking);

                    if (dlError != null && deepLinkResult.getStatus() == DeepLinkResult.Status.ERROR) {
                        deepLinkObj.put("status", afFailure);
                        deepLinkObj.put("data", dlError.toString());
                        deepLinkObj.put("isDeferred", false);
                    } else if (deepLinkResult.getStatus() == DeepLinkResult.Status.FOUND) {
                        deepLinkObj.put("data", deepLinkResult.getDeepLink().getClickEvent());
                        deepLinkObj.put("isDeferred", deepLinkResult.getDeepLink().isDeferred());
                    } else {
                        deepLinkObj.put("data", "deep link not found");
                        deepLinkObj.put("isDeferred", false);
                    }

                } catch (JSONException e) {
                    e.printStackTrace();
                }
                try {
                    sendEvent(reactContext, afOnDeepLinking, deepLinkObj.toString());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        };
    }

    private AppsFlyerConversionListener registerConversionListener() {
        return new AppsFlyerConversionListener() {

            @Override
            public void onAppOpenAttribution(Map<String, String> attributionData) {
                handleSuccess(afOnAppOpenAttribution, null, attributionData);
            }

            @Override
            public void onAttributionFailure(String errorMessage) {
                handleError(afOnAttributionFailure, errorMessage);
            }

            @Override
            public void onConversionDataSuccess(Map<String, Object> conversionData) {
                handleSuccess(afOnInstallConversionDataLoaded, conversionData, null);
            }

            @Override
            public void onConversionDataFail(String errorMessage) {
                handleError(afOnInstallConversionFailure, errorMessage);
            }
        };
    }

    private void handleSuccess(String eventType, Map<String, Object> conversionData, Map<String, String> attributionData) {
        JSONObject obj = new JSONObject();

        try {
            JSONObject data = new JSONObject(conversionData == null ? attributionData : conversionData);
            obj.put("status", afSuccess);
            obj.put("type", eventType);
            obj.put("data", data);
            if (eventType.equals(afOnInstallConversionDataLoaded)) {
                sendEvent(reactContext, afOnInstallConversionDataLoaded, obj.toString());
            } else if (eventType.equals(afOnAppOpenAttribution)) {
                sendEvent(reactContext, afOnAppOpenAttribution, obj.toString());
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private void handleError(String eventType, String errorMessage) {
        JSONObject obj = new JSONObject();

        try {
            obj.put("status", afFailure);
            obj.put("type", eventType);
            obj.put("data", errorMessage);
            sendEvent(reactContext, afOnInstallConversionDataLoaded, obj.toString());
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private void sendEvent(ReactContext reactContext,
                           String eventName,
                           Object params) {
        reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

    @ReactMethod
    public void startSdk() {
        Activity currentActivity = getCurrentActivity();
        if (currentActivity != null) {
            // register for lifecycle with Activity (automatically fetching deeplink from Activity if present)
            AppsFlyerLib.getInstance().start(currentActivity, this.personalDevKey);
        } else {
            // register for lifecycle with Application (cannot fetch deeplink without access to the Activity,
            // also sending first session manually)
            AppsFlyerLib.getInstance().logEvent(this.application, null, null);
            AppsFlyerLib.getInstance().start(this.application, this.personalDevKey);
        }
    }

    @ReactMethod
    public void logEvent(
            final String eventName, ReadableMap eventData,
            final Callback successCallback,
            final Callback errorCallback) {
        final CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        final CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);
        try {
            if (eventName.trim().equals("")) {
                guardedErrorCallback.invoke(NO_EVENT_NAME_FOUND);
                return;
            }
            Map<String, Object> data = RNUtil.toMap(eventData);
            if (data == null) { // in case of no values
                data = new HashMap<>();
            }
            Activity currentActivity = getCurrentActivity();
            if (currentActivity != null) {
                AppsFlyerLib.getInstance().logEvent(getCurrentActivity(), eventName, data, new AppsFlyerRequestListener() {
                    @Override
                    public void onSuccess() {
                        guardedSuccessCallback.invoke(SUCCESS);
                    }

                    @Override
                    public void onError(int i, @NonNull String s) {
                        guardedErrorCallback.invoke(s);
                    }
                });
            }
        } catch (Exception e) {
            guardedErrorCallback.invoke(e.getMessage());
            return;
        }
    }

    @ReactMethod
    public void logEventWithPromise(
            final String eventName, ReadableMap eventData, final Promise promise) {
        try {
            if (eventName.trim().equals("")) {
                promise.reject(NO_EVENT_NAME_FOUND, new Exception(NO_EVENT_NAME_FOUND).getMessage());
                return;
            }
            Map<String, Object> data = RNUtil.toMap(eventData);
            if (data == null) { // in case of no values
                data = new HashMap<>();
            }
            Activity currentActivity = getCurrentActivity();
            if (currentActivity != null) {
                AppsFlyerLib.getInstance().logEvent(getCurrentActivity(), eventName, data, new AppsFlyerRequestListener() {
                    @Override
                    public void onSuccess() {
                        promise.resolve(SUCCESS);
                    }

                    @Override
                    public void onError(int i, @NonNull String s) {
                        promise.reject(s);
                    }
                });
            }
        } catch (Exception e) {
            promise.reject(UNKNOWN_ERROR, e);
            return;
        }
    }

@ReactMethod
    public void logAdRevenue(ReadableMap adRevenueDictionary) {
        if (adRevenueDictionary == null || !adRevenueDictionary.keySetIterator().hasNextKey()) {
            Log.d("AppsFlyer", "adRevenueData is missing, the data is mandatory to use this API.");
            return;
        }

        String monetizationNetwork = adRevenueDictionary.getString(MONETIZATION_NETWORK);
        if (monetizationNetwork == null) {
            Log.d("AppsFlyer", "monetizationNetwork is missing");
            return;
        }

        String currencyIso4217Code = adRevenueDictionary.getString(CURRENCY_ISO4217_CODE);
        if (currencyIso4217Code == null) {
            Log.d("AppsFlyer", "currencyIso4217Code is missing");
            return;
        }

        if (!adRevenueDictionary.hasKey(AF_REVENUE) || adRevenueDictionary.getType(AF_REVENUE) != ReadableType.Number) {
            Log.d("AppsFlyer", "revenue is missing or not a number");
            return;
        }
        double revenue = adRevenueDictionary.getDouble(AF_REVENUE);

        ReadableMap additionalParameters = null;
        if (adRevenueDictionary.hasKey(AF_ADDITIONAL_PARAMETERS) && adRevenueDictionary.getType(AF_ADDITIONAL_PARAMETERS) == ReadableType.Map) {
            additionalParameters = adRevenueDictionary.getMap(AF_ADDITIONAL_PARAMETERS);
        }

        String mediationNetworkValue = adRevenueDictionary.getString(AF_MEDIATION_NETWORK);
        if (mediationNetworkValue == null || mediationNetworkValue.isEmpty()) {
            Log.d("AppsFlyer", "mediationNetwork is missing");
            return;
        }

        MediationNetwork mediationNetwork;
        try {
            mediationNetwork = MediationNetwork.valueOf(mediationNetworkValue.toUpperCase(Locale.ENGLISH));
        } catch (IllegalArgumentException e) {
            Log.d("AppsFlyer", "Invalid mediation network: " + mediationNetworkValue);
            return;
        }
        
        if (mediationNetwork == null) {
            Log.d("AppsFlyer", "Invalid mediation network");
            return;
        }

        AFAdRevenueData adRevenueData = new AFAdRevenueData(
                monetizationNetwork,
                mediationNetwork,
                currencyIso4217Code,
                revenue
        );

        // Log the ad revenue to the AppsFlyer SDK
        AppsFlyerLib.getInstance().logAdRevenue(adRevenueData, RNUtil.toMap(additionalParameters));
    }

    @ReactMethod
    public void getAppsFlyerUID(Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        String appId = AppsFlyerLib.getInstance().getAppsFlyerUID(getReactApplicationContext());
        guardedCallback.invoke(null, appId);
    }

    @ReactMethod
    public void updateServerUninstallToken(final String token, Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        AppsFlyerLib.getInstance().updateServerUninstallToken(getReactApplicationContext(), token);
        guardedCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void setCustomerUserId(final String userId, Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        AppsFlyerLib.getInstance().setCustomerUserId(userId);
        guardedCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void setCollectIMEI(boolean isCollect, Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        AppsFlyerLib.getInstance().setCollectIMEI(isCollect);
        guardedCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void setCollectAndroidID(boolean isCollect, Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        AppsFlyerLib.getInstance().setCollectAndroidID(isCollect);
        guardedCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void stop(boolean isStopped, Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        AppsFlyerLib.getInstance().stop(isStopped, getReactApplicationContext());
        guardedCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void setAdditionalData(ReadableMap additionalData, Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        Map<String, Object> data = null;
        try {
            data = RNUtil.toMap(additionalData);
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }

        if (data == null) { // in case of no values
            data = new HashMap<>();
        }

        HashMap<String, Object> copyData = new HashMap<>(data);
        AppsFlyerLib.getInstance().setAdditionalData(copyData);
        guardedCallback.invoke(SUCCESS);
    }


    @ReactMethod
    public void setUserEmails(ReadableMap _options,
                              Callback successCallback,
                              Callback errorCallback) {
        CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);

        JSONObject options = RNUtil.readableMapToJson(_options);

        int emailsCryptType = options.optInt(afEmailsCryptType, 0);
        JSONArray emailsJSON = options.optJSONArray(afEmails);

        if (emailsJSON.length() == 0) {
            guardedErrorCallback.invoke(new Exception(EMPTY_OR_CORRUPTED_LIST).getMessage());
            return;
        }

        EmailsCryptType type = EmailsCryptType.NONE; // default type

        for (EmailsCryptType _type : EmailsCryptType.values()) {
            if (_type.getValue() == emailsCryptType) {
                type = _type;
                break;
            }
        }

        String[] emailsList = new String[emailsJSON.length()];
        try {
            for (int i = 0; i < emailsJSON.length(); i++) {
                emailsList[i] = emailsJSON.getString(i);
            }
        } catch (JSONException e) {
            e.printStackTrace();
            guardedErrorCallback.invoke(new Exception(EMPTY_OR_CORRUPTED_LIST).getMessage());
            return;
        }

        AppsFlyerLib.getInstance().setUserEmails(type, emailsList);
        guardedSuccessCallback.invoke(SUCCESS);
    }


    @ReactMethod
    public void setAppInviteOneLinkID(final String oneLinkID, Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        AppsFlyerLib.getInstance().setAppInviteOneLink(oneLinkID);
        guardedCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void setCurrencyCode(final String currencyCode, Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        AppsFlyerLib.getInstance().setCurrencyCode(currencyCode);
        guardedCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void generateInviteLink(ReadableMap args, final Callback successCallback, final Callback errorCallback) {
        final CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        final CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);

        String channel = null;
        String campaign = null;
        String referrerName = null;
        String referrerImageUrl = null;
        String customerID = null;
        String baseDeepLink = null;
        String brandDomain = null;

        LinkGenerator linkGenerator = ShareInviteHelper.generateInviteUrl(getReactApplicationContext());

        try {

            JSONObject options = RNUtil.readableMapToJson(args);

            channel = options.optString(INVITE_CHANNEL, "");
            campaign = options.optString(INVITE_CAMPAIGN, "");
            referrerName = options.optString(INVITE_REFERRER, "");
            referrerImageUrl = options.optString(INVITE_IMAGEURL, "");
            customerID = options.optString(INVITE_CUSTOMERID, "");
            baseDeepLink = options.optString(INVITE_DEEPLINK, "");
            brandDomain = options.optString(INVITE_BRAND_DOMAIN, "");

            if (channel != null && channel != "") {
                linkGenerator.setChannel(channel);
            }
            if (campaign != null && campaign != "") {
                linkGenerator.setCampaign(campaign);
            }
            if (referrerName != null && referrerName != "") {
                linkGenerator.setReferrerName(referrerName);
            }
            if (referrerImageUrl != null && referrerImageUrl != "") {
                linkGenerator.setReferrerImageURL(referrerImageUrl);
            }
            if (customerID != null && customerID != "") {
                linkGenerator.setReferrerCustomerId(customerID);
            }
            if (baseDeepLink != null && baseDeepLink != "") {
                linkGenerator.setBaseDeeplink(baseDeepLink);
            }
            if (brandDomain != null && brandDomain != "") {
                linkGenerator.setBrandDomain(brandDomain);
            }


            if (options.length() > 1 && !options.get("userParams").equals("")) {

                JSONObject jsonCustomValues = options.getJSONObject("userParams");

                Iterator<?> keys = jsonCustomValues.keys();

                while (keys.hasNext()) {
                    String key = (String) keys.next();
                    Object keyvalue = jsonCustomValues.get(key);
                    linkGenerator.addParameter(key, keyvalue.toString());
                }
            }

        } catch (JSONException e) {

        }

        CreateOneLinkHttpTask.ResponseListener listener = new CreateOneLinkHttpTask.ResponseListener() {
            @Override
            public void onResponse(final String oneLinkUrl) {
                guardedSuccessCallback.invoke(oneLinkUrl);
            }

            @Override
            public void onResponseError(final String error) {
                guardedErrorCallback.invoke(error);
            }
        };

        linkGenerator.generateLink(getReactApplicationContext(), listener);

    }

    @ReactMethod
    public void logCrossPromotionImpression(final String appId, final String campaign, ReadableMap params) {
        try {
            Map<String, Object> temp = RNUtil.toMap(params);
            Map<String, String> data = null;
            data = (Map) temp;
            CrossPromotionHelper.logCrossPromoteImpression(getReactApplicationContext(), appId, campaign, data);
        } catch (Exception e) {
            CrossPromotionHelper.logCrossPromoteImpression(getReactApplicationContext(), appId, campaign);
        }
    }

    @ReactMethod
    public void logCrossPromotionAndOpenStore(final String appId, final String campaign, ReadableMap params) {
        Map<String, String> data = null;
        try {
            Map<String, Object> temp = RNUtil.toMap(params);
            data = (Map) temp;
        } catch (Exception e) {
        }
        CrossPromotionHelper.logAndOpenStore(getReactApplicationContext(), appId, campaign, data);
    }

    @ReactMethod
    public void anonymizeUser(boolean b, Callback callback) {
        CallbackGuard guardedCallback = new CallbackGuard(callback);
        AppsFlyerLib.getInstance().anonymizeUser(b);
        guardedCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void setOneLinkCustomDomains(ReadableArray domainsArray, Callback successCallback, Callback errorCallback) {
        CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);

        if (domainsArray.size() <= 0) {
            guardedErrorCallback.invoke(EMPTY_OR_CORRUPTED_LIST);
            return;
        }

//        ArrayList<Object> domainsList = domainsArray.toArrayList();
        List<Object> domainsList = RNUtil.toList(domainsArray);
        try {
            String[] domains = domainsList.toArray(new String[domainsList.size()]);
            AppsFlyerLib.getInstance().setOneLinkCustomDomain(domains);
            guardedSuccessCallback.invoke(SUCCESS);
        } catch (Exception e) {
            e.printStackTrace();
            guardedErrorCallback.invoke(EMPTY_OR_CORRUPTED_LIST);
        }
    }

    @ReactMethod
    public void setResolveDeepLinkURLs(ReadableArray urlsArray, Callback successCallback, Callback errorCallback) {
        CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);

        if (urlsArray.size() <= 0) {
            guardedErrorCallback.invoke(EMPTY_OR_CORRUPTED_LIST);
            return;
        }

//        ArrayList<Object> urlsList = urlsArray.toArrayList();
        List<Object> urlsList = RNUtil.toList(urlsArray);
        try {
            String[] urls = urlsList.toArray(new String[urlsList.size()]);
            AppsFlyerLib.getInstance().setResolveDeepLinkURLs(urls);
            guardedSuccessCallback.invoke(SUCCESS);
        } catch (Exception e) {
            e.printStackTrace();
            guardedErrorCallback.invoke(EMPTY_OR_CORRUPTED_LIST);
        }
    }

    @ReactMethod
    public void performOnAppAttribution(String urlString, Callback successCallback, Callback errorCallback) {
        CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);

        try {
            URI uri = URI.create(urlString);
            Context c = application.getApplicationContext();
            AppsFlyerLib.getInstance().performOnAppAttribution(c, uri);
            guardedSuccessCallback.invoke(SUCCESS);
        } catch (Exception e) {
            e.printStackTrace();
            guardedErrorCallback.invoke(INVALID_URI);
        }
    }

    @ReactMethod
    public void setSharingFilterForPartners(ReadableArray partnersArray) {
        List<Object> partnersList = RNUtil.toList(partnersArray);
        if(partnersList == null) {
          AppsFlyerLib.getInstance().setSharingFilterForPartners((String[]) null);
        } else{
          try {
              String[] partners = partnersList.toArray(new String[partnersList.size()]);
              AppsFlyerLib.getInstance().setSharingFilterForPartners(partners);
          } catch (Exception e) {
              e.printStackTrace();
          }  
        }
    }

    @ReactMethod
    public void logLocation(double longitude, double latitude, Callback successCallback) {
        CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        AppsFlyerLib.getInstance().logLocation(getReactApplicationContext(), latitude, longitude);
        guardedSuccessCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void validateAndLogInAppPurchase(ReadableMap purchaseInfo, Callback successCallback, Callback errorCallback) {
        CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);

        String publicKey = "";
        String signature = "";
        String purchaseData = "";
        String price = "";
        String currency = "";
        Map<String, String> additionalParameters = null;
        JSONObject additionalParametersJson;

        try {
            purchaseInfo.hasKey(ADDITIONAL_PARAMETERS);
            JSONObject purchaseJson = RNUtil.readableMapToJson(purchaseInfo);

            publicKey = purchaseJson.optString(PUBLIC_KEY, "");
            signature = purchaseJson.optString(SIGNATURE, "");
            purchaseData = purchaseJson.optString(PURCHASE_DATA, "");
            price = purchaseJson.optString(PRICE, "");
            currency = purchaseJson.optString(CURRENCY, "");
            if (purchaseInfo.hasKey(ADDITIONAL_PARAMETERS)) {
                additionalParametersJson = purchaseJson.optJSONObject(ADDITIONAL_PARAMETERS);
                additionalParameters = RNUtil.jsonObjectToMap(additionalParametersJson);
            }

            if (publicKey == "" || signature == "" || purchaseData == "" || price == "" || currency == "") {
                guardedErrorCallback.invoke(NO_PARAMETERS_ERROR);
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
            guardedErrorCallback.invoke(e);
            return;
        }
        initInAppPurchaseValidatorListenerInternal(guardedSuccessCallback, guardedErrorCallback);
        AppsFlyerLib.getInstance().validateAndLogInAppPurchase(reactContext, publicKey, signature, purchaseData, price, currency, additionalParameters);

    }

    private void initInAppPurchaseValidatorListenerInternal(final CallbackGuard guardedSuccess, final CallbackGuard guardedError) {
        AppsFlyerLib.getInstance().registerValidatorListener(reactContext, new AppsFlyerInAppPurchaseValidatorListener() {
            @Override
            public void onValidateInApp() {
                guardedSuccess.invoke(VALIDATE_SUCCESS);
            }

            @Override
            public void onValidateInAppFailure(String error) {
                guardedError.invoke(VALIDATE_FAILED + error);
            }
        });
    }

    @ReactMethod
    public void initInAppPurchaseValidatorListener(final Callback successCallback, final Callback errorCallback) {
        CallbackGuard guardedSuccess = new CallbackGuard(successCallback);
        CallbackGuard guardedError = new CallbackGuard(errorCallback);
        initInAppPurchaseValidatorListenerInternal(guardedSuccess, guardedError);
    }

    @ReactMethod
    public void validateAndLogInAppPurchaseV2(ReadableMap purchaseDetails, ReadableMap additionalParameters) {
        try {
            // Extract and validate required fields
            String purchaseType = purchaseDetails.getString("purchaseType");
            String purchaseToken = purchaseDetails.getString("transactionId");
            String productId = purchaseDetails.getString("productId");

            if (purchaseType == null || purchaseToken == null || productId == null) {
                sendValidationError("Missing required fields: purchaseType, transactionId, or productId");
                return;
            }

            // Convert purchase type
            AFPurchaseType afPurchaseType = "subscription".equals(purchaseType) 
                ? AFPurchaseType.SUBSCRIPTION 
                : AFPurchaseType.ONE_TIME_PURCHASE;

            // Create purchase details
            AFPurchaseDetails afPurchaseDetails = new AFPurchaseDetails(afPurchaseType, purchaseToken, productId);

            // Convert additional parameters to string map
            Map<String, String> additionalParams = convertReadableMapToStringMap(additionalParameters);

            // Validate purchase
            AppsFlyerLib.getInstance().validateAndLogInAppPurchase(
                afPurchaseDetails,
                additionalParams,
                new AppsFlyerInAppPurchaseValidationCallback() {
                    @Override
                    public void onInAppPurchaseValidationFinished(@NonNull Map<String, ?> result) {
                        sendValidationResult(result);
                    }

                    @Override
                    public void onInAppPurchaseValidationError(@NonNull Map<String, ?> error) {
                        sendValidationResult(error);
                    }
                }
            );
        } catch (Exception e) {
            sendValidationError("Validation failed: " + e.getMessage());
        }
    }

    private Map<String, String> convertReadableMapToStringMap(ReadableMap readableMap) {
        Map<String, String> result = new HashMap<>();
        if (readableMap == null) {
            return result;
        }

        ReadableMapKeySetIterator iterator = readableMap.keySetIterator();
        while (iterator.hasNextKey()) {
            String key = iterator.nextKey();
            Object value = readableMap.getDynamic(key);
            result.put(key, value != null ? value.toString() : null);
        }
        return result;
    }

    private void sendValidationResult(Map<String, ?> data) {
        try {
            JSONObject json = new JSONObject(data);
            sendEvent(reactContext, afOnValidationResult, json.toString());
        } catch (Exception e) {
            sendEvent(reactContext, afOnValidationResult, data.toString());
        }
    }

    private void sendValidationError(String errorMessage) {
        Map<String, Object> error = Collections.singletonMap("error", errorMessage);
        sendValidationResult(error);
    }

    @ReactMethod
    public void sendPushNotificationData(ReadableMap pushPayload, Callback errorCallback) {
        CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);
        JSONObject payload = RNUtil.readableMapToJson(pushPayload);
        String errorMsg;
        if (payload == null) {
            errorMsg = "PushNotification payload is null";
            handleErrorMessage(errorMsg, guardedErrorCallback);
            return;
        }
        Bundle bundle = null;
        try {
            bundle = RNUtil.jsonToBundle(payload);
        } catch (JSONException e) {
            e.printStackTrace();
            errorMsg = "Can't parse pushPayload to bundle";
            handleErrorMessage(errorMsg, guardedErrorCallback);
            return;
        }
        Activity activity = getCurrentActivity();
        if (activity != null) {
            Intent intent = activity.getIntent();
            if (intent != null) {
                intent.putExtras(bundle);
                activity.setIntent(intent);
                AppsFlyerLib.getInstance().sendPushNotificationData(activity);
            } else {
                errorMsg = "The intent is null. Push payload has not been sent!";
                handleErrorMessage(errorMsg, guardedErrorCallback);
            }
        } else {
            errorMsg = "The activity is null. Push payload has not been sent!";
            handleErrorMessage(errorMsg, guardedErrorCallback);
        }
    }

    private void handleErrorMessage(String errorMessage, CallbackGuard errorCB) {
        Log.d("AppsFlyer", errorMessage);
        if (errorCB != null) {
            errorCB.invoke(errorMessage);
        }
    }

    @ReactMethod
    public void setHost(String hostPrefix, String hostName, Callback successCallback) {
        CallbackGuard guardedCallback = new CallbackGuard(successCallback);
        AppsFlyerLib.getInstance().setHost(hostPrefix, hostName);
        guardedCallback.invoke(SUCCESS);
    }

    @ReactMethod
    public void addPushNotificationDeepLinkPath(ReadableArray path, Callback successCallback, Callback errorCallback) {
        CallbackGuard guardedSuccessCallback = new CallbackGuard(successCallback);
        CallbackGuard guardedErrorCallback = new CallbackGuard(errorCallback);

        if (path.size() <= 0) {
            guardedErrorCallback.invoke(EMPTY_OR_CORRUPTED_LIST);
            return;
        }
//        ArrayList<Object> pathList = path.toArrayList();
        List<Object> pathList = RNUtil.toList(path);
        try {
            String[] params = pathList.toArray(new String[pathList.size()]);
            AppsFlyerLib.getInstance().addPushNotificationDeepLinkPath(params);
            guardedSuccessCallback.invoke(SUCCESS);
        } catch (Exception e) {
            e.printStackTrace();
            guardedErrorCallback.invoke(e);
        }
    }

    @ReactMethod
    public void disableAdvertisingIdentifier(Boolean isDisabled) {
        AppsFlyerLib.getInstance().setDisableAdvertisingIdentifiers(isDisabled);
    }

    @ReactMethod
    public void setPartnerData(String partnerId, ReadableMap partnerData) {
        Map partnerDataAsMap = RNUtil.toMap(partnerData);
        AppsFlyerLib.getInstance().setPartnerData(partnerId, partnerDataAsMap);
    }

    @ReactMethod
    public void appendParametersToDeepLinkingURL(String contains, ReadableMap parameters) {
        Map parametersAsMap = RNUtil.toMap(parameters);
        AppsFlyerLib.getInstance().appendParametersToDeepLinkingURL(contains, parametersAsMap);
    }

    @ReactMethod
    public void setDisableNetworkData(Boolean disabled) {
        AppsFlyerLib.getInstance().setDisableNetworkData(disabled);
    }

    @ReactMethod
    public void performOnDeepLinking() {
        Activity activity = getCurrentActivity();
        if (activity != null) {
            Intent intent = activity.getIntent();
            if (intent != null) {
                AppsFlyerLib.getInstance().performOnDeepLinking(intent, this.application);
            } else {
                Log.d("AppsFlyer", "performOnDeepLinking: intent is null!");
            }
        } else{
            Log.d("AppsFlyer", "performOnDeepLinking: activity is null!");
        }
    }

    @ReactMethod
    public void disableAppSetId() {
        AppsFlyerLib.getInstance().disableAppSetId();
    }

    @ReactMethod
    public void enableTCFDataCollection(Boolean enabled) {
        AppsFlyerLib.getInstance().enableTCFDataCollection(enabled);
    }

    @ReactMethod
    public void setConsentData(@Nullable ReadableMap consentData) {
        if (consentData == null) {
            Log.e("AppsFlyer", "consentData is null");
            return;
        }

        JSONObject JSONConsentData = RNUtil.readableMapToJson(consentData);
        if (JSONConsentData == null) {
            Log.e("AppsFlyer", "Failed to convert consentData to JSON");
            return;
        }

        Boolean isUserSubjectToGDPR = JSONConsentData.has("isUserSubjectToGDPR") && !JSONConsentData.isNull("isUserSubjectToGDPR")
                ? JSONConsentData.optBoolean("isUserSubjectToGDPR")
                : null;

        Boolean hasConsentForDataUsage = JSONConsentData.has("hasConsentForDataUsage") && !JSONConsentData.isNull("hasConsentForDataUsage")
                ? JSONConsentData.optBoolean("hasConsentForDataUsage")
                : null;

        Boolean hasConsentForAdsPersonalization = JSONConsentData.has("hasConsentForAdsPersonalization") && !JSONConsentData.isNull("hasConsentForAdsPersonalization")
                ? JSONConsentData.optBoolean("hasConsentForAdsPersonalization")
                : null;

        Boolean hasConsentForAdStorage = JSONConsentData.has("hasConsentForAdStorage") && !JSONConsentData.isNull("hasConsentForAdStorage")
                ? JSONConsentData.optBoolean("hasConsentForAdStorage")
                : null;

        AppsFlyerConsent consentObject = new AppsFlyerConsent(
                isUserSubjectToGDPR,
                hasConsentForDataUsage,
                hasConsentForAdsPersonalization,
                hasConsentForAdStorage
        );

        AppsFlyerLib.getInstance().setConsentData(consentObject);
    }

    @ReactMethod
    public void addListener(String eventName) {
      // Keep: Required for RN built in Event Emitter Calls.
    }
  
    @ReactMethod
    public void removeListeners(Integer count) {
      // Keep: Required for RN built in Event Emitter Calls.
    }
}