import json
import os
import random
import re
import shutil
from base64 import b64encode, b64decode

import cv2
import numpy as np
import regex as re2
# from Cryptodome.Cipher import AES
# from Cryptodome.Random import get_random_bytes
# import chardet
import string
import random


def detect_encoding(file_path):
    with open(file_path, 'rb') as f:
        raw_data = f.read(1024)
        result = chardet.detect(raw_data)
    return result['encoding']


def get_classes_impl(result, class_type, path='.'):
    print("get_classes_impl start")
    listdir = os.listdir(path)
    for i in listdir:
        print(i)
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            get_classes_impl(result, class_type, path_join)
        elif os.path.isfile(path_join):
            if os.path.splitext(i)[-1][1:] in class_type:
                result.append(path_join)
                print(result)
    print("get_classes_impl over")


def get_classes(class_type=None):
    print("get_classes start")
    if class_type is None:
        class_type = ["kt", "java"]
    result = []
    get_classes_impl(result, class_type)
    print(result)
    print("get_classes over")
    return result


def find_text_impl(reg, result, skip_name, path, is_one):
    listdir = os.listdir(path)
    for i in listdir:
        if is_one and len(result) > 0:
            return
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            find_text_impl(reg, result, skip_name, path_join, is_one)
        elif os.path.isfile(path_join):
            if path.endswith('.') and i.split('.')[-1] in ['py']:
                continue
            is_skip = False
            for j in skip_name:
                if j in i:
                    is_skip = True
                    break
            if is_skip:
                continue
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue
                findall = re.findall(reg, text)
                if len(findall) > 0:
                    result.extend(findall)


def find_text(reg, path='.', is_one=False, skip_name=None):
    if skip_name is None:
        skip_name = []
    result = []
    if os.path.isdir(path):
        find_text_impl(reg, result, skip_name, path, is_one)
    else:
        with open(path, 'r', encoding='utf-8') as f:
            if f.readable():
                try:
                    text = f.read()
                    findall = re.findall(reg, text)
                    if len(findall) > 0:
                        result.extend(findall)
                except ValueError:
                    pass
    if is_one:
        if len(result) > 0:
            return result[0]
        else:
            return None
    return result


def find_file_impl(reg, result, skip_name, path='.'):
    listdir = os.listdir(path)
    for i in listdir:
        if len(result) > 0:
            return
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            find_file_impl(reg, result, skip_name, path_join)
        elif os.path.isfile(path_join):
            if path.endswith('.') and i not in ["pro"]:
                continue
            is_skip = False
            for j in skip_name:
                if j in i:
                    is_skip = True
                    break
            if is_skip:
                continue
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue
                findall = re.findall(reg, text)
                if len(findall) > 0:
                    result.append(path_join)


def find_file(reg, path='.', skip_name=None):
    print(find_file)
    if skip_name is None:
        skip_name = []
    result = []
    if os.path.isdir(path):
        find_file_impl(reg, result, skip_name, path)
    else:
        with open(path, 'r', encoding='utf-8') as f:
            if f.readable():
                try:
                    text = f.read()
                    findall = re.findall(reg, text)
                    if len(findall) > 0:
                        result.append(path)
                except ValueError:
                    pass
    return result[0] if len(result) > 0 else ""


def get_class_path(path):
    with open(path, 'r') as f:
        text = f.read()
        package = re.search('package (.*?)\\s', text).group(1)
        name = os.path.basename(path)
        name = name[:name.rfind('.')]
        return package + '.' + name


def replace_map_text(mapping, path='.'):
    listdir = os.listdir(path)
    for i in listdir:
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            replace_map_text(mapping, path_join)
        elif os.path.isfile(path_join):
            if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
                continue
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue
            with open(path_join, 'w', encoding='utf-8') as f:
                reverse = sorted(list(mapping.keys()), key=len, reverse=True)
                for name in reverse:
                    # text = text.replace(str(name), mapping[name])
                    text_result = re.sub(rf'\b{name}\b', mapping[name], text_result)
                f.write(text)


# 修复某些字符无法匹配的问题，如android:OEM
def sub_map_text(mapping, is_reverse=True, path='.', skip_type=None, skip_name=None):
    print("sub_map_text start")
    if len(mapping) == 0:
        return []
    if skip_type is None:
        skip_type = []
    if skip_name is None:
        skip_name = []
    skip_name.append('databinding')  # 跳过 databinding
    result = []
    sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path)
    print(result)
    print("sub_map_text over")
    return result


# def sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse=True, path='.'):
#     print("sub_map_text_impl start")
#     listdir = os.listdir(path)
#     for i in listdir:
#         path_join = os.path.join(path, i)
#         if os.path.isdir(path_join):
#             if path.endswith('.') and i in ["build", "debug", "release"]:
#                 continue
#             if path.endswith('src') and i in ["androidTest", "test"]:
#                 continue
#             sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path_join)
#         elif os.path.isfile(path_join):
#             if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
#                 continue
#             if os.path.splitext(i)[-1][1:] in skip_type:
#                 continue
#             is_skip = False
#             for j in skip_name:
#                 if j in i:
#                     print(j)
#                     is_skip = True
#                     break
#             if is_skip:
#                 continue
#             with open(path_join, 'r', encoding='utf-8') as f:
#                 if not f.readable():
#                     continue
#                 try:
#                     text = f.read()
#                 except ValueError:
#                     continue
#             with open(path_join, 'w', encoding='utf-8') as f:
#                 reverse = sorted(list(mapping.keys()), key=len, reverse=is_reverse)
#                 text_result = text
#                 for name in reverse:
#                     text_result = re2.sub(str(name), mapping[name], text_result)
#                     print(text_result)
#                 if text_result != text:
#                     result.append(path_join)
#                 print(result)
#                 f.write(text_result)
#                 print("sub_map_text_impl over")
# 修复datbinding问题
def sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse=True, path='.'):
    print("sub_map_text_impl start")
    listdir = os.listdir(path)
    for i in listdir:
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            sub_map_text_impl(mapping, result, skip_type, skip_name, is_reverse, path_join)
        elif os.path.isfile(path_join):
            if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
                continue
            if os.path.splitext(i)[-1][1:] in skip_type:
                continue
            is_skip = False
            for j in skip_name:
                if j in i:
                    print(j)
                    is_skip = True
                    break
            if is_skip:
                continue

            # 开始读取文件内容
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue

            # 替换逻辑
            with open(path_join, 'w', encoding='utf-8') as f:
                reverse = sorted(list(mapping.keys()), key=len, reverse=is_reverse)
                text_result = text

                patterns = sorted(mapping.keys(), key=len, reverse=is_reverse)

                for pattern in patterns:
                    try:
                        print(f"正在替换 pattern: {pattern}")
                        text_result = re.sub(pattern, mapping[pattern], text_result)
                    except Exception as e:
                        print(f"替换失败: pattern={pattern}, 错误: {e}")
                # # 遍历映射规则替换
                # for name in reverse:
                #     text_result = re2.sub(str(name), mapping[name], text_result)

                # 方案 3：针对特定 import 的直接替换
                text_result = re.sub(
                    r'import varietyed\.scratched\.assemblyed\.(R|databinding\.\w+)',
                    r'import com.pots.qasdf.phonemanager.\1',
                    text_result
                )

                # 检查替换结果
                if text_result != text:
                    result.append(path_join)

                f.write(text_result)
    print("sub_map_text_impl over")


def replace_text(old, new, path='.'):
    print("replace_text")
    listdir = os.listdir(path)
    for i in listdir:
        path_join = os.path.join(path, i)
        if os.path.isdir(path_join):
            if path.endswith('.') and i in ["build", "debug", "release"]:
                continue
            if path.endswith('src') and i in ["androidTest", "test"]:
                continue
            replace_text(old, new, path_join)
        elif os.path.isfile(path_join):
            if path.endswith('.') and os.path.splitext(i)[-1][1:] not in ["pro"]:
                continue
            with open(path_join, 'r', encoding='utf-8') as f:
                if not f.readable():
                    continue
                try:
                    text = f.read()
                except ValueError:
                    continue
            with open(path_join, 'w', encoding='utf-8') as f:
                f.write(text.replace(old, new))
                print(text)


def get_random_package():
    packages = [get_random_string(4) for _ in range(get_random_int(1, 3))]
    return '.'.join(packages)


def check_mapping():
    print("check_mapping start")
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get('dir', {})

    packages = find_text('package (.*?)[;]?\\s', 'src')
    packages.append(applicationId)
    packages = sorted(set(packages), key=packages.index)
    packages.sort(key=len)
    print(packages)

    mapping = {}
    for i in packages:
        # 解决databingding包被混淆
        if 'databinding' in i:  # 跳过 databinding 包
            mapping[i] = i
            continue
        if applicationId not in i:
            continue
        value = old_mapping.get(i)
        if value is not None:
            mapping[i] = value
            continue
        mapping[i] = get_random_package()

        value = mapping.get(i[:i.rfind('.')])
        if value is not None:
            mapping[i] = value + '.' + mapping[i]
    loads["dir"] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    print(json)
    print("check_mapping over")
    return mapping


