# utils.py
# /*
#  * Copyright (C) 2020, KylinSoft Co., Ltd.
#  *
#  * This program is free software: you can redistribute it and/or modify
#  * it under the terms of the GNU General Public License as published by
#  * the Free Software Foundation, either version 3 of the License, or
#  * (at your option) any later version.
#  *
#  * This program is distributed in the hope that it will be useful,
#  * but WITHOUT ANY WARRANTY; without even the implied warranty of
#  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#  * GNU General Public License for more details.
#  *
#  * You should have received a copy of the GNU General Public License
#  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
#  */

import os
import re
import apt_pkg
import subprocess
import logging
import apt_pkg
from .config import CURRENT_PROGRESS
from typing import List, Dict, Literal

# 从dpkg list中统一移除那些文件特殊的标识
REMOVE_FILES_LIST = ["/."]
DPKG_INFO_DIR = "/var/lib/dpkg/info"

def not_upgrade_pkgs_list(
    packages_info: Dict[str, dict],
    compare_op: Literal['>', '>='] = '>='
) -> List[str]:
    """
    根据版本号对比，检查哪些软件包不需要升级。

    :param packages_info: 软件包信息字典，格式为 {包名: {'current_version': 当前版本, 'new_version': 新版本}}
    :param compare_op: 版本比较运算符，'>' 表示严格大于，'>=' 表示大于等于
    :return: 不需要升级的软件包名称列表
    """
    not_upgrade = []

    for pkg, versions in packages_info.items():
        cur = versions.get('current_version', '')
        new = versions.get('new_version', '')

        # Skip invalid versions
        if not cur or not new:
            continue

        # Determine whether package should be skipped
        if (compare_op == '>' and apt_pkg.version_compare(cur, new) > 0) or \
           (compare_op == '>=' and apt_pkg.version_compare(cur, new) >= 0):
            not_upgrade.append(pkg)
            logging.info(
                f"Package '{pkg}' is skipped: current version {cur} vs available version {new} "
                f"(comparison {compare_op})"
            )

    return not_upgrade

def resolve_package_list_files(root_path, package_name: str) -> List[str]:
    list_dir_path  = os.path.join(root_path, DPKG_INFO_DIR.lstrip('/'))

    if not os.path.isdir(list_dir_path):
        logging.warning(f"Directory does not exist: {list_dir_path }")
        return []
    
    list_actual_path = None
    for entry in os.listdir(list_dir_path):
        if entry.startswith(package_name) and entry.endswith(".list"):
            full_entry_path = os.path.join(list_dir_path, entry)
            if os.path.isfile(full_entry_path):
                list_actual_path = full_entry_path
                break

    if not list_actual_path or not os.path.exists(list_actual_path):
        logging.warning(f"List file for package '{package_name}' not found in {list_dir_path}")
        return []
    
    with open(list_actual_path, 'r') as f:
        paths = [line.strip() for line in f if line.strip()]
    all_paths = set(paths)
    dir_paths = set()

    for path in all_paths:
        for other in all_paths:
            if other != path and other.startswith(path.rstrip('/') + '/'):
                dir_paths.add(path)
                break
    file_paths = all_paths - dir_paths

    for file in REMOVE_FILES_LIST:
        file_paths.remove(file)

    return sorted(file_paths)


def parse_apt_simulate_versions(output: str) -> Dict[str, Dict[str, str]]:
    """
    解析apt模拟安装的输出，提取包名与版本信息。
    
    支持：
    - 升级包：Inst pkg [old_ver] (new_ver)
    - 新安装包：Inst pkg (new_ver)
    - 最新版包：pkg is already the newest version (ver).
    
    返回格式：
    { pkg: {"current_version": old_ver_or_empty, "new_version": new_ver} }
    """
    def _extract_version(full_version: str) -> str:
        """提取版本号，去除后缀的架构或来源信息。"""
        return full_version.split()[0]

    upgrade_re = re.compile(r'^Inst (\S+) \[([^\]]+)\] \(([^)]+)\)')
    install_re = re.compile(r'^Inst (\S+) \(([^)]+)\)')
    newest_re = re.compile(r'^(\S+) is already the newest version \(([^)]+)\)\.')

    result = {}

    for line in output.splitlines():
        line = line.strip()

        m = upgrade_re.match(line)
        if m:
            try:
                pkg, current_ver, new_full = m.groups()
                new_ver = _extract_version(new_full)
                result[pkg] = {
                    "current_version": current_ver,
                    "new_version": new_ver,
                }
            except Exception as e:
                logging.warning(f"Failed parsing upgrade line: {line!r} error: {e}")
            continue

        m = install_re.match(line)
        if m:
            try:
                pkg, new_full = m.groups()
                if pkg not in result:
                    new_ver = _extract_version(new_full)
                    result[pkg] = {
                        "current_version": "",
                        "new_version": new_ver,
                    }
            except Exception as e:
                logging.warning(f"Failed parsing install line: {line!r} error: {e}")
            continue

        m = newest_re.match(line)
        if m:
            try:
                pkg, ver = m.groups()
                if pkg not in result:
                    result[pkg] = {
                        "current_version": ver,
                        "new_version": ver,
                    }
            except Exception as e:
                logging.warning(f"Failed parsing newest line: {line!r} error: {e}")
            continue

    return result

