package com.bradbumbalough.RCTTwilioChat;

import android.support.annotation.Nullable;

import com.facebook.react.bridge.Arguments;
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.Promise;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.ReadableMap;

import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.twilio.accessmanager.AccessManager;
import com.twilio.chat.Channel;
import com.twilio.chat.StatusListener;
import com.twilio.chat.CallbackListener;
import com.twilio.chat.ErrorInfo;
import com.twilio.chat.ChatClientListener;
import com.twilio.chat.ChatClient;
import com.twilio.chat.UserInfo;

import java.util.HashMap;
import java.util.Map;

import org.json.JSONObject;
import android.util.Log;


public class RCTTwilioChatClient extends ReactContextBaseJavaModule implements ChatClientListener {

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

    // Constants

    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();

        Map<String, String> channelStatus = new HashMap<>();
        channelStatus.put("Invited", Channel.ChannelStatus.INVITED.toString());
        channelStatus.put("Joined",Channel.ChannelStatus.JOINED.toString());
        channelStatus.put("NotParticipating",Channel.ChannelStatus.NOT_PARTICIPATING.toString());
        constants.put("TCHChannelStatus", channelStatus);

        Map<String, String> channelSyncStatus = new HashMap<>();
        channelSyncStatus.put("None",Channel.SynchronizationStatus.NONE.toString());
        channelSyncStatus.put("Identifier",Channel.SynchronizationStatus.IDENTIFIER.toString());
        channelSyncStatus.put("Metadata",Channel.SynchronizationStatus.METADATA.toString());
        channelSyncStatus.put("All",Channel.SynchronizationStatus.ALL.toString());
        channelSyncStatus.put("Failed",Channel.SynchronizationStatus.FAILED.toString());
        constants.put("TCHChannelSynchronizationStatus", channelSyncStatus);

        Map<String, String> channelType = new HashMap<>();
        channelType.put("Public",Channel.ChannelType.PUBLIC.toString());
        channelType.put("Private",Channel.ChannelType.PRIVATE.toString());
        constants.put("TCHChannelType", channelType);

        Map<String, String> clientSyncStatus = new HashMap<>();
        clientSyncStatus.put("Started",ChatClient.SynchronizationStatus.STARTED.toString());
        clientSyncStatus.put("ChannelListCompleted",ChatClient.SynchronizationStatus.CHANNELS_COMPLETED.toString());
        clientSyncStatus.put("Completed",ChatClient.SynchronizationStatus.COMPLETED.toString());
        clientSyncStatus.put("Failed",ChatClient.SynchronizationStatus.FAILED.toString());
        constants.put("TCHClientSynchronizationStatus", clientSyncStatus);

        Map<String, String> clientSyncStrategy = new HashMap<>();
        clientSyncStrategy.put("All",ChatClient.SynchronizationStrategy.ALL.toString());
        clientSyncStrategy.put("ChannelsList",ChatClient.SynchronizationStrategy.CHANNELS_LIST.toString());
        constants.put("TCHClientSynchronizationStrategy", clientSyncStrategy);

        Map<String, String> channelOption = new HashMap<>();
        channelOption.put("FriendlyName", "friendlyName");
        channelOption.put("UniqueName", "uniqueName");
        channelOption.put("Type", "type");
        channelOption.put("Attributes", "attributes");
        constants.put("TCHChannelOption", channelOption);

        Map<String, String> connectionState = new HashMap<>();
        connectionState.put("Connecting", ChatClient.ConnectionState.CONNECTING.toString());
        connectionState.put("Connected", ChatClient.ConnectionState.CONNECTED.toString());
        connectionState.put("Disconnected", ChatClient.ConnectionState.DISCONNECTED.toString());
        connectionState.put("Denied", ChatClient.ConnectionState.DENIED.toString());
        connectionState.put("Error", ChatClient.ConnectionState.FATAL_ERROR.toString());
        constants.put("TCHClientConnectionState", connectionState);

