package awaisomereport;

import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.Process;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.core.content.FileProvider;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import awais.instagrabber.BuildConfig;
import awais.instagrabber.utils.Utils;

public final class CrashReporter implements Thread.UncaughtExceptionHandler {
    private static CrashReporter reporterInstance;
    private final Application application;
    private final String email;
    private final File crashLogsZip;
    private boolean startAttempted = false;

    public static CrashReporter get(final Application application) {
        if (reporterInstance == null) reporterInstance = new CrashReporter(application);
        return reporterInstance;
    }

    private CrashReporter(@NonNull final Application application) {
        this.application = application;
        this.email = "instagrabber@austinhuang.me";
        this.crashLogsZip = new File(application.getExternalCacheDir(), "crash_logs.zip");
    }

    public void start() {
        if (!startAttempted) {
            Thread.setDefaultUncaughtExceptionHandler(this);
            startAttempted = true;
        }
    }

    @Override
    public void uncaughtException(@NonNull final Thread t, @NonNull final Throwable exception) {
        final StringBuilder reportBuilder = new StringBuilder();
        reportBuilder.append("Error report collected on: ").append(new Date().toString());
        reportBuilder.append("\r\n\r\nInformation:\r\n==============");

        reportBuilder
                .append("\r\nVERSION		: ").append(BuildConfig.VERSION_NAME)
                .append("\r\nVERSION_CODE	: ").append(BuildConfig.VERSION_CODE)
                .append("\r\nPHONE-MODEL	: ").append(Build.MODEL)
                .append("\r\nANDROID_VERS	: ").append(Build.VERSION.RELEASE)
                .append("\r\nANDROID_REL	: ").append(Build.VERSION.SDK_INT)
                .append("\r\nBRAND			: ").append(Build.BRAND)
                .append("\r\nMANUFACTURER	: ").append(Build.MANUFACTURER)
                .append("\r\nBOARD			: ").append(Build.BOARD)
                .append("\r\nDEVICE			: ").append(Build.DEVICE)
                .append("\r\nPRODUCT		: ").append(Build.PRODUCT)
                .append("\r\nHOST			: ").append(Build.HOST)
                .append("\r\nTAGS			: ").append(Build.TAGS)
                .append("\r\nINSTRUCTION	: Please attach details on how to produce this crash. Your help is appreciated.")
                .append("\r\nPRIVACY    	: Your email address and crash report will be made PUBLIC on the GitHub repository.");

        reportBuilder.append("\r\n\r\nStack:\r\n==============\r\n");
        final Writer result = new StringWriter();
        try (final PrintWriter printWriter = new PrintWriter(result)) {
            exception.printStackTrace(printWriter);
            reportBuilder.append(result.toString());

            reportBuilder.append("\r\nCause:\r\n==============");

            // for AsyncTask crashes
            Throwable cause = exception.getCause();
            while (cause != null) {
                cause.printStackTrace(printWriter);
                reportBuilder.append(result.toString());
                cause = cause.getCause();
            }
        }
        reportBuilder.append("\r\n\r\n**** End of current Report ***");

        final String errorContent = reportBuilder.toString();
        try (final FileOutputStream trace = application.openFileOutput("stack-" + System.currentTimeMillis() + ".stacktrace", Context.MODE_PRIVATE)) {
            trace.write(errorContent.getBytes());
        } catch (final Exception ex) {
            if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", ex);
        }

        application.startActivity(new Intent(application, ErrorReporterActivity.class).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));

        zipLogs();

        Process.killProcess(Process.myPid());
        System.exit(10);
    }

    public synchronized CrashReporter zipLogs() {
        final File logDir = Utils.logCollector != null ? Utils.logCollector.getLogDir() :
                new File(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ? application.getDataDir() : application.getFilesDir(), "crashlogs");

        try (final FileOutputStream fos = new FileOutputStream(crashLogsZip);
             final ZipOutputStream zos = new ZipOutputStream(fos)) {

            final File[] files = logDir.listFiles();

            if (files != null) {
                zos.setLevel(5);
                byte[] buffer;
                for (final File file : files) {
                    if (file != null && file.length() > 0) {
                        buffer = new byte[1024];
                        try (final FileInputStream fis = new FileInputStream(file)) {
                            zos.putNextEntry(new ZipEntry(file.getName()));
                            int length;
                            while ((length = fis.read(buffer)) > 0) zos.write(buffer, 0, length);
                            zos.closeEntry();
                        }
                    }
                }
            }

        } catch (final Exception e) {
            if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
        }

        return this;
    }

    @SuppressWarnings("ResultOfMethodCallIgnored")
    public void startCrashEmailIntent(final Context context, final boolean sendZipsOnly) {
        try {
            final String filePath = context.getFilesDir().getAbsolutePath();

            String[] errorFileList;

            if (sendZipsOnly) errorFileList = null;
            else {
                try {
                    final File dir = new File(filePath);
                    if (dir.exists() && !dir.isDirectory()) dir.delete();
                    dir.mkdir();
                    errorFileList = dir.list((d, name) -> name.endsWith(".stacktrace"));
                } catch (final Exception e) {
                    errorFileList = null;
                }
            }

            if ((errorFileList != null && errorFileList.length > 0) || sendZipsOnly) {
                final StringBuilder errorStringBuilder;

                if (sendZipsOnly) errorStringBuilder = new StringBuilder("So... what happened?\n\n");
                else {
                    errorStringBuilder = new StringBuilder("\r\n\r\n");
                    final int maxSendMail = 5;

                    int curIndex = 0;
                    for (final String curString : errorFileList) {
                        final File file = new File(filePath + '/' + curString);

                        if (curIndex++ <= maxSendMail) {
                            errorStringBuilder.append("New Trace collected:\r\n=====================\r\n");
                            try (final BufferedReader input = new BufferedReader(new FileReader(file))) {
                                String line;
                                while ((line = input.readLine()) != null)
                                    errorStringBuilder.append(line).append("\r\n");
                            }
                        }

                        file.delete();
                    }

                    errorStringBuilder.append("\r\n\r\n");
                }

                context.startActivity(Intent.createChooser(new Intent(Intent.ACTION_SEND).setType("message/rfc822")
                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
                        .putExtra(Intent.EXTRA_EMAIL, new String[]{email})
                        .putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(application, BuildConfig.APPLICATION_ID + ".provider", crashLogsZip))
                        .putExtra(Intent.EXTRA_SUBJECT, "InstaGrabber Crash Report")
                        .putExtra(Intent.EXTRA_TEXT, errorStringBuilder.toString()), "Select an email app to send crash logs"));
            }
        } catch (final Exception e) {
            if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
        }
    }
}
