package qiniu.predem.android.logcat;

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

import com.qiniu.android.http.ResponseInfo;
import com.qiniu.android.storage.UpCompletionHandler;
import com.qiniu.android.storage.UploadManager;

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

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.text.SimpleDateFormat;
import java.util.Date;

import qiniu.predem.android.bean.AppBean;
import qiniu.predem.android.config.Configuration;
import qiniu.predem.android.util.Functions;
import qiniu.predem.android.util.HttpURLConnectionBuilder;
import qiniu.predem.android.util.LogUtils;

import static qiniu.predem.android.config.FileConfig.KEY_READ_FILE_INDEX;
import static qiniu.predem.android.config.FileConfig.KEY_READ_FILE_POSITION;
import static qiniu.predem.android.config.FileConfig.KEY_WRITE_FILE_INDEX;
import static qiniu.predem.android.config.FileConfig.KEY_WRITE_FILE_POSITION;
import static qiniu.predem.android.config.FileConfig.LOGCAT_FILE_BASE_NAME;
import static qiniu.predem.android.config.FileConfig.LOGCAT_FILE_PREFIX;
import static qiniu.predem.android.config.FileConfig.LOGCAT_INDEX_FILE_NAME;
import static qiniu.predem.android.config.FileConfig.MAX_LOG_FILE_NUM;
import static qiniu.predem.android.config.FileConfig.MAX_LOG_FILE_SIZE;

/**
 * Created by Misty on 2017/9/20.
 */

public class PrintLogger {
    private static final String TAG = "PrintLogger";
    private static volatile PrintLogger instance = null;
    /**
     * always synchronized with the ‘index.json’
     */
    private long mReadFileIndex = 0;
    private long mReadFilePosition = 0;
    private long mWriteFileIndex = 0;
    private long mWriteFilePosition = 0;
    private SimpleDateFormat mFormat = null;
    private WriteThread mThread = null;
    private Context mContext;

    private String mCachedReportContent;

    private long mStartTime;
    private long mEndTime;

    private PrintLogger(Context context) {
        mThread = new WriteThread(context);
        mFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SS");
        mThread.start();

        this.mContext = context.getApplicationContext();
    }

    //单例模式
    public static PrintLogger getInstance(Context context) {
        if (instance == null) {
            synchronized (PrintLogger.class) {
                if (instance == null) {
                    instance = new PrintLogger(context);
                }
            }
        }
        return instance;
    }

    public synchronized void Log(String tag, String str) {
        String time = mFormat.format(new Date());
        mThread.enqueue(time + " " + tag + " " + str);
    }

    public void openLogs() {
        mStartTime = System.currentTimeMillis();
        parseIndexFile();
        submitLogcat();
    }

    public void closeLogs() {
        mEndTime = System.currentTimeMillis();
        submitLogcat();
    }