def write_apt_conf(config_path: str, apt_root: str, arch: str) -> None:
    """
    写入 APT 配置文件（apt.conf）内容

    :param config_path: apt.conf 文件的完整路径
    :param apt_root: APT 根目录（通常为 chroot 环境目录）
    :param arch: 架构名，如 'arm64' 或 'amd64'
    """
    content = f"""// 必须的配置 增加gpg校验
Dir::Etc::trusted "/etc/apt/trusted.gpg";
Dir::Etc::trustedparts "/etc/apt/trusted.gpg.d";

// 依赖计算关闭推荐和建议依赖
APT::Install-Recommends "false";
APT::Install-Suggests "false";

// 配置使用的软件源
Dir::Etc::sourceparts "";
Dir::Etc::SourceList "{apt_root}/etc/apt/sources.list";

// 配置设备架构
APT::Architecture "{arch}";

// APT 的缓存目录
Dir "{apt_root}/";
Dir::State "var/lib/apt";
Dir::State::lists "lists/";
Dir::State::extended_states "extended_states";

// 语言获取配置
Acquire::Languages "en";
Acquire::Languages:: "en_US";
Acquire::Languages:: "en";

// dpkg 状态文件位置
Dir::State::status "/var/lib/dpkg/status";
"""

    try:
        os.makedirs(os.path.dirname(config_path), exist_ok=True)
        with open(config_path, "w", encoding="utf-8") as f:
            f.write(content)
    except Exception as e:
        logging.error(f"Failed to write APT configuration to {config_path}: {e}")


def convert_status_to_apt_index_format(
    file_path: str,
    insert_lines: List[str],
    output_path: str = None
) -> None:
    """
    将status文件转为 apt 包索引格式

    :param file_path: 原始 status 文件路径
    :param insert_lines: 要插入的字段（每行一个字符串）
    :param output_path: 输出路径；为 None 时就地修改原文件
    """
    if not os.path.exists(file_path):
        logging.error(f"File not found: {file_path}")
        return

    logging.info(f"Converting status file to apt index format: {file_path}")
    try:
        with open(file_path, "r", encoding="utf-8") as f:
            lines = f.readlines()

        modified_lines = []
        for line in lines:
            modified_lines.append(line)
            if line.startswith("Package:"):
                modified_lines.extend([entry + "\n" for entry in insert_lines])

        target_path = output_path if output_path else file_path
        with open(target_path, "w", encoding="utf-8") as f:
            f.writelines(modified_lines)
    except Exception as e:
        logging.error(f"Error processing {file_path}: {e}")

def generate_simulate_depends_env(root_path: str, apt_conf: str) -> None:
    """
    初始化 APT 所需的基础目录结构，并写入 sources.list 和 status 修改信息。
    :param root_path: APT 根路径（用于 chroot 环境或模拟环境）
    """
    dirs_to_create = [
        "etc/apt",
        "var/lib/apt/lists",
        "var/lib/apt/lists/auxfiles",
        "var/lib/apt/lists/partial",
        "var/lib/dpkg",
        "var/cache/apt/archives/partial",
        "etc/apt/preferences.d",
    ]

    arch = apt_pkg.config.get('APT::Architecture', 'arm64')
    apt_conf2 = os.path.join(root_path, "etc/apt/apt-next.conf")

    logging.info(f"Generating APT environment at: {root_path} with architecture: {arch}")
    for rel_path in dirs_to_create:
        full_path = os.path.join(root_path, rel_path)
        try:
            os.makedirs(full_path, exist_ok=True)
        except Exception as e:
            logging.error(f"Failed to create directory {full_path}: {e}")

    # 写入 sources.list 内容
    sources_list_path = os.path.join(root_path, "etc/apt/sources.list")
    sources_list_content = (
        "deb http://archive.kylinos.cn/kylin/KYLIN-ALL "
        ".tags--10.1-2107-bugfix-limit--security-250714 main\n"
    )

    try:
        with open(sources_list_path, "w", encoding="utf-8") as f:
            f.write(sources_list_content)
    except Exception as e:
        logging.error(f"Failed to write sources.list: {e}")

    # 插入字段到 status 文件
    insert_lines = [
        f"Architecture: {arch}",
        "Filename: pool/arm64.deb",
        "Size: 5730",
    ]

    status_path = os.path.join(root_path, "var/lib/dpkg/status")
    packages_index_path = os.path.join(
        root_path,
        "var/lib/apt/lists/"
        "archive.kylinos.cn_kylin_KYLIN-ALL_dists_"
        ".tags--10.1-2107-bugfix-limit--security-250714_"
        f"main_binary-{arch}_Packages"
    )

    convert_status_to_apt_index_format(status_path, insert_lines, packages_index_path)

    logging.info(f"APT configuration written to: {apt_conf}")
    write_apt_conf(apt_conf, root_path, arch)
    write_apt_conf(apt_conf2, CURRENT_PROGRESS, arch)

