from urllib.parse import urlparse
from pathlib import Path
import shutil
import subprocess
import json
import os
import socket
import sys
from common.errors import KareErrors
import threading
import dbus
import ctypes
import pty
import configparser


# TODO 日志流合并

class ShellUtils():

    """
    @brief dbus接口执行shell
    @param  command shell命令
    """   
    def run_command_realtime(self, command, log=None):
        """执行命令并实时捕获输出"""
        master_fd, slave_fd = pty.openpty()
        process = None
        try:
            process = subprocess.Popen(
                command,
                stdin=slave_fd,
                stdout=slave_fd,
                stderr=subprocess.STDOUT,
                shell=True,
                close_fds=True
            )
            os.close(slave_fd)  # 子进程已继承，关闭父进程中的从端

            # 配置日志函数
            log_info = print
            if log and hasattr(log, 'info'):
                log_info = log.info

            # 启动日志线程
            log_thread = threading.Thread(
                target=self._dbus_stream,
                args=(master_fd, log_info)
            )
            log_thread.start()

            # 等待进程和线程结束
            process.wait()
            log_thread.join()

        finally:
            # 确保进程终止（防止僵尸进程）
            if process and process.poll() is None:
                process.terminate()
                process.wait()
        return process.returncode if process else -1


    def run_command_line_realtime(self, command, dbus_obj=None, token=None, log=None):
        """
        使用伪终端执行命令，区分 stdout 和 stderr，
        支持通过 token 发送 D-Bus 信号。
        """
        master_fd, slave_fd = pty.openpty()
        process = None
        try:
            process = subprocess.Popen(
                command,
                stdin=slave_fd,
                stdout=slave_fd,
                stderr=slave_fd,
                shell=True,
                close_fds=True
            )
            os.close(slave_fd)

            # 更新 token 对应的 pid 字段
            if dbus_obj and hasattr(dbus_obj, '_running_processes') and token:
                dbus_obj._running_processes[token] = process.pid
                dbus_obj.log.info(f"[PID] 已更新 token={token} 的进程 PID: {process.pid}")

            # 缓存输出日志
            stdout_lines = []
            stderr_lines = []

            def handle_stream():
                try:
                    while True:
                        data = os.read(master_fd, 1024)
                        if not data:
                            break
                        lines = data.decode(errors='replace').splitlines()
                        for line in lines:
                            if not line.strip():
                                continue
                            # 判断是否是 error（可根据关键字匹配更精细）
                            if any(kw in line.lower() for kw in ['error', 'failed', 'exception']):
                                stderr_lines.append(line)
                                if dbus_obj and hasattr(dbus_obj, 'kareErrorLogUpdate'):
                                    dbus_obj.kareErrorLogUpdate(token, line)
                                if log:
                                    log.error(line)
                            else:
                                stdout_lines.append(line)
                                if dbus_obj and hasattr(dbus_obj, 'kareStandardLogUpdate'):
                                    dbus_obj.kareStandardLogUpdate(token, line)
                                if log:
                                    log.info(line)
                finally:
                    os.close(master_fd)

            # 启动线程读取输出
            log_thread = threading.Thread(target=handle_stream)
            log_thread.start()

            # 等待进程结束
            process.wait()
            log_thread.join()

            # 命令执行完成后发送 finished 信号
            if dbus_obj and hasattr(dbus_obj, 'kareLogFinished'):
                stdout = '\n'.join(stdout_lines)
                stderr = '\n'.join(stderr_lines)
                dbus_obj.kareLogFinished(token, stdout, stderr)

            return process.returncode

        except Exception as e:
            self.log.error(f"执行命令失败: {e}", exc_info=True)
            return -1

    def _dbus_stream(self, fd, logger_func):
        """从文件描述符读取数据并记录"""
        try:
            while True:
                data = os.read(fd, 1024)
                if not data:
                    break  # 子进程关闭时退出循环
                # 按行处理输出
                for line in data.decode(errors='replace').splitlines():
                    if line.strip():
                        logger_func(line.strip())
        except (OSError, ValueError) as e:
            # 文件描述符已关闭或无效时静默处理
            pass
        finally:
            # 确保关闭文件描述符（仅一次）
            if fd and fd >= 0:
                try:
                    os.close(fd)
                except OSError:
                    pass

    # def _log_stream(self, stream, logger_func):
    #     """从流中读取内容并通过指定 logger 函数记录"""
    #     for line in iter(stream.readline, ''):
    #         if line.strip():
    #             logger_func(line.strip())
    #     stream.close()

    # """
    # @brief 执行shell命令
    # @param  command shell命令
    # @return 执行结果
    # """   
    # def run_command_realtime(self, command, log=None):
    #     process = subprocess.Popen(command.split(" "),
    #                                stdout=subprocess.PIPE,
    #                                stderr=subprocess.PIPE,
    #                                bufsize=1,  # 行缓冲模式
    #                                universal_newlines=True  # 返回字符串而非字节
    #                                )
    #     stdout_thread = threading.Thread(
    #         target=self._log_stream,
    #         args=(process.stdout, log.info)  # stdout 用 INFO 级别
    #     )
    #     stderr_thread = threading.Thread(
    #         target=self._log_stream,
    #         args=(process.stderr, log.error)  # stderr 用 ERROR 级别
    #     )
    #     # 启动线程
    #     stdout_thread.start()
    #     stderr_thread.start()

    #     # 等待进程结束
    #     process.wait()

    #     # 等待线程结束
    #     stdout_thread.join()
    #     stderr_thread.join()

    #     return process.returncode

    """
    @brief 执行shell命令
    @param  command shell命令
    @return 执行结果
    """   
    def run_command(self, command):
        ret = subprocess.getoutput(command)
        return ret

    """
    @brief 执行删除文件夹逻辑
    @param  被删除目录
    """   
    def remove_dir(self, dir):
        if os.path.ismount(dir):
            return
        target = Path(dir)
        if len(target.parts) > 2:
            try:
                shutil.rmtree(dir)
            except Exception:
                self.run_command(f"rf -rf {dir}")
    """
    @brief 执行shell命令
    @param  command shell命令
    @return 执行结果, 仅返回状态码
    """   
    def run_command_status(self, command):
        ret = subprocess.getstatusoutput(command)
        return ret[0]
    """
    @brief 执行shell命令
    @param  command shell命令
    @return 执行结果, 仅返回状态码,执行输出
    """   
    def run_command_status_output(self, command):
        ret = subprocess.getstatusoutput(command)
        return ret
    """
    @brief 处理拷贝相关逻辑，需处理软连接等相关问题
    @return 
    """    
    def copy_file(self, file, target):
        dst_dir = os.path.dirname(target)
        os.makedirs(dst_dir, exist_ok=True)
        try:
            shutil.copy(file, target)
        except Exception as e:
            pass

    """
    @brief 处理虚包拷贝逻辑
    @return 
    """    
    def kare_vp_cp(self, file, chroot, icons=False, index=3, log=None):
        if not chroot :
            p = (file.parts[0], "opt", "kare-applications", "vpws", "vp") + file.parts[index:]
        else:
            p = (file.parts[0], "kare-applications", "vpws", "vp") + file.parts[index:]
        target = Path(*p)
        dst_dir = os.path.dirname(target)
        os.makedirs(dst_dir, exist_ok=True)
        try:
            self.copy_file(file, target)
            if icons: os.chmod(target, 0o644)
        except Exception as e:
            if log is None:
                return
            log.error(f"copy file {file} to {target} failed")


