package org.autojs.autojs.util;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Service;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.pm.Signature;
import android.util.Base64;

import androidx.annotation.Nullable;

import org.autojs.autojs.app.GlobalAppContext;
import org.autojs.autojs.concurrent.UnderUseExecutors;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.security.MessageDigest;
import java.util.concurrent.ExecutorService;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

import kotlin.text.Regex;

/**
 * Created by Stardust on Apr 5, 2017.
 * Modified by SuperMonster003 as of Dec 1, 2021.
 */
public class DeveloperUtils {

    private static final Regex SIGNATURE_REX = new Regex("CbKua77m59vis|N7YkpKxKjsPWe");
    private static final ExecutorService sExecutor = UnderUseExecutors.getExecutor();

    // @Hint by JetBrains AI Assistant on Apr 4, 2025.
    //  ! SALT is used in cryptography to enhance the randomness and complexity of the hash algorithm.
    //  ! During the hashing or encryption process, simply processing the data might result in
    //  ! identical hash outputs for identical inputs, leaving it vulnerable to attacks, such as rainbow table attacks.
    //  ! By incorporating a predetermined SALT, even identical inputs will produce different hash outputs, thereby enhancing security.
    //  ! In simple terms, SALT is like adding a special seasoning to the data to create an extra layer of protection during hash computation.
    //  ! zh-CN:
    //  ! SALT 在密码学中用于增加散列 (Hash) 算法的随机性和复杂度.
    //  ! 在散列或加密过程中, 仅仅对数据进行处理可能会使得相同的数据产生相同的散列结果, 这样就容易受到如彩虹表攻击等威胁.
    //  ! 通过加入一个预先设定的 SALT, 能使得即使输入的数据相同, 最终的散列结果也会不同, 从而提升安全性.
    //  ! 简单来说, SALT 就像是在数据中 "加入了一点特殊调料", 使得在计算散列值时增加了一道额外的防护层.
    //  !
    // @Hint by SuperMonster003 on Apr 4, 2025.
    //  ! let life be beautiful like summer flowers and death like autumn leaves.
    //  ! Derived from Rabindranath Tagore's "Stray Birds", section 82.
    //  ! zh-CN:
    //  ! 使生如夏花之绚烂, 死如秋叶之静美. -- 郑振铎译
    //  ! 出自拉宾德拉纳特·泰戈尔的 <<飞鸟集>> 第 82 首.
    private static final String SALT = "let\nlife\nbe\nbeautiful\nlike\nsummer\nflowers\nand\ndeath\nlike\nautumn\nleaves\n.";

    public static boolean isSelfPackage(@Nullable String runningPackage) {
        return selfPackage().equals(runningPackage);
    }

    @Nullable
    public static String getSignatureSHA(Context context, String packageName) {
        try {
            @SuppressLint("PackageManagerGetSignatures")
            PackageInfo packageInfo = getPackageInfo(context, packageName, PackageManager.GET_SIGNATURES);
            if (packageInfo == null)
                return null;
            Signature[] signatures = packageInfo.signatures;
            StringBuilder builder = new StringBuilder();
            for (Signature signature : signatures) {
                MessageDigest md = MessageDigest.getInstance("SHA-256");
                md.update(signature.toByteArray());
                final String sha = Base64.encodeToString(md.digest(), Base64.NO_WRAP);
                builder.append(sha);
            }
            return builder.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    private static PackageInfo getPackageInfo(Context context, String packageName, int flags) {
        try {
            return context.getPackageManager().getPackageInfo(packageName, flags);
        } catch (PackageManager.NameNotFoundException e) {
            return null;
        }
    }

    // @Hint by Stardust (https://github.com/hyb1996) on Aug 15, 2017.
    //  ! 此方法仅防止那些不会改源码直接用 APK 编辑器修改应用内字符串 (QQ群号) 等的恶意用户行为.
    //  ! 为了开源社区的发展, 请善用源码 :-)
    //  !
    // @Hint by SuperMonster003 on Nov 28, 2024.
    //  ! Peace.
    public static boolean checkSignature(Context context) {
        return checkSignature(context, context.getPackageName());
    }

    public static boolean checkSignature(Context context, String packageName) {
        String sha = getSignatureSHA(context, packageName);
        if (sha == null)
            return false;
        if (sha.endsWith("\n")) {
            sha = sha.substring(0, sha.length() - 1);
        }
        return SIGNATURE_REX.containsMatchIn(sha);
    }

    public static String selfPackage() {
        return GlobalAppContext.get().getPackageName();
    }

    public static boolean isActivityRegistered(Context context, Class<? extends Activity> c) {
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_ACTIVITIES);
            ActivityInfo[] activities = packageInfo.activities;
            if (activities == null) {
                return false;
            }
            for (ActivityInfo info : activities) {
                if (c.getName().equals(info.name)) {
                    return true;
                }
            }
            return false;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    public static boolean isServiceRegistered(Context context, Class<? extends Service> c) {
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SERVICES);
            ServiceInfo[] activities = packageInfo.services;
            if (activities == null) {
                return false;
            }
            for (ServiceInfo info : activities) {
                if (c.getName().equals(info.name)) {
                    return true;
                }
            }
            return false;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }

    public static boolean checkDexFile(Context context, long[] crc) {
        String apkPath = context.getPackageCodePath();
        try {
            ZipFile zipFile = new ZipFile(apkPath);
            for (int i = 0; i < crc.length; i++) {
                String dexFile;
                if (i == 0) {
                    dexFile = "classes.dex";
                } else {
                    dexFile = "classes" + (i + 1) + ".dex";
                }
                ZipEntry dexEntry = zipFile.getEntry(dexFile);
                long dexEntryCrc = dexEntry.getCrc();
                //Log.d(LOG_TAG, String.valueOf(dexEntryCrc));
                if (dexEntryCrc != crc[i]) {
                    return false;
                }
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    public static void verifyApk(Activity activity) {
        final WeakReference<Activity> activityRef = new WeakReference<>(activity);
        sExecutor.execute(() -> {
            Activity a = activityRef.get();
            if (a != null && !checkSignature(a)) {
                a.finish();
            }
        });
    }

    private static long[] readCrc(String crcStr) {
        String[] crcStrings = crcStr.split("\n");
        StringBuilder iHash = new StringBuilder();
        long[] crc = new long[crcStrings.length - 1];
        for (int i = 0; i < crc.length; i++) {
            crc[i] = Long.parseLong(crcStrings[i]);
            iHash.append(iHash(crcStrings[i]));
        }
        if (!crcStrings[crcStrings.length - 1].equals(iHash.toString())) {
            return new long[crc.length];
        }
        return crc;
    }

    private static String iHash(String data) {
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            for (int i = 0; i < 8; i++) {
                data = Base64.encodeToString(md5.digest((data + SALT).getBytes()), Base64.NO_WRAP);
            }
            return data;
        } catch (Exception e) {
            return null;
        }
    }

}
