package com.xiaoyudesign.rnupdate;

import static com.xiaoyudesign.rnupdate.UpdateModuleImpl.getCompatibleField;

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

import com.facebook.react.ReactActivity;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactDelegate;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.JSBundleLoader;
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.ReadableMap;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;

import java.io.File;
import java.lang.reflect.Field;

public class UpdateModule extends ReactContextBaseJavaModule {

    UpdateContext updateContext;

    public static ReactApplicationContext mContext;

    public UpdateModule(ReactApplicationContext reactContext, UpdateContext updateContext) {
        super(reactContext);
        this.updateContext = updateContext;
        mContext = reactContext;
    }

    public UpdateModule(ReactApplicationContext reactContext) {
        this(reactContext, UpdateContext.getInstance(reactContext));
    }


    @Override
    public String getName() {
        return UpdateModuleImpl.NAME;
    }

    @ReactMethod
    public void getConstantsInfo(final Promise promise) {
        WritableMap map = Arguments.createMap();
        try {
            String currentVersion = updateContext.getCurrentVersion();
            map.putString("currentVersion", currentVersion != null ? currentVersion : "");
        } catch (Exception e) {
            // 如果上下文未准备好，返回默认值
            map.putString("currentVersion", "");
        }
        promise.resolve(map);
    }