class ConfigManager():
    
    """
    @brief 修改json文件
    @param config 配置文件
    @param key 配置文件key值
    @param value 配置文件value值
    @return 
    """     
    def set_config_json(self, config, key, value=None):
        try:
            with open(config, 'r') as file:
                data = json.load(file)
        except json.decoder.JSONDecodeError:
            data = {}
        except FileNotFoundError:
            data = {}
        if value is not None:
            data[key] = value
        else:
            data.pop(key, None)
        with open(config, 'w') as file:
            json.dump(data, file, indent=4) 

    """
    @brief 读取json文件值
    @param config 配置文件
    @param key 配置文件key值
    @return 配置文件value值
    """     
    def get_config_json(self, config, key=None):
        try:
            with open(config, 'r') as file:
                data = json.load(file)
            if key is not None:
                return data.get(key, None)
            else:
                return data
        except json.decoder.JSONDecodeError:
            return None
        except FileNotFoundError:
            return None

    """
    @brief 读取json文件值key值
    @param config 配置文件
    @return 配置文件所有key值
    """     
    def get_config_keys(self, config):
        try:
            with open(config, 'r') as file:
                data = json.load(file)
            return data.keys()
        except json.decoder.JSONDecodeError:
            return None
        except FileNotFoundError:
            return None


