package com.instabug.reactlibrary;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.os.Handler;
import android.os.Looper;

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.WritableMap;
import com.instabug.bug.BugReporting;
import com.instabug.bug.invocation.Option;
import com.instabug.chat.Replies;
import com.instabug.library.Feature;
import com.instabug.library.OnSdkDismissCallback;
import com.instabug.library.extendedbugreport.ExtendedBugReport;
import com.instabug.library.invocation.InstabugInvocationEvent;
import com.instabug.library.invocation.OnInvokeCallback;
import com.instabug.library.invocation.util.InstabugFloatingButtonEdge;
import com.instabug.library.invocation.util.InstabugVideoRecordingButtonPosition;
import com.instabug.reactlibrary.utils.ArrayUtil;
import com.instabug.reactlibrary.utils.InstabugUtil;
import com.instabug.reactlibrary.utils.MainThreadHandler;

import java.util.ArrayList;
import java.util.Arrays;

import javax.annotation.Nonnull;

public class RNInstabugBugReportingModule extends ReactContextBaseJavaModule {

    public RNInstabugBugReportingModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

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

    /**
     * Enable or disable all BugReporting related features.
     * @param isEnabled boolean indicating enabled or disabled.
     */
    @ReactMethod
    public void setEnabled(final boolean isEnabled) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    if (isEnabled) {
                        BugReporting.setState(Feature.State.ENABLED);
                    } else {
                        BugReporting.setState(Feature.State.DISABLED);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });

    }


    /**
     * Enable/Disable screen recording
     *
     * @param autoScreenRecordingEnabled boolean for enable/disable
     *                                   screen recording on crash feature
     */
    @TargetApi(21)
    @ReactMethod
    public void setAutoScreenRecordingEnabled(final boolean autoScreenRecordingEnabled) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    BugReporting.setAutoScreenRecordingEnabled(autoScreenRecordingEnabled);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Sets whether the extended bug report mode should be disabled,
     * enabled with required fields,  or enabled with optional fields.
     *
     * @param extendedBugReportMode
     */
    @ReactMethod
    public void setExtendedBugReportMode(final String extendedBugReportMode) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    BugReporting.setExtendedBugReportState(
                            ArgsRegistry.getDeserializedValue(extendedBugReportMode, ExtendedBugReport.State.class));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Enables or disables view hierarchy in the dashboard.
     * @param isEnabled boolean indicating enabled or disabled.
     */
    @ReactMethod
    public void setViewHierarchyEnabled(final boolean isEnabled) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    if (isEnabled) {
                        BugReporting.setViewHierarchyState(Feature.State.ENABLED);
                    } else {
                        BugReporting.setViewHierarchyState(Feature.State.DISABLED);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Sets the default corner at which the video recording floating button will be shown
     *
     * @param corner corner to stick the video recording floating button to
     */
    @ReactMethod
    public void setVideoRecordingFloatingButtonPosition(final String corner) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    BugReporting.setVideoRecordingFloatingButtonPosition(
                            ArgsRegistry.getDeserializedValue(corner, InstabugVideoRecordingButtonPosition.class));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Sets whether attachments in bug reporting and in-app messaging are enabled or not.
     *
     * @param  screenshot A boolean to enable or disable screenshot attachments.
     * @param {boolean} extraScreenShot A boolean to enable or disable extra screenshot attachments.
     * @param {boolean} galleryImage A boolean to enable or disable gallery image attachments.
     * @param {boolean} screenRecording A boolean to enable or disable screen recording attachments.
     */
    @ReactMethod
    public void setEnabledAttachmentTypes(final boolean screenshot, final boolean extraScreenshot, final boolean
            galleryImage, final boolean screenRecording) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    BugReporting.setAttachmentTypesEnabled(screenshot, extraScreenshot, galleryImage,
                            screenRecording);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * @deprecated
     * Sets the event used to invoke Instabug SDK
     *
     * @param invocationEventValue the invocation event value
     * @see InstabugInvocationEvent
     */
    @ReactMethod
    public void setInvocationEvent(final String invocationEventValue) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    BugReporting.setInvocationEvents(
                            ArgsRegistry.getDeserializedValue(invocationEventValue, InstabugInvocationEvent.class));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Sets the event used to invoke Instabug SDK
     *
     * @param invocationEventValues the invocation event value
     * @see InstabugInvocationEvent
     */
    @ReactMethod
    public void setInvocationEvents(ReadableArray invocationEventValues) {

        try {
            Object[] objectArray = ArrayUtil.toArray(invocationEventValues);
            String[] stringArray = Arrays.copyOf(objectArray, objectArray.length, String[].class);
            final ArrayList<InstabugInvocationEvent> parsedInvocationEvents = new ArrayList<>();

            for (String event : stringArray) {
                parsedInvocationEvents.add(ArgsRegistry.getDeserializedValue(event, InstabugInvocationEvent.class));
            }
            MainThreadHandler.runOnMainThread(new Runnable() {
                @Override
                public void run() {
                    try {
                        BugReporting.setInvocationEvents(parsedInvocationEvents.toArray(new InstabugInvocationEvent[0]));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Sets the options for the features in the SDK
     *
     * @param optionValues the invocation option value
     */
    @ReactMethod
    public void setOptions(final ReadableArray optionValues) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    Object[] objectArray = ArrayUtil.toArray(optionValues);
                    String[] stringArray = Arrays.copyOf(objectArray, objectArray.length, String[].class);
                    for (String option : stringArray) {
                        BugReporting.setOptions((int) ArgsRegistry.getRawValue(option));
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Sets a block of code to be executed just before the SDK's UI is presented.
     * This block is executed on the UI thread. Could be used for performing any
     * UI changes before the SDK's UI is shown.
     *
     * @param onInvokeHandler - A callback that gets executed before
     *                             invoking the SDK
     */
    @ReactMethod
    public void setOnInvokeHandler(final Callback onInvokeHandler) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                            BugReporting.setOnInvokeCallback(new OnInvokeCallback() {
                        @Override
                        public void onInvoke() {
                            InstabugUtil.sendEvent(getReactApplicationContext(), Constants.IBG_PRE_INVOCATION_HANDLER, null);
                        }
                    });
                } catch (java.lang.Exception exception) {
                    exception.printStackTrace();
                }
            }
        });
    }

    /**
     * Sets the position of the Instabug floating button on the screen.
     * @param floatingButtonEdge left or right edge of the screen.
     * @param floatingButtonOffset integer offset from the left or right edge of the screen.
     */
    @ReactMethod
    public void setFloatingButtonEdge(final String floatingButtonEdge, final int floatingButtonOffset) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                BugReporting.setFloatingButtonOffset(floatingButtonOffset);
                if (floatingButtonEdge.equals("left"))
                    BugReporting.setFloatingButtonEdge(InstabugFloatingButtonEdge.LEFT);
                else
                    BugReporting.setFloatingButtonEdge(InstabugFloatingButtonEdge.RIGHT);
            }
        });
    }

    /**
     * Sets a block of code to be executed right after the SDK's UI is dismissed.
     * This block is executed on the UI thread. Could be used for performing any
     * UI changes after the SDK's UI is dismissed.
     *
     * @param handler - A callback to get executed after
     *                              dismissing the SDK.
     */
    @ReactMethod
    public void setOnSDKDismissedHandler(final Callback handler) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    BugReporting.setOnDismissCallback(new OnSdkDismissCallback() {
                        @Override
                        public void call(DismissType dismissType, ReportType reportType) {
                            WritableMap params = Arguments.createMap();
                            params.putString("dismissType", dismissType.toString());
                            params.putString("reportType", reportType.toString());
                            InstabugUtil.sendEvent(getReactApplicationContext(), Constants.IBG_POST_INVOCATION_HANDLER, params);
                        }
                    });
                } catch (java.lang.Exception exception) {
                    exception.printStackTrace();
                }
            }
        });
    }

    /**
     * Sets the threshold value of the shake gesture for android devices.
     * Default for android is an integer value equals 350.
     * you could increase the shaking difficulty level by
     * increasing the `350` value and vice versa.
     *
     * @param androidThreshold Threshold for android devices.
     */
    @ReactMethod
    public void setShakingThresholdForAndroid(final int androidThreshold) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    BugReporting.setShakingThreshold(androidThreshold);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Sets the enabled report types to be shown in the prompt. Bug or Feedback or both.
     * @param types
     * @see BugReporting.ReportType
     */
    @SuppressLint("WrongConstant")
    @ReactMethod
    public void setReportTypes(ReadableArray types) {
        Object[] objectArray = ArrayUtil.toArray(types);
        String[] stringArray = Arrays.copyOf(objectArray, objectArray.length, String[].class);
        final int[] parsedReportTypes = new int[stringArray.length];
        for (int i = 0; i < stringArray.length; i++) {
            parsedReportTypes[i] = (int) ArgsRegistry.getRawValue(stringArray[i]);
        }
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                try {
                    BugReporting.setReportTypes(parsedReportTypes);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Shows a bug or feedback report with optional options.
     * @param reportType Bug or Feedback.
     * @param options array of options
     * @see BugReporting.ReportType
     * @see Option
     */
    @ReactMethod
    public void show(final String reportType, final ReadableArray options) {
        MainThreadHandler.runOnMainThread(new Runnable() {
            @Override
            public void run() {
                if (ArgsRegistry.getDeserializedValue(reportType, Integer.class) == null) {
                    return;
                }
                BugReporting.show((int) ArgsRegistry.getRawValue(reportType));
                setOptions(options);
            }
        });
    }


}