import os
import filecmp
import json
import logging
import shutil

class DirectoryComparator:
    def __init__(self, dir1, dir2, ignore_dirs: list = None):
        self.dir1 = dir1
        self.dir2 = dir2
        self.same = []
        self.differences = []
        self.errors = []
        # 默认忽略 dpkg 和 kylin-system-updater 目录
        self.ignore_dirs = ignore_dirs if ignore_dirs is not None else ["dpkg", "kylin-system-updater"]

    def get_all_files(self, root_dir):
        """
        获取目录下的所有文件

        只忽略根目录下的第一层 dpkg 和 kylin-system-updater 目录
        """
        file_dict = {}
        for root, dirs, files in os.walk(root_dir):
            # 只过滤根目录下的第一层子目录
            if root == root_dir:
                dirs[:] = [d for d in dirs if d not in self.ignore_dirs]
            for file in files:
                full_path = os.path.join(root, file)
                rel_path = os.path.relpath(full_path, root_dir)
                file_dict[rel_path] = full_path
        return file_dict

    def get_stat(self, path):
        try:
            return os.stat(path)
        except Exception:
            return None

    def fast_compare(self, file1, file2, rel_path):
        try:
            stat1 = self.get_stat(file1)
            stat2 = self.get_stat(file2)

            if not stat1 or not stat2:
                return ("error", rel_path)

            # # 权限不同 → 不同 （弃用，不考虑文件权限）
            # if stat1.st_mode != stat2.st_mode:
            #     return ("diff", rel_path)

            # 大小不同 → 不同
            if stat1.st_size != stat2.st_size:
                return ("diff", rel_path)

            # inode 相同（硬链接）→ 相同
            if stat1.st_ino == stat2.st_ino and stat1.st_dev == stat2.st_dev:
                return ("same", rel_path)

            # 快速内容比较（比 MD5 快）
            if filecmp.cmp(file1, file2, shallow=False):
                return ("same", rel_path)
            else:
                return ("diff", rel_path)

        except Exception:
            return ("error", rel_path)

    def compare(self):
        # 清除 filecmp 缓存，避免使用旧的比较结果
        filecmp.clear_cache()

        files1 = self.get_all_files(self.dir1)
        files2 = self.get_all_files(self.dir2)

        common_files = list(set(files1.keys()) & set(files2.keys()))

        # 顺序处理所有文件比较
        for rel_path in common_files:
            file1 = files1[rel_path]
            file2 = files2[rel_path]
            result, path = self.fast_compare(file1, file2, rel_path)

            if result == "same":
                self.same.append(path)
            elif result == "diff":
                self.differences.append(path)
            else:
                self.errors.append(path)

    def get_result(self):
        return {
            "same": self.same,
            "differences": self.differences,
            "errors": self.errors
        }

    def delete_same_files(self):
        """
        删除 dir2 目录中与 dir1 相同的文件
        将 dir2 的绝对路径和 same 字段中的相对路径拼接后删除
        支持删除普通文件和符号链接
        删除后清理空目录（保留有不同文件和异常文件的目录）
        """
        deleted_count = 0

        # 1. 先删除所有 same 文件
        for rel_path in self.same:
            # 拼接 dir2 的绝对路径和相对路径
            file_to_delete = os.path.join(self.dir2, rel_path)
            try:
                if os.path.islink(file_to_delete):
                    # 如果是符号链接，使用 unlink 删除
                    os.unlink(file_to_delete)
                    deleted_count += 1
                    # logging.info(f"[MarkClean] 已删除符号链接: {file_to_delete}")
                elif os.path.exists(file_to_delete):
                    os.remove(file_to_delete)
                    deleted_count += 1
                    # logging.info(f"[MarkClean] 已删除: {file_to_delete}")
                else:
                    logging.info(f"[MarkClean] 文件不存在: {file_to_delete}")
            except Exception as e:
                logging.info(f"[MarkClean] 删除失败 {file_to_delete}: {e}")

        logging.info(f"[MarkClean] 共删除 {deleted_count} 个相同文件/符号链接")

        # 2. 删除空目录
        self._delete_empty_dirs()

        return deleted_count

    def _delete_empty_dirs(self):
        """
        删除 dir2 中的所有空目录
        删完 same 文件后，只有 differences 和 errors 所在的目录不是空的
        其他空目录都可以删除
        """
        deleted_dir_count = self._remove_empty_dirs_recursive(self.dir2)
        logging.info(f"[MarkClean] 共删除 {deleted_dir_count} 个空目录")

    def _remove_empty_dirs_recursive(self, current_dir):
        """
        递归删除空目录
        从最深层开始删除，逐层向上
        """
        deleted_count = 0

        # 获取当前目录下的所有条目
        try:
            entries = os.listdir(current_dir)
        except OSError:
            return 0

        # 先递归处理子目录
        dirs_to_process = []
        for entry in entries:
            full_path = os.path.join(current_dir, entry)
            if os.path.isdir(full_path) and not os.path.islink(full_path):
                dirs_to_process.append(full_path)

        # 递归删除子目录中的空目录
        for sub_dir in dirs_to_process:
            # 检查路径是否在 dir2 范围内，防止符号链接绕出
            if not self._is_path_in_dir(sub_dir, self.dir2):
                continue
            deleted_count += self._remove_empty_dirs_recursive(sub_dir)

        # 再次检查当前目录内容（子目录可能已被删除）
        try:
            remaining_entries = os.listdir(current_dir)
        except OSError:
            return 0

        # 如果目录为空且不是根目录，则删除
        if not remaining_entries and current_dir != self.dir2:
            # 检查是否是特殊目录（mount point 等）
            if self._is_safe_to_delete(current_dir):
                try:
                    os.rmdir(current_dir)
                    deleted_count += 1
                    # logging.info(f"已删除空目录: {current_dir}")
                except OSError:
                    # 可能是权限问题或目录非空
                    pass

        return deleted_count

    def _is_path_in_dir(self, path, base_dir):
        """
        检查路径是否在 base_dir 范围内
        防止符号链接绕出目录
        """
        try:
            abs_path = os.path.realpath(path)
            abs_dir = os.path.realpath(base_dir)
            return abs_path.startswith(abs_dir + os.sep) or abs_path == abs_dir
        except OSError:
            return False

    def _is_safe_to_delete(self, dir_path):
        """
        检查目录是否可以安全删除
        排除 mount point、特殊目录等
        """
        try:
            # 检查是否是 mount point
            if os.path.ismount(dir_path):
                return False

            # 获取目录的 stat 信息
            stat_info = os.stat(dir_path)

            # 检查设备号，如果子目录和父目录的设备号不同，说明是 mount point
            parent_path = os.path.dirname(dir_path)
            if parent_path:
                parent_stat = os.stat(parent_path)
                if stat_info.st_dev != parent_stat.st_dev:
                    return False

            return True
        except OSError:
            return False