def check_class_mapping():
    print("check_class_mapping start")
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
        print(loads)
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get('class', {})

    mapping = {}
    classes = get_classes()
    for i in classes:
        #or os.path.basename(i) == "GlobalConfig.kt" 去除跳过GlobalConfig
        if '_D' in i :  # 跳过 GlobalConfig.kt
            continue
        print(i)
        name = os.path.basename(i)
        name = name[:name.rfind('.')]
        value = old_mapping.get(name)
        print(value)
        if value is not None:
            mapping[name] = value
            continue
        mapping[name] = get_title(get_random_string(4), True)
        print(mapping[name])
        # method_variable_mapping = generate_method_variable_mapping(i)  # 生成方法名和变量名的混淆映射
        # for old, new in method_variable_mapping.items():
        #     mapping[old] = new  # 添加方法名和变量名的混淆映射
    loads["class"] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    print(json)
    print("check_class_mapping over")
    return mapping


# def generate_method_variable_mapping(class_file):
#     # 生成方法名和变量名的混淆映射
#     mapping = {}
#
#     with open(class_file, 'r', encoding='utf-8') as f:
#         text = f.read()
#
#     methods = re.findall(
#         r'\b(public|private|protected|static|final|abstract|synchronized)?\s*(<[^>]+>\s*)?(\w+)\s+(\w+)\s*\(',
#         text
#     )
#     variables = re.findall(
#         r'\b(public|private|protected|static|final|transient|volatile)?\s*(<[^>]+>\s*)?(\w+)\s+(\w+)\s*[;=]',
#         text
#     )
#
#     for method in methods:
#         old_name = method[3]
#         new_name = get_random_string(6)
#         mapping[old_name] = new_name
#
#     for variable in variables:
#         old_name = variable[3]
#         new_name = get_random_string(6)
#         mapping[old_name] = new_name
#
#     return mapping

def check_type_ids_mapping(ids_type):
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get(ids_type + "_ids", {})

    mapping = {}
    type_ids = find_text('<' + ids_type + ' name="(.*?)".*?>', get_path('.\\src\\main\\res'))
    for i in type_ids:
        if i.endswith('_D'):
            continue
        value = old_mapping.get(i)
        if value is not None:
            mapping[i] = value
            continue
        mapping[i] = get_random_string(5, False)
    type_ids = find_text('<item name="(.*?)".*? type="' + ids_type + '">', get_path('.\\src\\main\\res'))
    for i in type_ids:
        value = old_mapping.get(i)
        if value is not None:
            mapping[i] = value
            continue
        mapping[i] = get_random_string(5, False)
    if len(mapping) != 0:
        loads[ids_type + "_ids"] = mapping
        json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def check_styleable_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get("styleable", {})

    mapping = {}
    styleables = find_text('<declare-styleable name=".*?">[\\s\\S]*?</declare-styleable>')
    for i in styleables:
        styleable_id = re.findall('<declare-styleable name="(.*?)">', i)[0]
        mapping[styleable_id] = old_mapping.get(styleable_id, get_random_string(5, False))
        # styleables_attr = re.findall('<attr name="(.*?)".*?/>', i)
        styleables_attr = re.findall('<attr name="(.*?)".*?>', i)  # 包括单行和多行定义
        for attr in styleables_attr:
            mapping[attr + '_A'] = old_mapping.get(attr + '_A', get_random_string(5))
    if len(mapping) != 0:
        loads["styleable"] = mapping
        json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def deal_ids_type(ids_type):
    type_ids_mapping = check_type_ids_mapping(ids_type)
    if len(type_ids_mapping) == 0:
        return
    print(type_ids_mapping)

    sub_map = {}
    for i in type_ids_mapping:
        if i in type_ids_mapping and type_ids_mapping[i].startswith("android"):
            continue
        # mapped = type_ids_mapping[i]
        #
        # sub_map['<' + ids_type + ' name="' + i + '"'] = '<' + ids_type + ' name="' + mapped + '"'
        # sub_map['<item name="' + i + '"(.*? type="' + ids_type + '")>'] = '<item name="' + mapped + '"\\g<1>>'
        # sub_map[r'(?<=[^\.])R\.' + ids_type + r'\.' + re.escape(i) + r'(?=\W)'] = 'R.' + ids_type + '.' + mapped
        # sub_map[r'(?<=>|")@' + ids_type + '/' + re.escape(i) + r'(?=<|")'] = '@' + ids_type + '/' + mapped
        # sub_map[r'(?<=[@=:])@' + ids_type + '/' + re.escape(i) + r'(?=[}"\'\s])'] = '@' + ids_type + '/' + mapped
        #
        # # ✅ DataBinding 表达式中所有 @xxx/yyy（包括拼接字符串等复杂表达式）
        # sub_map[r'(?<=@{[^}]*?)@' + ids_type + r'/' + re.escape(i) + r'(?=[^}]*})'] = '@' + ids_type + '/' + mapped
        #
        # # ✅ <style name="..." parent="xxx"> 资源引用
        # sub_map[r'<' + ids_type + r'(.*?) parent="' + re.escape(i) + r'"(.*?)>'] = (
        #         r'<' + ids_type + r'\g<1> parent="' + mapped + r'"\g<2>>'
        # )

        sub_map['<' + ids_type + ' name="' + i + '"'] = '<' + ids_type + ' name="' + type_ids_mapping[i] + '"'
        sub_map['<item name="' + i + '"(.*? type="' + ids_type + '")>'] = '<item name="' + type_ids_mapping[
            i] + '"\\g<1>>'
        sub_map['(?<=[^\\.])R\\.' + ids_type + '\\.' + i + '(?=\\W)'] = 'R.' + ids_type + '.' + type_ids_mapping[i]
        sub_map['(?<=>|")@' + ids_type + '/' + i + '(?=<|")'] = '@' + ids_type + '/' + type_ids_mapping[i]
        sub_map[r'(?<=[@=:])@' + ids_type + '/' + i + r'(?=[}"\'\s])'] = '@' + ids_type + '/' + type_ids_mapping[i]
        sub_map[r'(?<=@{[^}]*?)@' + ids_type + r'/' + i + r'(?=[^}]*})'] = '@' + ids_type + '/' + type_ids_mapping[
            i]  # ⬅ 适配 data binding 表达式
        sub_map['<' + ids_type + '(.*?) parent="' + i + '"(.*?)>'] = '<' + ids_type + '\\g<1> parent="' + \
                                                                     type_ids_mapping[i] + '"\\g<2>>'
        escaped_i = re.escape(i)
        sub_map[rf'(?<![a-zA-Z0-9_/])@{ids_type}/{escaped_i}(?![a-zA-Z0-9_])'] = f'@{ids_type}/{type_ids_mapping[i]}'
        # 带包名前缀的资源路径 com.retrytech.ledgeapp.R.color.xxx
        # sub_map[rf'(\b[\w\.]+)\.R\.{ids_type}\.{re.escape(i)}\b'] = rf'\1.R.{ids_type}.{type_ids_mapping[i]}'
        # sub_map[
        #     rf'(?<!android\.)(\b[\w\.]+)\.R\.{ids_type}\.{re.escape(i)}\b'] = rf'\1.R.{ids_type}.{type_ids_mapping[i]}'
        sub_map[
            rf'(?<!\bandroid\.)\b([\w\.]+)\.R\.{ids_type}\.{re.escape(i)}\b'] = rf'\1.R.{ids_type}.{type_ids_mapping[i]}'

        # ✅ 推荐的替换逻辑（万能适配 DataBinding 场景）
        # sub_map[f'@{ids_type}/{i}'] = f'@{ids_type}/{type_ids_mapping[i]}'

        # # ✅ 适配 DataBinding 表达式中包括拼接、三目等形式的字符串资源引用
        # sub_map[rf'(?<=@{{[^}}]*?)@{ids_type}/{re.escape(i)}(?=[^}}]*}})'] = f'@{ids_type}/{type_ids_mapping[i]}'
        # # DataBinding 表达式中字符串拼接，如：@{@string/subscribe_to+" "+@string/pro}
        # sub_map[r'(?<=@{[^}]*?)@' + ids_type + r'/' + re.escape(i) + r'(?=[^}]*})'] = '@' + ids_type + '/' + \
        #                                                                               type_ids_mapping[i]

        # sub_map['<' + ids_type + ' name="' + i + '"'] = '<' + ids_type + ' name="' + type_ids_mapping[i] + '"'
        # sub_map['<item name="' + i + '"(.*? type="' + ids_type + '")>'] = \
        #     '<item name="' + type_ids_mapping[i] + '"\\g<1>>'
        # sub_map['(?<=[^\\.])R\\.' + ids_type + '\\.' + i + '(?=\\W)'] = 'R.' + ids_type + '.' + type_ids_mapping[i]
        # sub_map['(?<=>|")@' + ids_type + '/' + i + '(?=<|")'] = '@' + ids_type + '/' + type_ids_mapping[i]
        # sub_map['(?<=[@=:])@' + ids_type + '/' + i + r'(?=[}"\'\s])'] = '@' + ids_type + '/' + type_ids_mapping[i]
        # sub_map['<' + ids_type + '(.*?) parent="' + i + '"(.*?)>'] = \
        #     '<' + ids_type + '\\g<1> parent="' + type_ids_mapping[i] + '"\\g<2>>'
        # # 匹配 @color/xxx 出现在 @{} 表达式中，适配 data binding 多种情况
        # sub_map[r'(?<=@{[^}]*?)@' + ids_type + r'/' + i + r'(?=[^}]*})'] = '@' + ids_type + '/' + type_ids_mapping[i]
        sub_map[
            rf'android\.R\.color\.(\w+)'] = lambda m: 'android.R.color.' + type_ids_mapping.get(m.group(1), m.group(1))

    sub_map_text(sub_map)