class Overlay(ShellUtils):

    """
    @brief 创建overlay路径
    @param  base_dirs 基础环境路径
    @param  package_path 目标路径
    @return 
    """   
    def create_overlay_path(self, base_dirs, package_path):
        for d in base_dirs:
            path = os.path.basename(d)
            os.makedirs(package_path + "/lower/" + path, exist_ok=True)
            os.makedirs(package_path + "/upper/" + path, exist_ok=True)
            os.makedirs(package_path + "/work/" + path, exist_ok=True)
            os.makedirs(package_path + "/merge/" + path, exist_ok=True)

    """
    @brief 挂载overlay路径
    @param  base_dirs 基础环境路径
    @param  package_path  目标路径
    @return 
    """   
    def mount_overlay(self, base_dirs, package_path):
        for d in base_dirs:
            lower_dirs = d
            if d == "/var":
                lower_dirs = self.ostree_var_deploy()
            upper_dir = package_path + "/upper/" + os.path.basename(d)
            work_dir = package_path + "/work/" + os.path.basename(d)
            mount_point = package_path + "/merge/" + os.path.basename(d)
            if os.path.ismount(mount_point):
                continue
            mount_cmd = [
                "mount", "-t", "overlay", "overlay",
                "-o", f"lowerdir={lower_dirs},upperdir={upper_dir},workdir={work_dir}",
                mount_point
            ]
            self.run_command(" ".join(mount_cmd))

    """
    @brief ostree var目录挂载逻辑
    @return lower_dirs var目录 
    """   
    def ostree_var_deploy(self):
        lower_var = "/var"
        if not os.path.exists("/sysroot/ostree/pkgs/system-ovl.conf"):
            return lower_var
        conf = configparser.ConfigParser()
        conf.read("/sysroot/ostree/pkgs/system-ovl.conf")
        if "General" not in conf:
            return lower_var
        deploy = conf["General"].get("Deploy")
        lower_var = f"/ostree/pkgs/ovl-{deploy}/var-ovl/var-tmpupper:/ostree/pkgs/ovl-{deploy}/var-ovl/var-upper:/ostree/pkgs/ovl-{deploy}/var-ovl/var-lower:{lower_var}"
        return lower_var


