package com.userleap.reactnative;

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

import androidx.fragment.app.FragmentActivity;

import com.facebook.react.bridge.Arguments;
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.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.userleap.EventListener;
import com.userleap.EventName;
import com.userleap.EventPayload;
import com.userleap.SprigEvent;
import com.userleap.SprigSurveyResult;
import com.userleap.SurveyState;
import com.userleap.UserLeap;
import com.userleap.SprigUserInterfaceMode;

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

import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import kotlin.Unit;
import kotlin.jvm.functions.Function1;

public class UserLeapModule extends ReactContextBaseJavaModule {

    private static final String TAG = "UserLeapModule";

    private final ReactApplicationContext reactContext;

    public UserLeapModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.reactContext = reactContext;
    }

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

    // ---------------------------------------------------------------------------
    // Bridge helpers
    // ---------------------------------------------------------------------------

    /**
     * Converts UPPER_SNAKE_CASE to camelCase to match the iOS event naming
     * convention expected by the JS layer.
     * e.g. "SURVEY_WILL_PRESENT" -> "surveyWillPresent"
     */
    private String toCamelCase(String upperSnakeCase) {
        String[] parts = upperSnakeCase.toLowerCase().split("_");
        StringBuilder sb = new StringBuilder(parts[0]);
        for (int i = 1; i < parts.length; i++) {
            sb.append(Character.toUpperCase(parts[i].charAt(0)));
            sb.append(parts[i].substring(1));
        }
        return sb.toString();
    }

    /**
     * Shallow-converts a JSONObject to a WritableMap for passing across the RN bridge.
     */
    private WritableMap jsonObjectToWritableMap(@Nullable JSONObject json) {
        WritableMap map = Arguments.createMap();
        if (json == null) {
            return map;
        }
        Iterator<String> keys = json.keys();
        while (keys.hasNext()) {
            String key = keys.next();
            try {
                Object value = json.get(key);
                if (value instanceof String) {
                    map.putString(key, (String) value);
                } else if (value instanceof Boolean) {
                    map.putBoolean(key, (Boolean) value);
                } else if (value instanceof Integer) {
                    map.putInt(key, (Integer) value);
                } else if (value instanceof Double) {
                    map.putDouble(key, (Double) value);
                } else if (value instanceof JSONObject) {
                    map.putMap(key, jsonObjectToWritableMap((JSONObject) value));
                } else if (value == JSONObject.NULL) {
                    map.putNull(key);
                } else {
                    map.putString(key, value.toString());
                }
            } catch (JSONException e) {
                Log.w(TAG, "Failed to convert key '" + key + "' from SprigEvent data", e);
            }
        }
        return map;
    }

    /**
     * Converts a SprigSurveyResult to a WritableMap for passing across the RN bridge.
     * Returns { surveyState: string, surveyId: int }
     */
    private WritableMap surveyResultToWritableMap(SprigSurveyResult surveyResult) {
        WritableMap resultMap = Arguments.createMap();
        resultMap.putString("surveyState", surveyResult.getSurveyState().name());
        resultMap.putInt("surveyId", surveyResult.getSurveyId());
        return resultMap;
    }

    /**
     * Wraps a RN Callback as a Function1<SprigSurveyResult, Unit> for the EventPayload API.
     * Returns null if the callback is null.
     */
    private @Nullable Function1<SprigSurveyResult, Unit> wrapResultCallback(@Nullable final Callback callback) {
        if (callback == null) {
            return null;
        }
        return new Function1<SprigSurveyResult, Unit>() {
            @Override
            public Unit invoke(SprigSurveyResult surveyResult) {
                callback.invoke(surveyResultToWritableMap(surveyResult));
                return null;
            }
        };
    }

    private Map<String, String> stringifyMap(ReadableMap map) {
        Map<String, String> stringifiedMap = new HashMap<>();
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            stringifiedMap = map.toHashMap().entrySet().stream()
                    .filter(m -> m.getKey() != null && m.getValue() != null)
                    .collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().toString()));
        }
        return stringifiedMap;
    }

    private @Nullable FragmentActivity getFragmentActivity() {
        Activity activity = getCurrentActivity();
        if (activity instanceof FragmentActivity) {
            return (FragmentActivity) activity;
        } else {
            return null;
        }
    }

    // ---------------------------------------------------------------------------
    // SDK properties
    // ---------------------------------------------------------------------------

    @ReactMethod(isBlockingSynchronousMethod = true)
    public int visitorIdentifier() {
        final Integer visitorIdentifierObject = UserLeap.INSTANCE.getVisitorIdentifier();
        return visitorIdentifierObject == null ? 0 : visitorIdentifierObject;
    }

    @ReactMethod(isBlockingSynchronousMethod = true)
    public String visitorIdentifierString() {
        final String visitorIdentifierObject = UserLeap.INSTANCE.getVisitorIdentifierString();
        return visitorIdentifierObject == null ? "" : visitorIdentifierObject;
    }

    @ReactMethod(isBlockingSynchronousMethod = true)
    public String getSdkVersion() {
        return UserLeap.INSTANCE.getSdkVersion();
    }

    // ---------------------------------------------------------------------------
    // Configuration
    // ---------------------------------------------------------------------------

    @ReactMethod
    public void configure(String environment, ReadableMap configuration) {
        UserLeap.INSTANCE.addEventListener(EventName.LOGGING_EVENT, new EventListener() {
            @Override
            public void onEvent(SprigEvent event) {
                String tag = "SprigLoggingEvent";
                switch (event.getLogLevel()) {
                    case INFO:
                        Log.i(tag, event.getLogMessage());
                        break;
                    case DEBUG:
                        Log.d(tag, event.getLogMessage());
                        break;
                    case WARNING:
                        Log.w(tag, event.getLogMessage());
                        break;
                    case ERROR:
                    case CRITICAL:
                        Log.e(tag, event.getLogMessage());
                        break;
                }
            }
        });

        UserLeap.INSTANCE.configure(
                reactContext,
                environment,
                stringifyMap(configuration),
                getFragmentActivity()
        );
    }

    // ---------------------------------------------------------------------------
    // Deprecated track methods (return SurveyState string)
    // ---------------------------------------------------------------------------

    @ReactMethod
    public void track(String event, final Callback surveyStateCallback) {
        trackAndIdentify(event, null, null, surveyStateCallback);
    }

    @ReactMethod
    public void trackWithProperties(String event, String userId, String partnerAnonymousId, ReadableMap properties, final Callback surveyStateCallback) {
        if (surveyStateCallback == null) {
            UserLeap.INSTANCE.track(event, userId, partnerAnonymousId, stringifyMap(properties));
        } else {
            UserLeap.INSTANCE.track(event, userId, partnerAnonymousId, stringifyMap(properties), new Function1<SurveyState, Unit>() {
                @Override
                public Unit invoke(SurveyState surveyState) {
                    surveyStateCallback.invoke(surveyState.name());
                    return null;
                }
            });
        }
    }

    @ReactMethod
    public void trackAndIdentify(String event, String userId, String partnerAnonymousId, final Callback surveyStateCallback) {
        if (surveyStateCallback == null) {
            UserLeap.INSTANCE.track(event, userId, partnerAnonymousId);
        } else {
            UserLeap.INSTANCE.track(event, userId, partnerAnonymousId, new Function1<SurveyState, Unit>() {
                @Override
                public Unit invoke(SurveyState surveyState) {
                    surveyStateCallback.invoke(surveyState.name());
                    return null;
                }
            });
        }
    }

    // ---------------------------------------------------------------------------
    // New track methods (return SprigSurveyResult via EventPayload)
    // ---------------------------------------------------------------------------

    @ReactMethod
    public void trackEvent(String event, final Callback surveyStateCallback) {
        trackEventAndIdentify(event, null, null, surveyStateCallback);
    }

    @ReactMethod
    public void trackEventWithProperties(String event, String userId, String partnerAnonymousId, ReadableMap properties, final Callback surveyStateCallback) {
        EventPayload payload = new EventPayload(
                event,
                userId,
                partnerAnonymousId,
                stringifyMap(properties),
                wrapResultCallback(surveyStateCallback),
                null, // shouldShowSurveyCallback
                null  // deprecated callback
        );
        UserLeap.INSTANCE.track(payload);
    }

    @ReactMethod
    public void trackEventAndIdentify(String event, String userId, String partnerAnonymousId, final Callback surveyStateCallback) {
        EventPayload payload = new EventPayload(
                event,
                userId,
                partnerAnonymousId,
                null, // properties
                wrapResultCallback(surveyStateCallback),
                null, // shouldShowSurveyCallback
                null  // deprecated callback
        );
        UserLeap.INSTANCE.track(payload);
    }

    // ---------------------------------------------------------------------------
    // Track and present
    // ---------------------------------------------------------------------------

    @ReactMethod
    public void trackAndPresent(String event) {
        UserLeap.INSTANCE.trackAndPresent(event, getFragmentActivity());
    }

    @ReactMethod
    public void trackIdentifyAndPresent(String event, String userId, String partnerAnonymousId) {
        UserLeap.INSTANCE.trackAndPresent(event, userId, partnerAnonymousId, getFragmentActivity());
    }

    // ---------------------------------------------------------------------------
    // Survey presentation
    // ---------------------------------------------------------------------------

    @ReactMethod
    public void presentSurvey() {
        UserLeap.INSTANCE.presentSurvey(null);
    }

    @ReactMethod
    public void dismissActiveSurvey() {
        UserLeap.INSTANCE.dismissActiveSurvey();
    }

    @ReactMethod
    public void pauseDisplayingSurveys() {
        UserLeap.INSTANCE.pauseDisplayingSurveys();
    }

    @ReactMethod
    public void unpauseDisplayingSurveys() {
        UserLeap.INSTANCE.unpauseDisplayingSurveys();
    }

    // ---------------------------------------------------------------------------
    // User identity & attributes
    // ---------------------------------------------------------------------------

    @ReactMethod
    public void setEmailAddress(String emailAddress) {
        UserLeap.INSTANCE.setEmailAddress(emailAddress);
    }

    @ReactMethod
    public void setVisitorAttribute(String key, String value) {
        UserLeap.INSTANCE.setVisitorAttribute(key, value);
    }

    @ReactMethod
    public void setVisitorAttributes(ReadableMap attributes) {
        UserLeap.INSTANCE.setVisitorAttributes(stringifyMap(attributes));
    }

    @ReactMethod
    public void setVisitorAttributesAndIdentify(ReadableMap attributes, String userId, String partnerAnonymousId) {
        UserLeap.INSTANCE.setVisitorAttributes(stringifyMap(attributes), userId, partnerAnonymousId);
    }

    @ReactMethod
    public void removeVisitorAttributes(ReadableArray attributes) {
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
            List<String> attributeStrings = attributes.toArrayList().stream()
                    .map(object -> Objects.toString(object, null))
                    .collect(Collectors.toList());
            UserLeap.INSTANCE.removeVisitorAttributes(attributeStrings);
        }
    }

    @ReactMethod
    public void setPreviewKey(String previewKey) {
        UserLeap.INSTANCE.setPreviewKey(previewKey);
    }

    @ReactMethod
    public void setUserIdentifier(String identifier) {
        UserLeap.INSTANCE.setUserIdentifier(identifier);
    }

    @ReactMethod
    public void overrideUserInterfaceMode(double mode) {
        int modeInt = (int) mode;
        for (SprigUserInterfaceMode m : SprigUserInterfaceMode.values()) {
            if (m.getValue() == modeInt) {
                UserLeap.INSTANCE.overrideUserInterfaceMode(m);
                return;
            }
        }
    }

    @ReactMethod
    public void logout() {
        UserLeap.INSTANCE.logout();
    }
}