def compare_etc_dir(comparator, result_dict):
    """对比 etc 目录，暂时弃用，预留多线程对比"""
    comparator.compare()
    result_dict["etc"] = comparator.get_result()

def compare_varlib_dir(comparator, result_dict):
    """对比 var/lib 目录，暂时弃用，预留多线程对比"""
    comparator.compare()
    result_dict["varlib"] = comparator.get_result()

def security_docs_delete(base_dir1: str, base_dir2: str, delete_same: bool = True):
    """
    对比两个目录下的 etc 目录，并可选删除相同的文件

    Args:
        base_dir1: 基础目录1为当前部署的部署目录（如 /ostree/deploy/kylin/deploy/xxx.x）
        base_dir2: 基础目录2为升级部署async后的upper层（如 /ostree/pkgs/ovl-xxx.x）
        delete_same: 是否删除 dir2 中与 dir1 相同的文件，默认 True
    """
    # 对比 etc 目录
    dir1_etc = f"{base_dir1}/etc"
    dir2_etc = f"{base_dir2}/etc-ovl/etc-upper"
    comparator_etc = DirectoryComparator(dir1_etc, dir2_etc)
    comparator_etc.compare()
    result_etc = comparator_etc.get_result()

    # 保存 comparator_etc 结果到 JSON 文件
    cache_dir = "/var/cache/kylin-system-updater/dpkg_status"
    os.makedirs(cache_dir, exist_ok=True)

    # 先保存到临时文件
    temp_json_path = "/tmp/security_comparator_etc.json"
    with open(temp_json_path, 'w', encoding='utf-8') as f:
        json.dump(result_etc, f, ensure_ascii=False, indent=2)

    # 使用 shutil.copy2 复制到目标位置
    target_json_path = os.path.join(cache_dir, "security_comparator_etc.json")
    shutil.copy2(temp_json_path, target_json_path)

    # for f in result_etc.get("same", []):
    #     logging.info(f"[MarkClean] 相同文件: {f}")
    # for f in result_etc.get("differences", []):
    #     logging.info(f"[MarkClean] 不同文件: {f}")
    # for f in result_etc.get("errors", []):
    #     logging.info(f"[MarkClean] 错误文件: {f}")
    logging.info(f"[MarkClean] 相同文件数量: {len(result_etc.get('same', []))}")
    logging.info(f"[MarkClean] 不同文件数量: {len(result_etc.get('differences', []))}")
    logging.info(f"[MarkClean] 错误文件数量: {len(result_etc.get('errors', []))}")

    # 可选删除 dir2 中与 dir1 相同的文件
    if delete_same:
        comparator_etc.delete_same_files()

    return

# if __name__ == "__main__":

#     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__)

#     # 基础路径
#     base_dir1 = "/ostree/deploy/kylin/deploy/157e0d8e45f7535f2cebb61870397302f0d1ba775678edeb0efbd7cf5cb30cbb.1"
#     base_dir2 = "/ostree/pkgs/ovl-7f20d0e73a7abe15b345b7242c5efa5439f852e6c411ecdb4928a3f719a58302.0"
    
#     # 调用接口函数
#     security_docs_delete(base_dir1, base_dir2, delete_same=True)