class HostMessage(ShellUtils, ConfigManager):
    ARCH={
        "x86_64": "amd64",
        "aarch64": "arm64",
        "mips64": "mips64el",
        "loongarch64": "loongarch64",
        "sw_64": "sw64"
    }

    """
    @brief 获取宿主机系统版本
    @return 宿主机系统
    """
    @property
    def get_host_release(self):
        return 

    """
    @brief 处理文件编辑相关逻辑
    @return 
    """   
    def edit_file(self, file, key, value):
        pass
  
    """
    @brief 获取当前生效容器类型
    @return 容器类型
    """   
    @property
    def container_type(self)-> str:
        container = self.get_config_json("/etc/kare/kare.json", "container")
        if container:
            return container
        return "bwrap"

    """
    @brief 是否为不可变系统
    @return 
    """
    @property      
    def is_ostree(self)-> bool:
        return os.path.exists("/sysroot")
    
    def is_shadow(self, release)-> bool:
        return True if release == "shadow" else False

    @property      
    def get_user_by_login1(self):
        try:
            # 获取系统总线
            bus = dbus.SystemBus()
            
            # 获取 login1 管理接口
            login1_proxy = bus.get_object(
                "org.freedesktop.login1",
                "/org/freedesktop/login1"
            )
            manager_iface = dbus.Interface(
                login1_proxy,
                "org.freedesktop.login1.Manager"
            )
            
            # 调用 ListUsers 方法
            users = manager_iface.ListUsers()
            
            # 遍历用户
            for user in users:
                # user 结构为 (uid, username, object_path)
                user_path = user[2]
                user_proxy = bus.get_object("org.freedesktop.login1", user_path)
                
                # 获取用户属性接口
                user_iface = dbus.Interface(
                    user_proxy,
                    dbus_interface=dbus.PROPERTIES_IFACE
                )
                
                # 读取 State 属性
                state = user_iface.Get(
                    "org.freedesktop.login1.User",
                    "State"
                )
                
                if state == "active":
                    return (user[0], user[1])  # uid, 返回用户名
            
            return (None, None)
        except dbus.exceptions.DBusException as e:
            return (None, None)
        except Exception as e:
            return (None, None)
    # """
    # @brief 是否为驱动包
    # @return 
    # """      
    # def is_driver(self)-> bool:
    #     return False

    """
    @brief 处理运行时参数
    """
    def edit_exec():
        pass

    """
    @brief 获取当前系统架构
    @return 系统架构
    """
    def get_build_arch(self) -> str:
        machine = self.run_command("uname -m")
        return self.ARCH.get(machine, None)
    
    """
    @brief 判断当前系统存储空间是否足够
    @return True 足够 False 不够
    """    
    def avaliable_space(self, path) -> bool:
        usage = shutil.disk_usage(path)
        free = int(usage.free / (1024 ** 3))
        return True if free > 10 else False
        
    """
    @brief 基础环境是否存在
    @return 基础环境存储目录
    """   
    def get_release_path(self, env) -> str:
        release_path = self.get_config_json("/opt/kare-applications/config/release.json", env)
        return release_path

    """
    @brief 源地址
    @return 网络是否可用
    """   
    def check_website_port_connectivity(self, url) -> bool:
        try:
            parsed_url = urlparse(url)
            hostname = parsed_url.hostname
            port = parsed_url.port
            
            if not port:
                port = 80 if parsed_url.scheme == "http" else 443

            ip = socket.gethostbyname(hostname)
            
            with socket.create_connection((ip, port), timeout=5) as sock:
                request = f"HEAD {parsed_url.path} HTTP/1.1\r\nHost: {hostname}\r\n\r\n"
                sock.sendall(request.encode())
                response = sock.recv(1024).decode()
                status_line = response.split("\r\n")[0]
                status_code = int(status_line.split(" ")[1])
                if 200 <= status_code < 500:
                    print(f"源 {url} 响应正常（状态码 {status_code}）")
                    return True
                else:
                    print(f"源 {url} 返回非预期状态码 {status_code}")
                    return False

        except socket.timeout:
            print(f"[超时] 连接源 {url} 超时")
            return False
        except socket.error as e:
            print(f"[错误] 无法连接源 {url}: {e}")
            return False
        except (ValueError, IndexError, KeyError) as e:
            print(f"[错误] 解析响应失败: {e}")
            return False
    

    """
    调用 ksaf kare应用标记设置接口
    @app_path 应用安装路径
    @app_files 宿主机文件列表
    @app_name 包名称
    """
    def ksaf_set_label_for_kare(self, app_files, app_name):
        KSAF_LABELMGR_PATH = "/usr/lib/libksaflabelmgr.so"
        ksaf_labelmgr_lib = ctypes.CDLL(KSAF_LABELMGR_PATH)
        set_label_for_kare = ksaf_labelmgr_lib.setLabelForKare
        set_label_for_kare.argtypes = [
            ctypes.POINTER(ctypes.c_char_p),
            ctypes.c_char_p
        ]
        set_label_for_kare.restype =  ctypes.c_int
        app_name_bytes = app_name.encode('utf-8')
        app_files_bytes = (ctypes.c_char_p * (len(app_files)+1))(*[f.encode('utf-8') for f in app_files])
        result = set_label_for_kare(app_files_bytes, app_name_bytes)
        return result

    """
    调用 ksaf kare基础环境安装时脚本解释器标记设置接口
    @path 安装的基础环境路径
    """
    def ksaf_set_label_for_kare_interpreter(self, path):
        KSAF_LABELMGR_PATH = "/usr/lib/libksaflabelmgr.so"
        ksaf_labelmgr_lib = ctypes.CDLL(KSAF_LABELMGR_PATH)
        set_label_for_kare_interpreter = ksaf_labelmgr_lib.setLabelForKareInterpreter
        set_label_for_kare_interpreter.argtypes = [ctypes.c_char_p]
        set_label_for_kare_interpreter.restype =  ctypes.c_int
        path_bytes = path.encode('utf-8')
        result = set_label_for_kare_interpreter(path_bytes)
        return result

    """
    调用 ksaf 执行控制接口
    @app_path 包安装路径
    @paths 文件路径数组
    @count 数组中文件数量
    """
    def ksaf_exectl_xattr_multi_set(self, app_paths, count):
        KSAF_EXECTL_PATH = "/usr/lib/libkysec2_exectl_wlist.so.0.0.0"
        ksaf_exectl_lib = ctypes.CDLL(KSAF_EXECTL_PATH)
        exectl_wlist_multi_add = ksaf_exectl_lib.exectl_wlist_multi_add
        exectl_wlist_multi_add.argtypes = [ctypes.POINTER(ctypes.c_char_p), ctypes.c_size_t]
        exectl_wlist_multi_add.restype =  ctypes.c_int
        app_paths_bytes = (ctypes.c_char_p * (count+1))(*[f.encode('utf-8') for f in app_paths])
        result = exectl_wlist_multi_add(app_paths_bytes, count)
        return result