def check_view_ids_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    mapping = loads.get('view_ids', {})

    view_ids = find_text('"\\@\\+id/(.*?)"', get_path('.\\src\\main\\res'))
    for i in view_ids:
        if i == 'root':
            continue
        key = get_title(i)
        if mapping.get(key) is not None:
            continue
        mapping[key] = get_random_string(7)
    loads["view_ids"] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def check_layout_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    mapping = loads.get('layout', {})

    layout_path = get_path('.\\src\\main\\res\\layout')
    listdir = os.listdir(layout_path)
    for i in listdir:
        name = i[:i.rfind('.')]
        if mapping.get(name) is not None:
            continue
        mapping[name] = get_random_string(6)
    loads["layout"] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def check_res_mapping(res_type):
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    mapping = loads.get(res_type, {})

    res_path = get_path('.\\src\\main\\res')
    listdir = os.listdir(res_path)
    for i in listdir:
        if not i.startswith(res_type):
            continue
        for j in os.listdir(os.path.join(res_path, i)):
            if j.startswith('book_'):
                continue
            if j.endswith('.9.png'):
                name = j[:-len('.9.png')]
            else:
                name = j.rsplit('.', 1)[0]
            if mapping.get(name) is not None:
                continue
            mapping[name] = normalize_filename(get_random_string(8))
    if len(mapping) != 0:
        loads[res_type] = mapping
        json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


used_words = set()


def get_random_string(length=8, is_ascii=True):
    global used_words  # 使用全局变量 used_words
    words = load_words_from_file('word_file.json')
    if words:
        while words:
            random_word = random.choice(words)
            if random_word not in used_words:
                used_words.add(random_word)
                if is_ascii and all(ord(c) < 128 for c in random_word):
                    return random_word
                elif not is_ascii:
                    return random_word
        return None


def load_words_from_file(file_path):
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
            return data.get("word", [])  # 返回单词列表
    except FileNotFoundError:
        print(f"Error: Word file not found at: {file_path}")
        return []
    except json.JSONDecodeError:
        print(f"Error: Invalid JSON format in: {file_path}")
        return []


def get_random_int(a=0, b=10000):
    return random.randint(a, b)


def get_dictionary_string(length=10):
    random_str = random.choice(string.ascii_letters)
    random_str += ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length - 1))
    return random_str


def check_obfuscation_dictionary():
    if not os.path.exists('dictionary.txt'):
        dictionary = set()
        while len(dictionary) != 5000:
            dictionary.add(get_dictionary_string())
        with open('dictionary.txt', 'w', encoding='utf-8') as f:
            f.write('\n'.join(dictionary))
    with open('proguard-rules.pro', 'r', encoding='utf-8') as f:
        text = f.read()
    if '-obfuscationdictionary' in text:
        return
    text += '\n-obfuscationdictionary dictionary.txt'
    text += '\n-classobfuscationdictionary dictionary.txt'
    text += '\n-packageobfuscationdictionary dictionary.txt'
    with open('proguard-rules.pro', 'w', encoding='utf-8') as f:
        f.write(text)


def mkdir(dir_path):
    try:
        os.makedirs(dir_path)
    except OSError:
        pass


def replace_click_method():
    print("replace_click_method start")
    try:
        path = get_path('.\\src\\main\\java')
        file = find_file('fun View.setTrackedOnClickListener', path)
        if file:
            class_path = get_class_path(file) + '.setTrackedOnClickListener'
            # java类不能直接使用扩展函数
            sub_map_text({
                '([.@])setOnClickListener': '\\g<1>setTrackedOnClickListener',
            }, path=path, skip_type=['java'])
            add_import(class_path, get_classes(['kt']))
            print("replace_click_method over")
    except IndexError:
        return


def replace_package_in_imports(root_path, old_package, new_package):
    # 匹配旧的 import 包名部分的正则表达式
    patterns = [
        (re.compile(rf'import {re.escape(old_package)}(\.databinding\.\w+)'), rf'import {new_package}\1'),
        (re.compile(rf'import {re.escape(old_package)}\.R\b'), rf'import {new_package}.R'),
        (re.compile(rf'import {re.escape(old_package)}\.BuildConfig'), rf'import {new_package}.BuildConfig'),
        (re.compile(rf'{re.escape(old_package)}\.R\b'), rf'{new_package}.R'),

    ]

    # 遍历目录中的所有文件
    for dirpath, _, filenames in os.walk(root_path):
        for filename in filenames:
            # 只处理 .kt 和 .java 文件
            if filename.endswith(('.kt', '.java')):
                file_path = os.path.join(dirpath, filename)

                with open(file_path, 'r', encoding='utf-8') as file:
                    content = file.read()

                # 替换匹配到的旧包名
                updated_content = content
                for pattern, replacement in patterns:
                    updated_content = pattern.sub(replacement, updated_content)

                # 如果内容发生了改变，写回文件
                if content != updated_content:
                    with open(file_path, 'w', encoding='utf-8') as file:
                        file.write(updated_content)
                    print(f"已更新文件: {file_path}")


# def get_file_names_without_extension(root_path):
#     # 遍历 root_path 下的所有 .kt 和 .java 文件
#     all_files = []
#     for dirpath, _, filenames in os.walk(root_path):
#         for filename in filenames:
#             if filename.endswith(('.kt', '.java')):
#                 all_files.append(os.path.join(dirpath, filename))
#
#     return all_files


# def get_class_name(text, filename):
#     """
#     获取文件中定义的类名（适用于 Java 和 Kotlin 文件）
#     :param text: 文件内容
#     :param filename: 文件名
#     :return: 类名列表
#     """
#     if filename.endswith('.kt'):
#         class_pattern = r'\bclass\s+(\w+)'
#     elif filename.endswith('.java'):
#         class_pattern = r'\bclass\s+(\w+)'
#     else:
#         return []
#
#     # 获取类名
#     return re.findall(class_pattern, text)


def get_method_names(text, filename):
    """
    获取 Java 或 Kotlin 文件中的所有方法名
    :param text: 文件内容
    :param filename: 文件名
    :return: 方法名列表
    """
    if filename.endswith('.kt'):
        # Kotlin 方法名：匹配包括访问修饰符（private/public）和 fun 关键字，不排除 override
        method_pattern = r'\b(?:private|protected|public|internal|inline|suspend|)?\s*fun\s+(\w+)\s*\((.*?)\)\s*(?::\s*([\w<>,\s?*]*))?\s*{'
    elif filename.endswith('.java'):
        # Java 方法名：包括常见的修饰符和方法名
        method_pattern = r'\b(?:public|private|protected|static|final|void|int|boolean|float|long|double|char|short|byte|void|[\w<>]+)\s+(\w+)\s*\(.*\)\s*\{'
    else:
        return []

    # 匹配所有方法名
    return re.findall(method_pattern, text)


def get_variable_names(text, filename):
    """
    获取 Java 或 Kotlin 文件中的所有变量名
    :param text: 文件内容
    :param filename: 文件名
    :return: 变量名列表
    """
    if filename.endswith('.kt'):
        # Kotlin 变量名：支持 `val` 和 `var` 关键字的变量声明
        variable_pattern = r'\b(?:val|var)\s+(\w+)\s*(?=[;=])'
    elif filename.endswith('.java'):
        # Java 变量名：包括常见的类型声明
        variable_pattern = r'\b(?:int|boolean|float|long|double|char|short|byte|String|[\w<>]+)\s+(\w+)\s*(?=[;=])'
    else:
        return []

    # 匹配所有变量名
    return re.findall(variable_pattern, text)


