package com.azure.reactnative.notificationhub;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;

import androidx.core.app.NotificationManagerCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.WritableMap;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;

import com.microsoft.windowsazure.messaging.NotificationHub;

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.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.UiThreadUtil;

import static com.azure.reactnative.notificationhub.ReactNativeConstants.*;

public class ReactNativeNotificationHubModule extends ReactContextBaseJavaModule implements
        ActivityEventListener, LifecycleEventListener {
    private static final int PLAY_SERVICES_RESOLUTION_REQUEST = 9000;
    private static final int NOTIFICATION_DELAY_ON_START = 3000;

    private ReactApplicationContext mReactContext;
    private LocalBroadcastReceiver mLocalBroadcastReceiver;

    public ReactNativeNotificationHubModule(ReactApplicationContext reactContext) {
        super(reactContext);
        this.mReactContext = reactContext;
        this.mLocalBroadcastReceiver = new LocalBroadcastReceiver();
        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(reactContext);
        localBroadcastManager.registerReceiver(mLocalBroadcastReceiver, new IntentFilter(ReactNativeRegistrationIntentService.TAG));
        localBroadcastManager.registerReceiver(mLocalBroadcastReceiver, new IntentFilter(ReactNativeNotificationsHandler.TAG));
        reactContext.addLifecycleEventListener(this);
        reactContext.addActivityEventListener(this);
    }

    @Override
    public String getName() {
        return AZURE_NOTIFICATION_HUB_NAME;
    }

    public void setIsForeground(boolean isForeground) {
        ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance();
        notificationHubUtil.setAppIsForeground(isForeground);
    }

    public boolean getIsForeground() {
        ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance();
        return notificationHubUtil.getAppIsForeground();
    }

    @ReactMethod
    public void getInitialNotification(Promise promise) {
        Activity activity = getCurrentActivity();
        if (activity == null) {
            promise.reject(ERROR_GET_INIT_NOTIFICATION, ERROR_ACTIVITY_IS_NULL);
            return;
        }

        Intent intent = activity.getIntent();
        if (intent != null && intent.getAction() != null) {
            if (intent.getExtras() == null) {
                // In certain cases while app cold launches, i.getExtras() returns null.
                // Adding the check to make sure app won't crash,
                // and still successfully launches from notification
                promise.reject(ERROR_GET_INIT_NOTIFICATION, ERROR_INTENT_EXTRAS_IS_NULL);
            } else {
                promise.resolve(ReactNativeUtil.convertBundleToMap(intent.getExtras()));
            }
        } else {
            promise.reject(ERROR_GET_INIT_NOTIFICATION, ERROR_ACTIVITY_INTENT_IS_NULL);
        }
    }

    @ReactMethod
    public void register(ReadableMap config, Promise promise) {
        ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance();
        String connectionString = config.getString(KEY_REGISTRATION_CONNECTIONSTRING);
        if (connectionString == null) {
            promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_CONNECTION_STRING);
            return;
        }

        String hubName = config.getString(KEY_REGISTRATION_HUBNAME);
        if (hubName == null) {
            promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_HUBNAME);
            return;
        }

        String senderID = config.getString(KEY_REGISTRATION_SENDERID);
        if (senderID == null) {
            promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_SENDER_ID);
            return;
        }

        String[] tags = null;
        if (config.hasKey(KEY_REGISTRATION_TAGS) && !config.isNull(KEY_REGISTRATION_TAGS)) {
            ReadableArray tagsJson = config.getArray(KEY_REGISTRATION_TAGS);
            tags = new String[tagsJson.size()];
            for (int i = 0; i < tagsJson.size(); ++i) {
                tags[i] = tagsJson.getString(i);
            }
        }

        ReactContext reactContext = getReactApplicationContext();
        notificationHubUtil.setConnectionString(reactContext, connectionString);
        notificationHubUtil.setHubName(reactContext, hubName);
        notificationHubUtil.setSenderID(reactContext, senderID);
        notificationHubUtil.setTemplated(reactContext, false);
        notificationHubUtil.setTags(reactContext, tags);

        if (config.hasKey(KEY_REGISTRATION_CHANNELNAME)) {
            String channelName = config.getString(KEY_REGISTRATION_CHANNELNAME);
            notificationHubUtil.setChannelName(reactContext, channelName);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELDESCRIPTION)) {
            String channelDescription = config.getString(KEY_REGISTRATION_CHANNELDESCRIPTION);
            notificationHubUtil.setChannelDescription(reactContext, channelDescription);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELIMPORTANCE)) {
            int channelImportance = config.getInt(KEY_REGISTRATION_CHANNELIMPORTANCE);
            notificationHubUtil.setChannelImportance(reactContext, channelImportance);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELSHOWBADGE)) {
            boolean channelShowBadge = config.getBoolean(KEY_REGISTRATION_CHANNELSHOWBADGE);
            notificationHubUtil.setChannelShowBadge(reactContext, channelShowBadge);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELENABLELIGHTS)) {
            boolean channelEnableLights = config.getBoolean(KEY_REGISTRATION_CHANNELENABLELIGHTS);
            notificationHubUtil.setChannelEnableLights(reactContext, channelEnableLights);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELENABLEVIBRATION)) {
            boolean channelEnableVibration = config.getBoolean(KEY_REGISTRATION_CHANNELENABLEVIBRATION);
            notificationHubUtil.setChannelEnableVibration(reactContext, channelEnableVibration);
        }

        String uuid = notificationHubUtil.getUUID(reactContext);
        if (uuid == null) {
            uuid = ReactNativeUtil.genUUID();
            notificationHubUtil.setUUID(reactContext, uuid);
        }

        GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
        int resultCode = apiAvailability.isGooglePlayServicesAvailable(reactContext);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (apiAvailability.isUserResolvableError(resultCode)) {
                UiThreadUtil.runOnUiThread(
                        new GoogleApiAvailabilityRunnable(
                                getCurrentActivity(),
                                apiAvailability,
                                resultCode));
                promise.reject(ERROR_PLAY_SERVICES, ERROR_PLAY_SERVICES_DISABLED);
            } else {
                promise.reject(ERROR_PLAY_SERVICES, ERROR_PLAY_SERVICES_UNSUPPORTED);
            }
            return;
        }

        Intent intent = ReactNativeNotificationHubUtil.IntentFactory.createIntent(
                reactContext, ReactNativeRegistrationIntentService.class);
        ReactNativeRegistrationIntentService.enqueueWork(reactContext, intent);

        WritableMap res = Arguments.createMap();
        res.putString(KEY_PROMISE_RESOLVE_UUID, uuid);
        promise.resolve(res);
    }

    @ReactMethod
    public void registerTemplate(ReadableMap config, Promise promise) {
        ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance();
        String connectionString = config.getString(KEY_REGISTRATION_CONNECTIONSTRING);
        if (connectionString == null) {
            promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_CONNECTION_STRING);
            return;
        }

        String hubName = config.getString(KEY_REGISTRATION_HUBNAME);
        if (hubName == null) {
            promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_HUBNAME);
            return;
        }

        String senderID = config.getString(KEY_REGISTRATION_SENDERID);
        if (senderID == null) {
            promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_SENDER_ID);
            return;
        }

        String templateName = config.getString(KEY_REGISTRATION_TEMPLATENAME);
        if (templateName == null) {
            promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_TEMPLATE_NAME);
            return;
        }

        String template = config.getString(KEY_REGISTRATION_TEMPLATE);
        if (template == null) {
            promise.reject(ERROR_INVALID_ARGUMENTS, ERROR_INVALID_TEMPLATE);
            return;
        }

        String[] tags = null;
        if (config.hasKey(KEY_REGISTRATION_TAGS) && !config.isNull(KEY_REGISTRATION_TAGS)) {
            ReadableArray tagsJson = config.getArray(KEY_REGISTRATION_TAGS);
            tags = new String[tagsJson.size()];
            for (int i = 0; i < tagsJson.size(); ++i) {
                tags[i] = tagsJson.getString(i);
            }
        }

        ReactContext reactContext = getReactApplicationContext();
        notificationHubUtil.setConnectionString(reactContext, connectionString);
        notificationHubUtil.setHubName(reactContext, hubName);
        notificationHubUtil.setSenderID(reactContext, senderID);
        notificationHubUtil.setTemplateName(reactContext, templateName);
        notificationHubUtil.setTemplate(reactContext, template);
        notificationHubUtil.setTemplated(reactContext, true);
        notificationHubUtil.setTags(reactContext, tags);

        if (config.hasKey(KEY_REGISTRATION_CHANNELNAME)) {
            String channelName = config.getString(KEY_REGISTRATION_CHANNELNAME);
            notificationHubUtil.setChannelName(reactContext, channelName);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELDESCRIPTION)) {
            String channelDescription = config.getString(KEY_REGISTRATION_CHANNELDESCRIPTION);
            notificationHubUtil.setChannelDescription(reactContext, channelDescription);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELIMPORTANCE)) {
            int channelImportance = config.getInt(KEY_REGISTRATION_CHANNELIMPORTANCE);
            notificationHubUtil.setChannelImportance(reactContext, channelImportance);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELSHOWBADGE)) {
            boolean channelShowBadge = config.getBoolean(KEY_REGISTRATION_CHANNELSHOWBADGE);
            notificationHubUtil.setChannelShowBadge(reactContext, channelShowBadge);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELENABLELIGHTS)) {
            boolean channelEnableLights = config.getBoolean(KEY_REGISTRATION_CHANNELENABLELIGHTS);
            notificationHubUtil.setChannelEnableLights(reactContext, channelEnableLights);
        }

        if (config.hasKey(KEY_REGISTRATION_CHANNELENABLEVIBRATION)) {
            boolean channelEnableVibration = config.getBoolean(KEY_REGISTRATION_CHANNELENABLEVIBRATION);
            notificationHubUtil.setChannelEnableVibration(reactContext, channelEnableVibration);
        }

        String uuid = notificationHubUtil.getUUID(reactContext);
        if (uuid == null) {
            uuid = ReactNativeUtil.genUUID();
            notificationHubUtil.setUUID(reactContext, uuid);
        }

        GoogleApiAvailability apiAvailability = GoogleApiAvailability.getInstance();
        int resultCode = apiAvailability.isGooglePlayServicesAvailable(reactContext);
        if (resultCode != ConnectionResult.SUCCESS) {
            if (apiAvailability.isUserResolvableError(resultCode)) {
                UiThreadUtil.runOnUiThread(
                        new GoogleApiAvailabilityRunnable(
                                getCurrentActivity(),
                                apiAvailability,
                                resultCode));
                promise.reject(ERROR_PLAY_SERVICES, ERROR_PLAY_SERVICES_DISABLED);
            } else {
                promise.reject(ERROR_PLAY_SERVICES, ERROR_PLAY_SERVICES_UNSUPPORTED);
            }
            return;
        }

        Intent intent = ReactNativeNotificationHubUtil.IntentFactory.createIntent(
                reactContext, ReactNativeRegistrationIntentService.class);
        ReactNativeRegistrationIntentService.enqueueWork(reactContext, intent);

        WritableMap res = Arguments.createMap();
        res.putString(KEY_PROMISE_RESOLVE_UUID, uuid);
        promise.resolve(res);
    }

    @ReactMethod
    public void unregister(Promise promise) {
        ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance();

        ReactContext reactContext = getReactApplicationContext();
        String connectionString = notificationHubUtil.getConnectionString(reactContext);
        String hubName = notificationHubUtil.getHubName(reactContext);
        String registrationId = notificationHubUtil.getRegistrationID(reactContext);

        if (connectionString == null || hubName == null || registrationId == null) {
            promise.reject(ERROR_NOT_REGISTERED, ERROR_NOT_REGISTERED_DESC);
            return;
        }

        NotificationHub hub = ReactNativeUtil.createNotificationHub(hubName, connectionString, reactContext);
        try {
            hub.unregister();
            notificationHubUtil.setRegistrationID(reactContext, null);
            notificationHubUtil.setUUID(reactContext, null);
            promise.resolve(AZURE_NOTIFICATION_HUB_UNREGISTERED);
        } catch (Exception e) {
            promise.reject(ERROR_NOTIFICATION_HUB, e);
        }
    }

    @ReactMethod
    public void unregisterTemplate(String templateName, Promise promise) {
        ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance();

        ReactContext reactContext = getReactApplicationContext();
        String connectionString = notificationHubUtil.getConnectionString(reactContext);
        String hubName = notificationHubUtil.getHubName(reactContext);
        String registrationId = notificationHubUtil.getRegistrationID(reactContext);

        if (connectionString == null || hubName == null || registrationId == null) {
            promise.reject(ERROR_NOT_REGISTERED, ERROR_NOT_REGISTERED_DESC);
            return;
        }

        NotificationHub hub = ReactNativeUtil.createNotificationHub(hubName, connectionString, reactContext);
        try {
            hub.unregisterTemplate(templateName);
            notificationHubUtil.setRegistrationID(reactContext, null);
            notificationHubUtil.setUUID(reactContext, null);
            promise.resolve(AZURE_NOTIFICATION_HUB_UNREGISTERED);
        } catch (Exception e) {
            promise.reject(ERROR_NOTIFICATION_HUB, e);
        }
    }

    @ReactMethod
    public void getUUID(Boolean autoGen, Promise promise) {
        ReactNativeNotificationHubUtil notificationHubUtil = ReactNativeNotificationHubUtil.getInstance();
        ReactContext reactContext = getReactApplicationContext();
        String uuid = notificationHubUtil.getUUID(reactContext);

        if (uuid != null) {
            promise.resolve(uuid);
        } else if (autoGen) {
            uuid = ReactNativeUtil.genUUID();
            notificationHubUtil.setUUID(reactContext, uuid);
            promise.resolve(uuid);
        } else {
            promise.reject(ERROR_GET_UUID, ERROR_NO_UUID_SET);
        }
    }

    @ReactMethod
    public void isNotificationEnabledOnOSLevel(Promise promise) {
        ReactContext reactContext = getReactApplicationContext();
        NotificationManagerCompat notificationManagerCompat = NotificationManagerCompat.from(reactContext);
        boolean areNotificationsEnabled = notificationManagerCompat.areNotificationsEnabled();
        promise.resolve(areNotificationsEnabled);
    }

    @Override
    public void onHostResume() {
        setIsForeground(true);

        Activity activity = getCurrentActivity();
        if (activity != null) {
            Intent intent = activity.getIntent();
            if (intent != null) {
                Bundle bundle = ReactNativeUtil.getBundleFromIntent(intent);
                if (bundle != null) {
                    ReactNativeUtil.removeNotificationFromIntent(intent);
                    bundle.putBoolean(KEY_REMOTE_NOTIFICATION_FOREGROUND, false);
                    bundle.putBoolean(KEY_REMOTE_NOTIFICATION_USER_INTERACTION, true);
                    bundle.putBoolean(KEY_REMOTE_NOTIFICATION_COLDSTART, true);
                    ReactNativeNotificationsHandler.sendBroadcast(
                            mReactContext, bundle, NOTIFICATION_DELAY_ON_START);
                }
            }
        }
    }

    @Override
    public void onHostPause() {
        setIsForeground(false);
    }

    @Override
    public void onHostDestroy() {
    }

    @Override
    public void onNewIntent(Intent intent) {
        Bundle bundle = ReactNativeUtil.getBundleFromIntent(intent);
        if (bundle != null) {
            bundle.putBoolean(KEY_REMOTE_NOTIFICATION_FOREGROUND, false);
            bundle.putBoolean(KEY_REMOTE_NOTIFICATION_USER_INTERACTION, true);
            ReactNativeNotificationsHandler.sendBroadcast(
                    mReactContext, bundle, NOTIFICATION_DELAY_ON_START);
        }
    }

    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
    }

    public class LocalBroadcastReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            if (getIsForeground()) {
                ReactNativeUtil.emitIntent(mReactContext, intent);
            }
        }
    }

    private static class GoogleApiAvailabilityRunnable implements Runnable {
        private final Activity activity;
        private final GoogleApiAvailability apiAvailability;
        private final int resultCode;

        public GoogleApiAvailabilityRunnable(
                Activity activity,
                GoogleApiAvailability apiAvailability,
                int resultCode) {
            this.activity = activity;
            this.apiAvailability = apiAvailability;
            this.resultCode = resultCode;
        }

        @Override
        public void run() {
            apiAvailability.getErrorDialog(activity, resultCode, PLAY_SERVICES_RESOLUTION_REQUEST).show();
        }
    }
}