package com.miraclegarden.smsmessage.xposed.hook;

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

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

import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.List;

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;

/**
 * Telegram 专用 Hook：监听 NotificationCenter.didReceiveNewMessages，
 * 从 MessageObject 提取已解密的 messageText。
 */
public final class TelegramMessageHook {

    private static final String TAG = "notiMessageHook/Telegram";
    private static final String NOTIFICATION_CENTER = "org.telegram.messenger.NotificationCenter";
    private static final String NOTIFICATIONS_CONTROLLER = "org.telegram.messenger.NotificationsController";
    private static final String MESSAGE_OBJECT = "org.telegram.messenger.MessageObject";
    private static final int DEDUP_SIZE = 512;

    private static final ArrayDeque<String> RECENT_KEYS = new ArrayDeque<>();
    private static final HashSet<String> RECENT_SET = new HashSet<>();

    private TelegramMessageHook() {
    }

    public static void install(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            Class<?> notificationCenter = XposedHelpers.findClass(
                    NOTIFICATION_CENTER, lpparam.classLoader);
            final int didReceiveNewMessages = XposedHelpers.getStaticIntField(
                    notificationCenter, "didReceiveNewMessages");

            XposedHelpers.findAndHookMethod(
                    notificationCenter,
                    "postNotificationName",
                    int.class,
                    Object[].class,
                    new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) {
                            int id = (int) param.args[0];
                            if (id != didReceiveNewMessages) {
                                return;
                            }
                            Object[] args = (Object[]) param.args[1];
                            if (args == null || args.length == 0) {
                                return;
                            }

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

                            for (Object arg : args) {
                                if (arg instanceof List) {
                                    processMessageList(context, lpparam.packageName, (List<?>) arg);
                                } else if (isMessageObject(arg)) {
                                    forwardMessageObject(context, lpparam.packageName, arg);
                                }
                            }
                        }
                    }
            );

            installNotificationsControllerHook(lpparam);

            XposedBridge.log(TAG + " installed for " + lpparam.packageName
                    + ", didReceiveNewMessages=" + didReceiveNewMessages);
        } catch (Throwable t) {
            XposedBridge.log(TAG + " install failed: " + t.getMessage());
            installMessageObjectFallback(lpparam);
        }
    }

    /** NotificationCenter Hook 失败时的兜底：Hook MessageObject 构造完成后的消息。 */
    private static void installMessageObjectFallback(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            Class<?> messageObjectClass = XposedHelpers.findClass(
                    MESSAGE_OBJECT, lpparam.classLoader);
            XposedBridge.hookAllConstructors(messageObjectClass, new XC_MethodHook() {
                @Override
                protected void afterHookedMethod(MethodHookParam param) {
                    Context context = getContext();
                    if (context == null) {
                        return;
                    }
                    forwardMessageObject(context, lpparam.packageName, param.thisObject);
                }
            });
            XposedBridge.log(TAG + " fallback MessageObject hook installed for "
                    + lpparam.packageName);
        } catch (Throwable t) {
            XposedBridge.log(TAG + " fallback install failed: " + t.getMessage());
        }
    }

    /** 后台弹通知路径：NotificationsController.appendMessage(MessageObject) */
    private static void installNotificationsControllerHook(XC_LoadPackage.LoadPackageParam lpparam) {
        try {
            Class<?> controllerClass = XposedHelpers.findClass(
                    NOTIFICATIONS_CONTROLLER, lpparam.classLoader);
            Class<?> messageObjectClass = XposedHelpers.findClass(
                    MESSAGE_OBJECT, lpparam.classLoader);
            XposedHelpers.findAndHookMethod(
                    controllerClass,
                    "appendMessage",
                    messageObjectClass,
                    new XC_MethodHook() {
                        @Override
                        protected void afterHookedMethod(MethodHookParam param) {
                            Context context = getContext();
                            if (context == null || param.args[0] == null) {
                                return;
                            }
                            forwardMessageObject(context, lpparam.packageName, param.args[0]);
                        }
                    }
            );
            XposedBridge.log(TAG + " appendMessage hook installed for " + lpparam.packageName);
        } catch (Throwable t) {
            XposedBridge.log(TAG + " appendMessage hook failed: " + t.getMessage());
        }
    }

    private static boolean isMessageObject(Object arg) {
        return arg != null && MESSAGE_OBJECT.equals(arg.getClass().getName());
    }

    private static void processMessageList(Context context, String packageName, List<?> messages) {
        for (Object item : messages) {
            if (item == null) {
                continue;
            }
            if (!MESSAGE_OBJECT.equals(item.getClass().getName())) {
                continue;
            }
            forwardMessageObject(context, packageName, item);
        }
    }

    private static void forwardMessageObject(Context context, String packageName, Object messageObject) {
        try {
            Object messageOwner = XposedHelpers.getObjectField(messageObject, "messageOwner");
            if (messageOwner == null) {
                return;
            }

            if (XposedHelpers.getBooleanField(messageOwner, "out")) {
                return;
            }

            int messageId = XposedHelpers.getIntField(messageOwner, "id");
            long dialogId = extractDialogId(messageObject, messageOwner);
            if (!rememberMessage(dialogId, messageId)) {
                return;
            }

            String content = extractContent(messageObject, messageOwner);
            if (TextUtils.isEmpty(content)) {
                return;
            }

            String title = extractTitle(messageObject, messageOwner, dialogId);
            long timestamp = XposedHelpers.getIntField(messageOwner, "date") * 1000L;
            if (timestamp <= 0) {
                timestamp = System.currentTimeMillis();
            }

            String sender = extractSenderName(messageObject);
            String body = content;
            if (!TextUtils.isEmpty(sender) && !sender.equals(title)) {
                body = sender + ": " + content;
            }

            HookForwarder.forward(context, packageName, title, body,
                    HookBridge.SOURCE_XPOSED_TELEGRAM);
        } catch (Throwable t) {
            XposedBridge.log(TAG + " forward failed: " + t.getMessage());
        }
    }

    private static long extractDialogId(Object messageObject, Object messageOwner) {
        try {
            return ((Number) XposedHelpers.callMethod(messageObject, "getDialogId")).longValue();
        } catch (Throwable ignored) {
        }
        try {
            return XposedHelpers.getLongField(messageOwner, "dialog_id");
        } catch (Throwable ignored) {
        }
        return 0L;
    }

    private static String extractContent(Object messageObject, Object messageOwner) {
        String caption = firstNonEmpty(
                safeText(XposedHelpers.getObjectField(messageObject, "caption")),
                safeText(XposedHelpers.getObjectField(messageOwner, "message")),
                safeText(callMethodSafe(messageObject, "getCaption"))
        );

        String messageText = firstNonEmpty(
                safeText(XposedHelpers.getObjectField(messageObject, "messageText")),
                safeText(callMethodSafe(messageObject, "getMessageText"))
        );

        String mediaLabel = detectMediaLabel(messageObject, messageOwner);

        if (!TextUtils.isEmpty(caption)) {
            if (!TextUtils.isEmpty(mediaLabel)) {
                return mediaLabel + "\n" + caption;
            }
            if (!TextUtils.isEmpty(messageText) && !isMediaPlaceholder(messageText)
                    && !messageText.equals(caption)) {
                return messageText + "\n" + caption;
            }
            return caption;
        }

        if (!TextUtils.isEmpty(messageText) && !isMediaPlaceholder(messageText)) {
            return messageText;
        }

        if (!TextUtils.isEmpty(mediaLabel)) {
            return mediaLabel;
        }

        if (!TextUtils.isEmpty(messageText)) {
            return messageText;
        }

        return "";
    }

    private static String detectMediaLabel(Object messageObject, Object messageOwner) {
        try {
            if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isSticker"))) {
                return "[贴纸]";
            }
        } catch (Throwable ignored) {
        }
        try {
            if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isPhoto"))) {
                return "[图片]";
            }
        } catch (Throwable ignored) {
        }
        try {
            if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isVideo"))) {
                return "[视频]";
            }
        } catch (Throwable ignored) {
        }
        try {
            if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isGif"))) {
                return "[GIF]";
            }
        } catch (Throwable ignored) {
        }
        try {
            if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isVoice"))) {
                return "[语音]";
            }
        } catch (Throwable ignored) {
        }
        try {
            if (Boolean.TRUE.equals(XposedHelpers.callMethod(messageObject, "isMusic"))) {
                return "[音频]";
            }
        } catch (Throwable ignored) {
        }

        Object media = XposedHelpers.getObjectField(messageOwner, "media");
        if (media == null) {
            return "";
        }
        String mediaClass = media.getClass().getName();
        if (mediaClass.contains("Photo")) {
            return "[图片]";
        }
        if (mediaClass.contains("Document")) {
            return "[文件]";
        }
        if (mediaClass.contains("Video")) {
            return "[视频]";
        }
        if (mediaClass.contains("Geo")) {
            return "[位置]";
        }
        if (mediaClass.contains("Contact")) {
            return "[联系人]";
        }
        return "[媒体消息]";
    }

    private static boolean isMediaPlaceholder(String text) {
        if (TextUtils.isEmpty(text)) {
            return true;
        }
        String value = text.trim();
        return value.equals("图片")
                || value.equals("Photo")
                || value.equals("照片")
                || value.equals("贴纸")
                || value.equals("Sticker")
                || value.equals("Video")
                || value.equals("视频")
                || value.equals("GIF")
                || value.equals("动画")
                || value.equals("文件")
                || value.equals("Document")
                || value.equals("语音")
                || value.equals("Voice")
                || value.equals("音频")
                || value.equals("Audio")
                || value.equals("位置")
                || value.equals("Location")
                || value.startsWith("[媒体")
                || value.startsWith("[图片")
                || value.startsWith("[贴纸")
                || value.startsWith("[视频");
    }

    private static String firstNonEmpty(String... values) {
        for (String value : values) {
            if (!TextUtils.isEmpty(value)) {
                return value;
            }
        }
        return "";
    }

    private static String callMethodSafe(Object target, String methodName) {
        try {
            return safeText(XposedHelpers.callMethod(target, methodName));
        } catch (Throwable ignored) {
            return "";
        }
    }

    private static String extractTitle(Object messageObject, Object messageOwner, long dialogId) {
        String viaController = resolveTitleViaController(messageObject, dialogId);
        if (!TextUtils.isEmpty(viaController)) {
            return viaController;
        }

        try {
            Object dialogName = XposedHelpers.callMethod(messageObject, "getName");
            String name = safeText(dialogName);
            if (!TextUtils.isEmpty(name)) {
                return name;
            }
        } catch (Throwable ignored) {
        }

        try {
            Object chat = XposedHelpers.callMethod(messageObject, "getChat");
            if (chat != null) {
                String chatTitle = safeText(XposedHelpers.getObjectField(chat, "title"));
                if (!TextUtils.isEmpty(chatTitle)) {
                    return chatTitle;
                }
            }
        } catch (Throwable ignored) {
        }

        String localName = safeText(XposedHelpers.getObjectField(messageObject, "localName"));
        if (!TextUtils.isEmpty(localName)) {
            return localName;
        }

        return dialogId != 0 ? ("对话 " + dialogId) : "Telegram";
    }

    private static String resolveTitleViaController(Object messageObject, long dialogId) {
        if (dialogId == 0) {
            return null;
        }
        try {
            int account = XposedHelpers.getIntField(messageObject, "currentAccount");
            ClassLoader cl = messageObject.getClass().getClassLoader();
            Class<?> mcClass = XposedHelpers.findClass(
                    "org.telegram.messenger.MessagesController", cl);
            Object mc = XposedHelpers.callStaticMethod(mcClass, "getInstance", account);

            Object title = invokeGetPeerTitle(mc, dialogId);

            String text = safeText(title);
            if (!TextUtils.isEmpty(text)) {
                return text;
            }

            if (dialogId < 0) {
                Object chat = XposedHelpers.callMethod(mc, "getChat", -dialogId);
                if (chat == null) {
                    chat = XposedHelpers.callMethod(mc, "getChat", dialogId);
                }
                if (chat != null) {
                    text = safeText(XposedHelpers.getObjectField(chat, "title"));
                    if (!TextUtils.isEmpty(text)) {
                        return text;
                    }
                }
            } else {
                Object user = XposedHelpers.callMethod(mc, "getUser", dialogId);
                text = formatUserName(user);
                if (!TextUtils.isEmpty(text)) {
                    return text;
                }
            }
        } catch (Throwable t) {
            XposedBridge.log(TAG + " resolveTitleViaController: " + t.getMessage());
        }
        return null;
    }

    private static Object invokeGetPeerTitle(Object mc, long dialogId) {
        try {
            return XposedHelpers.callMethod(mc, "getPeerTitle", dialogId, false);
        } catch (Throwable ignored) {
        }
        try {
            return XposedHelpers.callMethod(mc, "getPeerTitle", dialogId);
        } catch (Throwable ignored) {
        }
        try {
            java.lang.reflect.Method method = mc.getClass().getMethod("getPeerTitle", long.class, boolean.class);
            return method.invoke(mc, dialogId, false);
        } catch (Throwable ignored) {
        }
        try {
            java.lang.reflect.Method method = mc.getClass().getMethod("getPeerTitle", long.class);
            return method.invoke(mc, dialogId);
        } catch (Throwable ignored) {
        }
        return null;
    }

    private static String formatUserName(Object user) {
        if (user == null) {
            return "";
        }
        String first = safeText(XposedHelpers.getObjectField(user, "first_name"));
        String last = safeText(XposedHelpers.getObjectField(user, "last_name"));
        String username = safeText(XposedHelpers.getObjectField(user, "username"));
        StringBuilder sb = new StringBuilder();
        if (!TextUtils.isEmpty(first)) {
            sb.append(first);
        }
        if (!TextUtils.isEmpty(last)) {
            if (sb.length() > 0) {
                sb.append(' ');
            }
            sb.append(last);
        }
        if (sb.length() > 0) {
            return sb.toString();
        }
        if (!TextUtils.isEmpty(username)) {
            return "@" + username;
        }
        return "";
    }

    private static String safeText(Object value) {
        if (value == null) {
            return "";
        }
        if (!(value instanceof CharSequence) && !(value instanceof Number) && !(value instanceof Boolean)) {
            String className = value.getClass().getName();
            if (className.startsWith("org.telegram.tgnet.")
                    || className.contains("TLRPC$")
                    || value.getClass().getName().contains("Peer")) {
                return "";
            }
        }
        String text = String.valueOf(value).trim();
        if (text.contains("TLRPC$") || text.contains("org.telegram.tgnet.")) {
            return "";
        }
        return text;
    }

    private static String extractSenderName(Object messageObject) {
        try {
            String fromName = safeText(XposedHelpers.callMethod(messageObject, "getFromName"));
            if (!TextUtils.isEmpty(fromName)) {
                return fromName;
            }
        } catch (Throwable ignored) {
        }
        return "";
    }

    private static synchronized boolean rememberMessage(long dialogId, int messageId) {
        if (messageId <= 0) {
            return true;
        }
        String key = dialogId + ":" + messageId;
        if (RECENT_SET.contains(key)) {
            return false;
        }
        RECENT_SET.add(key);
        RECENT_KEYS.addLast(key);
        while (RECENT_KEYS.size() > DEDUP_SIZE) {
            String oldest = RECENT_KEYS.removeFirst();
            RECENT_SET.remove(oldest);
        }
        return true;
    }

    private static Context getContext() {
        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;
    }
}