def get_file_names_without_extension(root_path):
    """
    获取所有 `.kt` 和 `.java` 文件的路径（无扩展名）
    :param root_path: 根目录路径
    :return: 所有文件的路径列表
    """
    all_files = []
    for dirpath, _, filenames in os.walk(root_path):
        for filename in filenames:
            if filename.endswith(('.kt', '.java')):
                all_files.append(os.path.join(dirpath, filename))
    return all_files


# 存储每个文件名的映射关系
file_to_class_map = {}


def get_constant_names(text, filename):
    """
    获取 Java 或 Kotlin 文件中的所有常量名
    :param text: 文件内容
    :param filename: 文件名
    :return: 常量名列表
    """
    if filename.endswith('.kt'):
        # Kotlin 常量名：匹配以 `const val` 声明的常量
        constant_pattern = r'const\s+val\s+(\w+)\s*=\s*.*'
    elif filename.endswith('.java'):
        # Java 常量名：匹配以 `static final` 声明的常量
        constant_pattern = r'public\s+static\s+final\s+\w+\s+(\w+)\s*=\s*.*'
    else:
        return []

    # 匹配所有常量名
    return re.findall(constant_pattern, text)


def obfuscate_code_in_files(all_files):
    """
    遍历所有文件，混淆其中的方法名、变量名和常量名，并返回混淆名称的映射。
    :param all_files: 所有文件的路径列表。
    :return: 一个字典，键为原始名称，值为混淆后的名称。
    """
    name_mapping = {}  # 用于存储原始名称和混淆名称的映射
    constant_mapping = {}  # 用于存储常量的映射

    for file_path in all_files:
        current_file_name = os.path.basename(file_path).split('.')[0]  # 获取文件名
        # 读取文件内容
        with open(file_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()

        # 获取文件中的常量名
        constants = get_constant_names("".join(lines), file_path)

        # 为常量名生成混淆名称
        for constant in constants:
            if constant not in name_mapping:
                name_mapping[constant] = get_random_string()
            constant_mapping[constant] = name_mapping[constant]

        # 获取类名（用于判断常量是否在类内引用）
        new_lines = []
        for line in lines:
            if line.startswith("import "):  # 跳过 import 行
                new_lines.append(line)
            else:
                # 替换常量名：进行统一的替换，不区分引用的类型
                for constant, obfuscated_name in constant_mapping.items():
                    # 直接替换常量，无论是在类内还是外部引用
                    line = re.sub(r'\b' + re.escape(constant) + r'\b', obfuscated_name, line)

                # 替换方法名和变量名
                for original_name, obfuscated_name in name_mapping.items():
                    line = re.sub(r'\b' + re.escape(original_name) + r'\b', obfuscated_name, line)

                new_lines.append(line)

        # 写回文件
        with open(file_path, 'w', encoding='utf-8') as f:
            f.writelines(new_lines)

    return constant_mapping


def replace_with_mapped_names(all_files, name_mapping, constant_mapping):
    """
    在所有文件中替换已经混淆的名称，保证不同文件间共享混淆名称。
    :param all_files: 所有文件的路径列表。
    :param name_mapping: 名称映射字典。
    :param constant_mapping: 常量名称映射字典。
    """
    for file_path in all_files:
        # 使用在 obfuscate_code_in_files 中存储的文件名
        current_file_name = file_to_class_map.get(file_path, None)
        if not current_file_name:
            print(f"未找到文件 {file_path} 的类名映射，跳过。")
            continue

        with open(file_path, 'r', encoding='utf-8') as f:
            lines = f.readlines()

        new_lines = []
        excluded_constants = set()

        # 第一步：提取 `const val` 定义的常量名并排除混淆
        for line in lines:
            match = re.match(r'^\s*const\s+val\s+(\w+)\s*=.*$', line)
            if match:
                constant_name = match.group(1)
                excluded_constants.add(constant_name)

        for line in lines:
            if line.startswith("import "):  # 跳过 import 行
                new_lines.append(line)
                continue

            # 跳过 context 或 activity 的调用
            if 'context.' in line or 'activity.' in line:
                new_lines.append(line)
                continue

            # 替换变量和方法名
            for original_name, obfuscated_name in name_mapping.items():
                if isinstance(original_name, tuple):
                    original_name = ''.join(original_name)

                # 如果是 `excluded_constants` 中的常量名，跳过替换
                if original_name in excluded_constants:
                    continue

                # 替换代码中的变量或方法名
                line = re.sub(r'\b' + re.escape(original_name) + r'\b', obfuscated_name, line)

            new_lines.append(line)

        with open(file_path, 'w', encoding='utf-8') as f:
            f.writelines(new_lines)

        print(f"文件 {file_path} 引用的名称已进行统一混淆处理。")


def deal_code():
    print("deal_code start")
    # 替换点击事件
    replace_click_method()

    # 生成包映射文件
    mapping = check_mapping()
    packages = list(mapping.keys())
    print("mapping: " + json.dumps(mapping, indent=4))

    # 移动文件
    root_path = get_path('.\\src\\main\\java')
    for key in packages:
        key = str(key)
        print(key)
        old_path = get_path(root_path + '\\' + key.replace('.', '\\'))
        new_path = get_path(root_path + '\\' + mapping[key].replace('.', '\\'))
        print("mapping[key]" + mapping[key])
        if not os.path.exists(old_path):
            mkdir(old_path)

        if not os.path.exists(new_path):
            mkdir(new_path)

        listdir = os.listdir(old_path)
        for i in listdir:
            path_join = os.path.join(old_path, i)
            if os.path.isdir(path_join):
                continue
            shutil.move(path_join, os.path.join(new_path, i))
    shutil.rmtree(get_path(root_path + '\\' + applicationId[:applicationId.find('.')]))

    # 修改manifest
    with open(get_path('.\\src\\main\\AndroidManifest.xml'), 'r', encoding='utf-8') as f:
        text = f.read()

    findall = re.findall('android:name=(".*?")', text)
    for i in findall:
        if i.startswith('".'):
            text = text.replace(i, '"' + applicationId + i[1:-1] + '"')
            print(text)
            print(applicationId)

    with open(get_path('.\\src\\main\\AndroidManifest.xml'), 'w', encoding='utf-8') as f:
        f.write(text)

    # 修改依赖
    sub_map = {}
    for i in mapping:
        sub_map['(?<=\\W)' + i + '(?=\\W)'] = mapping[i]
        print(sub_map['(?<=\\W)' + i + '(?=\\W)'])
        print(mapping[i])

    # 过滤修改
    sub_map['(?<=\\W)' + mapping[applicationId] + '.R(?=\\W)'] = applicationId + '.R'
    sub_map['(?<=\\W)' + mapping[applicationId] + '.databinding(?=\\W)'] = applicationId + '.databinding'
    sub_map['(?<=\\W)' + mapping[applicationId] + '.BuildConfig(?=\\W)'] = applicationId + '.BuildConfig'
    sub_map_text(sub_map)

    # 根包名下 R 单独处理
    new_path = get_path(root_path + '\\' + mapping[packages[0]].replace('.', '\\'))
    listdir = os.listdir(new_path)
    result_path = []
    for i in listdir:
        if os.path.isdir(os.path.join(new_path, i)):
            continue
        result_path.append(os.path.join(new_path, i))
    # add_import(applicationId + '.R', result_path)
    # add_import(applicationId + '.BuildConfig', result_path)

    # 类名
    print("类名 start")
    class_mapping = check_class_mapping()
    classes = get_classes()
    print(class_mapping)
    for i in classes:
        #"or os.path.basename(i) == "GlobalConfig.kt"  去除跳过 GlobalConfig.kt
        if '_D' in i :
            continue
        name = os.path.basename(i)
        name = name[:name.rfind('.')]
        shutil.move(i, i.replace(name, class_mapping[name]))

    sub_map = {}
    for i in class_mapping:
        # 原规则匹配整个单词
        # 添加负向环视以排除前后是引号的情况,解决匹配attrs的问题，
        # 添加排除前缀 "styleable." 的逻辑
        # 原规则匹配整个单词，添加排除前缀 "styleable." 的逻辑，并允许后面有引号的匹配
        regex_pattern = r'(?<!")(?<!\bstyleable\.)\b' + i + r'\b'
        if 'AppComponent' in class_mapping:
            obfuscated_name = class_mapping['AppComponent']
            dagger_pattern = rf'(?<!")(?<!\bstyleable\.)\bDaggerAppComponent\b'
            sub_map[dagger_pattern] = f'Dagger{obfuscated_name}'
        else:
            print("⚠️ class_mapping 中没有 AppComponent，跳过 DaggerAppComponent 规则")

        # 保持原有规则的匹配，优先处理非引号的情况
        sub_map[regex_pattern] = class_mapping[i]
        print(class_mapping[i])
    sub_map_text(sub_map)
    print("类名 over")

    # 混淆字典
    check_obfuscation_dictionary()
    print(root_path)
    print(mapping[applicationId])
    print(applicationId)
    # 获取混淆名称映射
    # all_files = get_file_names_without_extension(root_path)
    # name_mapping = obfuscate_code_in_files(all_files)
    #
    # # 获取常量映射
    # constant_mapping = {}  # 获取常量的映射
    # for file_path in all_files:
    #     with open(file_path, 'r', encoding='utf-8') as f:
    #         lines = f.readlines()
    #         constants = get_constant_names("".join(lines), file_path)
    #         for constant in constants:
    #             if constant not in name_mapping:
    #                 name_mapping[constant] = get_random_string()
    #             constant_mapping[constant] = name_mapping[constant]
    #
    # # 第一步：混淆代码，并获取所有混淆的名称映射
    # name_mapping = obfuscate_code_in_files(all_files)
    #
    # # 第二步：替换其他文件中的引用，保证同样的混淆名
    # replace_with_mapped_names(all_files, name_mapping, constant_mapping)

    replace_package_in_imports(root_path, mapping[applicationId], applicationId)


def deal_res_type(res_type):
    type_mapping = check_res_mapping(res_type)
    if len(type_mapping) == 0:
        return
    print(type_mapping)

    res_path = get_path('.\\src\\main\\res')
    listdir = os.listdir(res_path)
    for i in listdir:
        if not i.startswith(res_type):
            continue
        path_join = os.path.join(res_path, i)
        for j in os.listdir(path_join):
            if j.startswith('book_'):
                continue
            if j.endswith('.9.png'):
                name = j[:-len('.9.png')]
            else:
                name = j.rsplit('.', 1)[0]
            path = os.path.join(path_join, j)
            if '.DS_Store' not in path:
                # shutil.move(path, path.replace(name, type_mapping[name]))
                # 仅替换文件名部分
                dir_path, file_name = os.path.split(path)
                new_file_name = file_name.replace(name, type_mapping[name])
                new_path = os.path.join(dir_path, new_file_name)

                # 打印路径调试信息
                print(f"源路径: {path}")
                print(f"目标路径: {new_path}")

                # 移动文件
                shutil.move(path, new_path)

    sub_map = {}
    for i in type_mapping:
        mapped = type_mapping[i]

        # 普通 R.xxx.xxx
        sub_map[r'R\.' + res_type + r'\.' + re.escape(i) + r'(\W)'] = r'R.' + res_type + '.' + mapped + r'\1'

        # # # 包名限定的 com.retrytech.ledgeapp.R.drawable.xxx
        # sub_map[r'(\b[\w\.]+)\.R\.' + res_type + r'\.' + re.escape(
        #     i) + r'(\W)'] = r'\1.R.' + res_type + '.' + mapped + r'\2'
        # sub_map[r'(\b[\w\.]+)\.R\.' + res_type + r'\.' + re.escape(
        #     i) + r'(?=[\s)\];,])'] = r'\1.R.' + res_type + '.' + mapped
        # ✅ 推荐统一写法：以单词边界结尾，适配更多结尾符号（避免重复）
        sub_map[rf'(\b[\w\.]+)\.R\.{res_type}\.{re.escape(i)}\b'] = rf'\1.R.{res_type}.{mapped}'

        # XML 资源：@drawable/xxx
        sub_map[r'(>|")@' + res_type + r'/' + re.escape(i) + r'(<|")'] = r'\1@' + res_type + '/' + mapped + r'\2'

        # DataBinding 表达式中：@{ condition ? @drawable/xxx :@drawable/yyy }
        # # 匹配 ? 后的资源
        # sub_map[r'(?<=\? *@' + res_type + r'\/)' + re.escape(i) + r'(?= *:)'] = mapped
        # # 匹配 : 后的资源
        # sub_map[r'(?<=: *@' + res_type + r'\/)' + re.escape(i) + r'(?= *})'] = mapped
        # ? 后的资源
        sub_map[r'(?<=\? *@' + res_type + r'/)' + re.escape(i) + r'(?=\b)'] = mapped

        # : 后的资源
        sub_map[r'(?<=: *@' + res_type + r'/)' + re.escape(i) + r'(?=\b)'] = mapped
        # ✅ 通配 DataBinding 内的任意资源引用
        sub_map[r'(@\{[^}]*?)@' + res_type + r'/' + re.escape(i) + r'(?=[^}]*})'] = r'\1@' + res_type + '/' + mapped

        # 通用 DataBinding 表达式中任意位置（不拆三元）
        # sub_map[r'(?<=@{[^}]*?)@' + res_type + r'/' + re.escape(i) + r'(?=[^}]*})'] = '@' + res_type + '/' + mapped
        # sub_map['R\\.' + res_type + '\\.' + i + '(\\W)'] = 'R.' + res_type + '.' + type_mapping[i] + '\\g<1>'
        # sub_map[
        #     r'(\b[\w\.]+)\.R\.' + res_type + r'\.' + i + r'(\W)'
        #     ] = r'\1.R.' + res_type + '.' + type_mapping[i] + r'\2'
        #
        # sub_map['(>|")@' + res_type + '/' + i + '(<|")'] = '\\g<1>@' + res_type + '/' + type_mapping[i] + '\\g<2>'
        # # 适配 DataBinding 表达式中的 @drawable/xxx 写法
        # sub_map[r'(?<=@{[^}]*?)@' + res_type + r'/' + i + r'(?=[^}]*})'] = '@' + res_type + '/' + type_mapping[i]
    sub_map_text(sub_map)


# 解决mipmip命名中含有大写字母造成的报错
# 'E' is not a valid file-based resource name character: File-based resource names must contain only lowercase a-z, 0-9, or underscore
def generate_random_letter():
    # 生成一个随机小写字母
    return random.choice(string.ascii_lowercase)


def normalize_filename(name):
    # 将名称转为小写
    normalized_name = name.lower()

    # 替换所有非法字符为随机字母
    normalized_name = re.sub(r'[^a-z]', lambda match: generate_random_letter(), normalized_name)

    # 确保名称以字母开头，如果不是，则以随机字母开头
    if not normalized_name[0].isalpha():
        normalized_name = generate_random_letter() + normalized_name[1:]

    return normalized_name


def add_image_noise(path):
    try:
        image = cv2.imread(path, cv2.IMREAD_UNCHANGED)
        if path.endswith('.9.png'):
            return
        # # 添加随机噪声
        # noise = np.random.randint(0, 2, image.shape, np.uint8)
        # image = cv2.add(image, noise)
        # 添加 ±10 的随机噪声
        noise = np.random.randint(0, 2, image.shape, np.int16)  # 噪声范围 [-10, 10]
        noisy_image = np.clip(image.astype(np.int16) + noise, 0, 255).astype(np.uint8)  # 防止溢出
        #计算原始图像与添加噪声后图像的差异：如果差值的范围包含 10（噪声范围），说明噪声添加成功。
        difference = np.abs(image.astype(np.int16) - noisy_image.astype(np.int16))
        print("Difference range:", difference.min(), "-", difference.max())

        new_path = path.rsplit('.', 1)[0] + '.webp'
        cv2.imwrite(new_path, noisy_image, [cv2.IMWRITE_WEBP_QUALITY, 75])
        if new_path != path:
            os.remove(path)
    except AttributeError:
        return


def deal_image():
    res_path = get_path('.\\src\\main\\res')
    listdir = os.listdir(res_path)
    for i in listdir:
        path_join = os.path.join(res_path, i)
        if os.path.isdir(path_join):
            for j in os.listdir(path_join):
                # add_image_noise(os.path.join(path_join, j))
                if j.endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff', '.webp')):
                    add_image_noise(os.path.join(path_join, j))


def get_title(string, is_all_upper=False):
    splits = string.split('_')
    print(splits)
    s = ''
    for i in range(len(splits)):
        if i == 0 and not is_all_upper:
            s = splits[i]
        elif len(splits[i]) > 0:
            s += splits[i][0].upper() + splits[i][1:]
    return s


def add_import(path, listfile):
    for i in listfile:
        if 'databinding' in path:  # 跳过 databinding
            continue
        with open(i, 'r', encoding='utf-8') as f:
            text = f.read()
            if len(re.findall('import ' + path, text)) != 0:
                continue
            if i.endswith('kt'):
                text = re.sub('(package \\S+?\\s+?)(?=\\S)', '\\g<1>import ' + path + '\n', text, flags=re.S)
                print(text)
            elif i.endswith('java'):
                text = re.sub('(package \\S+?\\s+?)(?=\\S)', '\\g<1>import ' + path + ';\n', text, flags=re.S)
        with open(i, 'w', encoding='utf-8') as f:
            f.write(text)


def check_class_string_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    mapping = loads.get('class_string', {})

    classes = get_classes(['kt'])
    for path in classes:
        if os.path.basename(path).startswith('AESHelper'):
            continue
        with open(path, 'r', encoding='utf-8') as f:
            text = f.read()
        text = re.sub('@SuppressLint.*?\n', '\n', text)
        text = re.sub('//[^"]*?(("[^"]*?){2})*?\n', '\n', text)
        text = re.sub(' +\n', '\n', text)
        text = re.sub('=\\s+', '= ', text)
        text = re.sub(':\\s+', ': ', text)
        text = re.sub(',\\s+', ', ', text)
        text = re.sub('\n{2,}', '\n\n', text)
        result_text = text
        strings = re.findall('".*?[^\\\\]"', re.sub(r'@[^)]*', lambda x: x.group().replace('"', "'"), result_text))
        last_string = ''
        for string in strings:
            if len(string) <= 4 or string.isspace() or \
                    string[1:-1] in ['\\\\n', '\\\\r', '\\\\\'', '\\\\\\"', '\\\\?', '&amp;', 'UTF-8']:
                continue
            if string in last_string:
                continue
            start = text.find(string)
            if start == -1:
                continue
            index = start + 1
            sign_stack = ['"']
            while len(sign_stack) != 0:
                if text[index] == '\\':
                    index += 1
                elif text[index] == '"' and sign_stack[-1] == '"':
                    sign_stack.pop()
                elif text[index] == '"':
                    sign_stack.append('"')
                elif text[index] == '}' and sign_stack[-1] == '${':
                    sign_stack.pop()
                elif text[index - 1:index + 1] == '${':
                    sign_stack.append('${')
                index += 1
            string = text[start:index]
            last_string = string
            params = []
            result = string
            if '$' in string:
                count = 0
                last = -1
                res = []
                for i in range(len(string)):
                    if last == -1 and string[i] == '$':
                        if count == 0:
                            last = i
                    elif last != -1:
                        if string[i] == '{':
                            count += 1
                        elif string[i] == '}':
                            count -= 1
                            if count == 0:
                                res.append((last, i + 1))
                                last = -1
                        elif count == 0:
                            last = -1
                for i in re.findall('\\$\\w*', string):
                    if i == '$':
                        continue
                    find = string.find(i)
                    res.append((find, find + len(i)))
                res = sorted(res, key=lambda l: l[0])
                last_res = None
                for i in res:
                    if last_res is not None and i[0] < last_res[1]:
                        continue
                    params.append(string[i[0]:i[1]])
                    last_res = i
                for i in params:
                    result = result.replace(i, "[str]")
            result = result[1:-1].replace('%', '%%').replace('\\\\', '\\').replace('[str]', '%s')
            if mapping.get(result) is not None:
                continue
            mapping[result] = get_random_string(5, False) + '_D'
    loads['class_string'] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def deal_class_string():
    # 获取映射
    string_mapping = check_class_string_mapping()
    print(string_mapping)

    with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'r', encoding='utf-8') as f:
        text = f.read()
    rfind = text.rfind('\n')
    text_result = text[:rfind]
    for i in string_mapping:
        string = i
        string = string.replace('&', '&amp;')
        string = string.replace('?', '\\?')
        string = string.replace('\'', '\\\'')
        text_result += '\n    <string name="' + string_mapping[i] + '" translatable="false">' + string + '</string>'
    text_result += text[rfind:]
    with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'w', encoding='utf-8') as f:
        f.write(text_result)

    # 搜索 Int.string() 路径
    file_path = find_file('fun Int.string', get_path('.\\src\\main\\java'))
    class_path = get_class_path(file_path) + '.string' if file_path else ""
    classes = get_classes(['kt'])
    for path in classes:
        # 跳过 AESHelper 类中的字符串，避免嵌套
        if os.path.basename(path).startswith('AESHelper'):
            continue
        with open(path, 'r', encoding='utf-8') as f:
            text = f.read()
        text = re.sub('@SuppressLint.*?\n', '\n', text)
        text = re.sub('//[^"]*?(("[^"]*?){2})*?\n', '\n', text)
        text = re.sub(' +\n', '\n', text)
        text = re.sub('=\\s+', '= ', text)
        text = re.sub(':\\s+', ': ', text)
        text = re.sub(',\\s+', ', ', text)
        text = re.sub('\n{2,}', '\n\n', text)
        result_text = text
        strings = re.findall('".*?[^\\\\]"', re.sub(r'@[^)]*', lambda x: x.group().replace('"', "'"), result_text))
        last_string = ''
        for string in strings:
            if len(string) <= 4 or string.isspace() or \
                    string[1:-1] in ['\\\\n', '\\\\r', '\\\\\'', '\\\\\\"', '\\\\?', '&amp;', 'UTF-8']:
                continue
            if string in last_string:
                continue
            start = text.find(string)
            if start == -1:
                continue
            index = start + 1
            sign_stack = ['"']
            while len(sign_stack) != 0:
                if text[index] == '\\':
                    index += 1
                elif text[index] == '"' and sign_stack[-1] == '"':
                    sign_stack.pop()
                elif text[index] == '"':
                    sign_stack.append('"')
                elif text[index] == '}' and sign_stack[-1] == '${':
                    sign_stack.pop()
                elif text[index - 1:index + 1] == '${':
                    sign_stack.append('${')
                index += 1
            # 得到完整字符串
            string = text[start:index]
            last_string = string
            # 参数
            params = []
            result = string
            # 处理字符串拼接
            if '$' in string:
                count = 0
                last = -1
                res = []
                for i in range(len(string)):
                    if last == -1 and string[i] == '$':
                        if count == 0:
                            last = i
                    elif last != -1:
                        if string[i] == '{':
                            count += 1
                        elif string[i] == '}':
                            count -= 1
                            if count == 0:
                                res.append((last, i + 1))
                                last = -1
                        elif count == 0:
                            last = -1
                for i in re.findall('\\$\\w*', string):
                    if i == '$':
                        continue
                    find = string.find(i)
                    res.append((find, find + len(i)))
                res = sorted(res, key=lambda l: l[0])
                last_res = None
                for i in res:
                    if last_res is not None and i[0] < last_res[1]:
                        continue
                    params.append(string[i[0]:i[1]])
                    last_res = i
                for i in params:
                    result = result.replace(i, "[str]")
                for i in range(len(params)):
                    if params[i][1] == '{':
                        params[i] = params[i][2:-1]
                    else:
                        params[i] = params[i][1:]
                    # 每个参数加上 toString
                    params[i] = '(' + params[i] + ').toString()'
            # 拼接部分替换为 %s
            result = result[1:-1].replace('%', '%%').replace('\\\\', '\\').replace('[str]', '%s')

        # 添加 Int.string() 方法引用
        if class_path:
            if len(re.findall('import ' + class_path, text)) == 0:
                result_text = re.sub('(package \\S+?\\s+?)(?=\\S)',
                                     '\\g<1>import ' + class_path + '\n', result_text, flags=re.S)

        # 添加资源 R 引用
        if len(re.findall('import ' + applicationId + '.R', text)) == 0:
            result_text = re.sub('(package \\S+?\\s+?)(?=\\S)',
                                 '\\g<1>import ' + applicationId + '.R' + '\n', result_text, flags=re.S)

        # 去除 const
        result_text = re.sub('const (val.*?R\\.string\\.\\S+?\\.string\\(\\))', '\\g<1>', result_text)
        with open(path, 'w', encoding='utf-8') as f:
            f.write(result_text)


