package iku.os.net;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Binder;
import android.os.IBinder;
import android.text.TextUtils;

import androidx.localbroadcastmanager.content.LocalBroadcastManager;

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

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;

public class IkuService extends Service {

    public static final String ACTION_MSG_RECEIVED = "msgReceived";
    public static final String ACTION_STATUS_DESC_CHANGED = "statusDescChanged";

    public static final String ACTION_WEBSOCKET_STATE_RECEIVED = "websocketStateReceived";
    public static final String ACTION_WEBSOCKET_STATE_GET = "websocketStateGet";
    public static final String ACTION_NETWORK_STATE_CHANGED = "networkStateChanged";
    public static final String ACTION_WEBSOCKET_START = "websocketStart";
    public static final String ACTION_WEBSOCKET_STOP = "websocketStop";

    private static final String TAG = IkuService.class.getSimpleName();

    private static String workDir = "/data/iku/";

    private final IBinder mBinder = new WebSocketsBinder();
    private IkuClient mClient;

    private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            if (action.equals(ACTION_WEBSOCKET_START))
            {
                String url = intent.getStringExtra("ws_url");
                SharedPreferences sharedPreferences = getSharedPreferences("iku_net_config", MODE_PRIVATE);
                sharedPreferences.edit().putString("ws_url", url).apply();
                startSocket(url);
            }
            else if (action.equals(ACTION_WEBSOCKET_STOP))
            {
                stopSocket();
            }
            else if (action.equals(ACTION_NETWORK_STATE_CHANGED))
            {
                boolean networkIsOn = intent.getBooleanExtra(ACTION_NETWORK_STATE_CHANGED, false);
                if (networkIsOn) {
                    startSocket(getWsUrlFromLocal());
                } else {
                    stopSocket();
                }
            }
            else if (action.equals(ACTION_WEBSOCKET_STATE_GET))
            {
                boolean connected = mClient != null? mClient.isConnected():false;
                sendWebsocketStateEvent(connected);
            }

        }
    };
    private String getWsUrlFromLocal()
    {
        SharedPreferences sharedPreferences = getSharedPreferences("iku_net_config", MODE_PRIVATE);
        return sharedPreferences.getString("ws_url", "");
    }
    private void registerAction(String action)
    {
        LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter(action));
    }

    public File getWorkDir()
    {
        File dir = new File(workDir);
        if (!dir.exists())
            dir.mkdirs();
        return dir;
    }
    private File getEpTaskConfigFile(String packageName, String extractType)
    {
        return new File(getWorkDir().getAbsoluteFile() + "/ep_task-" + packageName + "-" + extractType + ".json");
    }
    private void writeConfigFile(String deviceId, String packageName, String extractType)  {
        try {
            File file = getEpTaskConfigFile(packageName, extractType);
            JSONObject jsonObject = new JSONObject();

            URI uri = mClient.getConnectedURI();
            String host = uri.getHost();
            int port = uri.getPort();
            jsonObject.put("ws_url", "http://" + host + ":" + port);
            jsonObject.put("device_id", deviceId);
            jsonObject.put("package_name", packageName);
            jsonObject.put("extract_type", extractType);
            jsonObject.put("extract_params_callback", "/callbacks/extract-params");

            FileOutputStream fos = new FileOutputStream(file);
            fos.write(jsonObject.toString().getBytes());
            fos.flush();
            fos.close();

            file.setReadable(true, false);
        } catch (JSONException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }


    }

    private JSONArray getPackageInfos() throws JSONException {
        JSONArray result = new JSONArray();

        PackageManager pm = getPackageManager();
        List<PackageInfo> list = pm.getInstalledPackages(0);
        for (int i = 0; i < list.size(); i++) {
            PackageInfo packageInfo = list.get(i);
            String name = packageInfo.packageName;

            if (name.indexOf("com.android") == 0 ||
                    name.indexOf("com.google") == 0||
                    name.indexOf("android") == 0||
                    name.indexOf("com.qualcomm") == 0 ||
                    name.indexOf("iku.os") == 0)
                continue;
            JSONObject item = new JSONObject();
            item.put("packageName", packageInfo.packageName);
            result.put(item);
        }
        return result;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        mClient = IkuClient.getInstance();
        mClient.init(getApplicationContext(), new IkuClient.IkuClientListener() {
            @Override
            public void onStatusLog(String msg) {
                sendStatusChangedEvent(msg);
            }

            @Override
            public void onData(JSONObject data) {
                sendMessageReceivedEvent(data);
                try {
                    String cmd = data.getString("cmd");
                    if (cmd.equals("getInstalledApps"))
                    {
                        JSONObject d = new JSONObject();
                        d.put("list", getPackageInfos());
                        d.put("deviceId", mClient.getDeviceId());
                        mClient.sendMsg("onGetInstalledApps", d);
                    }
                    else if (cmd.equals("startExtractParamsTask"))
                    {
                        JSONObject d = data.getJSONObject("data");
                        startExtractParamsTask(d.getString("packageName"), d.getString("extractType"));
                    }
                    else if (cmd.equals("stopExtractParamsTask"))
                    {
                        JSONObject d = data.getJSONObject("data");
                        stopExtractParamsTask(d.getString("packageName"), d.getString("extractType"));
                    }
                }
                catch (Throwable e)
                {
                    e.printStackTrace();
                }
            }

            @Override
            public void onStateChanged(boolean connected) {
                sendWebsocketStateEvent(connected);
            }

            @Override
            public void onConnected() {

            }
        });
    }

    public static void copy(File src, File dst) throws IOException {
        try (InputStream in = new FileInputStream(src)) {
            try (OutputStream out = new FileOutputStream(dst)) {
                // Transfer bytes from in to out
                byte[] buf = new byte[1024];
                int len;
                while ((len = in.read(buf)) > 0) {
                    out.write(buf, 0, len);
                }
            }
        }
    }
    private void taskStatusLog(String status) throws JSONException {
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("status", status);
        mClient.sendMsg("taskStatusLog", jsonObject);
    }
    private void startExtractParamsTask(String packageName, String extractType) throws IOException, JSONException {

        String injectedPath = workDir + extractType + ".dex";
        File injectedFile = new File(injectedPath);
        if (injectedFile.exists())
        {
            String targetPath = workDir + packageName + "_injected.dex";
            File targetFile = new File(targetPath);
            if (!targetFile.exists())
            {
                targetFile.createNewFile();
                targetFile.setReadable(true, false);
            }
            copy(injectedFile, targetFile);

            taskStatusLog("注入Hook文件成功，请打开APP并等待提参... (提参类型: " + extractType + ", 包名: " + packageName + ")");

            writeConfigFile(mClient.getDeviceId(), packageName, extractType);
        }
        else {
            taskStatusLog("未找到" + extractType + "提参Hook文件，无法提参！");
        }
    }

    private void stopExtractParamsTask(String packageName, String extractType) throws IOException, JSONException {
        String targetPath = workDir + packageName + "_injected.dex";
        File targetFile = new File(targetPath);
        if (targetFile.exists())
            targetFile.delete();

        File epTaskConfigFile = getEpTaskConfigFile(packageName, extractType);
        if (epTaskConfigFile.exists())
            epTaskConfigFile.delete();
    }


    @Override
    public IBinder onBind(Intent intent) {
        registerAction(IkuService.ACTION_NETWORK_STATE_CHANGED);
        registerAction(IkuService.ACTION_WEBSOCKET_START);
        registerAction(IkuService.ACTION_WEBSOCKET_STOP);
        registerAction(IkuService.ACTION_WEBSOCKET_STATE_GET);
        return mBinder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
        return false;
    }

    private void startSocket(String url) {
        if (mClient != null) {
            if (!TextUtils.isEmpty(url))
                mClient.connect(url);
        }
    }

    private void stopSocket() {
        if (mClient != null) {
            mClient.disconnect();
        }
    }

    private void sendWebsocketStateEvent(boolean connected)
    {
        Intent intent = new Intent(ACTION_WEBSOCKET_STATE_RECEIVED);
        intent.putExtra("connected", connected);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }
    private void sendMessageReceivedEvent(JSONObject msg) {
        Intent intent = new Intent(ACTION_MSG_RECEIVED);
        intent.putExtra("msg", msg.toString());
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    private void sendStatusChangedEvent(String msg) {
        Intent intent = new Intent(ACTION_STATUS_DESC_CHANGED);
        intent.putExtra("msg", msg);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    public final class WebSocketsBinder extends Binder {
        public IkuService getService() {
            return IkuService.this;
        }
    }
}