def get_versions_from_status(packages: List[str], status_file: str) -> Dict[str, Dict[str, str]]:
    """
    从 dpkg 状态文件中获取指定包的旧版本与新版本信息。
    优化点：
      - 避免重复解析逻辑
      - 支持健壮错误处理
      - 提升可读性与可维护性
    """
    versions = {pkg: {"current_version": "", "new_version": ""} for pkg in packages}
    system_status = "/var/lib/dpkg/status"

    def parse_status_file(file_path: str) -> Dict[str, str]:
        """解析单个 status 文件，返回 {包名: 版本号}"""
        result = {}
        if not os.path.exists(file_path):
            logging.warning("Status file does not exist: %s", file_path)
            return result

        try:
            with open(file_path, "r", encoding="utf-8") as f:
                block = []
                for line in f:
                    if line.strip() == "":
                        pkg_name = version = None
                        for entry in block:
                            if entry.startswith("Package: "):
                                pkg_name = entry.split(":", 1)[1].strip()
                            elif entry.startswith("Version: "):
                                version = entry.split(":", 1)[1].strip()
                        if pkg_name and version:
                            result[pkg_name] = version
                        block = []
                    else:
                        block.append(line.strip())
        except Exception as e:
            logging.error(f"Error reading status file {file_path}: {e}")

        return result

    # 分别解析系统状态与目标状态
    current_versions = parse_status_file(system_status)
    new_versions = parse_status_file(status_file)

    # 合并版本信息
    for pkg in packages:
        if pkg in current_versions:
            versions[pkg]["current_version"] = current_versions[pkg]
        if pkg in new_versions:
            versions[pkg]["new_version"] = new_versions[pkg]

    # 打印未找到的包
    not_found = [pkg for pkg, info in versions.items() if not any(info.values())]
    if not_found:
        logging.info("Packages not found in either status file: %s", ", ".join(not_found))

    return versions

def simulate_apt_install_and_parse_versions(version_root: str, packages: List[str]) -> Dict[str, dict]:
    """
    通过 apt 模拟安装，获取指定软件包及其依赖的版本信息。

    Args:
        packages: 需要模拟安装的包名列表。
        apt_conf: apt 配置文件路径。

    Returns:
        解析后的包版本信息字典，结构为 {包名: {"current_version": ..., "new_version": ...}}。
        如果出现任何错误，返回空字典。
    """
    
    apt_conf = os.path.join(version_root, "etc/apt/apt.conf")
    if not packages:
        logging.warning("Package list is empty, returning empty result.")
        return {}

    env = os.environ.copy()
    env["LANG"] = "C"
    env["LANGUAGE"] = "C"
    env["LC_ALL"] = "C"
    cmd = ["apt", "-c", apt_conf, "install", "--simulate"] + packages

    try:
        generate_simulate_depends_env(version_root, apt_conf)
        result = subprocess.run(cmd, capture_output=True, text=True, env=env, check=True, timeout=10)
        packages_info = parse_apt_simulate_versions(result.stdout)
        return packages_info
    except subprocess.TimeoutExpired:
        logging.error(f"APT simulate command timed out after 10 seconds: {' '.join(cmd)}")
    except subprocess.CalledProcessError as e:
        logging.error(f"APT simulate failed with error: {e.stderr.strip()}")
    except Exception as e:
        logging.error(f"Unexpected error during APT simulate: {e}")

    packages_info = get_versions_from_status(packages, os.path.join(version_root, "var/lib/dpkg/status"))

    return packages_info