def check_xml_string_mapping():
    try:
        loads = json.load(open('mapping.json', 'r', encoding='utf-8'))
    except (ValueError, IOError):
        loads = {}
    old_mapping = loads.get('xml_string', {})
    strings = find_text('android:text="(?<!@string/)([^@"]+?)"', get_path('.\\src\\main\\res\\layout'),
                        skip_name=['notify', 'notity'])

    mapping = {}
    for i in strings:
        value = old_mapping.get(i)
        if value is not None:
            mapping[i] = value
            continue
        # 添加 '_D' 结尾，资源混淆时过滤不用再次改名
        mapping[i] = get_random_string(5, False) + '_D'
    loads['xml_string'] = mapping
    json.dump(loads, open('mapping.json', 'w', encoding='utf-8'), indent=4)
    return mapping


def deal_xml_string():
    # 获取映射
    string_mapping = check_xml_string_mapping()
    print(string_mapping)

    with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'r', encoding='utf-8') as f:
        text = f.read()
    rfind = text.rfind('\n')
    text_result = text[:rfind]
    for i in string_mapping:
        string = i
        string = string.replace('&', '&amp;')
        string = string.replace('?', '\\?')
        string = string.replace('\'', '\\\'')
        text_result += '\n    <string name="' + string_mapping[i] + '" translatable="false">' + string + '</string>'
    text_result += text[rfind:]
    with open(get_path('.\\src\\main\\res\\values\\strings.xml'), 'w', encoding='utf-8') as f:
        f.write(text_result)

    sub_map = {}
    for i in string_mapping:
        sub_map['(?<=android:text=")' + re.escape(i) + '(?=")'] = '@string/' + re.escape(string_mapping[i])
    # 替换时跳过通知布局
    sub_map_text(sub_map, path=get_path('.\\src\\main\\res\\layout'), skip_name=['notify', 'notity'])


