package com.miraclegarden.smsmessage.xposed.hook;

import android.content.ContentValues;
import android.content.Context;
import android.text.TextUtils;

import com.miraclegarden.smsmessage.xposed.HookBridge;
import com.miraclegarden.smsmessage.xposed.HookForwarder;

import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage;

public final class SqliteMessageHook {

    private static final String TAG = "notiMessageHook/Sqlite";
    private static final String[] SQLITE_CLASSES = {
            "android.database.sqlite.SQLiteDatabase",
            "androidx.sqlite.db.framework.FrameworkSQLiteDatabase"
    };

    private SqliteMessageHook() {
    }

    public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
        for (String sqliteClass : SQLITE_CLASSES) {
            try {
                hookInsert(lpparam, sqliteClass, "insert");
                hookInsert(lpparam, sqliteClass, "insertWithOnConflict");
            } catch (Throwable t) {
                XposedBridge.log(TAG + " skip " + sqliteClass + ": " + t.getMessage());
            }
        }
    }

    private static void hookInsert(XC_LoadPackage.LoadPackageParam lpparam,
                                   String className, String methodName) {
        XposedHelpers.findAndHookMethod(
                className,
                lpparam.classLoader,
                methodName,
                String.class,
                String.class,
                ContentValues.class,
                new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) {
                        handleInsert(lpparam.packageName, param);
                    }
                }
        );
    }

    private static void handleInsert(String packageName, XC_MethodHook.MethodHookParam param) {
        if (param.args == null || param.args.length < 3) {
            return;
        }

        Object tableArg = param.args[0];
        Object valuesArg = param.args[2];
        if (!(tableArg instanceof String) || !(valuesArg instanceof ContentValues)) {
            return;
        }

        String table = ((String) tableArg).toLowerCase();
        if (!isMessageTable(table)) {
            return;
        }

        ContentValues values = (ContentValues) valuesArg;
        String title = firstNonEmpty(values, "talker", "sender", "from", "title", "name");
        String content = firstNonEmpty(values, "content", "text", "body", "msg", "message");
        if (TextUtils.isEmpty(content)) {
            return;
        }

        Context context = getContext(param.thisObject);
        if (context == null) {
            return;
        }

        HookForwarder.forward(context, packageName, title, content,
                HookBridge.SOURCE_XPOSED_SQLITE);
    }

    private static boolean isMessageTable(String table) {
        return "message".equals(table)
                || "messages".equals(table)
                || table.endsWith("_message")
                || table.contains("message");
    }

    private static String firstNonEmpty(ContentValues values, String... keys) {
        for (String key : keys) {
            String value = values.getAsString(key);
            if (!TextUtils.isEmpty(value)) {
                return value;
            }
        }
        return "";
    }

    private static Context getContext(Object database) {
        try {
            Object ctx = XposedHelpers.callMethod(database, "getContext");
            if (ctx instanceof Context) {
                return (Context) ctx;
            }
        } catch (Throwable ignored) {
        }

        try {
            Class<?> activityThread = XposedHelpers.findClass("android.app.ActivityThread", null);
            Object app = XposedHelpers.callStaticMethod(activityThread, "currentApplication");
            if (app instanceof Context) {
                return (Context) app;
            }
        } catch (Throwable ignored) {
        }
        return null;
    }
}
