package com.quantummetric.reactnative;

import android.app.Activity;
import android.util.Log;

import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import org.json.JSONObject;

import java.lang.ref.WeakReference;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;

public class QuantumMetricLibraryModule extends ReactContextBaseJavaModule {
    private static WeakReference<QuantumMetricLibraryModule> instance;
    private static JSONObject config = null;
    private final ReactApplicationContext reactContext;
    private Class<?> quantumClass = null;
    private boolean setSessionCallback;

    public QuantumMetricLibraryModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
        instance = new WeakReference<>(this);

        try {
            quantumClass = Class.forName("com.quantummetric.instrument.QuantumMetric");
        } catch (Throwable throwable) {
            Log.d("QM","Not able to access QuantumMetric class ", throwable);
        }
    }

    @Override
    public void initialize() {
        super.initialize();

        // we can start after module will be initialized
        startIfOldSkd();
    }

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

    @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.
    }

    @ReactMethod
    public void networkRequestCompleted(String url, int requestStartTime, String requestString, String responseString, int status, String requestHeaders, String responseHeaders, int responseTimeMilliseconds, String httpMethod, Callback callback) {
        try {
            //turn req/res headers into map
            Map<String, ?> requestHeadersMap = Utilities.stringToMap(requestHeaders);
            Map<String, ?> responseHeadersMap = Utilities.stringToMap(responseHeaders);

            try {
                Method method = quantumClass.getMethod("logRequest", String.class, String.class, long.class, long.class, int.class, String.class, String.class, Map.class, Map.class);
                method.invoke(null, url, httpMethod, responseTimeMilliseconds, requestStartTime, status, requestString, responseString, requestHeadersMap, responseHeadersMap);
            } catch (Exception e) {
                Log.d("QM","logRequest reflection exception: ", e);
            }
        } catch (Throwable e) {
            Log.d("QM", e.toString());
        }
    }

    @ReactMethod
    public void sendEventNative(int id, String value, String json, int type) {
        try {
            Class<?> eventTypeClass = Class.forName("com.quantummetric.instrument.EventType");

            Constructor<?> eventTypeConstructor = eventTypeClass.getDeclaredConstructor(int.class);
            eventTypeConstructor.setAccessible(true);
            Object eventType = eventTypeConstructor.newInstance(type);

            Object eventTypeArray = Array.newInstance(eventTypeClass, 1);
            Array.set(eventTypeArray, 0, eventType);

            // attempt to construct JSONObject from json string. If parsing fails, event will not be sent
            if (json != null && !json.isEmpty()) {
                JSONObject jsonObj = new JSONObject(json);
                Method method = quantumClass.getMethod("sendEvent", int.class, String.class, JSONObject.class, eventTypeArray.getClass());
                method.invoke(null, id, value, jsonObj, eventTypeArray);
            } else {
                Method method = quantumClass.getMethod("sendEvent", int.class, String.class, eventTypeArray.getClass());
                method.invoke(null, id, value, eventTypeArray);
            }
        } catch (Throwable e) {
            Log.d("QM", "Exception while trying to send event " + e);
        }
    }

    @ReactMethod
    public void sendNewPageNamedNative(String pageName) {
        try {
            quantumClass.getDeclaredMethod("sendNewPageNamed", String.class).invoke(null, pageName);
        } catch (Throwable e) {
            Log.d("QM", "Exception while trying to send new page " + e);
        }
    }

    @ReactMethod
    public void requestConfig() {
        if (config != null) {
            sendStartConfigToJS(config);
        }
    }

    @ReactMethod
    public void initializeNative(String subscription, String uid) {
        try {
            Activity activity = getCurrentActivity();

            Method method = quantumClass.getMethod("initialize", String.class, String.class, Activity.class);
            Object builder = method.invoke(null, subscription, uid, activity);
            builder.getClass().getDeclaredMethod("start").invoke(builder);
        } catch (Throwable e) {
            Log.d("QM", "Exception while trying to initialize with sub " + e);
        }
    }

    @ReactMethod
    public void initializeWithOptionsNative(String subscription, String uid, ReadableMap options) {
        try {
            Activity activity = getCurrentActivity();

            Method method = quantumClass.getMethod("initialize", String.class, String.class, Activity.class);
            Object builder = method.invoke(null, subscription, uid, activity);
            Class<?> builderClass = builder.getClass();

            if (options != null) {

                Map<String, ?> optionsMap = Utilities.readableMapToHashMap(options);
                Object testMode = optionsMap.get("testModeEnabled");
                Object sslPinningEnabled = optionsMap.get("sslPinningEnabled");
                Object certificateKey = optionsMap.get("certificateKey");
                Object crashReportingEnabled = optionsMap.get("crashReportingEnabled");

                if (testMode instanceof Boolean && (Boolean) testMode) {
                    builderClass.getDeclaredMethod("enableTestMode").invoke(builder);
                }

                if (sslPinningEnabled instanceof Boolean && (Boolean) sslPinningEnabled) {
                    builderClass.getDeclaredMethod("enableCertificatePinning").invoke(builder);
                    if (certificateKey instanceof String) {
                        Method addCertificateKeyMethod = builderClass.getDeclaredMethod("addCertificateKey", String.class);
                        addCertificateKeyMethod.invoke(builder, certificateKey);
                    }
                }

                if (crashReportingEnabled instanceof Boolean && !((Boolean) crashReportingEnabled)) {
                    builderClass.getDeclaredMethod("disableCrashReporting").invoke(builder);
                }
            }

            builderClass.getDeclaredMethod("start").invoke(builder);
        } catch (Throwable e) {
            Log.d("QM", "Exception while trying to initializeWithOptionsNative " + e);
        }
    }

    @ReactMethod
    public void pauseQMNative() {
        try {
            quantumClass.getDeclaredMethod("pause").invoke(null);
        } catch (Throwable e) {
            Log.d("QM", "Exception while trying to pause QM: " + e);
        }
    }

    @ReactMethod
    public void resumeQMNative() {
        try {
            quantumClass.getDeclaredMethod("resume").invoke(null);
        } catch (Throwable e) {
            Log.d("QM", "Exception while trying to resume QM: " + e);
        }
    }

    @ReactMethod
    public void stopQMNative() {
        try {
            quantumClass.getDeclaredMethod("stop").invoke(null);
        } catch (Throwable e) {
            Log.d("QM", "Exception while trying to stop QM: " + e);
        }
    }

    @ReactMethod
    public void resetSessionQMNative(boolean resetUser) {
        try {
            quantumClass.getDeclaredMethod("resetSession", boolean.class).invoke(null, resetUser);
        } catch (Throwable e) {
            Log.d("QM", "Exception while trying to reset QM: " + e);
        }
    }

    @ReactMethod
    public void enableTestConfigNative(boolean enabled) {
        Log.d("QM", "enableTestConfig not available just yet");
    }

    @ReactMethod
    public void setSessionCookieCallbackQMNative() {
        setSessionCallback = true;
    }

    private void startIfOldSkd() {
        try {
            Class<?> buildConfigClass = Class.forName("com.quantummetric.instrument.BuildConfig");
            int versionCode = buildConfigClass.getDeclaredField("VERSION_CODE").getInt(null);

            // if sdk < 0.3.3 - start (in case if somebody update module without sdk update)
            if (versionCode < 54) {
                sendStartConfigToJS(new JSONObject());
            }
        } catch (Exception ignore) {
        }
    }

    public static void sendStartConfigToJS(JSONObject json) {
        config = json;

        if (instance != null && instance.get() != null) {
            instance.get().reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                    .emit("QMStartNotification", Utilities.convertJsonToMap(json));
        }
    }

    public static void onCookieChangeListener(String sessionCookie, String userCookie) {
        try {
            if (instance != null && instance.get() != null && instance.get().setSessionCallback) {
                JSONObject sessionJson = new JSONObject()
                        .put("sessionCookie", sessionCookie)
                        .put("userString", userCookie);

                instance.get().reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                        .emit("QMSessionCookieCallback", Utilities.convertJsonToMap(sessionJson));
            }
        } catch (Throwable ignore) {
        }
    }
}