def deal_code_string():
    # 处理布局文件中的明文字符串
    deal_xml_string()
    # 处理代码文件中的明文字符串
    deal_class_string()


def deal_res():
    # 改 string id
    deal_ids_type('string')

    # 改 color id
    deal_ids_type('color')

    # 改 dimen id
    deal_ids_type('dimen')

    # 改 style id
    deal_ids_type('style')

    # 改 declare-styleable
    styleable_mapping = check_styleable_mapping()
    print(styleable_mapping)
    sub_map = {}
    styleables = find_text('<declare-styleable name=".*?">[\\s\\S]*?</declare-styleable>')
    for i in styleables:
        result = i
        styleable_id = re.findall('<declare-styleable name="(.*?)">', i)[0]
        result = result.replace('<declare-styleable name="' + styleable_id + '">',
                                '<declare-styleable name="' + styleable_mapping[styleable_id] + '">')
        sub_map['R\\.styleable\\.' + styleable_id + '(\\W)'] = \
            'R.styleable.' + styleable_mapping[styleable_id] + '\\g<1>'
        # styleables_attr = re.findall('<attr name="(.*?)".*?/>', i)
        styleables_attr = re.findall('<attr name="(.*?)".*?>', i)  # 包括单行和多行定义
        for attr in styleables_attr:
            # else:
            #     new_attr = attr
            result = result.replace('<attr name="' + attr + '"', '<attr name="' + styleable_mapping[attr + '_A'] + '"')
            sub_map['R\\.styleable\\.' + styleable_id + '_' + attr + '(\\W)'] = \
                'R.styleable.' + styleable_mapping[styleable_id] + '_' + styleable_mapping[attr + '_A'] + '\\g<1>'
            sub_map['app:' + attr + '='] = 'app:' + styleable_mapping[attr + '_A'] + '='
            sub_map['\\?attr/' + attr] = '?attr/' + styleable_mapping[attr + '_A']  # 新增的替换规则
            sub_map['<item name=\"' + attr + '\"'] = '<item name=\"' + styleable_mapping[attr + '_A'] + '\"'  # 新增的替换规则
            # 判断 attr 是否以 "android:" 开头
            if attr.startswith("android:"):
                new_attr = "android_" + attr[8:]  # 去掉 "android:" 并替换为 "android_"
                sub_map['R\\.styleable\\.' + styleable_id + '_' + new_attr + '(\\W)'] = \
                    'R.styleable.' + styleable_mapping[styleable_id] + '_' + styleable_mapping[attr + '_A'] + '\\g<1>'

        sub_map[re.escape(i)] = result
    sub_map_text(sub_map)

    # 改 view id
    view_ids = find_text('"\\@\\+id/(.*?)"', get_path('.\\src\\main\\res'))
    view_ids_mapping = check_view_ids_mapping()
    print(view_ids_mapping)
    sub_map = {}
    for i in view_ids:
        if i == 'root':
            continue
        key = get_title(i)
        sub_map['(>|")@(\\+)?id/' + i + '(<|")'] = '\\g<1>@\\g<2>id/' + view_ids_mapping[key] + '\\g<3>'
        sub_map['R\\.id\\.' + i + '(\\W)'] = 'R.id.' + view_ids_mapping[key] + '\\g<1>'
        sub_map['([bB]inding\\??(?:\\.\\w+)?\\??)\\.' + key + '(\\W)'] = '\\g<1>.' + view_ids_mapping[key] + '\\g<2>'
        sub_map['([bB]inding\\??(?:\\.\\w+)?\\??)\\.' + key + '\\.' + key + '(\\W)'] = '\\g<1>.' + view_ids_mapping[
            key] + '.' + view_ids_mapping[key] + '\\g<2>'
        sub_map[r'\(binding as (\w+Binding)\)\.' + key + r'(\W)'] = r'(binding as \1).' + view_ids_mapping[key] + r'\2'
        sub_map['(?<=app:constraint_referenced_ids=".*?)' + i + '(?=.*?")'] = view_ids_mapping[key]

        # 新增 Kotlin 特有写法适配
        sub_map[r'(binding!!(?:\.\w+)*?)\.' + key + r'(\W)'] = r'\1.' + view_ids_mapping[key] + r'\2'
        sub_map[r'(binding\?\?(?:\.\w+)*?)\.' + key + r'(\W)'] = r'\1.' + view_ids_mapping[key] + r'\2'
        sub_map[r'(binding(?:\.\w+)*?)\.' + key + r'(\W)'] = r'\1.' + view_ids_mapping[key] + r'\2'

        sub_map['this\\.' + i + '(\\W)'] = 'this.' + view_ids_mapping[key] + '\\g<1>'
        sub_map['this\\.' + i + '(\\.[\\w\\?]+(?:\\([^)]*\\))?)'] = 'this.' + view_ids_mapping[key] + '\\g<1>'
        # sub_map['this\\.(\\w+)(\\.[\\w\\?]+(?:\\([^)]*\\))?)'] = 'this.' + view_ids_mapping[key] + '\\g<1>\\g<2>'

        sub_map['popupView\\.' + i + '(\\W)'] = 'popupView.' + view_ids_mapping[key] + '\\g<1>'

        # sub_map['\\b' + i + '\\.(\\w+)(\\s*=\\s*[^\\s]+)'] = view_ids_mapping[key] + '.\\g<1>\\g<2>'
        sub_map['(?<!\\()\\b' + i + '\\.(\\w+)(\\s*=\\s*[^\\s]+)'] = view_ids_mapping[key] + '.\\g<1>\\g<2>'
        # sub_map['this\\.' + view_ids_mapping['title']] = 'this.title'
        if i == 'imgIcon':
            sub_map[r'\.into\(\s*this\.(imgIcon)\s*\)'] = r'.into(this.' + view_ids_mapping[key] + ')'

    sub_map_text(sub_map)

    # 改 layout 文件名
    layout_mapping = check_layout_mapping()
    print(layout_mapping)

    layout_path = get_path('.\\src\\main\\res\\layout')
    listdir = os.listdir(layout_path)
    for i in listdir:
        name = i[:i.rfind('.')]
        if layout_mapping.get(name) is None:
            continue
        path_join = os.path.join(layout_path, i)
        #解决layout出现大写的问题
        shutil.move(path_join, path_join.replace(name, normalize_filename(layout_mapping[name])))

    sub_map = {}
    for i in layout_mapping:
        sub_map['R\\.layout\\.' + i + '(\\W)'] = 'R.layout.' + layout_mapping[i] + '\\g<1>'
        # ViewBinding 同步修改
        sub_map[get_title(i, True) + 'Binding'] = get_title(layout_mapping[i], True) + 'Binding'
        sub_map['(>|")@layout/' + i + '(<|")'] = '\\g<1>@layout/' + layout_mapping[i] + '\\g<2>'
    sub_map_text(sub_map)

    # 改 drawable 文件名
    deal_res_type('drawable')

    # 改 mipmap 文件名
    deal_res_type('mipmap')

    # 改 raw 文件名
    deal_res_type('raw')

    # 改 raw 文件名
    deal_res_type('xml')

    # 改图片内容
    deal_image()

    # 主执行逻辑
    mapping_file = "./mapping.json"
    java_path = get_path('.\\src\\main\\java')
    mapping_data = load_color_mapping(mapping_file)  # 调用正确的函数

    # 调试信息：检查 color_ids_mapping 内容
    print("Color IDs Mapping:")
    for original_color, mapped_color in mapping_data.items():
        print(f"{original_color}: {mapped_color}")

    # 进行文件处理
    process_color_files(java_path, mapping_data)


