package ugen.fy.plugin;

import javax.annotation.Nullable;

import android.Manifest;
import android.app.Activity;
import android.app.Application;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.location.LocationManager;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.aliyun.alink.business.devicecenter.api.discovery.DiscoveryType;
import com.aliyun.alink.linksdk.tmp.TmpSdk;
import com.aliyun.alink.linksdk.tmp.api.DeviceManager;
import com.aliyun.alink.linksdk.tmp.api.OutputParams;
import com.aliyun.alink.linksdk.tmp.device.panel.PanelDevice;
import com.aliyun.alink.linksdk.tmp.device.panel.listener.IPanelCallback;
import com.aliyun.alink.linksdk.tmp.device.panel.listener.IPanelEventCallback;
import com.aliyun.alink.linksdk.tmp.listener.IDevListener;
import com.aliyun.alink.linksdk.tmp.utils.ErrorInfo;
import com.aliyun.iot.aep.sdk.IoTSmart;
import com.aliyun.iot.aep.sdk.apiclient.callback.IoTCallback;
import com.aliyun.iot.aep.sdk.apiclient.callback.IoTResponse;
import com.aliyun.iot.aep.sdk.apiclient.request.IoTRequest;
import com.aliyun.iot.aep.sdk.credential.IotCredentialManager.IoTCredentialManageImpl;
import com.aliyun.iot.aep.sdk.credential.listener.IoTTokenInvalidListener;
import com.aliyun.iot.aep.sdk.framework.AApplication;
import com.aliyun.iot.aep.sdk.login.ILoginCallback;
import com.aliyun.iot.aep.sdk.login.ILogoutCallback;
import com.aliyun.iot.aep.sdk.login.LoginBusiness;
import com.aliyun.iot.ilop.startpage.list.main.countryselect.CountryListActivity;
import com.facebook.react.bridge.ActivityEventListener;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
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.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

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

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class AliLiving extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener {

    // Debugging
    private static final boolean D = true;

    private static final int REQUEST_CODE_LOCATION_SETTINGS = 2;

    private static final int ACCESS_COARSE_LOCATION_RESULT_CODE = 4;
    private static final int BLUETOOTH_RESULT_CODE = 5;

    // Event names
    public static final String BT_ENABLED = "bluetoothEnabled";
    public static final String BT_DISABLED = "bluetoothDisabled";
    public static final String SYSTEM_LOCATION_ENABLED = "systemLocationEnabled";
    public static final String SYSTEM_LOCATION_DISABLED = "systemLocationDisabled";
    public static final String SERVICE_CONNECTED = "serviceConnected";
    public static final String SERVICE_DISCONNECTED = "serviceDisconnected";
    public static final String NOTIFICATION_ONLINE_STATUS = "notificationOnlineStatus";
    public static final String NOTIFICATION_GET_DEVICE_STATE = "notificationGetDeviceState";
    public static final String NOTIFICATION_VENDOR_RESPONSE = "notificationVendorResponse";
    public static final String NOTIFICATION_DATA_GET_ON_OFF = "notificationDataGetOnOff";
    public static final String NOTIFICATION_DATA_GET_LEVEL = "notificationDataGetLevel";
    public static final String NOTIFICATION_DATA_GET_LIGHTNESS = "notificationDataGetLightness";
    public static final String NOTIFICATION_DATA_GET_CTL = "notificationDataGetCtl";
    public static final String NOTIFICATION_DATA_GET_TEMP = "notificationDataGetTemp";
    public static final String NOTIFICATION_DATA_GET_VERSION = "notificationDataGetVersion";
    public static final String NOTIFICATION_DATA_GET_MESH_OTA_PROGRESS = "notificationDataGetMeshOtaProgress";
    public static final String NOTIFICATION_DATA_GET_MESH_OTA_APPLY_STATUS = "notificationDataGetMeshOtaApplyStatus";
    public static final String NOTIFICATION_DATA_GET_MESH_OTA_FIRMWARE_DISTRIBUTION_STATUS = "notificationDataGetMeshOtaFirmwareDistributionStatus";
    public static final String NOTIFICATION_DATA_GET_OTA_STATE = "notificationDataGetOtaState";
    public static final String NOTIFICATION_DATA_SET_OTA_MODE_RES = "notificationDataSetOtaModeRes";
    public static final String DEVICE_STATUS_CONNECTING = "deviceStatusConnecting";
    public static final String DEVICE_STATUS_CONNECTED = "deviceStatusConnected";
    public static final String DEVICE_STATUS_LOGINING = "deviceStatusLogining";
    public static final String DEVICE_STATUS_LOGIN = "deviceStatusLogin";
    public static final String DEVICE_STATUS_LOGOUT = "deviceStatusLogout";
    public static final String DEVICE_STATUS_ERROR_N = "deviceStatusErrorAndroidN";
    public static final String DEVICE_STATUS_UPDATE_MESH_COMPLETED = "deviceStatusUpdateMeshCompleted";
    public static final String DEVICE_STATUS_UPDATING_MESH = "deviceStatusUpdatingMesh";
    public static final String DEVICE_STATUS_UPDATE_MESH_FAILURE = "deviceStatusUpdateMeshFailure";
    public static final String DEVICE_STATUS_UPDATE_ALL_MESH_COMPLETED = "deviceStatusUpdateAllMeshCompleted";
    public static final String DEVICE_STATUS_GET_LTK_COMPLETED = "deviceStatusGetLtkCompleted";
    public static final String DEVICE_STATUS_GET_LTK_FAILURE = "deviceStatusGetLtkFailure";
    public static final String DEVICE_STATUS_MESH_OFFLINE = "deviceStatusMeshOffline";
    public static final String DEVICE_STATUS_MESH_SCAN_COMPLETED = "deviceStatusMeshScanCompleted";
    public static final String DEVICE_STATUS_MESH_SCAN_TIMEOUT = "deviceStatusMeshScanTimeout";
    public static final String DEVICE_STATUS_OTA_MASTER_PROGRESS = "deviceStatusOtaMasterProgress";
    public static final String DEVICE_STATUS_OTA_MASTER_COMPLETE = "deviceStatusOtaMasterComplete";
    public static final String DEVICE_STATUS_OTA_MASTER_FAIL = "deviceStatusOtaMasterFail";
    public static final String DEVICE_STATUS_GET_FIRMWARE_COMPLETED = "deviceStatusGetFirmwareCompleted";
    public static final String DEVICE_STATUS_GET_FIRMWARE_FAILURE = "deviceStatusGetFirmwareFailure";
    public static final String DEVICE_STATUS_DELETE_COMPLETED = "deviceStatusDeleteCompleted";
    public static final String DEVICE_STATUS_DELETE_FAILURE = "deviceStatusDeleteFailure";
    public static final String LE_SCAN = "leScan";
    public static final String LE_SCAN_COMPLETED = "leScanCompleted";
    public static final String LE_SCAN_TIMEOUT = "leScanTimeout";
    public static final String MESH_OFFLINE = "meshOffline";
    public static final String SAVE_OR_UPDATE_JS = "saveOrUpdateJS";

    public static final String TAG = AliLiving.class.getSimpleName();
    private BluetoothAdapter mBluetoothAdapter;
    private ReactApplicationContext mReactContext;
    protected Context mContext;
    private FYSDK sdk;
    // private Handler mHandler = new Handler(Looper.getMainLooper());
    private boolean is1stResume = true;

    public Map<String, Object> panelDevices = new HashMap<>();

    final BroadcastReceiver mBluetoothStateReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            final String action = intent.getAction();

            if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
                final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR);
                switch (state) {
                    case BluetoothAdapter.STATE_OFF:
                        if (D) Log.d(TAG, "Bluetooth was disabled");
                        sendEvent(BT_DISABLED);
                        break;
                    case BluetoothAdapter.STATE_ON:
                        if (D) Log.d(TAG, "Bluetooth was enabled");
                        sendEvent(BT_ENABLED);
                        break;
                }
            }
        }
    };

    public AliLiving(ReactApplicationContext reactContext) {
        super(reactContext);
        mReactContext = reactContext;
        mContext = mReactContext.getApplicationContext();
    }

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

    @Override
    public void onActivityResult(Activity activity, int requestCode, int resultCode, Intent intent) {
        if (D) Log.d(TAG, "On activity result request: " + requestCode + ", result: " + resultCode);
        // if (requestCode == REQUEST_ENABLE_BLUETOOTH) {
        //     if (resultCode == Activity.RESULT_OK) {
        //         if (D) Log.d(TAG, "User enabled Bluetooth");
        //         if (mEnabledPromise != null) {
        //             mEnabledPromise.resolve(true);
        //         }
        //     } else {
        //         if (D) Log.d(TAG, "User did *NOT* enable Bluetooth");
        //         if (mEnabledPromise != null) {
        //             mEnabledPromise.reject(new Exception("User did not enable Bluetooth"));
        //         }
        //     }
        //     mEnabledPromise = null;
        // }

        // if (requestCode == REQUEST_PAIR_DEVICE) {
        //     if (resultCode == Activity.RESULT_OK) {
        //         if (D) Log.d(TAG, "Pairing ok");
        //     } else {
        //         if (D) Log.d(TAG, "Pairing failed");
        //     }
        // }

        if (requestCode == REQUEST_CODE_LOCATION_SETTINGS) {
            checkSystemLocation();
        }
    }

    private void gotoCountryList() {
        IoTSmart.ICountrySelectCallBack callBack = (country) -> {
            IoTSmart.setCountry(country, new IoTSmart.ICountrySetCallBack() {
                @Override
                public void onCountrySet(boolean needRestartApp) {
                    if (needRestartApp) {
                        if (D) Log.d(TAG, "needRestartApp");
                        sendEvent(SERVICE_DISCONNECTED);
                    } else {
                        SDKInitHelper.init(AApplication.getInstance());
                        startLogin();
                    }
                }
            });
        };

        boolean useDefault = true;
        // 是否使用默认的国家选择页面
        if (!useDefault) {
            Intent intent = new Intent(getCurrentActivity(), CountryListActivity.class);
            intent.putExtra("type", "mail");
            getCurrentActivity().startActivity(intent);
        } else {
            IoTSmart.showCountryList(callBack);
        }

    }

    private void startLogin() {
        //跳转到登录界面
        LoginBusiness.login(new ILoginCallback() {
            @Override
            public void onLoginSuccess() {
                sendEvent(SERVICE_CONNECTED);
                if (D) Log.d(TAG, "onLoginSuccess");
            }

            @Override
            public void onLoginFailed(int i, String s) {
                // LinkToast.makeText(getApplicationContext(), s).show();
                if (D) Log.d(TAG, "onLoginFailed");
            }
        });
    }

    @Override
    public void onNewIntent(Intent intent) {
        if (D) Log.d(TAG, "On new intent");
    }

    @Override
    public void onHostResume() {
        if (D) Log.d(TAG, "Host resume");
        if (sdk != null && is1stResume) {
            // don't know why we need is1stResume in react-native-ali-smartliving,
            // otherwise ActivityCompat.requestPermissions() in doResume() below
            // will cause onHostPause() then loop back onHostResume() again ... frequently,
            // therefor APP stuck
            is1stResume = false;
            this.doResume();
        }
    }

    private void checkPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // M is Android API 23
            boolean reqPermLoc = false;
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Q is Android API 29
                reqPermLoc = ContextCompat.checkSelfPermission(getCurrentActivity(),
                    Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED ||
                ContextCompat.checkSelfPermission(getCurrentActivity(),
                    Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED;
            } else {
                // If use above when running on Android 9 (SDK < 29), and use
                // checkPermissions() in doResume(), will frequently
                // sendEvent(SYSTEM_LOCATION_ENABLED) to JS which cause APP stuck,
                // that's why need below to prevent it.
                // If use below when running on Android 10 (SDK >= 29), will not
                // have any device result after startScan(), that's why need above.
                reqPermLoc = ContextCompat.checkSelfPermission(getCurrentActivity(),
                    Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED;
            }

            if (reqPermLoc) {
                ActivityCompat.requestPermissions(getCurrentActivity(),
                        new String[]{Manifest.permission.ACCESS_COARSE_LOCATION,
                            Manifest.permission.ACCESS_FINE_LOCATION},
                        ACCESS_COARSE_LOCATION_RESULT_CODE);
            }
            if (ContextCompat.checkSelfPermission(getCurrentActivity(),
                    Manifest.permission.BLUETOOTH) != PackageManager.PERMISSION_GRANTED) {
                ActivityCompat.requestPermissions(getCurrentActivity(),
                        new String[]{Manifest.permission.BLUETOOTH},
                        BLUETOOTH_RESULT_CODE);
            }
            // if (ContextCompat.checkSelfPermission(getCurrentActivity(),
            //         Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            //     ActivityCompat.requestPermissions(getCurrentActivity(),
            //             new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
            //             STORAGE_RESULT_CODE);
            // }
        }
    }

    public boolean isLocationEnable() {
        LocationManager lm = null;
        boolean gps_enabled = false;
        boolean network_enabled = false;

        lm = (LocationManager) getCurrentActivity().getSystemService(mContext.LOCATION_SERVICE);
        // exceptions will be thrown if provider is not permitted.
        try {
            gps_enabled = lm.isProviderEnabled(LocationManager.GPS_PROVIDER);
            Log.d(TAG, "gps_enabled: " + gps_enabled);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        try {
            network_enabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
            Log.d(TAG, "network_enabled:" + network_enabled);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return gps_enabled || network_enabled;
    }

    private void checkSystemLocation() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (isLocationEnable()) {
                sendEvent(SYSTEM_LOCATION_ENABLED);
            } else {
                sendEvent(SYSTEM_LOCATION_DISABLED);
            }
        }
    }

    @ReactMethod
    public void enableBluetooth() {
        if (mBluetoothAdapter != null && !mBluetoothAdapter.isEnabled()) {
            mBluetoothAdapter.enable();
        }
    }

    @ReactMethod
    public void enableSystemLocation() {
        Intent locationIntent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
        getCurrentActivity().startActivityForResult(locationIntent, REQUEST_CODE_LOCATION_SETTINGS);
    }

    @Override
    public void onHostPause() {
        if (D) Log.d(TAG, "Host pause");
    }

    @Override
    public void onHostDestroy() {
        if (D) Log.d(TAG, "Host destroy");
        // APP 切到后台时也会调用此处，导致切回前台 Resume 时无法再正常使用本组件，因此使不在此处调用 doDestroySdk
        // this.doDestroySdk();
    }

    @Override
    public void onCatalystInstanceDestroy() {
        if (D) Log.d(TAG, "Catalyst instance destroyed");
        super.onCatalystInstanceDestroy();
        this.doDestroySdk();
    }


    /**
     * 获取设备状态
     */
    public void getStatus(String iotId) {
        PanelDevice panelDevice = (PanelDevice)panelDevices.get(iotId);
        if (panelDevice != null) {
            panelDevice.getStatus((isSuccess, data) -> {
                Log.d(TAG, "statusCallback isSuccess->" + isSuccess + " data->" + data);
// data->{
//     "code": 200,
//     "data": {
//         "time": 1604976420650,
//         "status": 1
//     }
// }

                if (isSuccess) {
                    WritableMap map = Arguments.createMap();
                    map.putString("meshAddress", iotId);
                    map.putString("opcode", "getStatus");
                    map.putString("params", data.toString());
                    sendEvent(NOTIFICATION_VENDOR_RESPONSE, map);
                }
            });
        }
    }

    /**
     * 获取设备属性
     */
    public void getProperties(String iotId) {
        PanelDevice panelDevice = (PanelDevice)panelDevices.get(iotId);
        if (panelDevice != null) {
            panelDevice.getProperties((isSuccess, data) -> {
                Log.d(TAG, "getPropsCallBack isSuccess->" + isSuccess + " data->" + data);
// // from online router
// isSuccess->true
// data->{
//     "code": 200,
//     "data": {
//         "CountDownList": {
//             "time": 1604371244935,
//             "value": {}
//         },
//         "PowerSwitch": {
//             "time": 1604967137081,
//             "value": 1
//         },
//         "LocalTimer": {
//             "time": 1604371244935,
//             "value": []
//         },
//         "CountDown": {
//             "time": 1604371244935,
//             "value": {}
//         }
//     }
// }

// // from offline router
// isSuccess->true
// data->{
//     "code": 200,
//     "message": "success",
//     "data": {
//         "CountDownList": {
//             "time": 0,
//             "value": {
//                 "Target": "PowerSwitch",
//                 "Contents": "",
//                 "PowerSwitch": 0
//             }
//         },
//         "PowerSwitch": {
//             "time": 0,
//             "value": 1
//         },
//         "LocalTimer": {
//             "time": 0,
//             "value": []
//         }
//     }
// }

                if (isSuccess) {
                    WritableMap map = Arguments.createMap();
                    map.putString("meshAddress", iotId);
                    map.putString("opcode", "getProperties");
                    map.putString("params", data.toString());
                    sendEvent(NOTIFICATION_VENDOR_RESPONSE, map);
                }
            });
        }
    }

    @ReactMethod
    public void getProperties(String iotId, Promise promise) {
        PanelDevice panelDevice = (PanelDevice)panelDevices.get(iotId);
        if (panelDevice != null) {
            panelDevice.getProperties((isSuccess, data) -> {
                Log.d(TAG, "getPropsCallBack promise isSuccess->" + isSuccess + " data->" + data);
                WritableMap map = Arguments.createMap();
                if (isSuccess) {
                    map.putString("meshAddress", iotId);
                    map.putString("opcode", "getProperties");
                    map.putString("params", data.toString());
                    promise.resolve(map);
                } else {
                    promise.reject(new Exception("getProperties isSuccess->false"));
                }
            });
        } else {
            promise.reject(new Exception("getProperties no such device"));
        }
    }

    /**
     * 设置设备属性
     */
    @ReactMethod
    public void setProperties(String iotId, String params) {
//params 格式参考如下：
/*
{
    "items":{
        "PowerSwitch": 0
    },
    "iotId":"s66CDxxxxXH000102"
}
*/
        Log.d(TAG, "setProperties params->" + params);
        PanelDevice panelDevice = (PanelDevice)panelDevices.get(iotId);
        if (panelDevice != null) {
            panelDevice.setProperties(params, (isSuccess, data) -> {
                Log.d(TAG, "setPropsCallBack isSuccess->" + isSuccess + " data->" + data);
// data->{
//     "code": 200,
//     "message": "success",
//     "data": {}
// }

                // 因为上面已经 subAllEvents ，使得每次设置属性时都会
                // 有包含属性的事件返回了，所以这里没必要多取一次属性
                // if (isSuccess) {
                //     getProperties(iotId);
                // }
            });
        } else {
            // When RELOAD js of react-native, then add startAddDevice(),
            // then setProperties(), will cause here.
            // Since RELOAD only work in DEBUG, so it seems not a problem.
            Log.e(TAG, "setProperties but panelDevice is null!");
        }
    }

    /**
     * 调用服务
     */
    @ReactMethod
    public void invokeService(String iotId, String params) {
//params 格式参考如下
/*
{
    "args":{
        "Saturation":80,
        "LightDuration":50,
        "Hue":325,
        "Value":50
    },
    "identifier":"Rhythm",
    "iotId":"s66CDxxxxItXH000102"
}
*/
        Log.d(TAG, "invokeService params->" + params);
        PanelDevice panelDevice = (PanelDevice)panelDevices.get(iotId);
        if (panelDevice != null) {
            panelDevice.invokeService(params, (isSuccess, data) -> {
                Log.d(TAG, "invokeServiceCallBack isSuccess->" + isSuccess + " data->" + data);
                if (isSuccess) {
                }
            });
        }
    }

    /**
     * 订阅所有事件
     */
    public void subAllEvents(String iotId) {
        PanelDevice panelDevice = (PanelDevice)panelDevices.get(iotId);
        if (panelDevice != null) {
            panelDevice.subAllEvents(eventCallback, (isSuccess, data) -> Log.d(TAG, "subAllEvents isSuccess->" + isSuccess + " data->" + data));
        }
    }

    /**
     * 订阅事件回调
     *
     * @iotId 参数是设备 iotId
     * @topic 参数是回调的事件主题字符串
     * @Object data 是触发事件的内容
     */
    private IPanelEventCallback eventCallback = (iotId, topic, data) -> {
        Log.d(TAG, "eventCallback topic->" + topic + " data->" + data);
// topic->"/app/down/thing/status"
// data->{
//     "groupIdList": [
//         {
//             "groupType": "ISOLATION",
//             "groupId": "a103ToHAxVJe1yaP"
//         },
//         {
//             "groupType": "ILOP_APP",
//             "groupId": "hAPrJuBJ4UHQLbLZ4PH4010100"
//         }
//     ],
//     "netType": "NET_WIFI", // NET_LORA（表示LoRa）；NET_CELLULAR（表示2G/3G/4G/5G蜂窝网）；NET_WIFI（表示Wi-Fi）；NET_ZIGBEE（表示ZigBee）；NET_ETHERNET（表示以太网）；NET_OTHER（表示其他网络类型）
//     "activeTime": 1603673532205,
//     "ip": "112.17.177.241",
//     "aliyunCommodityCode": "iothub_senior",
//     "categoryKey": "light",
//     "nodeType": "DEVICE", // 设备的节点类型：DEVICE，GATEWAY
//     "productKey": "a17hN4W4777",
//     "statusLast": 3,
//     "deviceName": "testDeviceName1",
//     "iotId": "vvssa4DEzVglIGxDl777000000",
//     "namespace": "TmallGenie",
//     "tenantId": "19A31790C888469BAFE4C384FFD60777",
//     "thingType": "DEVICE",
//     "categoryId": 487,
//     "status": {
//         "time": 1604985312231,
//         "value": 1
//     }
// }

// // after device plug in or out
// topic->"/app/down/thing/properties"
// data->{
//     "iotId": "vvssa4DEzVglIGxDl777000000",
//     "productKey": "a17hN4W4777",
//     "deviceName": "testDeviceName1",
//     "items": {
//         "CountDownList": {
//             "time": 1604371244935,
//             "value": {}
//         },
//         "PowerSwitch": {
//             "time": 1604984199857,
//             "value": 1
//         },
//         "LocalTimer": {
//             "time": 1604371244935,
//             "value": []
//         },
//         "CountDown": {
//             "time": 1604371244935,
//             "value": {}
//         }
//     }
// }"


// // after setProperties on APP, event from LAN
// topic->/app/down/thing/properties
// data->{
//     "iotId": "vvssa4DEzVglIGxDl777000000",
//     "productKey": "a17hN4W4777",
//     "deviceName": "testDeviceName1",
//     "items": {
//         "PowerSwitch": {
//             "time": 1610417474327,
//             "value": 0
//         }
//     }
// }

// // after setProperties on APP or web, event from WAN
// topic->"/app/down/thing/properties"
// data->{
//     "checkFailedData": {},
//     "groupIdList": [
//         "a103ToHAxVJe1yaP"
//     ],
//     "groupId": "a103ToHAxVJe1yaP",
//     "categoryKey": "light",
//     "batchId": "da1809cf1bcd4e468950892e3e28b626",
//     "gmtCreate": 1604993408075,
//     "productKey": "a17hN4W4777",
//     "deviceName": "testDeviceName1",
//     "iotId": "vvssa4DEzVglIGxDl777000000",
//     "checkLevel": 0,
//     "namespace": "TmallGenie",
//     "tenantId": "19A31790C888469BAFE4C384FFD60777",
//     "thingType": "DEVICE",
//     "items": {
//         "PowerSwitch": {
//             "time": 1604993407523,
//             "value": 0
//         }
//     },
//     "tenantInstanceId": "iotx-oxssharez200"
// }

// // sometimes
// data->{
//     "iotId": "vvssa4DEzVglIGxDl777000000",
//     "productKey": "a17hN4W4777",
//     "deviceName": "testDeviceName1"
// }

// // after '/living/device/reset', or
// // after breezeSubDevLogin() in FYSDK.java against a reset device while it's data still in panelDevices
// topic->/app/down/_thing/event/notify
// data->{
//     "identifier": "awss.BindNotify",
//     "value": {
//         "iotId": "vvssa4DEzVglIGxDl777000000",
//         "identityId": "5022op8fb006f88b482b3f54d8541e0c1dfd0254",
//         "owned": 1,
//         "productKey": "a17hN4W4777",
//         "deviceName": "testDeviceName1",
//         "operation": "Unbind"
//     }
// }

// // after userBindByTimeWindow() in FYSDK.java against a reset device while it's data still in panelDevices
// topic->/app/down/_thing/event/notify
// data->{
//     "identifier": "awss.BindNotify",
//     "value": {
//         "iotId": "vvssa4DEzVglIGxDl777000000",
//         "identityId": "5022op8fb006f88b482b3f54d8541e0c1dfd0254",
//         "owned": 1,
//         "productKey": "a17hN4W4777",
//         "deviceName": "testDeviceName1",
//         "operation": "Bind"
//     }
// }

// // 本地在线离线变更的通知
// topic->BoneThingLocalConnectionChange
// data->{
//     "localConnectionState": 2, // 1 表示本地在线，2 表示本地离线，3 表示本地连接中
//     "type": 1 // 据阿里人员说这个不用关心
// }

// // 本地在线离线变更的通知
// topic->BoneThingLocalConnectionChange
// data->{
//     "localConnectionState": 2,
//     "type": 0 // 设备 reset 后会出现
// }

        // use whitelist or blacklist events to prevent slowing the APP performance
// ------- whitelist events
        switch (topic) {
            case "/app/down/thing/status":
            case "BoneThingLocalConnectionChange":
                this.sdk.hackOnProvisionedResult(iotId);
                break;
            default:
                break;
        }
        switch (topic) {
            case "/app/down/thing/properties":
                HashMap<String, Object> paramsMap = (HashMap<String, Object>) parseUnknowObjectToJson(data);
                HashMap<String, Object> items = (HashMap<String, Object>) paramsMap.get("items");
                if (items != null) {
                    // only keep PowerSwitch in whitelist, if add heart
                    // beat with PowerSwitch, then even can remove
                    // "/app/down/thing/properties" from whitelist
                    if (items.get("PowerSwitch") == null) {
                        return;
                    }
                }
            case "/app/down/thing/status":
            case "/app/down/_thing/event/notify":
            case "BoneThingLocalConnectionChange":
                WritableMap map = Arguments.createMap();
                map.putString("meshAddress", iotId);
                map.putString("opcode", topic);
                map.putString("params", data.toString());
                sendEvent(NOTIFICATION_VENDOR_RESPONSE, map);
                break;
            default:
                break;
        }

// ------- blacklist events
        // HashMap<String, Object> paramsMap = (HashMap<String, Object>) parseUnknowObjectToJson(data);

        // // prevent meanless sendEvent
        // if (isResCheckFailedData(topic, paramsMap)) {
        //     return;
        // }

        // // My project change brightness against sound frequently,
        // // so need this to prevent slowing the APP performance
        // // by huge amount sendEvent
        // // flyskywhy@gmail.com 2021.1.7
        // if (isResChangeBrightness(topic, paramsMap)) {
        //     return;
        // }

        // WritableMap map = Arguments.createMap();
        // map.putString("meshAddress", iotId);
        // map.putString("opcode", topic);
        // map.putString("params", data.toString());
        // sendEvent(NOTIFICATION_VENDOR_RESPONSE, map);
    };

    // the native version vs js version isResCheckFailedData():
    // can reduce the jsbridge communication
    public boolean isResCheckFailedData(String opcode, HashMap<String, Object> params) {
        if (opcode.equals("/app/down/thing/properties")) {
            HashMap<String, Object> checkFailedData = (HashMap<String, Object>) params.get("checkFailedData");
            if (checkFailedData != null) {
                if (!checkFailedData.isEmpty()) {
                    HashMap<String, Object> items = (HashMap<String, Object>) params.get("items");
                    if (items == null) {
                        return true;
                    } else {
                        if (items.isEmpty()) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    // the native version vs js version isResChangeBrightness():
    // can reduce the jsbridge communication
    public boolean isResChangeBrightness(String opcode, HashMap<String, Object> params) {
        if (opcode.equals("/app/down/thing/properties")) {
            HashMap<String, Object> items = (HashMap<String, Object>) params.get("items");
            if (items != null) {
                if (items.get("luminance") != null) {
                    return true;
                }

                HashMap<String, Object> itemsOther = (HashMap<String, Object>) items.get("other");
                if (itemsOther != null) {
                    HashMap<String, Object> itemsOtherValue = (HashMap<String, Object>) itemsOther.get("value");
                    if (itemsOtherValue != null) {
                        if (itemsOtherValue.get("luminance") != null) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    }

    public void insertDevice(String iotId) {
        Log.i(TAG, "insertDevice " + iotId);
        PanelDevice panelDevice = (PanelDevice)panelDevices.get(iotId);
        Boolean needInit = false;
        if (panelDevice == null) {
            Log.i(TAG, "insertDevice new PanelDevice");
            panelDevice = new PanelDevice(iotId);
            needInit = true;
        } else {
            Log.e(TAG, "insertDevice previous panelDevice->" + panelDevice);

            if (!panelDevice.isInit()) {
                Log.i(TAG, "insertDevice previous panelDevice needInit");
                needInit = true;
            }
        }

        panelDevices.put(iotId, panelDevice);

        if (needInit) {
            panelDevice.init(mReactContext, (isSuccess, data) -> {
                if (isSuccess) {
                    getStatus(iotId);
                    // getProperties(iotId); // ref the comment in getNodesProperties() of index.native.js
                    subAllEvents(iotId);
                }
                Log.d(TAG, "initCallback isSuccess->" + isSuccess + " data->" + data);

                // ref to demo APP code, but OutputParams->null means discoverDevices() useless?
//                 TmpSdk.getDeviceManager().discoverDevices(null, false, 5000, new IDevListener() {
//                     @Override
//                     public void onSuccess(Object o, OutputParams outputParams) {
//                         Log.d(TAG, "discoverDevices Object->" + o + " OutputParams->" + outputParams);
// // discoverDevices Object->null OutputParams->{device_name=com.aliyun.alink.linksdk.tmp.device.payload.ValueWrapper$StringValueWrapper@accbd4, product_key=com.aliyun.alink.linksdk.tmp.device.payload.ValueWrapper$StringValueWrapper@9a5917d, model_type=com.aliyun.alink.linksdk.tmp.device.payload.ValueWrapper$StringValueWrapper@1813872}
// // discoverDevices Object->null OutputParams->null
//                     }

//                     @Override
//                     public void onFail(Object o, ErrorInfo errorInfo) {
//                         Log.d(TAG, "discoverDevices Object->" + o + " errorInfo->" + errorInfo);
//                     }
//                 });
            });
        }
    }

    @ReactMethod
    public void removeDevice(String iotId) {
        Log.i(TAG, "removeDevice " + iotId);
        PanelDevice panelDevice = (PanelDevice)panelDevices.get(iotId);
        if (panelDevice != null) {
            if (panelDevice.isInit()) {
                panelDevice.uninit();
            }
            panelDevices.remove(iotId);
        }
    }

    @ReactMethod
    public void setDevices(ReadableArray devices) {
        int size = devices.size();
        for(int i = 0; i < size; i++) {
            ReadableMap device = devices.getMap(i);
            if (device.hasKey("meshAddress")) {
                String iotId = device.getString("meshAddress");
                insertDevice(iotId);
            }
        }
    }

    @ReactMethod
    public void doInit() {
        if (this.sdk != null) {
            // 参见下面 doDestroy() 中的注释
            return;
        }

        this.sdk = FYSDK.getInstance(mReactContext, getCurrentActivity().getApplication(), this);
        this.sdk.init();

        if (mBluetoothAdapter == null) {
            mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        }

        if (mBluetoothAdapter != null && mBluetoothAdapter.isEnabled()) {
            sendEvent(BT_ENABLED);
        } else {
            sendEvent(BT_DISABLED);
        }

        mReactContext.addActivityEventListener(this);
        mReactContext.addLifecycleEventListener(this);

        IntentFilter intentFilter = new IntentFilter();
        intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
        mReactContext.registerReceiver(mBluetoothStateReceiver, intentFilter);

        sendEvent(DEVICE_STATUS_LOGOUT);

        IoTTokenInvalidListener ioTTokenInvalidListener = new IoTTokenInvalidListener() {
            @Override
            public void onIoTTokenInvalid() {
                sdk.logout(null);
                startLogin();
            }
        };
        IoTCredentialManageImpl.getInstance(AApplication.getInstance()).setIotTokenInvalidListener(ioTTokenInvalidListener);

        if (this.sdk.isLogin()) {
            sendEvent(SERVICE_CONNECTED);
        } else {
            startLogin();
            // 曾经试过注册了一个德国账户，然后设备配网打印看到它也的确在德国的服务器
            // iot-as-mqtt.eu-central-1.aliyuncs.com 上有动作了。然后通过非 WiFi
            // 开关灯发现，延迟也只要 1 秒。对大部分项目来说，非局域网环境下  1 秒已经
            // 足够快了，这样的话，账户注册时就没必要选哪个服务器了，统一选择中国大陆
            // 或是德国就行（本组件在 OALoginActivity.java 中默认选择了德国）
            // gotoCountryList();
        }
    }

    private void doDestroySdk() {
        if (sdk != null) {
            // mHandler.removeCallbacksAndMessages(null);
            mReactContext.unregisterReceiver(mBluetoothStateReceiver);
            // sdk.doDestroy();
            sdk = null;
        }
    }

    @ReactMethod
    public void doDestroy() {
        // 发现调试 APP 时 reload JS 所导致的 onCatalystInstanceDestroy() 调用后，
        // this.sdk.hackOnProvisionedResult(iotId) 这个补救措施就不会被触发，导致
        // 有些灯串有时在配网成功时没有向 APP 发送 onProvisionedResult() ，因而需要
        // 补救措施而不可得，虽然在正式版 APP 中 onCatalystInstanceDestroy() 一旦
        // 发生就表明 APP 退出了而不会发生此类问题，但是以此推之，如果 APP 正常运行时主
        // 动调用 doDestroySdk() ，则后续再次调用调用 doInit() 也会发生此类问题，因
        // 此不在此处调用 doDestroySdk()
        // this.doDestroySdk();
    }

    @ReactMethod
    public void doResume() {
        Log.d(TAG, "onResume");
        //检查是否支持蓝牙设备
        BluetoothManager manager = (BluetoothManager) mContext
            .getSystemService(Context.BLUETOOTH_SERVICE);
        if (manager.getAdapter() == null) {
            Toast.makeText(mContext, "ble not support", Toast.LENGTH_SHORT).show();
            return;
        }

        checkPermissions();
        checkSystemLocation();
    }

    @ReactMethod
    public void login(final Promise promise){
        ILoginCallback var = new ILoginCallback() {
            @Override
            public void onLoginSuccess() {
                promise.resolve("success");
            }
            @Override
            public void onLoginFailed(int i, String s) {
                promise.reject(String.valueOf(i),s);
            }
        };
        this.sdk.login(var);
    }
    @ReactMethod
    public void logout(final Promise promise){
        ILogoutCallback callback = new ILogoutCallback() {
            @Override
            public void onLogoutSuccess() {
                // 账号退出时需要清理账号缓存的数据
                DeviceManager.getInstance().clearAccessTokenCache();

                promise.resolve("success");
            }

            @Override
            public void onLogoutFailed(int i, String s) {
                promise.reject(String.valueOf(i),s);
            }
        };
        this.sdk.logout(callback);
    }
    @ReactMethod
    public void isLogin(Promise promise){
        promise.resolve(this.sdk.isLogin());
    }
    @ReactMethod
    public void startScanLocalDevice(Boolean unclaimed){

        try{
            EventListener listener = new EventListener() {
                @Override
                void onEvent(String event, JSONObject json) {
                    Log.i(TAG,event);
                    try {
                        if(json != null)
                            sendEvent(event,JsonConvert.jsonToReact(json));
                        else
                            sendEvent(event);
                    } catch (JSONException e) {
                        sendErrorEvent(e.getMessage());
                    }
                }
            };
            Log.i(TAG,"startScanLocalDevice:"+listener);
            EnumSet<DiscoveryType> enumSet = unclaimed ? EnumSet.of(
                DiscoveryType.CLOUD_ENROLLEE_DEVICE,     // 1 云端待配设备，一般是指零配或路由器配网方式发现并上报的待配网设备，从云端获取
                DiscoveryType.BLE_ENROLLEE_DEVICE,       // 2 发现的是蓝牙 WiFi 双模设备（蓝牙模块广播的subType=2即为双模设备），需根据扫描到的蓝牙设备去云端获取该设备是蓝牙 WiFi 双模设备还是普通的蓝牙设备
                DiscoveryType.SOFT_AP_DEVICE,            // 3 附近的 AP 热点，需要符合 adh_$PK_$mac 格式
                DiscoveryType.BEACON_DEVICE,             // 4 一键配网发现的待配设备 adn$PK_$mac
                DiscoveryType.COMBO_SUBTYPE_0X03_DEVICE, // 5 未配网的蓝牙 Combo 设备，表示未配置 Wi-Fi ，此时您需要绑定设备和配网设备
                DiscoveryType.COMBO_SUBTYPE_0X04_DEVICE, // 6 已配网的蓝牙 Combo 设备，表示已配置 Wi-Fi ，此时您只需绑定设备即可
                DiscoveryType.APP_FOUND_BLE_MESH_DEVICE, // 7
                DiscoveryType.CLOUD_BLE_MESH_DEVICE      // 8
            ) : EnumSet.of(DiscoveryType.LOCAL_ONLINE_DEVICE); // 0 本地在线设备，即当前和手机在同一局域网已配网在线的设备

            this.sdk.startScanLocalDevice(listener, enumSet, unclaimed);
        }catch (Exception e){
            sendErrorEvent(e.getMessage());
        }
    }
    @ReactMethod
    public void stopScanLocalDevice(){
        Log.i(TAG,"stopScanLocalDevice");
        this.sdk.stopScanLocalDevice();
    }
    @ReactMethod
    public void provisionDeviceInfo(String productKey, Promise promise) {
        this.sdk.provisionDeviceInfo(productKey, promise);
    }

    @ReactMethod
    public void getCurrentSsid(Promise promise) {
        this.sdk.getCurrentSsid(promise);
    }

    @ReactMethod
    public void startAddDevice(String macAddress, String productKey, int discoveryType, ReadableMap cfg, Promise promise) {
            String ssid = cfg.getString("newName");
            String password = cfg.getString("newPwd");
            Log.i(TAG, "claiming " + macAddress + " " + DiscoveryType.values()[discoveryType]);

            if (discoveryType == DiscoveryType.COMBO_SUBTYPE_0X03_DEVICE.getType()) {
                this.sdk.breezeSubDevLogin(productKey, macAddress, ssid, password, promise);
            } else {
                Log.e(TAG, "not debug yet with this discoveryType");
                promise.reject(new Exception("not debug yet with this discoveryType"));
                // this.sdk.wifiConnect(macAddress, null, ssid, password, promise)
            }
    }

    @ReactMethod
    public void stopAddDevice(){
        this.sdk.stopAddDevice();
    }

    @ReactMethod
    public void toggleProvision(String ssid,String password,String timeout){
        Log.d(TAG,"toggleProvision:"+ssid+"-"+password+"-"+timeout);
        this.sdk.toggleProvision(ssid,password,Integer.valueOf(timeout));
    }

    @ReactMethod
    public void getDeviceToken(String productKey, String deviceName, String timeout, final Promise promise) {
        this.sdk.getDeviceToken(productKey, deviceName, timeout, promise);
    }

    @ReactMethod
    public void startAliSocketListener(final Promise promise) {
        this.sdk.startAliSocketListener(promise);
    }

    @ReactMethod
    public void stopAliSocketListener(final Promise promise) {
        this.sdk.stopAliSocketListener(promise);
    }

    @ReactMethod
    public void getAliSocketListenerState(final Promise promise) {
        promise.resolve(this.sdk.getAliSocketListenerState().toString());
    }

    @ReactMethod
    public void subscribe(String topic) {
        try {
            EventListener listener = new EventListener() {
                @Override
                void onEvent(String event, JSONObject json) {
                    Log.i(TAG, event);
                    try {
                        if(json != null)
                            sendEvent(event, JsonConvert.jsonToReact(json));
                        else
                            sendEvent(event);
                    } catch (JSONException e) {
                        sendErrorEvent(e.getMessage());
                    }
                }
                void onError(String event, Exception e) {
                    sendErrorEvent(e.getMessage());
                }
            };
            this.sdk.subscribe(topic, listener);
        } catch (Exception e) {
            sendErrorEvent(e.getMessage());
        }
    }

    @ReactMethod
    public void unsubscribe(String topic) {
        this.sdk.unsubscribe(topic);
    }

    @ReactMethod
    public void ayncSendPublishRequest(String topic, ReadableMap params, Promise promise) {
        try {
            this.sdk.ayncSendPublishRequest(topic, JsonConvert.reactToJSON(params), promise);
        } catch (JSONException e) {
            promise.reject(e.getMessage(), e);
            e.printStackTrace();
        }
    }

    @ReactMethod
    public void getCurrentAccountMessage(final Promise promise) throws JSONException {
        this.sdk.getCurrentAccountMessage(promise);
    }

    @ReactMethod
    public void send(String path, String params, String version, boolean needAuth, final Promise promise){
        Map<String, Object> json = (Map<String, Object>) this.parseJSONString(params);
        if(json.isEmpty()){
            json = new HashMap<String, Object>();
        }

        this.sdk.send(path, json, version, needAuth, new IoTCallback() {
            @Override
            public void onFailure(IoTRequest ioTRequest, Exception e) {
                promise.reject(e.getMessage(),e);
            }

            @Override
            public void onResponse(IoTRequest ioTRequest, IoTResponse ioTResponse) {
                int code = ioTResponse.getCode();

                // 200 代表成功
                if(200 != code){
                    String message = ioTResponse.getMessage();
                    promise.reject(String.valueOf(code),message);
                    return;
                }
                Object dataJson = ioTResponse.getData();
                Log.d(TAG,"------========="+dataJson.toString());
                JSONObject data = new JSONObject();
                try {
                    data.put("data",dataJson);
                    promise.resolve(data.toString());
                } catch (JSONException e) {
                    promise.resolve("");
                    e.printStackTrace();
                }
            }
        });
    }
    @ReactMethod
    public void isWifi5G(Promise promise){
        promise.resolve(this.isWifi5G(mReactContext));
    }
    //判断是否是5G
    public boolean isWifi5G(Context context) {
        int freq = 0;
        WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        if (android.os.Build.VERSION.SDK_INT > android.os.Build.VERSION_CODES.LOLLIPOP) {
            freq = wifiInfo.getFrequency();
        } else {
            String ssid = wifiInfo.getSSID();
            if (ssid != null && ssid.length() > 2) {
                String ssidTemp = ssid.substring(1, ssid.length() - 1);
                List<ScanResult> scanResults = wifiManager.getScanResults();
                for (ScanResult scanResult : scanResults) {
                    if (scanResult.SSID.equals(ssidTemp)) {
                        freq = scanResult.frequency;
                        break;
                    }
                }
            }
        }
        return freq > 4900 && freq < 5900;
    }
    public static Object parseJSONString(String json) {
        try {
            return parseJSONObject(new JSONObject(json));
        } catch (JSONException e) {
            // e.printStackTrace();
        }
        return json;
    }
    public static HashMap<String, Object> parseJSONObject(JSONObject jsonobj){
        JSONArray a_name = jsonobj.names();
        HashMap<String, Object> map =new HashMap<String, Object>();
        if(a_name !=null) {
            int i =0;
            while(i < a_name.length()) {
                String key;
                try{
                    key = a_name.getString(i);
                    Object obj = jsonobj.get(key);
                    map.put(key,parseUnknowObjectToJson(obj));
                }catch(JSONException e) {
                    e.printStackTrace();
                }
                i++;
            }
        }
        return map;
    }
    private static Object parseUnknowObjectToJson(Object o) {
        if(o instanceof JSONObject) {
            return parseJSONObject((JSONObject)o);
        }
        else if (o instanceof JSONArray) {
            return parseJSONArray((JSONArray)o);
        }
        else if (o instanceof String) {
            return parseJSONString((String)o);
        }
        return o;
    }

    public static ArrayList<Object> parseJSONArray(JSONArray jsonarr) {
        ArrayList<Object> list =new ArrayList<Object>();
        int len = jsonarr.length();
        for(int i =0; i < len; i++) {
            Object o;
            try{
                o = jsonarr.get(i);
                list.add(parseUnknowObjectToJson(o));
            }catch(JSONException e) {
                e.printStackTrace();
            }
        }
        return list;
    }
    public static boolean isEmpty(Object obj)
    {
        if (obj == null)
        {
            return true;
        }
        if ((obj instanceof List))
        {
            return ((List) obj).size() == 0;
        }
        if ((obj instanceof String))
        {
            return ((String) obj).trim().equals("");
        }
        return false;
    }

    private void sendErrorEvent(String str) {
        sendEvent("error", str);
    }

    public void sendEvent(String eventName, @Nullable WritableMap params) {
        // TODO: 不知为何调试 APP 时 reload JS 所导致的 onCatalystInstanceDestroy()
        // 调用后，就 emit 不出去了
        if (mReactContext.hasActiveCatalystInstance()) {
            if (D) Log.d(TAG, "Sending event: " + eventName);
            mReactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
        }
    }

    public void sendEvent(String eventName, @Nullable WritableArray params) {
        if (mReactContext.hasActiveCatalystInstance()) {
            if (D) Log.d(TAG, "Sending event: " + eventName);
            mReactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, params);
        }
    }

    public void sendEvent(String eventName, @Nullable String str) {
        if (mReactContext.hasActiveCatalystInstance()) {
            if (D) Log.d(TAG, "Sending event: " + eventName);
            mReactContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(eventName, str);
        }
    }

    private void sendEvent(String eventName) {
        this.sendEvent(eventName,"");
    }
}
