import sys
import subprocess
import os
import re
from difflib import SequenceMatcher
from elftools.elf.elffile import ELFFile


class FixDepends:

    def __init__(self, package):
        self.package = package
        self.package_files = []
        self._update_file_list
        self.EXCLUDED_EXTS = {
            '.mp3', '.mp4', '.png', '.xml', '.jpg', '.bmp', '.cur', '.enc',
            '.js', '.html', '.mkv', '.gif', '.svg', '.pfb', '.ttf', '.dwg',
            '.dwt', '.so', '.zrx'
            }
        self.EXCLUDED_PATTERNS = {'.so.'}
        self.elf_files = set()
        self.python_imports = set()

    @property
    def _update_file_list(self):
        self.package_files = subprocess.getoutput("dpkg -L {}".format(self.package )).split("\n")

    def read_python_file(self, file_path):
    # python3依赖下载
        if "Python" not in subprocess.getoutput("file {}".format(file_path)):
            return
        for package in subprocess.getoutput("strings {} | grep import".format(file_path)).split("\n"):
            if package.strip().startswith("import") or package.strip().startswith("from"):
                self.python_imports.add(package)

    def find_exec_files(self):
        for file in self.package_files:
            # 跳过非文件对象
            if not os.path.isfile(file):
                continue
            # 执行 file 命令分析文件类型
            try:
                output = subprocess.check_output(['file', file], stderr=subprocess.DEVNULL).decode('utf-8')
            except subprocess.CalledProcessError:
                continue
            # 提取小写扩展名
            _, ext = os.path.splitext(file)
            ext = ext.lower()
        
            # 跳过指定扩展名或包含特殊路径的文件
            if ext in self.EXCLUDED_EXTS or any(p in file for p in self.EXCLUDED_PATTERNS):
                continue
            if output.split(file+":")[-1].strip().startswith("ELF") is True:
                self.elf_files.add(file)
            elif "Python" in output.split(file+":")[-1].strip():
                self.read_python_file(file)

    def dpkg_get_exec_depends(self, need):
        try:
            outputs = subprocess.check_output(['dpkg', '-S', need], stderr=subprocess.STDOUT).decode('utf-8').split("\n")
        except subprocess.CalledProcessError:
            return None
        for deb in outputs:
            if "/usr/lib" in deb or "/lib" in deb:
                need_path = deb.split(":")[1].strip()
                if need_path.startswith("/") is False:
                    need_path = deb.split(":")[2].strip()
                if need_path.startswith("/usr/lib") or need_path.startswith("/lib"):
                    return deb.split(":")[0]
                
    def apt_file_get_depends(self, need):
        needs = []
        depends = subprocess.getoutput('apt-file search {}'.format(need)).split("\n")
        if depends[0] == "":
            return None
        for depend in depends:
            if len(depend) < 2:
                continue
            arch = subprocess.getoutput("arch")
            need_path = depend.split(":")[1].strip()
            if need_path.startswith("/usr/lib"):
                if need_path.split("/usr/lib/")[-1] == need:
                    needs.append(depend.split(":")[0])
                    # return depend.split(":")[0]
            if need_path.startswith("/lib"):
                if need_path.split("/lib/")[-1]== need:
                    needs.append(depend.split(":")[0])
                    # return depend.split(":")[0]
            if arch not in depend:
                continue
            if "/usr/lib" in depend or "/lib" in depend:
                if need_path.startswith("/") is False:
                    need_path = depend.split(":")[2].strip()
                if need_path.startswith("/usr/lib") or need_path.startswith("/lib"):
                    needs.append(depend.split(":")[0])
                    # return depend.split(":")[0]

        if len(needs) == 0:
            return None
        elif len(needs) == 1:
            if SequenceMatcher(None, needs[0], need).ratio() > 0.5:
                return needs[0]
            else:
                return None
        else:
            return self.similar(needs , need)
        
    def similar(self, needs, target, threshold= 0.5):

        scored = (
            (candidate, SequenceMatcher(None, candidate, target).ratio())
            for candidate in needs
        )
        # 获取最高分候选
        best_match, best_score = max(scored, key=lambda x: x[1], default=(None, 0))
        
        return best_match if best_score > threshold else None

    def check_dlopen(self, filename):
        pattern = r'\blib\w*\.so\b'
        libs=[]
        isDlopen=False
        try:
            out = subprocess.check_output(['readelf', '-s', filename]).decode('utf-8').split("\n")
            for line in out:
                if 'dlopen' in line:
                    isDlopen = True 
            if isDlopen is False:
                return list()
        except Exception as e:  
            return list()
        try:    
            with open(filename, 'rb') as f:
                elf_file = ELFFile(f)
                for section in elf_file.iter_sections():
                    if section.name == '.rodata' or section.name == b'.rodata':
                        data = section.data()
                        string_data = data.decode('utf-8', 'ignore').split('\0')
                        for s in string_data:
                            if s is not None:
                                matches = re.findall(pattern, s)
                                if matches.__len__() > 0:
                                    libs.append(matches[0])
        except Exception as e:
            print("错误: {}".format(e))
            return list()
        
        return libs
    def check_ldd (self, path):
        libs=set()
        lines = subprocess.getoutput("ldd  {}".format(path)).split("\t")
        for line in lines:
            if "=> not found" in line:
                libs.add(line.split("=> ")[0].strip())
        return libs

    def get_exec_depends(self, elf):
        try:
            output = subprocess.check_output(['objdump', '-p', elf]).decode('utf-8')
        except Exception as e:
            return set()
        dependencies = set()
        needed = set()

        # 解析 objdump 输出，提取依赖项
        lines = output.split('\n')
        for line in lines:
            if "NEEDED" in line:
                dependency = line.split("NEEDED")[1].strip()
                if self.dpkg_get_exec_depends(dependency) is None:
                    dependencies.add(dependency)
        if len(dependencies) != 0:
            for need in dependencies:
                lib = self.apt_file_get_depends(need)
                if lib is None:
                    continue
                needed.add(lib)
        # ldd 检测依赖
        ldd_libs = self.check_ldd(elf)
        if len(ldd_libs) > 0:
            for lib in ldd_libs:
                if self.dpkg_get_exec_depends(lib) is None:
                    depend = self.apt_file_get_depends(lib)
                    if depend is None:
                        continue
                    needed.add(depend)

        #  dlopen依赖问题
        dl_libs = self.check_dlopen(elf)
        if len(dl_libs) > 0 :
            for lib in dl_libs:
                if self.dpkg_get_exec_depends(lib) is None:
                    depend = self.apt_file_get_depends(lib)
                    if depend is None:
                        continue
                    if depend == "libfuse2":
                        needed.add("fuse")
                    needed.add(depend)
        return needed

    def install_depend(self, depends):
        subprocess.getoutput("apt install -y -f")
        for depend in depends:
            try:
                subprocess.check_output(['apt', 'install',depend, '-y'], stderr=subprocess.STDOUT).decode('utf-8').split("\n")                     
            except subprocess.CalledProcessError:
                continue
            print("安装依赖: {} 成功".format(depend))

    def install_python_depends(self):
        modules = set()
        for package in self.python_imports:
            ret = subprocess.getoutput('python3 -c "{}"'.format(package))
            if ret and "ModuleNotFoundError" in ret:
                module = ret.split("ModuleNotFoundError")[-1].strip("'").split("No module named '")[-1].replace("_", "-").lower()
                if "." in module:
                    module = module.split(".")[0]
                modules.add(module)

        for module in modules:
            try:
                subprocess.check_output(['apt', 'install', 'python3-'+module, '-y'], stderr=subprocess.STDOUT).decode('utf-8').split("\n")
            except subprocess.CalledProcessError as e:
                continue
            print("安装依赖: python3-{} 成功".format(module))


if __name__ == "__main__":
    package = sys.argv[1]
    fix_depends = FixDepends(package)
    fix_depends.find_exec_files()
    depends = set()
    for exec in fix_depends.elf_files:
        depends |= fix_depends.get_exec_depends(exec)
    fix_depends.install_depend(depends)