    private void submitLogcat() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                if (mReadFileIndex <= mWriteFileIndex) {
                    for (long i = mReadFileIndex; i <= mWriteFileIndex; i++) {
                        //TODO
                        sendRequest(i);
                    }
                }
            }
        }).start();
    }

    private void sendRequest(long readFileIndex) {
        try {
            String content = getReportContent(readFileIndex);
            if (content == null) {
                return;
            }
            final String filename = LOGCAT_FILE_BASE_NAME + readFileIndex;
            String md5 = Functions.getStringMd5(content);
            HttpURLConnection urlConnection = new HttpURLConnectionBuilder(Configuration.getLogcatUpToken() + "?md5=" + md5)
                    .setRequestMethod("GET")
                    .build();
            int responseCode = urlConnection.getResponseCode();
            String token = null;
            String key = null;
            InputStream is = null;
            if (responseCode == 200) {
                try {
                    is = urlConnection.getInputStream();
                    byte[] data = new byte[8 * 1024];
                    is.read(data);
                    String resp = new String(data);
                    JSONObject jsonObject = new JSONObject(resp);
                    token = jsonObject.optString("token");
                    key = jsonObject.optString("key");
                } catch (Exception e) {
                    LogUtils.e(TAG, "------" + e.toString());
                } finally {
                    if (is != null) {
                        is.close();
                    }
                }
            }
            if (token == null) {
                return;
            }

            UploadManager uploadManager = Functions.getUploadManager();
            uploadManager.put(content.getBytes(), key, token, new UpCompletionHandler() {
                @Override
                public void complete(final String key, ResponseInfo info, JSONObject response) {
                    Log.d(TAG, "------upload result " + info.toString());
                    if (info.isOK()) {
                        new Thread() {
                            @Override
                            public void run() {
                                report(filename, key);
                            }
                        }.start();
                    } else {
                        return;
                    }
                }
            }, null);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void report(String filename, String key) {
        //3、上报服务器
        HttpURLConnection url = null;
        boolean successful = false;
        try {
            JSONObject parameters = new JSONObject();

            parameters.put("app_bundle_id", AppBean.APP_PACKAGE);
            parameters.put("app_name", AppBean.APP_NAME);
            parameters.put("app_version", AppBean.APP_VERSION);
            parameters.put("device_model", AppBean.PHONE_MODEL);
            parameters.put("os_platform", AppBean.ANDROID_PLATFORM);
            parameters.put("os_version", AppBean.ANDROID_VERSION);
            parameters.put("os_build", AppBean.ANDROID_BUILD);
            parameters.put("sdk_version", AppBean.SDK_VERSION);
            parameters.put("sdk_id", AppBean.SDK_ID);
            parameters.put("device_id", AppBean.DEVICE_IDENTIFIER);
            parameters.put("tag", AppBean.APP_TAG);
            parameters.put("manufacturer", AppBean.PHONE_MANUFACTURER);
            parameters.put("start_time", mStartTime);
            parameters.put("end_time", mEndTime);
            parameters.put("log_key", key);
            parameters.put("log_tags", "");
            parameters.put("error_count", 0);

            url = new HttpURLConnectionBuilder(Configuration.getLogcatUrl())
                    .setRequestMethod("POST")
                    .setHeader("Content-Type", "application/json")
                    .setRequestBody(parameters.toString())
                    .build();

            int responseCode = url.getResponseCode();

            successful = (responseCode == HttpURLConnection.HTTP_ACCEPTED || responseCode == HttpURLConnection.HTTP_CREATED || responseCode == HttpURLConnection.HTTP_OK);
        } catch (Exception e) {
            LogUtils.e(TAG, "----" + e.toString());
//            e.printStackTrace();
        } finally {
            if (url != null) {
                url.disconnect();
            }
            if (successful) {
                mContext.deleteFile(filename);
            } else {
                LogUtils.d("-----Transmission failed, will retry on next register() call");
            }
        }
    }

    private String getReportContent(long readFileIndex) {
        if (mCachedReportContent != null) {
            return mCachedReportContent;
        }

        if (readFileIndex == mWriteFileIndex && mReadFilePosition == mWriteFilePosition) {
            return null;
        }

        synchronized (this) {
            String filename = LOGCAT_FILE_BASE_NAME + readFileIndex;
            Log.d(TAG, "------filename " + filename);
            mCachedReportContent = readFileOnce(filename, mReadFilePosition);
            if (mCachedReportContent == null) {
                return null;
            }

            mReadFilePosition += mCachedReportContent.length();
            if (mReadFileIndex < mWriteFileIndex && readFileIndex != mReadFileIndex) {
                mReadFileIndex = readFileIndex + 1;
                mReadFilePosition = 0;
            }
            updateIndexFile();
            // delete the file
            mContext.deleteFile(LOGCAT_FILE_BASE_NAME + readFileIndex);
        }
        return mCachedReportContent;
    }

    protected boolean addReportContent(String content) {
        // so many log files unreported, maybe the server closed forever
        if (mWriteFileIndex - mReadFileIndex > MAX_LOG_FILE_NUM) {
            return false;
        }

        synchronized (this) {
            String filename = LOGCAT_FILE_BASE_NAME + mWriteFileIndex;
            if (!writeFileOnce(filename, content, Context.MODE_PRIVATE + Context.MODE_APPEND)) {
                return false;
            }

            mWriteFilePosition += content.length();
            if (mWriteFilePosition >= MAX_LOG_FILE_SIZE) {
                mWriteFileIndex++;
                mWriteFilePosition = 0;
            }
            updateIndexFile();
        }
        return true;
    }

    private boolean updateIndexFile() {
        try {
            JSONObject json = new JSONObject();
            json.put(KEY_READ_FILE_INDEX, mReadFileIndex);
            json.put(KEY_READ_FILE_POSITION, mReadFilePosition);
            json.put(KEY_WRITE_FILE_INDEX, mWriteFileIndex);
            json.put(KEY_WRITE_FILE_POSITION, mWriteFilePosition);
            return writeFileOnce(LOGCAT_INDEX_FILE_NAME, json.toString(), Context.MODE_PRIVATE);
        } catch (JSONException e) {
            LogUtils.e(TAG, "-----" + e.toString());
//            e.printStackTrace();
        }
        return false;
    }

    private boolean writeFileOnce(String filename, String content, int mode) {
        FileOutputStream output = null;
        BufferedWriter writer = null;
        try {
            output = mContext.openFileOutput(filename, mode);
            writer = new BufferedWriter(new OutputStreamWriter(output));
            writer.write(content);
            writer.write("\n");
            writer.close();
            return true;
        } catch (FileNotFoundException e) {
            LogUtils.e(TAG, "------" + e.toString());
//            e.printStackTrace();
        } catch (IOException e) {
            LogUtils.e(TAG, "------" + e.toString());
//            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            LogUtils.d(TAG, "------" + e.toString());
//            e.printStackTrace();
        } finally {
            closeSilently(output);
            closeSilently(writer);
        }
        return false;
    }

    /**
     * 解析 index 文件
     *
     * @return
     */
    private boolean parseIndexFile() {
        try {
            String content = readFileOnce(LOGCAT_INDEX_FILE_NAME, 0);
            if (content == null) {
                deleteAllQosFiles();
                return false;
            }
            JSONObject json = new JSONObject(String.valueOf(content));
            mReadFileIndex = json.getLong(KEY_READ_FILE_INDEX);
            mReadFilePosition = json.getLong(KEY_READ_FILE_POSITION);
            mWriteFileIndex = json.getLong(KEY_WRITE_FILE_INDEX);
            mWriteFilePosition = json.getLong(KEY_WRITE_FILE_POSITION);
            return true;
        } catch (JSONException e) {
            e.printStackTrace();
        }
        deleteAllQosFiles();
        return false;
    }

    private String readFileOnce(String filename, long offset) {
        FileInputStream input = null;
        BufferedReader reader = null;
        try {
            input = mContext.openFileInput(filename);
            reader = new BufferedReader(new InputStreamReader(input));
            reader.skip(offset);
            StringBuilder builder = new StringBuilder();
            String content;
            while ((content = reader.readLine()) != null) {
                builder.append(content);
                builder.append("\n");
            }
            String result = builder.toString();
            Log.d(TAG, "----result " + result);
            if ("".equals(result)) {
                return null;
            }
            return result;
        } catch (FileNotFoundException e) {
            Log.e(TAG, "-----" + e.toString());
        } catch (IOException e) {
            Log.e(TAG, "-----" + e.toString());
        } catch (OutOfMemoryError e) {
            Log.e(TAG, "-----" + e.toString());
        } finally {
            closeSilently(input);
            closeSilently(reader);
        }
        return null;
    }

    private void closeSilently(Closeable closeable) {
        if (closeable == null) {
            return;
        }
        try {
            closeable.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void deleteAllQosFiles() {
        String[] files = mContext.fileList();
        for (String file : files) {
            if (file.startsWith(LOGCAT_FILE_PREFIX)) {
                mContext.deleteFile(file);
            }
        }
    }
}