class PathManager(ConfigManager):

    INSTALL_JSON="/opt/kare-applications/config/install.json"
    RELEASE_JSON="/opt/kare-applications/config/release.json"
    # KAIMING_BASE_PATH=f"/opt/kaiming/layers/stable/{}/base/cn.kylin.v10sp1-base/binary/1.0.4/files"
    WHITELIST_FILE = "/etc/kare/whitelist/white"
    WHITELIST_RUN_FILE = "/etc/kare/whitelist/run"
    WHITELIST_DESKTOP_FILE = "/etc/kare/whitelist/desktops"
    KARE_JSON="/etc/kare/kare.json"
    PACKAGE_SAVE_PATH="/opt/kare-applications/"
    VP_DEBIAN_PATH="/opt/kare-applications/vpws/vp/DEBIAN"
    CONTAINER_PATH="/usr/share/kare/container-scripts"
    PLUGIN_PATH="/usr/share/kare/plugins"
    LIB_MODULES="/lib/modules"
    OPT_SYSTEM_LIB_MODULES="/opt/system/lib/modules"
    VAR_LIB_DKMS="/var/lib/dkms"
    ETC_DKMS_CONFIG = "/etc/dkms/dkms-kare.json"
    UPGRADE_CONFIG_PATH="/var/cache/kylin-revision-manager/package-desktop.info"
    UPGRADE_COMPAT_DESKTOP_PATH="/var/cache/kylin-revision-manager/compat-desktops"
    DPKG_INFO="/var/lib/dpkg/info/"
    MIMETYPES_PATH="/usr/share/icons/hicolor/scalable/mimetypes"
    SCALABLE_APPS_PATH="/usr/share/icons/hicolor/scalable/apps"
    ICON_HICOLOR_PATH="/usr/share/icons/hicolor"
    APPLICATIONS_PATH="/usr/share/applications"
    PIXMAPS_PATH="/usr/share/pixmaps"
    EXEC_PATH="/usr/bin"
    AUTOSTART_PATH="/etc/xdg/autostart"

    """
    @brief 获取文件保存路径
    @return 存储路径
    """
    def get_save_path(self, base) -> str:
        path = self.get_config_json(self.KARE_JSON, "save-path")
        if path:
            return path + base 
        return "/var/" + base
    
    """
    @brief 获取解压缩后exec文件存储路径
    @return 存储路径
    """
    def get_exec_path(self, package_name, exec_path) -> str:
        return "/tmp/" + package_name + exec_path


class RequireRoot:
    """
    类装饰器：确保只有 root 能实例化被装饰的类
    """
    def __init__(self, cls):
        self.cls = cls
        self._patch_new_method()

    def _patch_new_method(self):
        original_new = self.cls.__new__

        def wrapped_new(cls_, *args, **kwargs):
            if os.getuid() != 0:
                print("错误：需要 root 权限")
                sys.exit(1)
            if original_new is object.__new__:
                return super(self.cls, cls_).__new__(cls_)
            else:
                return original_new(cls_, *args, **kwargs)

        self.cls.__new__ = wrapped_new

    def __call__(self, *args, **kwargs):
        return self.cls(*args, **kwargs)



class GlobalSingleton():    
    """
    文件锁类，用于实现单例模式
    """
    def __init__(self):
        lock_path = Path(self.LOCK_FILE)
        lock_path.parent.mkdir(parents=True, exist_ok=True)
        
        if os.path.exists(self.LOCK_FILE):
            raise RuntimeError(KareErrors().SINGLE_ERROR)
        self.flag = True
        lock_file = open(self.LOCK_FILE, 'w')
        lock_file.close()

    def __del__(self):
        if self.flag:
            os.remove(self.LOCK_FILE)