    @ReactMethod
    public void downloadPPK(ReadableMap options, final Promise promise) {
        String url = options.getString("url");
        String versionCode = options.getString("newVersion");
        File outputFile = updateContext.getPPKOutputFile(versionCode);


        if(
            url == null || url.isEmpty() ||
            versionCode == null || versionCode.isEmpty()
        ) {
            WritableMap map = Arguments.createMap();
            map.putDouble("code", UpdateDownloadParams.DownloadStatus.DOWNLOAD_PARAMS_NOT_EMPTY.code);
            map.putString("msg", UpdateDownloadParams.DownloadStatus.DOWNLOAD_PARAMS_NOT_EMPTY.desc);
            promise.resolve(map);
            return;
        }

        try {
            UpdateDownload.download(new UpdateDownloadParams(url, outputFile, new UpdateDownloadParams.DownloadProgressCallback() {
                @Override
                public void onDownloadProgress(long currentSize, long totalSize, int progress) {
                    sendProgressHandler(currentSize, totalSize, progress);
                }

                @Override
                public void onDownloadStatus(UpdateDownloadParams.DownloadStatus status, String errorMsg) {
                    sendDownloadStatusHandler(status, errorMsg);
                    if(status == UpdateDownloadParams.DownloadStatus.DOWNLOAD_FAILED) {
                        WritableMap map = Arguments.createMap();
                        map.putDouble("code", status.code);
                        map.putString("msg", errorMsg == null || errorMsg.isEmpty() ? status.desc : errorMsg);
                        map.putString("savePath", outputFile.getAbsolutePath());
                        promise.resolve(map);
                    }
                }

                @Override
                public void onDownloadComplete() {
                    UpdateDownloadParams.DownloadStatus downloadSuccess = UpdateDownloadParams.DownloadStatus.DOWNLOAD_SUCCESS;
                    UpdateDownloadParams.DownloadStatus unzipFailed = UpdateDownloadParams.DownloadStatus.UNZIP_FAILED;
                    try {
                        // 解压文件
                        File outputFile = updateContext.getPPKOutputFile(versionCode);
                        boolean b = updateContext.extractZip(outputFile, versionCode);
                        UpdateDownloadParams.DownloadStatus result = b ? downloadSuccess : unzipFailed;

                        WritableMap map = Arguments.createMap();
                        map.putDouble("code", result.code);
                        map.putString("msg", result.desc);
                        map.putString("path", outputFile.getAbsolutePath());
                        promise.resolve(map);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            }));
        } catch (Exception e) {
            WritableMap map = Arguments.createMap();
            map.putDouble("code", UpdateDownloadParams.DownloadStatus.DOWNLOAD_FAILED.code);
            map.putString("msg", e.getMessage());
            promise.resolve(map);
        }
    }

    private void sendProgressHandler(long currentSize, long totalSize, int progress) {
        WritableMap params = Arguments.createMap();
        params.putDouble("currentSize", currentSize);
        params.putDouble("totalSize", totalSize);
        params.putDouble("progress", progress);
        mContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(UpdateConstant.EventDownloadProgress.getValue(), params);
    }


    private void sendDownloadStatusHandler(UpdateDownloadParams.DownloadStatus status, String errorMsg) {
        WritableMap params = Arguments.createMap();
        params.putInt("status", status.code);
        params.putString("errorMsg", errorMsg);
        mContext
                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
                .emit(UpdateConstant.EventDownloadStatus.getValue(), params);
    }

    private void loadBundleLegacy() {
        final Activity currentActivity = getCurrentActivity();
        if (currentActivity == null) {
            return;
        }

        currentActivity.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                currentActivity.recreate();
            }
        });
    }

    @ReactMethod
    public void reloadUpdate(ReadableMap options, final Promise promise) {
        final String versionCode = options.getString("newVersion");

        if(versionCode == null || versionCode.isEmpty()) {
            promise.resolve(UpdateResultParams.ERROR_PARAMS.setDesc("版本号不能为空").toReadableMap());
            return;
        }

        UiThreadUtil.runOnUiThread(new Runnable() {
            @Override
            public void run() {
              boolean b = updateContext.switchVersion(versionCode);
              if(!b) {
                promise.resolve(UpdateResultParams.ERROR_RUNTIME.setDesc("版本切换失败").toReadableMap());
                return;
              }

              final Context application = getReactApplicationContext().getApplicationContext();
                JSBundleLoader loader = JSBundleLoader.createFileLoader(UpdateContext.getBundleUrl(application));

                try {
                    ReactInstanceManager instanceManager = ((ReactApplication) application).getReactNativeHost().getReactInstanceManager();

                    try {
                        Field loadField = instanceManager.getClass().getDeclaredField("mBundleLoader");
                        loadField.setAccessible(true);
                        loadField.set(instanceManager, loader);
                    } catch (Throwable err) {
                        // promise.resolve(UpdateResultParams.ERROR_RUNTIME.setDesc(err.getMessage()).toReadableMap());
                        Field jsBundleField = instanceManager.getClass().getDeclaredField("mJSBundleFile");
                        jsBundleField.setAccessible(true);
                        jsBundleField.set(instanceManager, UpdateContext.getBundleUrl(application));
                    }

                    instanceManager.recreateReactContextInBackground();
                    promise.resolve(UpdateResultParams.SUCCESS.toReadableMap());
                } catch (Throwable err) {
                    Log.e("update", "switchVersion failed ", err);
                    // loadBundleLegacy();

                    final Activity currentActivity = mContext.getCurrentActivity();
                    if (currentActivity == null) {
                        promise.resolve(UpdateResultParams.ERROR_RUNTIME.setDesc("currentActivity is null").toReadableMap());
                        return;
                    }
                    try {
                        java.lang.reflect.Method getReactDelegateMethod =
                                ReactActivity.class.getMethod("getReactDelegate");

                        ReactDelegate reactDelegate = (ReactDelegate)
                                getReactDelegateMethod.invoke(currentActivity);

                        Field reactHostField = getCompatibleField(ReactDelegate.class, "reactHost");
                        reactHostField.setAccessible(true);
                        Object reactHost = reactHostField.get(reactDelegate);

                        Field devSupport = getCompatibleField(reactHost.getClass(), "useDevSupport");
                        devSupport.setAccessible(true);
                        devSupport.set(reactHost, false);

                        // Access the ReactHostDelegate field (compatible with mReactHostDelegate/reactHostDelegate)
                        Field reactHostDelegateField = getCompatibleField(reactHost.getClass(), "reactHostDelegate");
                        reactHostDelegateField.setAccessible(true);
                        Object reactHostDelegate = reactHostDelegateField.get(reactHost);

                        String bundleFieldName = "jsBundleLoader";
                        if (reactHostDelegate.getClass().getCanonicalName().equals("expo.modules.ExpoReactHostFactory.ExpoReactHostDelegate")) {
                            bundleFieldName = "_jsBundleLoader";
                        }

                        // Modify the jsBundleLoader field
                        Field jsBundleLoaderField = reactHostDelegate.getClass().getDeclaredField(bundleFieldName);
                        jsBundleLoaderField.setAccessible(true);
                        jsBundleLoaderField.set(reactHostDelegate, loader);

                        // Get the reload method with a String parameter
                        java.lang.reflect.Method reloadMethod = reactHost.getClass().getMethod("reload", String.class);

                        // Invoke the reload method with a reason
                        reloadMethod.invoke(reactHost, "react-native-update");
                    } catch (Throwable e) {
                        promise.resolve(UpdateResultParams.ERROR_RUNTIME.setDesc(err.getMessage()).toReadableMap());
                        currentActivity.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                currentActivity.recreate();
                            }
                        });
                    }
                }
            }
        });
    }

    @ReactMethod
    public void checkVersionExists(ReadableMap options, final Promise promise) {
        final String versionCode = options.getString("versionCode");
        if(versionCode == null || versionCode.isEmpty()) {
            promise.resolve(UpdateResultParams.ERROR_PARAMS.setDesc("参数不能为空").toReadableMap());
            return;
        }
        if(updateContext.checkVersionExists(versionCode)) {
            boolean isInstall = updateContext.getCurrentVersion() != null && versionCode.equals(updateContext.getCurrentVersion());
            if(isInstall) {
                promise.resolve(UpdateResultParams.PPK_VERSION_INSTALL.setDesc(UpdateResultParams.PPK_VERSION_INSTALL.getDesc()).toReadableMap());
            } else {
                promise.resolve(UpdateResultParams.PPK_VERSION_UNINSTALL.setDesc(UpdateResultParams.PPK_VERSION_UNINSTALL.getDesc()).toReadableMap());
            }
        } else {
            promise.resolve(UpdateResultParams.PPK_VERSION_UNDOWNLOAD.toReadableMap());
        }
    }

}