def process_color_files(src_path, color_ids):
    for root, dirs, files in os.walk(src_path):
        for file in files:
            if file.endswith(".kt") or file.endswith(".java"):
                file_path = os.path.join(root, file)
                print(f"Processing file: {file_path}")
                replace_color_ids_in_file(file_path, color_ids)


def replace_color_ids_in_file(file_path, color_ids_mapping):
    print("replace_color_ids_in_file")
    print(color_ids_mapping)
    with open(file_path, 'r', encoding='utf-8') as f:
        content = f.read()
    if "paperedto" in content:
        print(f"Found 'paperedto' in: {file_path}")
    if "android.R.color.paperedto" in content:
        print(f"Found 'android.R.color.paperedto' in: {file_path}")
    changed = False

    for original_color, mapped_color in color_ids_mapping.items():
        print('original_color'+original_color)
        print('mapped_color'+mapped_color)
        if mapped_color in content:
            pattern = r'android.R.color.' + re.escape(mapped_color) + r'(?=[^\w])'
            print(pattern)
            replacement = 'android.R.color.' + original_color
            print(replacement)
            if re.search(pattern, content):
                print(f"Matched: {mapped_color} → {original_color}")

            matches = re.findall(pattern, content)
            if matches:
                print(f"Matched: {mapped_color} → {original_color} ({len(matches)} times)")
                content = re.sub(pattern, replacement, content)
                changed = True

    if changed:
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(content)

