#!/usr/bin/env python3
import os
import shutil
import subprocess
from typing import List, Tuple, Iterable
import logging
import re
import threading
from gi.repository import GObject, GLib
import time
import math
import shlex
from SystemUpdater.configs import G_settings
import apt_pkg
from SystemUpdater.Core.enums import ERROR_DATA_MIGRATION_FAILED, ERROR_DATA_MIGRATION_REPACK_FAILED, ERROR_DATA_MIGRATION_INSTALL_FAILED

class MigrationBackend(GObject.GObject):
    __gsignals__ = {
        "migration-progress": (
            GObject.SignalFlags.RUN_FIRST,
            None,
            (str,str,int,)      # percent
        ),
        "migration-done": (
            GObject.SignalFlags.RUN_FIRST,
            None,
            (bool,str,)     # success
        ),
    }
    def __init__(self, deploy_hash: str, deploy_base_dir: str = "/sysroot/ostree/deploy/kylin/deploy"):
        super().__init__()
        self.deploy_hash = deploy_hash
        logging.info(f"[DebRepacker] Deploy hash: {deploy_hash}")
        logging.info(f"[DebRepacker] Deploy base dir: {deploy_base_dir}")
        self.deploy_root, idx = self._find_max_deploy(deploy_base_dir, deploy_hash)
        self.ovl_root = f"/sysroot/ostree/pkgs/ovl-{deploy_hash}.{idx}"
        logging.info(f"[DebRepacker] Deploy root: {self.deploy_root}")
        logging.info(f"[DebRepacker] Overlay root: {self.ovl_root}")

        # overlay 目录映射
        self.overlay_map = {
            "etc": {
                "lower": f"{self.ovl_root}/etc-ovl/etc-lower:{self.deploy_root}/etc",
                "upper": f"{self.ovl_root}/etc-ovl/etc-upper",
                "work":  f"{self.ovl_root}/etc-ovl/etc-work",
                "target": f"{self.deploy_root}/etc",
            },
            "usr": {
                "lower": f"{self.ovl_root}/usr-ovl/usr-lower:{self.deploy_root}/usr",
                "upper": f"{self.ovl_root}/usr-ovl/usr-upper",
                "work":  f"{self.ovl_root}/usr-ovl/usr-work",
                "target": f"{self.deploy_root}/usr",
            },
            "var/lib": {
                "lower": f"{self.ovl_root}/var-ovl/var-lower/lib:{self.deploy_root}/var/lib",
                "upper": f"{self.ovl_root}/var-ovl/var-upper/lib",
                "work":  f"{self.ovl_root}/var-ovl/var-work/lib",
                "target": f"{self.deploy_root}/var/lib",
            },
        }

        #repack 输出目录准备
        self.chroot_output_dir = "/var/cache/deb-packages"
        # lstrip 去掉开头的 /，避免 os.path.join 覆盖前面的路径
        self.output_dir = os.path.join(self.deploy_root, self.chroot_output_dir.lstrip("/"))
        logging.info(f"[DebRepacker] Output directory: {self.output_dir}")
        self._prepare_output_dir()

    # -------------------- 公开接口 --------------------

    def run(self, migration_dict, migration_dict_1):
        """Start running a data migrate transaction asynchronously."""
        logging.info("Processing data migrate transaction...")
        GLib.idle_add(self._run_transaction_idle, migration_dict, migration_dict_1)

    def _run_transaction_idle(self, migration_dict, migration_dict_1):
        """Run the transaction in an idle callback."""
        try:
            self._run_transaction(migration_dict, migration_dict_1)
        except Exception as e:
            logging.warning("Data migrate failed and error: %s...",str(e))
            self._data_migration_result_handler(False, ERROR_DATA_MIGRATION_FAILED)

    def _run_transaction(self, migration_dict, migration_dict_1):
        try:
            #异步线程
            if (migration_dict == None or migration_dict == {}) and (migration_dict_1 == None or migration_dict_1 == {}):  
                logging.info("[DebRepacker] No package needs to migrate.")
                # 即便没有数据迁移，也要删除dpkg数据库
                self.remove_dpkg_db()
                self._data_migration_rate_handler("install", "all", 100)
                self._data_migration_result_handler(True)
            else:
                t = threading.Thread(target=self.auto_run,
                                    args=(migration_dict,migration_dict_1,),
                                    daemon=True)
                t.start()
        except Exception as e:
            logging.error(f"[DebRepacker] Data migration failed: {e}")
            self._data_migration_rate_handler("install", "all", 100)
            self._data_migration_result_handler(False, ERROR_DATA_MIGRATION_FAILED)
            return
        
    def _find_max_deploy(self, base_dir: str, name: str) -> Tuple[str, int]:
        """
        在 base_dir 中查找 name.x，返回 (完整路径, x)
        """
        max_idx = -1
        max_path = None
        pattern = re.compile(rf"^{re.escape(name)}\.(\d+)$")

        for entry in os.listdir(base_dir):
            m = pattern.match(entry)
            if not m:
                continue

            idx = int(m.group(1))
            if idx > max_idx:
                max_idx = idx
                max_path = os.path.join(base_dir, entry)

        if max_path is None:
            raise RuntimeError(f"No deployment found for {name} in {base_dir}")

        return max_path, max_idx
    
    def _if_mounted(self, target: str, force: bool = False) -> bool:
        """判断目标目录是否已经被挂载,如果挂载则解挂载"""
        try:
            # 检查是否挂载
            is_mounted = False
            with open("/proc/self/mountinfo", "r") as f:
                for line in f:
                    # mountinfo 格式: ... mount_point ...
                    parts = line.split()
                    if len(parts) >= 5 and parts[4] == target:
                        is_mounted = True
                        logging.info(f"[DebRepacker] {target} is mounted, info is {line}")
                        break
            return is_mounted

            if not is_mounted:
                logging.info(f"[DebRepacker] {target} is not mounted, no action needed")
                return True

            # # 执行 umount
            # cmd = ["umount"]
            # if force:
            #     cmd.append("-l")  # 或 "-f" 根据需要选择
            # cmd.append(target)

            # result = subprocess.run(
            #     cmd,
            #     check=False,
            #     stdout=subprocess.PIPE,
            #     stderr=subprocess.PIPE,
            #     text=True,
            # )

            # if result.returncode == 0:
            #     logging.info(f"[DebRepacker] Successfully unmounted {target}")
            #     return True
            # else:
            #     logging.warning(f"[DebRepacker] Failed to unmount {target}: {result.stderr}")
            #     return False

        except Exception as e:
            logging.error(f"[DebRepacker] Exception during unmount of {target}: {e}")
            return False

    def _ensure_dir(self, path: str):
        os.makedirs(path, exist_ok=True)

    def mount_overlay(self, name: str, cfg: dict):
        """
        cfg:
        - lower
        - upper
        - work
        - target
        """
        for key in ("lower", "upper", "work", "target"):
            self._ensure_dir(cfg[key])

        is_mounted = self._if_mounted(cfg["target"], True)
        if is_mounted == True:
            return True

        opts = (
            f"lowerdir={cfg['lower']},"
            f"upperdir={cfg['upper']},"
            f"workdir={cfg['work']}"
        )

        cmd = [
            "mount",
            "-t", "overlay",
            "overlay",
            "-o", opts,
            cfg["target"],
        ]

        logging.info(f"[DebRepacker][Overlay Mount] {name} -> {cfg['target']}")
        subprocess.run(cmd, check=True)

    def umount_deployroot_overlay(self):
        try:
            for path in self.overlay_map:
                target = self.overlay_map[path]["target"]
                if self._if_mounted(target) == True:
                    result = subprocess.run(
                        ["umount", "-l", target],
                        check=False,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        text=True
                    )
                    if result.returncode == 0:
                        logging.info(f"[DebRepacker] Unmounted {target} -> {target}")
                    else:
                        logging.warning(f"[DebRepacker] Failed to Unmount {target} -> {target}: {result.stderr}")
        except Exception as e:
            logging.error(f"umount deployroot overlay failed :{e}")
    def remove_dpkg_db(self):
        try:
            #删除upper和lower的dpkg，把基础层的dpkg数据库复制过来
            dpkg_paths = [
                f"{self.ovl_root}/var-ovl/var-lower/lib/dpkg",
                f"{self.ovl_root}/var-ovl/var-upper/lib/dpkg",
                ]
            
            for dpkg_path in dpkg_paths:
                if os.path.exists(dpkg_path):
                    cmd = ["rm", "-rf", dpkg_path]
                    logging.info(f"[DebRepacker] Running command: {' '.join(cmd)}") 
                    result = subprocess.run(cmd, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
                    if result.stdout != "":
                        logging.info(f"[DebRepacker] rm dpkg output: {result.stdout}")
                    if result.stderr != "":
                        logging.warning(f"[DebRepacker] Error removing dpkg: {result.stderr}")
                cmd = ["cp", "-r", f"{self.deploy_root}/var/lib/dpkg", dpkg_path]
                logging.info(f"[DebRepacker] Running command: {' '.join(cmd)}")
                result = subprocess.run(cmd, check=False, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
                if result.stdout != "":
                    logging.info(f"[DebRepacker] cp dpkg output: {result.stdout}")
                if result.stderr != "":
                    logging.warning(f"[DebRepacker] Error copying dpkg: {result.stderr}")
        except Exception as e:
            logging.error(f"[DebRepacker] Failed to remove dpkg overlays: {e}")
    def mount_overlay_map(self, overlay_map: dict, remove_dpkg=True, is_install= False):
        try:
            if remove_dpkg == True:
                self.remove_dpkg_db()
        except Exception as e:
            logging.error(f"[DebRepacker] Failed to remove dpkg overlays: {e}")
        
        try:
            for name, cfg in overlay_map.items():
                self.mount_overlay(name, cfg)
        except Exception as e:
            logging.error(f"[DebRepacker] Overlay mount failed: {e}")
            raise e
        
        if is_install == False:
            return
        # 挂载必要的 bind 目录，保证 dpkg/dpkg-repack 在 chroot 可用
        try:
            self.bind_dirs = G_settings.get("data_migrator_bind_dirs", ["/ostree","/boot"])
            # bind_dirs = ["/ostree","/boot"]
            for bdir in self.bind_dirs:
                target = os.path.join(self.deploy_root, bdir.lstrip("/"))
                os.makedirs(target, exist_ok=True)
                result = subprocess.run(
                    ["mount", "--bind", bdir, target],
                    check=False,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True
                )
                if result.returncode == 0:
                    logging.info(f"[DebRepacker] Bind mounted {bdir} -> {target}")
                else:
                    logging.warning(f"[DebRepacker] Failed to bind {bdir} -> {target}: {result.stderr}")
        except Exception as e:
            logging.error(f"[DebRepacker] Bind mount failed: {e}")

            
        # 解挂载必要的 bind 目录，保证 dpkg/dpkg-repack 在 chroot 可用
        try:
            unbind_dirs = G_settings.get("data_migrator_umount_dirs", ["/run"])
            # bind_dirs = ["/run"]
            for bdir in unbind_dirs:
                target = os.path.join(self.deploy_root, bdir.lstrip("/"))
                if self._if_mounted(target, True) == True:
                    result = subprocess.run(
                        ["umount", "-l", target],
                        check=False,
                        stdout=subprocess.PIPE,
                        stderr=subprocess.PIPE,
                        text=True
                    )
                    if result.returncode == 0:
                        logging.info(f"[DebRepacker] Unmounted {bdir} -> {target}")
                    else:
                        logging.warning(f"[DebRepacker] Failed to Unmount {bdir} -> {target}: {result.stderr}")
        except Exception as e:
            logging.error(f"[DebRepacker] Unmount mount failed: {e}")
    def umount_bind_dirs(self):
        try:
            if not hasattr(self, 'bind_dirs') or not hasattr(self, 'deploy_root'):
                logging.warning("[DebRepacker] Required attributes not found")
                return
            if not self.bind_dirs:  # 处理 None 或空列表
                return
            for bdir in self.bind_dirs:
                target = os.path.join(self.deploy_root, bdir.lstrip("/"))
                result = subprocess.run(
                    ["umount", "-l", target],
                    check=False,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True
                )
                if result.returncode == 0:
                    logging.info(f"[DebRepacker] Unmounted {bdir} -> {target}")
                else:
                    logging.warning(f"[DebRepacker] Failed to Unmount {bdir} -> {target}: {result.stderr}")
        except Exception as e:
            logging.error(f"[DebRepacker] Unmount bind dirs failed: {e}")

    # ---------------- 输出目录准备 ----------------
    def _prepare_output_dir(self):
        try:
            self.__rm_all_under(self.output_dir)
        except Exception as e:
            raise RuntimeError(f"[DebRepacker] Directory preparation failed: {self.output_dir} ({e})")

    # ---------------- 包安装检查 ----------------
    def _is_package_installed(self, pkg: str) -> bool:
        result = subprocess.run(
            ["dpkg-query", "-W", "-f=${Status}", pkg],
            stdout=subprocess.PIPE,
            stderr=subprocess.DEVNULL,
            text=True,
        )
        return "install ok installed" in result.stdout

    # ---------------- 单包打包 ----------------
    def _repack_package(self, pkg, chroot_dir=None):
        if chroot_dir == None or chroot_dir == "":
            if os.path.exists("/opt/update-module/current/usr/bin/dpkg-repack"):
                cmd_candidates = [
                    ["/opt/update-module/current/usr/bin/update-launcher", "/opt/update-module/current/usr/bin/dpkg-repack", "-d", "--compression=none", pkg],
                    ["/opt/update-module/current/usr/bin/dpkg-repack", "-d", "--compression=none", pkg],
                ]
            else:
                cmd_candidates = [
                    ["/usr/bin/update-launcher", "/usr/bin/dpkg-repack", "-d", "--compression=none", pkg],
                    ["/usr/bin/dpkg-repack", "-d", "--compression=none", pkg],
                ]
                   
        else:
            cmd_candidates = [
                ["/usr/bin/update-launcher", "/usr/bin/dpkg-repack", "-d", "--compression=none", pkg],
                ["/usr/bin/dpkg-repack", "-d", "--compression=none", pkg],
            ]
        for cmd in cmd_candidates:
            try:
                if chroot_dir == None or chroot_dir == "":
                    full_cmd = f"cd {shlex.quote(self.output_dir)} &&" + " ".join(shlex.quote(x) for x in cmd)
                else:
                    shell_cmd = "cd {dir} && {cmd}".format(
                            dir=shlex.quote(self.chroot_output_dir),
                            cmd=" ".join(shlex.quote(x) for x in cmd)
                        )
                    full_cmd = f"/usr/sbin/chroot {chroot_dir} /usr/bin/bash -c {shlex.quote(shell_cmd)}"
                logging.info(f"[DebRepacker] Running command: {full_cmd}")
                result = subprocess.run(
                    full_cmd,
                    shell=True,
                    check=False,
                    stdout=subprocess.PIPE,
                    stderr=subprocess.PIPE,
                    text=True,
                )

                # 校验输出
                for fname in os.listdir(self.output_dir):
                    if fname.startswith(pkg + "_") and fname.endswith(".deb"):
                        parts = fname[:-4].split("_", 2)
                        if len(parts) >= 3:
                            deb_name, version, arch = parts
                        else:
                            version = None
                        return True, os.path.join(self.output_dir, fname), version, arch
                # 输出文件未找到，打印日志
                logging.error(f"[DebRepacker] command failed: {full_cmd})")
                if result.stdout != "":
                    logging.info(f"[DebRepacker] Command stdout: {result.stdout}")
                if result.stderr != "":
                    logging.info(f"[DebRepacker] Command stderr: {result.stderr}")
            except Exception as e:
                logging.error(f"[DebRepacker] Command failed: {full_cmd} ({e})")
        else:
            logging.error(f"[DebRepacker] All repack commands failed!")

        return False, f"[DebRepacker] No deb generated for: {pkg}", None, None
    
    def __rm_all_under(self, path: str):
        """
        删除指定目录下的所有文件和子目录（不删除目录本身）
        等价于：rm -rf path/*
        """
        try:
            path = os.path.abspath(path)

            if not os.path.exists(path):
                os.makedirs(path)

            for name in os.listdir(path):
                full_path = os.path.join(path, name)
                if os.path.isfile(full_path) or os.path.islink(full_path):
                    os.unlink(full_path)
                elif os.path.isdir(full_path):
                    shutil.rmtree(full_path)
        except Exception as e:
            logging.error(f"[DebRepacker] Failed to clear directory: {path} ({e})")

    # ---------------- 批量打包 ----------------
    def repack_packages(self, packages_chroot: dict, packages_org: dict, finished_callback=None) -> Tuple[dict, dict]:
        """
        批量打包
        """
        success_pkgs = {}
        failed_pkgs = {}
        all_pkgs_len = len(set(list(packages_chroot.keys()) + list(packages_org.keys())))
        # repack 旧系统 overlay 对比旧系统base需要迁移的包
        try:
            for i, pkg in enumerate(packages_org, 1):
                logging.info(f"[DebRepacker] Repacking package: {pkg} -> {self.output_dir}")

                ok, result, version, arch = self._repack_package(pkg)
                if ok:
                    success_pkgs[pkg] = version
                    logging.info(f"[DebRepacker] Repack success: {pkg} ({version})")
                else:
                    failed_pkgs[pkg] = result
                    logging.error(result)

                # 打包完成一个上报进度
                # 打包进度为1-50
                progress = int((i / all_pkgs_len) * 30)
                progress = min(max(progress, 1), 30)
                self._data_migration_rate_handler("repack_old", pkg, progress)

        except Exception as e:
            logging.error(f"[DebRepacker] Repackaging failed: {e}")

        # repack 旧系统overlay对比新系统base需要迁移的包
        try:
            for i, pkg in enumerate(packages_chroot, 1):
                logging.info(f"[DebRepacker] Repacking package: {pkg} -> {self.output_dir}")

                ok, result, version, arch = self._repack_package(pkg, self.deploy_root)
                if ok:
                    success_pkgs[pkg] = version
                    logging.info(f"[DebRepacker] Repack success: {pkg} ({version})")
                else:
                    failed_pkgs[pkg] = result
                    logging.error(result)

                # 打包完成一个上报进度
                # 打包进度为1-50
                # 续接上一个循环的进度
                base_start_i = len(packages_org)
                i_chroot = i + base_start_i
                progress = int((i_chroot / all_pkgs_len) * 30)
                progress = min(max(progress, 1), 30)
                self._data_migration_rate_handler("repack_new", pkg, progress)

        except Exception as e:
            logging.error(f"[DebRepacker] Repackaging failed: {e}")

        return success_pkgs, failed_pkgs
    

    def _run_in_chroot(self, cmd: list, chroot_dir: str, is_install: bool = False, check_status: bool = True):
        """
        在 self.deploy_root 下执行命令
        """
        def join_cmd(c):
            return " ".join(shlex.quote(x) for x in c)
        shell_cmds = []

        # 需要关闭验签和恢复验签，否则会安装失败
        if is_install == True:
            shell_cmds.append("mv /usr/sbin/service /usr/sbin/service.real || true")
            shell_cmds.append("printf '#!/bin/sh\\necho \"[build skip] service $@\"\\nexit 0\\n' > /usr/sbin/service")
            shell_cmds.append("chmod +x /usr/sbin/service")
            shell_cmds.append("cp /etc/dpkg/dpkg.cfg /etc/dpkg/dpkg.cfg.updateback || true")
            shell_cmds.append("setsignstatus off || true")
            shell_cmds.append("trap 'cp -f /etc/dpkg/dpkg.cfg.updateback /etc/dpkg/dpkg.cfg 2>/dev/null || true;' EXIT")
        shell_cmds.append(join_cmd(cmd))
        if is_install == True:
            shell_cmds.append("if [ -f /usr/sbin/service.real ]; then mv /usr/sbin/service.real /usr/sbin/service; fi")

        final_cmd = " && ".join(shell_cmds)
        # final_cmd = " ; ".join(shell_cmds)

        # full_cmd = ["chroot", chroot_dir, "/bin/bash", "-c", final_cmd, ]
        # full_cmd = f"/usr/sbin/chroot {chroot_dir} /bin/bash -c {shlex.quote(final_cmd)}"
        full_cmd = [
            "/usr/sbin/chroot",
            chroot_dir,
            "/bin/bash",
            "-c",
            final_cmd,
        ]
                
        logging.info(f"[DebRepacker] Running in chroot: {full_cmd}")
        result = subprocess.run(
            full_cmd,
            # shell=True,
            check=check_status,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
        )
        return result
    def _disable_kysec(self):
        try:
            logging.info("kysec_policy exec off") 
            env=os.environ.copy()
            subprocess.run(
                ["/usr/sbin/kysec_policy", "exec", "off"],
                check=True,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                env=env,
            )
        except subprocess.CalledProcessError as e:
            logging.error("kysec_policy exec off failed")
            logging.error("returncode:", e.returncode)
            logging.error("stdout:", e.stdout)
            logging.error("stderr:", e.stderr)
        except Exception as e:
            logging.error("kysec_policy exec off failed with exception")
            logging.error(e)
        
    
    def _monitor_dpkg_log(self, chroot_dir: str, deb_paths, install_thread: threading.Thread):
        """
        实时监控 /var/log/dpkg.log 并上报安装进度
        :param chroot_dir: 安装环境根路径
        :param pkg_names: 待安装包名列表
        :param total_pkgs: 待安装包总数
        :param completed_pkgs: 已完成包集合（线程共享）
        :param install_thread: 执行 dpkg -i 的线程对象
        """
        # 获取包名列表
        pkg_names = [os.path.basename(p).split("_")[0] for p in deb_paths]
        # 记录已完成的包
        completed_pkgs_unpack = set()
        completed_pkgs_install = set()
        total_pkgs = len(deb_paths)
        dpkg_log_path = os.path.join(chroot_dir, "var/log/dpkg.log")
        last_size = 0
        last_progress = 40

        while install_thread.is_alive():
            if not os.path.exists(dpkg_log_path):
                time.sleep(0.5)
                continue
            curr_size = os.path.getsize(dpkg_log_path)
            if curr_size < last_size:
                # dpkg.log 轮转或清空
                last_size = 0
            if curr_size == last_size:
                time.sleep(0.1)
                continue

            with open(dpkg_log_path, "r") as f:
                f.seek(last_size)
                lines = f.readlines()
                last_size = f.tell()

            for line in lines:
                line = line.strip()
                parts = line.split()
                if len(parts) < 4:
                    continue
                pkg = parts[-2].split(":")[0]

                # 解压完成
                if "status unpacked" in line and pkg in pkg_names and pkg not in completed_pkgs_unpack:
                    completed_pkgs_unpack.add(pkg)
                    base = 60  # 解压进度起点
                    factor = 20  # 解压占 20%
                    progress = math.ceil(base + (len(completed_pkgs_unpack) / total_pkgs) * factor)
                    progress = min(max(progress, 41), 60)
                    if progress > last_progress:
                        last_progress = progress
                        GLib.idle_add(self._data_migration_rate_handler, "install", pkg, progress)
                    else:
                        pass

                # 安装完成
                elif "status installed" in line and pkg in pkg_names and pkg not in completed_pkgs_install:
                    completed_pkgs_install.add(pkg)
                    base = 80  # 安装进度起点
                    factor = 19  # 安装占 19%
                    progress = math.ceil(base + (len(completed_pkgs_install) / total_pkgs) * factor)
                    progress = min(max(progress, 61), 99)
                    if progress > last_progress:
                        last_progress = progress
                        GLib.idle_add(self._data_migration_rate_handler, "install", pkg, progress)
                    else:
                        pass

            time.sleep(0.1)

        # 安装线程结束后，最终进度 100%
        # GLib.idle_add(self._data_migration_rate_handler, "install", "all", 100)

    def check_and_install_debs(self, reinstal_pkg: dict, chroot_dir: str = None) -> bool:
        """
        校验 output_dir 下的 deb 文件与 pkg_versions 是否一致，
        通过 apt reinstall --simulate 模拟安装，
        无异常则执行 dpkg -i 安装

        :param pkg_versions: {pkg: version}
        :return: True / False
        """
        fail_event = threading.Event()
        deb_paths = []
        try:
            if reinstal_pkg == {} or reinstal_pkg == None:
                logging.warning("[DebRepacker] No package need to be installed. Skip installation.")
                GLib.idle_add(self._data_migration_result_handler, True)
                return True
            if not self.output_dir or not os.path.isdir(self.output_dir):
                logging.error(f"[DebRepacker] Invalid output_dir: {self.output_dir}")
                GLib.idle_add(self._data_migration_result_handler, False, ERROR_DATA_MIGRATION_INSTALL_FAILED)
                return False
            # ---------- 1. 扫描 output_dir ----------
            for root, _, files in os.walk(self.output_dir):
                for fname in files:
                    if fname.endswith(".deb"):
                        deb_paths.append(os.path.join(self.chroot_output_dir, fname))
            if deb_paths == []:
                logging.error("[DebRepacker] No deb files found")
                GLib.idle_add(self._data_migration_result_handler, False, ERROR_DATA_MIGRATION_INSTALL_FAILED)
                return False
            
            logging.info(f"[DebRepacker] Total debs to install: {deb_paths}")
            # 进度增长
            self._data_migration_rate_handler("install", "", 35)
            logging.info("[DebRepacker] Mounting overlay...")
            
            # ---------- 3. 依赖模拟安装 ----------
            apt_conf = os.path.join(self.output_dir, "99-apt-data-migration.conf")
            chroot_apt_conf = os.path.join(self.chroot_output_dir, "99-apt-data-migration.conf")
            with open(apt_conf, "w") as f:
                f.write("""
APT::Install-Recommends "false";
APT::Install-Suggests "false";

Dpkg::Options {
  "--force-confdef";
  "--force-confold";
  "--force-unsafe-io";
};

Dpkg::Post-Invoke "";
Dir::Etc::sourceparts="";
""")
            simulate_cmd = ["apt", "install", "--simulate", "--allow-downgrades", "--reinstall", "-c", chroot_apt_conf] + deb_paths
            logging.info("[DebRepacker] Simulating install (apt reinstall --simulate)...")
            try:
                result = self._run_in_chroot(simulate_cmd, chroot_dir=chroot_dir, check_status=True)
                if result.stdout:
                    logging.info(result.stdout)
                if result.stderr:
                    logging.error(result.stderr)
            except Exception as e:
                logging.error("[DebRepacker] Dependency simulation failed")
                logging.error(e)
            
            logging.info("[DebRepacker] Simulation passed")
            # ---------- 4. 正式安装 ----------
            logging.info("[DebRepacker] Installing deb packages...")
            # dpkg 安装线程
            def install_thread():
                try:
                    #实际测试发现，此处安全影响会导致命令失败
                    # self._disable_kysec()
                    install_cmd = ["apt", "install", "-y", "--allow-downgrades", "--reinstall", "-c", chroot_apt_conf] + deb_paths
                    # install_cmd = ["DEBIAN_FRONTEND=noninteractive", "INITRAMFS=no", "dpkg", "-i", "--force-confdef", "--force-confold", "--force-unsafe-io",] + deb_paths
                    result = self._run_in_chroot(install_cmd, chroot_dir=chroot_dir, is_install=True, check_status=True)
                    if result.stdout:
                        logging.info(result.stdout)
                    if result.stderr:
                        logging.error(result.stderr)
                    #openkylin系统中返回值不为0的时候有可能不raise error
                    logging.error(f"[DebRepacker] Installation process return code {result.returncode}")
                    # if result.returncode != 0:
                    #     fail_event.set()
                    #     logging.error(f"[DebRepacker] Installation failed but not error raise")
                    #     GLib.idle_add(self._data_migration_result_handler, False, ERROR_DATA_MIGRATION_INSTALL_FAILED)
                except subprocess.CalledProcessError as e:
                    if e.stdout:
                        logging.info(e.stdout)
                    if e.stderr:
                        logging.error(e.stderr)
                    logging.error(f"[DebRepacker] Installation failed: {e}")
                    # 上报失败
                    fail_event.set()
                    GLib.idle_add(self._data_migration_result_handler, False, ERROR_DATA_MIGRATION_INSTALL_FAILED)

                except Exception as e:
                    logging.error("[DebRepacker] Installation failed: %s", e)
                    # 上报失败
                    fail_event.set()
                    GLib.idle_add(self._data_migration_result_handler, False, ERROR_DATA_MIGRATION_INSTALL_FAILED)
            
            # 启动安装线程
            t = threading.Thread(target=install_thread)
            t.start()
            # 监控日志线程
            self._monitor_dpkg_log(chroot_dir, deb_paths, t)
            # 等待线程完成
            t.join()
            if fail_event.is_set():
                logging.error("[DebRepacker] Installation thread failed")
                return False
            # 最终上报完成
            logging.info("[DebRepacker] Installation thread completed")
            GLib.idle_add(self._data_migration_rate_handler, "install", "all", 100)
            GLib.idle_add(self._data_migration_result_handler, True)
        except Exception as e:
            logging.error(f"[DebRepacker] check_and_reinstall_debs failed: {e}")
            GLib.idle_add(self._data_migration_result_handler, False, ERROR_DATA_MIGRATION_INSTALL_FAILED)
            return False

    def delete_overlay_files(self, base_paths: dict, file_paths_upper:list, file_paths_lower:list):
        """
        :param base_paths: 叠加根路径列表，例如 ['/ostree/hash1/etc-ovl/etc-lower']
        :param file_paths: 需要删除的文件路径列表，例如 ['/etc/apc/1.txt']
        """
        if not file_paths_upper and not file_paths_lower:
            logging.info("Files path list is empty!")
            return
        try:
            for key, paths in base_paths.items():
                prefix = f"/{key}/"
                # 处理upper
                for fpath in file_paths_upper:
                    # 必须匹配当前 overlay 类型=yes',
                    if not fpath.startswith(prefix):
                        continue
                    relative_path = fpath[len(prefix):]
                    raw_path = paths.get("upper")
                    if not raw_path:
                        continue
                    full_path = os.path.join(raw_path, relative_path)

                    if os.path.exists(full_path):
                        try:
                            if os.path.isfile(full_path) or os.path.islink(full_path):
                                os.remove(full_path)
                            # elif os.path.isdir(full_path):
                            #     shutil.rmtree(full_path)
                            logging.info(f"[OK] Deleted: {full_path}")
                        except Exception as e:
                            logging.error(f"[ERROR] Failed to delete {full_path}: {e}")
                # 处理lower
                for fpath in file_paths_lower:
                    if not fpath.startswith(prefix):
                        continue

                    relative_path = fpath[len(prefix):]
                    raw_lower = paths.get("lower")
                    if not raw_lower:
                        continue

                    lower_dir = raw_lower.split(":")[0]

                    full_path = os.path.join(lower_dir, relative_path)

                    if not os.path.exists(full_path):
                        continue
                    try:
                        if os.path.isfile(full_path) or os.path.islink(full_path):
                            os.remove(full_path)
                        # elif os.path.isdir(full_path):
                        #     shutil.rmtree(full_path)
                        logging.info(f"[OK] Deleted: {full_path}")
                    except Exception as e:
                        logging.error(f"[ERROR] Failed to delete lower {full_path}: {e}")

        except Exception as e:
            logging.error(f"delete purge packages files failed!")
            logging.error(e)
    def _get_pkg_files(self, pkgs_list:list):
        """
        获取指定包的文件列表（严格版本匹配）

        :param pkgs_list: ["test_deb1=version1", "test_deb2"]
        :return: {
            "test_deb1": [...],
            "test_deb2": [...]
        }
        """
        try:
            dpkg_info_dir = "/var/lib/dpkg/info"
            apt_pkg.init()
            cache = apt_pkg.Cache()
            result_upper = {}
            result_lower = {}
            for item in pkgs_list:
                # -------------------------
                # 1️⃣ 解析 pkg=version
                # -------------------------
                if "=" in item:
                    pkg_name, required_version = item.split("=", 1)
                    pkg_name = pkg_name.strip()
                    required_version = required_version.strip()
                else:
                    pkg_name = item.strip()
                    required_version = None

                if not pkg_name:
                    continue

                # -------------------------
                # 2️⃣ 检查是否存在
                # -------------------------
                if pkg_name not in cache:
                    logging.info(f"[SKIP] Package not found: {pkg_name}")
                    continue

                pkg = cache[pkg_name]

                if not pkg.current_ver:
                    logging.info(f"[SKIP] Package not installed: {pkg_name}")
                    continue

                installed_version = pkg.current_ver.ver_str
                # -------------------------
                # 3️⃣ 版本匹配（使用 Debian 规则）
                # -------------------------
                if required_version:
                    if apt_pkg.version_compare(installed_version, required_version) != 0:
                        logging.info(
                            f"[SKIP] Version mismatch for {pkg_name}: "
                            f"installed={installed_version}, required={required_version}"
                        )
                        continue
                # -------------------------
                # 4️⃣ 读取 .list 文件
                # -------------------------
                list_file = os.path.join(dpkg_info_dir, f"{pkg_name}.list")

                if not os.path.exists(list_file):
                    logging.info(f"[WARN] .list not found for {pkg_name}")
                    result_upper[pkg_name] = []
                    continue

                try:
                    with open(list_file, "r", encoding="utf-8", errors="ignore") as f:
                        files = [line.strip() for line in f if line.strip()]
                        result_upper[pkg_name] = files

                except Exception as e:
                    logging.error(f"[ERROR] Failed to read {list_file}: {e}")
                    result_upper[pkg_name] = []

                # 读取lower层的dpkg数据库
                list_file_lower = os.path.join(f"{self.ovl_root}/var-ovl/var-lower/lib", "dpkg/info", f"{pkg_name}.list")
                try:
                    with open(list_file_lower, "r", encoding="utf-8", errors="ignore") as f:
                        files = [line.strip() for line in f if line.strip()]
                        result_lower[pkg_name] = files
                except Exception as e:
                    logging.error(f"[ERROR] Failed to read {list_file_lower}: {e}")
                    result_lower[pkg_name] = []

            for pkg, files in result_upper.items():
                logging.info(f"Find {pkg} files number: {len(files)}")
            for pkg, files in result_lower.items():
                logging.info(f"Find {pkg} files number: {len(files)}")

            files_list_upper = list(set(f for files in result_upper.values() for f in files))
            pkgs_list_upper = list(result_upper.keys())
            files_list_lower = list(set(f for files in result_lower.values() for f in files))
            pkgs_list_lower = list(result_lower.keys())

            return files_list_upper, files_list_lower, pkgs_list_upper, pkgs_list_lower
        except Exception as e:
            logging.error("Find pkg files failed:")
            logging.error(e)
            return [],[]
        
    def purge_old_pkg_files(self, purge_list):
        files_list_upper, files_list_lower, pkgs_list_upper, pkgs_list_lower = self._get_pkg_files(purge_list)
        self.delete_overlay_files(self.overlay_map, files_list_upper, files_list_lower)
        return pkgs_list_upper + pkgs_list_lower
    
    def auto_run(self, packages_chroot: dict, packages_org: dict) -> bool:
        """
        一键打包并安装
        :param packages_chroot:     需要基于新系统的base重打包安装的包列表
        :param packages_org:        需要基于旧系统的base重打包安装的包列表
        :return: True / False
        """
        logging.info(f"[DebRepacker] Data migration for deployed os: {packages_chroot}")
        logging.info(f"[DebRepacker] Data migration for expired os: {packages_org}")
        if "kylin-system-updater" in packages_chroot:
            packages_chroot.pop("kylin-system-updater", None)
        if "kylin-system-updater" in packages_org:
            packages_org.pop("kylin-system-updater", None)
        try:
            
            purge_list = G_settings.get("data_migrator_purge_list", [])
            if not purge_list:
                logging.info("data_migrator_purge_list is empty!")
            else:
                pkg_list = self.purge_old_pkg_files(purge_list)
                for pkg in pkg_list:
                    if pkg in packages_chroot:
                        packages_chroot.pop(pkg, None)
                    if pkg in packages_org:
                        packages_org.pop(pkg, None)
            #将overlay的/etc /usr /var/lib解挂载
            self.umount_deployroot_overlay()
            success_pkgs, failed_pkgs = self.repack_packages(packages_chroot, packages_org)
            if failed_pkgs:
                for pkg, err in failed_pkgs.items():
                    logging.error(f"[DebRepacker] Repack failed: {pkg} -> {failed_pkgs[pkg]}")
                # 策略： 失败一个就退出
                self._data_migration_result_handler(False, ERROR_DATA_MIGRATION_REPACK_FAILED)
                return False
            if success_pkgs == {} or success_pkgs == None:
                logging.warning("[DebRepacker] No packages repacked successfully")
                self._data_migration_result_handler(False, ERROR_DATA_MIGRATION_REPACK_FAILED)
                return False
            # 挂载 overlay
            logging.info("[DebRepacker] Mounting overlay...")
            self.mount_overlay_map(self.overlay_map, is_install=True)
            return self.check_and_install_debs(success_pkgs, chroot_dir=self.deploy_root)
        except Exception as e:
            logging.error(f"[DebRepacker] auto_run failed: {e}")
            self._data_migration_result_handler(False, ERROR_DATA_MIGRATION_FAILED)
            return False
        
    # -------------------- 信号回调 --------------------
    def _data_migration_rate_handler(self, status, pkg, progress):
        """Handle backup progress updates from the backend."""
        self.emit("migration-progress", status, pkg, progress)

    def _data_migration_result_handler(self, result, status=None):
        """Handle backup completion result from the backend."""
        self.__rm_all_under(self.output_dir)
        self.umount_bind_dirs()
        logging.info(f"migration result: {result}, {status}")
        self.emit("migration-done", result, status)
        

# if __name__ == "__main__":

#     def _on_migration_progress(obj, status, pkg, percent):
#         logging.info(f"\nMigration progress: {percent}%")
#     def _on_migration_done(obj, success, status):
#         logging.info(f"\nMigration done: success={success}, status={status}")
#         loop.quit()

#     # -----------------------------
#     # 配置 logging 打印到终端
#     # -----------------------------
#     logging.basicConfig(
#         level=logging.INFO,  # 日志级别，可改为 DEBUG 查看详细信息
#         format="%(asctime)s [%(levelname)s] %(message)s",
#         datefmt="%Y-%m-%d %H:%M:%S",
#     )
#     # 获取 logger 对象
#     logger = logging.getLogger(__name__)
#     # 示例用法
#     repacker = MigrationBackend(deploy_hash="ef5cdf0f0824cd3db994a98017f3947d2af6364a38404c7c705d1a8eb5c34e77")
#     dict1 = {'kylin-photo-viewer': '4.21.1.0-ok0.1k0.16', 'ukui-system-monitor':'4.20.0.0-ok1.2k1.12','kylin-fonts':'1.2.7.1-0k1.0','ukui-search':'4.21.1.0-ok0.1k0.3'}
#     dict2 = {'wps-office': '12.1.2.1123.AK.preload.sw','ssh':'1:9.6p1-ok6k0.11','ukui-session-manager':'4.20.0.0-ok1k0.22~1','kylin-printer':'1.3.1.0-ok16.1k4.0','ukui-greeter':'4.10.0.0-ok5.1k0.3','openssh-server':'1:9.6p1-ok6k0.11','openssh-client':'1:9.6p1-ok6k0.11','ukui-session-wayland':'4.20.0.0-ok1k0.22~1','libfido2-1':'1.14.0-ok1k0.1','openssh-sftp-server':'1:9.6p1-ok6k0.11'}
#     # 创建 MainLoop
#     # -----------------------------
#     loop = GLib.MainLoop()
#     repacker.connect("migration-progress", _on_migration_progress)
#     repacker.connect("migration-done", _on_migration_done)
#     repacker.run(dict1, dict2)

#     loop.run()
#     # repacker.check_and_install_debs(success_pkgs, chroot_dir=repacker.deploy_root)