package com.foxx.androidcast.crash;

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

import com.foxx.androidcast.stats.SessionStatsContext;

import org.json.JSONObject;

import java.io.File;

/**
 * Crash capture for main and monitored isolated processes ({@code :vpn}, {@code :netselftest}).
 * Persists JSON to shared app storage and wakes {@link CrashWatcherService}.
 */
public final class CrashReporter {
    private static final String TAG = "CrashReporter";

    private CrashReporter() {}

    /** Install handlers appropriate for the current process. */
    public static void install(Context context) {
        Context app = context.getApplicationContext();
        String proc = currentProcessName();
        if (isMainProcess(app, proc)) {
            installMainProcess(app);
        } else if (isMonitoredIsolatedProcess(proc)) {
            installIsolatedProcess(app);
        }
    }

    private static void installMainProcess(Context app) {
        if (app instanceof Application) {
            CrashRuntimeContext.init((Application) app);
        }
        CrashNativeBridge.init(app);
        final Thread.UncaughtExceptionHandler previous =
                Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
            try {
                SessionStatsContext.flushActiveOnUncaughtException(app);
            } catch (Throwable ignored) {
            }
            try {
                recordJavaCrash(app, thread, throwable);
            } catch (Throwable t) {
                Log.e(TAG, "recordJavaCrash failed", t);
            }
            if (previous != null) {
                previous.uncaughtException(thread, throwable);
            }
        });
        CrashWatcherService.scheduleUploadSweep(app);
    }

    private static void installIsolatedProcess(Context app) {
        CrashNativeBridge.init(app);
        final Thread.UncaughtExceptionHandler previous =
                Thread.getDefaultUncaughtExceptionHandler();
        Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> {
            try {
                recordJavaCrash(app, thread, throwable);
            } catch (Throwable t) {
                Log.e(TAG, "isolated recordJavaCrash failed", t);
            }
            if (previous != null) {
                previous.uncaughtException(thread, throwable);
            } else {
                System.exit(10);
            }
        });
        Log.i(TAG, "crash capture enabled in " + currentProcessName());
    }

    public static void recordJavaCrash(Context context, Thread thread, Throwable throwable) {
        if (!CrashSettingsStore.load(context).enabled) {
            return;
        }
        JSONObject report = CrashReportBuilder.buildJavaCrash(context, thread, throwable);
        persistAndWake(context, report);
    }

    public static void recordNativeCrash(Context context, String signalName, String backtrace) {
        if (!CrashSettingsStore.load(context).enabled) {
            return;
        }
        JSONObject report = CrashReportBuilder.buildNativeCrash(context, signalName, backtrace);
        persistAndWake(context, report);
    }

    static void persistAndWake(Context context, JSONObject report) {
        if (report == null) {
            return;
        }
        try {
            long generatedAt = report.optLong("generated_at_epoch_ms", System.currentTimeMillis());
            File file = CrashReportStore.newPendingFile(context.getFilesDir(), generatedAt);
            report.put("report_id", file.getName().substring(
                    CrashReportStore.PREFIX.length(),
                    file.getName().length() - ".json".length()));
            report.put("report_file", file.getName());
            CrashReportStore.writeJson(file, report);
            CrashReportStore.pruneOldestPending(context.getFilesDir(),
                    CrashSettingsStore.load(context).maxPendingFiles);
            wakeWatcher(context);
        } catch (Exception e) {
            Log.e(TAG, "persist failed", e);
        }
    }

    private static void wakeWatcher(Context context) {
        CrashWatcherService.requestImmediateSweep(context, true);
    }

    public static boolean isMainProcess(Context context, String procName) {
        return context.getPackageName().equals(procName);
    }

    public static boolean isMonitoredIsolatedProcess(String procName) {
        if (procName == null || procName.isEmpty()) {
            return false;
        }
        return procName.endsWith(":vpn") || procName.endsWith(":netselftest")
                || procName.endsWith(":ra_control");
    }

    public static String currentProcessName() {
        try {
            return Application.getProcessName();
        } catch (Exception e) {
            return "";
        }
    }
}