def get_path(path):
    paths = path.split('\\')
    return str(os.path.join(*paths))


def deal_global():
    mapping_file_path = 'mapping.json'
    src_dir = 'src/main/java'

    # 加载映射文件
    with open(mapping_file_path, 'r', encoding='utf-8') as f:
        mapping_data = json.load(f)

    class_mapping = mapping_data.get("class", {})
    dir_mapping = mapping_data.get("dir", {})

    # 获取混淆后的 GlobalConfig 文件名
    obfuscated_name = class_mapping.get("GlobalConfig")
    if not obfuscated_name:
        print("❌ GlobalConfig not found in class mapping.")
        return

    obfuscated_filename = obfuscated_name + '.kt'
    reversed_dir_mapping = {v: k for k, v in dir_mapping.items()}

    found = False
    for root, dirs, files in os.walk(src_dir):
        if obfuscated_filename in files:
            found = True
            kt_file_path = os.path.join(root, obfuscated_filename)

            with open(kt_file_path, 'r', encoding='utf-8') as f:
                content = f.read()

            original_content = content
            updated = False

            # 精确匹配 PACKAGE_NAME 行
            match = re.search(r'(const\s+val\s+PACKAGE_NAME\s*=\s*")([^"]+)(")', content)
            if match:
                current_package = match.group(2)

                for obf_pkg, original_pkg in reversed_dir_mapping.items():
                    if current_package.startswith(obf_pkg):
                        restored_package = current_package.replace(obf_pkg, original_pkg, 1)
                        new_line = f'{match.group(1)}{restored_package}{match.group(3)}'
                        content = content.replace(match.group(0), new_line)
                        print(f"✅ Updated PACKAGE_NAME in {kt_file_path}: '{current_package}' → '{restored_package}'")
                        updated = True
                        break
            else:
                print(f"❌ PACKAGE_NAME not found in {kt_file_path}")

            if updated and content != original_content:
                with open(kt_file_path, 'w', encoding='utf-8') as f:
                    f.write(content)
            else:
                print(f"ℹ️ No PACKAGE_NAME replacement needed in {kt_file_path}")

            break

    if not found:
        print(f"❌ File {obfuscated_filename} not found under {src_dir}")


def main():
    if not os.path.exists(get_path('.\\src\\main\\java\\' + applicationId[:applicationId.find('.')])):
        print(get_path('.\\src\\main\\java\\' + applicationId[:applicationId.find('.')]))
        return

    # 资源混淆
    deal_res()

    deal_code()

    deal_global()


def load_mapping(file_path):
    """加载 mapping.json 文件并打印内容."""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
            print("Loaded Mapping:")
            print(json.dumps(data, indent=4, ensure_ascii=False))  # 打印映射内容
            return data.get("view_ids", {})
    except (FileNotFoundError, json.JSONDecodeError) as e:
        print(f"Error loading mapping file: {e}")
        return {}


def replace_identifiers_in_file(file_path, mapping, replacements_log):
    """替换单个文件中的标识符，并存储替换的内容到 JSON 日志."""
    with open(file_path, 'r', encoding='utf-8') as f:
        original_text = f.read()

    print(f"\nProcessing File: {file_path}")
    print("Original Content:")
    print(original_text)  # 打印文件原始内容

    modified_text = original_text
    replacements = []

    # 替换所有绑定相关的引用
    def replace_binding_references(match):
        identifier = match.group(2) or match.group(3)
        return f"{match.group(1)}.{mapping.get(identifier, identifier)}"

    # 替换类似 `binding.tvTest1` 和 `it.tvTest1` 的结构
    modified_text = re.sub(
        r'\b(binding|it)\.([a-zA-Z_0-9]+)\b',
        replace_binding_references,
        modified_text
    )

    # 替换无前缀的变量名，如 `tvTest1` 和 `tvTest2`，根据 mapping 文件中的 view_ids 替换
    def replace_variable_names(match):
        identifier = match.group(1)
        # 使用 mapping 中的 view_ids 替换变量名
        new_identifier = mapping.get(identifier, identifier)
        if new_identifier != identifier:
            replacements.append({"original": identifier, "modified": new_identifier})
        return new_identifier

    # 先提取所有引号中的内容
    quoted_texts = re.findall(r'"([^"]*)"', modified_text)

    # 将引号中的内容临时替换为占位符
    placeholders = [f"__PLACEHOLDER_{i}__" for i in range(len(quoted_texts))]
    for i, quoted in enumerate(quoted_texts):
        modified_text = modified_text.replace(f'"{quoted}"', placeholders[i])

    # 过滤掉方法调用中的标识符（例如 taichiPref.edit() 不替换 edit）
    def is_function_call(match):
        # 检查匹配的标识符后是否跟着 `()`
        return f"{match.group(1)}()" if match.group(2) else None

    # 替换变量名，排除函数调用
    modified_text = re.sub(
        r'\b(' + '|'.join(mapping.keys()) + r')\b(?!\()',
        replace_variable_names,
        modified_text
    )

    # 将占位符替换回原始引号内容
    for i, quoted in enumerate(quoted_texts):
        modified_text = modified_text.replace(placeholders[i], f'"{quoted}"')

    # 过滤重复的替换项：根据 (original, modified) 进行去重
    seen_replacements = set()
    unique_replacements = []

    for replacement in replacements:
        replacement_tuple = (replacement["original"], replacement["modified"])
        if replacement_tuple not in seen_replacements:
            unique_replacements.append(replacement)
            seen_replacements.add(replacement_tuple)

    if unique_replacements:
        replacements_log[file_path] = unique_replacements

        # 写入修改后的内容
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write(modified_text)

    else:
        print("No replacements made.")


def process_files_in_directory(directory, mapping, replacements_log):
    """处理目录下的所有文件并打印文件路径."""
    for root, _, files in os.walk(directory):
        for file in files:
            if file.endswith(".kt"):  # 仅处理 Kotlin 文件
                file_path = os.path.join(root, file)
                replace_identifiers_in_file(file_path, mapping, replacements_log)


def solve_runapplyalso():
    # 配置路径
    mapping_file = "mapping.json"
    src_directory = "./src/main/java"
    log_file = "replacement_log.json"

    # 加载映射表
    mapping = load_mapping(mapping_file)

    # 检查路径
    if not os.path.exists(src_directory):
        print(f"Source directory not found: {src_directory}")
    elif not mapping:
        print("Mapping file is empty or not found.")
    else:
        # 存储替换日志
        replacements_log = {}

        # 开始处理文件
        process_files_in_directory(src_directory, mapping, replacements_log)

        # 将替换日志保存为 JSON 文件
        with open(log_file, 'w', encoding='utf-8') as log:
            json.dump(replacements_log, log, ensure_ascii=False, indent=4)

        print(f"\nReplacement completed. Log saved to {log_file}.")


def load_color_mapping(file_path):
    """加载 mapping.json 文件并打印内容."""
    try:
        with open(file_path, 'r', encoding='utf-8') as f:
            data = json.load(f)
            print("Loaded color Mapping:")
            print(json.dumps(data, indent=4, ensure_ascii=False))  # 打印映射内容
            color_ids = data.get("color_ids", {})
            print(f"color_ids extracted: {color_ids}")  # 检查 color_ids 是否存在
            return color_ids
    except (FileNotFoundError, json.JSONDecodeError) as e:
        print(f"Error loading mapping file: {e}")
        return {}



if __name__ == '__main__':
    if os.path.exists('build.gradle'):
        gradle_path = 'build.gradle'
    elif os.path.exists('build.gradle.kts'):
        gradle_path = 'build.gradle.kts'
    else:
        exit('找不到 build.gradle 文件')
    applicationId = re.search('namespace .*?["\'](.*?)["\']', open(gradle_path, 'r', encoding='utf-8').read())[1]
    print(applicationId)
    main()
    # solve_runapplyalso()