        Map<String, Integer> logLevel = new HashMap<>();
        logLevel.put("Fatal", Log.ERROR);
        logLevel.put("Critical", Log.ERROR);
        logLevel.put("Warning", Log.WARN);
        logLevel.put("Info", Log.INFO);
        logLevel.put("Debug", Log.DEBUG);
        constants.put("TCHLogLevel", logLevel);

        Map<String, String> userInfo = new HashMap<>();
        userInfo.put("Attributes", UserInfo.UpdateReason.ATTRIBUTES.toString());
        userInfo.put("FriendlyName", UserInfo.UpdateReason.FRIENDLY_NAME.toString());
        userInfo.put("ReachabilityNotifiable", UserInfo.UpdateReason.REACHABILITY_NOTIFIABLE.toString());
        userInfo.put("ReachabilityOnline", UserInfo.UpdateReason.REACHABILITY_ONLINE.toString());
        constants.put("TCHUserInfoUpdate", userInfo);

        return constants;
    }

    public ChatClient client = null;
    private ReactApplicationContext reactContext;

    private static RCTTwilioChatClient rctTwilioChatClient;

    public static RCTTwilioChatClient getInstance() {
        return rctTwilioChatClient;
    }

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

    private void sendEvent(String eventName, @Nullable WritableMap params) {
        RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();
        tmp.reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
    }

    private void sendEvent(String eventName, @Nullable String body) {
        RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();
        tmp.reactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, body);
    }

    private StatusListener generateStatusListener(final Promise promise, final String errorCode, final String errorMessage) {
        return new StatusListener() {
            @Override
            public void onError(ErrorInfo errorInfo) {
                super.onError(errorInfo);
                promise.reject(errorCode, errorMessage + " Error Message: " + errorInfo.getErrorText());
            }

            @Override
            public void onSuccess() {
                promise.resolve(true);
            }
        };
    }

    // Methods

    @ReactMethod
    public void createClient(String token, ReadableMap props, final Promise promise) {
        final RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();
        ChatClient.Properties.Builder builder = new ChatClient.Properties.Builder();

        if (props != null) {
            if (props.hasKey("initialMessageCount")) {
                builder.setInitialMessageCount(props.getInt("initialMessageCount"));
            }
            if (props.hasKey("synchronizationStrategy")) {
                builder.setSynchronizationStrategy(ChatClient.SynchronizationStrategy.valueOf(props.getString("synchronizationStrategy")));
            }
        }

        ChatClient.create(reactContext, token, builder.createProperties(), new CallbackListener<ChatClient>() {
            @Override
            public void onError(ErrorInfo errorInfo) {
                super.onError(errorInfo);
                promise.reject("create-client-error", "Error occurred while attempting to create the client. Error Message: " + errorInfo.getErrorText());
            }

            @Override
            public void onSuccess(ChatClient twilioChatClient) {
                tmp.client = twilioChatClient;
                tmp.client.setListener(tmp);
                promise.resolve(RCTConvert.ChatClient(tmp.client));
            }
        });
    }

    @ReactMethod
    public void userInfo(Promise promise) {
        RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();
        promise.resolve(RCTConvert.UserInfo(tmp.client.getMyUserInfo()));
    }

    @ReactMethod
    public void register(String token) {
        RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();

        StatusListener listener = new StatusListener() {
            @Override
            public void onError(ErrorInfo errorInfo) {
                super.onError(errorInfo);
            }

            @Override
            public void onSuccess() {

            }
        };
        tmp.client.registerGCMToken(token, listener);
    }

    @ReactMethod
    public void unregister(String token) {
        RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();

        StatusListener listener = new StatusListener() {
            @Override
            public void onError(ErrorInfo errorInfo) {
                super.onError(errorInfo);
            }

            @Override
            public void onSuccess() {

            }
        };
        tmp.client.unregisterGCMToken(token, listener);
    }

    @ReactMethod
    public void handleNotification(ReadableMap notification) {
        RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();
        HashMap map = RCTConvert.readableMapToHashMap(notification);
//        TODO
//        tmp.client.handleNotification(map);
    }

    @ReactMethod
    public void shutdown() {
        RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();
        tmp.client.shutdown();
    }

    @ReactMethod
    public void setLogLevel(Integer logLevel) {
        ChatClient.setLogLevel(logLevel);
    }

    // UserInfo methods

    @ReactMethod
    public void setFriendlyName(String friendlyName, final Promise promise) {
        RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();

        StatusListener listener = new StatusListener() {
            @Override
            public void onError(ErrorInfo errorInfo) {
                super.onError(errorInfo);
                promise.reject("set-friendly-name-error", "Error occurred while attempting to setFriendlyName on user.");
            }

            @Override
            public void onSuccess() {
                promise.resolve(true);
            }
        };

        tmp.client.getMyUserInfo().setFriendlyName(friendlyName, listener);
    }

    @ReactMethod
    public void setAttributes(ReadableMap attributes, final Promise promise) {
        RCTTwilioChatClient tmp = RCTTwilioChatClient.getInstance();
        JSONObject json = RCTConvert.readableMapToJson(attributes);

        StatusListener listener = new StatusListener() {
            @Override
            public void onError(ErrorInfo errorInfo) {
                super.onError(errorInfo);
                promise.reject("set-attributes-error", "Error occurred while attempting to setAttributes on user.");
            }

            @Override
            public void onSuccess() {
                promise.resolve(true);
            }
        };

        tmp.client.getMyUserInfo().setAttributes(json, listener);
    }

    // Listeners

    @Override
    public void onConnectionStateChange(ChatClient.ConnectionState state) {
        sendEvent("chatClient:connectionStateChanged", state.toString());
    }

    @Override
    public void onChannelAdd(final Channel channel) {
        sendEvent("chatClient:channelAdded", RCTConvert.Channel(channel));
    }

    @Override
    public void onChannelChange(Channel channel) {
        sendEvent("chatClient:channelChanged", RCTConvert.Channel(channel));
    }

    @Override
    public void onChannelInvite(Channel channel) {
        sendEvent("chatClient:channelInvited", RCTConvert.Channel(channel));
    }

    @Override
    public void onChannelDelete(Channel channel){
        sendEvent("chatClient:channelRemoved", RCTConvert.Channel(channel));
    }

    @Override
    public void onChannelSynchronizationChange(Channel channel) {
        WritableMap map = Arguments.createMap();
        map.putString("channelSid",channel.getSid());
        map.putString("status", channel.getSynchronizationStatus().toString());

        sendEvent("chatClient:channel:synchronizationStatusChanged", map);
    }

    @Override
    public void onClientSynchronization(ChatClient.SynchronizationStatus synchronizationStatus) {
        sendEvent("chatClient:synchronizationStatusChanged", synchronizationStatus.toString());
    }

    @Override
    public void onError(ErrorInfo errorInfo) {
        WritableMap map = Arguments.createMap();
        map.putString("error",errorInfo.getErrorText());
        map.putString("userInfo", errorInfo.toString());

        sendEvent("chatClient:errorReceived", map);
    }

    @Override
    public void onToastFailed(ErrorInfo errorInfo) {
        WritableMap map = Arguments.createMap();
        map.putString("error",errorInfo.getErrorText());
        map.putString("userInfo", errorInfo.toString());

        sendEvent("chatClient:toastFailed", map);
    }

    @Override
    public void onToastSubscribed() {
        sendEvent("chatClient:toastSubscribed", "");
    }

    @Override
    public void onToastNotification(String channelSid, String messageSid) {
        WritableMap map = Arguments.createMap();
        map.putString("channelSid", channelSid);
        map.putString("messageSid", messageSid);

        sendEvent("chatClient:toastReceived", map);
    }

    @Override
    public void onUserInfoChange(UserInfo userInfo, UserInfo.UpdateReason updateReason) {
        WritableMap map = Arguments.createMap();
        map.putString("updated", updateReason.toString());
        map.putMap("userInfo", RCTConvert.UserInfo(userInfo));
        sendEvent("chatClient:userInfoUpdated", map);
    }
}