import sys
import time
import shlex
import threading
import subprocess
from collections import deque
from typing import Optional, Union, List, Dict, Any, Callable


class ShellResult:
    """封装命令执行结果的类，支持布尔判断、输出获取、错误获取和返回码获取"""
    def __init__(self, 
            returncode: int, 
            stdout: str, 
            stderr: str, 
            metadata: Optional[Dict[str, Any]] = None):
        self._returncode = returncode
        self._stdout = stdout
        self._stderr = stderr
        self._metadata = metadata or {}  # 存储额外信息，如包名、版本等

    def __bool__(self):
        """支持 if result: 直接判断命令是否成功"""
        return self._returncode == 0

    def output(self) -> str:
        """获取命令的标准输出"""
        return self._stdout

    def error(self) -> str:
        """获取命令的标准错误"""
        return self._stderr

    def code(self) -> int:
        """获取命令的返回码"""
        return self._returncode

    def output_lines(self) -> List[str]:
        """返回输出行列表，过滤空行"""
        return [line for line in self._stdout.splitlines() if line]

    def error_lines(self) -> List[str]:
        return [line for line in self._stderr.splitlines() if line]
    
    def get_metadata(self, key: str, default=None):
        """获取元数据"""
        return self._metadata.get(key, default)
    
    def set_metadata(self, key: str, value: Any):
        """设置元数据"""
        self._metadata[key] = value


class ShellExecutor:
    """
    Shell命令执行器基类,提供通用的命令执行功能
    子类可重写方法以实现特定命令的定制行为
    """
    
    def __init__(self, 
            show_animation: bool = True, 
            animation_chars: str = '|/-\\',
            show_last_line: bool = True,
            max_display_lines: int = 1
            ):
        """
        初始化执行器
        
        参数:
            show_animation      : 是否显示动态动画
            animation_chars     : 动画字符集
            show_last_line      : 是否显示最后一行
            max_display_lines   : 最大显示行数，超过则省略号显示
        """
        self.show_animation     = show_animation
        self.animation_chars    = animation_chars
        self.show_last_line     = show_last_line
        self.max_display_lines  = max_display_lines
        self._line_count        = 0
        self._last_lines        = deque(maxlen=max_display_lines)
        
    def pre_execute_hook(self, cmd: Union[str, List[str]]) -> Union[str, List[str]]:
        """
        执行前的钩子函数，可以修改命令或做预处理
        子类可重写此方法
        """
        return cmd
    
    def post_execute_hook(self, result: ShellResult, cmd: Union[str, List[str]]) -> ShellResult:
        """
        执行后的钩子函数，可以处理结果、提取信息等
        子类可重写此方法
        """
        return result
    
    def get_display_message(self, cmd: Union[str, List[str]]) -> str:
        """
        获取执行时显示的消息
        子类可重写以显示不同的提示信息
        """
        return "Running..."
    
    def get_completion_message(self, cmd: Union[str, List[str]], result: ShellResult) -> str:
        """
        获取完成时显示的消息
        子类可重写以显示不同的完成信息
        """
        if self._line_count > 0:
            return f"Completed! (lines: {self._line_count})"
        else:
            return "Completed!"

    def get_realtime_info(self, line: str) -> str:
        """
        获取实时显示的信息（可被子类重写以显示不同的信息格式）
        
        参数:
            line: 最新读取的一行输出
            
        返回:
            要显示的附加信息字符串
        """
        # 默认显示最后一行内容（截断过长内容）
        if self.show_last_line and line:
            # 限制行长度，避免显示过长
            max_length = 80
            if len(line) > max_length:
                line = line[:max_length-3] + "..."
            return f" [{line}]"
        return ""
    
    def update_realtime_output(self, line: str):
        """
        更新实时输出信息（可被子类重写以处理特殊输出）
        
        参数:
            line: 最新读取的一行输出
        """
        self._last_lines.append(line.rstrip('\n\r'))

    def parse_output_for_metadata(self, 
            result: ShellResult, 
            cmd: Union[str, List[str]]) -> Dict[str, Any]:
        """
        解析输出提取元数据
        子类可重写此方法
        """
        return {}
    
    def _RRread_output_thread(self, process):
        """读取输出的线程函数"""
        for line in iter(process.stdout.readline, ''):
            if line:
                self._line_count += 1
        process.stdout.close()
    
    def _read_output_thread(self, process, is_stderr=False):
        """读取输出的线程函数"""
        if is_stderr:
            # 错误输出只读取，不显示在动画中
            for line in iter(process.stderr.readline, ''):
                if line:
                    pass  # 可以在这里处理stderr
            process.stderr.close()
        else:
            # 标准输出需要计数和显示
            for line in iter(process.stdout.readline, ''):
                if line:
                    self._line_count += 1
                    # 更新实时输出
                    self.update_realtime_output(line)
                    # 可以添加一个回调，让子类实时处理每一行
                    self.on_output_line(line)
            process.stdout.close()

    def _display_animation(self, process):
        """显示动画的函数"""
        if not self.show_animation:
            return
            
        idx = 0
        while process.poll() is None:
            spin_char = self.animation_chars[idx % len(self.animation_chars)]
            if self._line_count > 0:
                display = f"\r{spin_char} {self.get_display_message(process.args)} (lines: {self._line_count})"
            else:
                display = f"\r{spin_char} {self.get_display_message(process.args)}"
            sys.stdout.write(display)
            sys.stdout.flush()
            idx += 1
            time.sleep(0.1)
        
        # 清除当前行并显示完成信息
        completion_msg = self.get_completion_message(process.args, ShellResult(0, "", ""))
        sys.stdout.write(f"\r✓ {completion_msg}            \n")
        sys.stdout.flush()

    def on_output_line(self, line: str):
        """
        当有新的输出行时的回调函数（可被子类重写）
        
        参数:
            line: 新输出的行
        """
        pass

    def execute(self, 
            cmd: Union[str, List[str]], 
            t_cmd: Optional[Union[str, List[str]]] = None, 
            quiet: bool = False,
            timeout: Optional[int] = None,
            show_animation: bool = True,
            show_real_time: bool = True,
        ) -> ShellResult:
        """
        执行shell命令
        
        参数:
            cmd             : 要执行的字符串或列表
            t_cmd           : 可选，执行完成后的测试命令
            timeout         : 超时时间（秒）
            show_real_time  : 是否显示实时输出(仅当show_animation为True时有效)

        返回:
            ShellResult 实例
        """
        # 执行预处理钩子
        processed_cmd = self.pre_execute_hook(cmd)
        
        # 转换命令为列表形式
        if isinstance(processed_cmd, str):
            cmd_list = shlex.split(processed_cmd)
        else:
            cmd_list = processed_cmd
        
        # 重置计数器
        self._line_count = 0
        self._last_lines.clear()
        
        # 启动子进程
        process = subprocess.Popen(
            cmd_list,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            bufsize=1
        )

        # 用于存储输出
        stdout_lines = []
        stderr_lines = []

        # 创建读取线程
        def collect_stdout():
            for line in iter(process.stdout.readline, ''):
                if line:
                    stdout_lines.append(line)
                    self._line_count += 1
                    self.update_realtime_output(line)
                    self.on_output_line(line)
            process.stdout.close()
        
        def collect_stderr():
            for line in iter(process.stderr.readline, ''):
                if line:
                    stderr_lines.append(line)
            process.stderr.close()
        
        stdout_thread = threading.Thread(target=collect_stdout)
        stderr_thread = threading.Thread(target=collect_stderr)
        stdout_thread.daemon = True
        stderr_thread.daemon = True
        
        stdout_thread.start()
        stderr_thread.start()

        # 显示动画
        if self.show_animation:
            idx = 0
            last_display = ""
            while process.poll() is None:
                spin_char = self.animation_chars[idx % len(self.animation_chars)]
                
                # 构建显示内容
                if self._line_count > 0:
                    base_display = f"{spin_char} {self.get_display_message(cmd_list)} (lines: {self._line_count})"
                    # 添加实时输出信息
                    if show_real_time and self._last_lines:
                        # 显示最后一行内容
                        last_line = list(self._last_lines)[-1]
                        realtime_info = self.get_realtime_info(last_line)
                        display = f"\r{base_display}{realtime_info}"
                    else:
                        display = f"\r{base_display}"
                else:
                    display = f"\r{spin_char} {self.get_display_message(cmd_list)}"
                
                # 只在内容变化时更新，减少闪烁
                if display != last_display or self._line_count != last_line_count:
                    sys.stdout.write(f'\r{display}\033[K')
                    sys.stdout.flush()
                    last_display = display
                    last_line_count = self._line_count
                
                idx += 1
                time.sleep(0.1)
            
            # 等待线程结束
            stdout_thread.join(timeout=1)
            stderr_thread.join(timeout=1)

            # 清除当前行
            self._clear_line()
            
            # 显示完成信息
            if not quiet:
                completion_msg = self.get_completion_message(cmd_list, ShellResult(0, "", ""))
                # 清除当前行并显示完成信息
                sys.stdout.write(f"\r✓ {completion_msg}\n")
                sys.stdout.flush()
        else:
            # 不显示动画时，直接等待进程结束
            process.wait()
            stdout_thread.join()
            stderr_thread.join()
        
        # 合并输出
        stdout = ''.join(stdout_lines)
        stderr = ''.join(stderr_lines)

        # 如果有测试命令，执行测试命令
        if t_cmd is not None:
            if isinstance(t_cmd, str):
                test_cmd_list = shlex.split(t_cmd)
            else:
                test_cmd_list = t_cmd
            test_process = subprocess.run(
                test_cmd_list,
                input=stdout,
                capture_output=True,
                text=True
            )
            final_returncode = 0 if test_process.returncode == 0 else 1
            final_stderr = stderr + test_process.stderr
        else:
            final_returncode = process.returncode
            final_stderr = stderr
        
        # 创建结果对象
        result = ShellResult(final_returncode, stdout, final_stderr)
        
        # 解析元数据（如果子类需要）
        metadata = self.parse_output_for_metadata(result, processed_cmd)
        for key, value in metadata.items():
            result.set_metadata(key, value)
        
        # 执行后置钩子
        result = self.post_execute_hook(result, processed_cmd)
        
        return result

    def _clear_line(self):
        """彻底清除当前行"""
        # 先移动到行首，然后清除到行尾
        sys.stdout.write('\r\033[K')  # \033[K 清除从光标到行尾的内容
        sys.stdout.flush()

class DnfExecutor(ShellExecutor):
    """针对dnf命令的专用执行器"""
    
    def get_display_message(self, cmd: Union[str, List[str]]) -> str:
        """显示dnf特定消息"""
        cmd_str = ' '.join(cmd) if isinstance(cmd, list) else cmd
        if 'install' in cmd_str:
            return "Installing packages..."
        elif 'remove' in cmd_str or 'erase' in cmd_str:
            return "Removing packages..."
        elif 'update' in cmd_str:
            return "Updating packages..."
        elif 'search' in cmd_str:
            return "Searching packages..."
        else:
            return "DNF Running..."
    
    def parse_output_for_metadata(self, result: ShellResult, cmd: Union[str, List[str]]) -> Dict[str, Any]:
        """从dnf输出中提取包信息"""
        metadata = {}
        cmd_str = ' '.join(cmd) if isinstance(cmd, list) else cmd
        
        # 提取安装的包名
        if 'install' in cmd_str or 'remove' in cmd_str:
            packages = []
            for line in result.output_lines():
                if 'Installing:' in line or 'Removing:' in line:
                    # 提取包名
                    parts = line.split()
                    for part in parts:
                        if '.x86_64' in part or '.noarch' in part:
                            packages.append(part)
            if packages:
                metadata['packages'] = packages
                metadata['package_count'] = len(packages)
        
        return metadata
    
    def post_execute_hook(self, result: ShellResult, cmd: Union[str, List[str]]) -> ShellResult:
        """dnf执行后的额外处理"""
        if result and 'install' in str(cmd):
            # 如果安装成功，提取安装的包列表
            packages = result.get_metadata('packeds', [])
            if packages:
                result.set_metadata('installed_packages', packages)
        return result


class RpmExecutor(ShellExecutor):
    """针对rpm命令的专用执行器"""
    
    def __init__(self, query_format: Optional[str] = None, **kwargs):
        """
        初始化rpm执行器
        
        参数:
            query_format: 查询格式，如 "%{NAME} %{VERSION}"
        """
        super().__init__(**kwargs)
        self.query_format = query_format
    
    def pre_execute_hook(self, cmd: Union[str, List[str]]) -> Union[str, List[str]]:
        """预处理rpm命令"""
        if isinstance(cmd, str):
            cmd_parts = shlex.split(cmd)
        else:
            cmd_parts = cmd.copy()
        
        # 如果是查询命令且指定了格式，添加--qf参数
        if self.query_format and len(cmd_parts) > 1 and cmd_parts[0] == 'rpm':
            if cmd_parts[1] in ['-q', '--query', '-qa', '--query', '-qi']:
                # 插入格式参数
                if '--qf' not in cmd_parts and '--queryformat' not in cmd_parts:
                    cmd_parts.insert(2, '--qf')
                    cmd_parts.insert(3, self.query_format)
        
        return cmd_parts
    
    def get_display_message(self, cmd: Union[str, List[str]]) -> str:
        """显示rpm特定消息"""
        cmd_str = ' '.join(cmd) if isinstance(cmd, list) else cmd
        if '-i' in cmd_str and 'install' in cmd_str:
            return "Installing RPM packages..."
        elif '-e' in cmd_str:
            return "Erasing RPM packages..."
        elif '-q' in cmd_str:
            return "Querying RPM database..."
        elif '-U' in cmd_str or '-F' in cmd_str:
            return "Upgrading RPM packages..."
        else:
            return "RPM Running..."
    
    def parse_output_for_metadata(self, result: ShellResult, cmd: Union[str, List[str]]) -> Dict[str, Any]:
        """从rpm输出中提取包信息"""
        metadata = {}
        cmd_str = ' '.join(cmd) if isinstance(cmd, list) else cmd
        
        # 提取查询结果的包名和版本
        if '-q' in cmd_str and result and result.output():
            packages = []
            for line in result.output_lines():
                if line.strip():
                    parts = line.split()
                    if len(parts) >= 2:
                        packages.append({
                            'name': parts[0],
                            'version': parts[1] if len(parts) > 1 else ''
                        })
            if packages:
                metadata['packages'] = packages
                metadata['package_count'] = len(packages)
        
        return metadata


class SystemdExecutor(ShellExecutor):
    """针对systemctl命令的专用执行器"""
    
    def get_display_message(self, cmd: Union[str, List[str]]) -> str:
        """显示systemctl特定消息"""
        cmd_str = ' '.join(cmd) if isinstance(cmd, list) else cmd
        if 'start' in cmd_str:
            return "Starting service..."
        elif 'stop' in cmd_str:
            return "Stopping service..."
        elif 'restart' in cmd_str:
            return "Restarting service..."
        elif 'status' in cmd_str:
            return "Checking service status..."
        elif 'enable' in cmd_str:
            return "Enabling service..."
        elif 'disable' in cmd_str:
            return "Disabling service..."
        else:
            return "Systemctl Running..."
    
    def parse_output_for_metadata(self, result: ShellResult, cmd: Union[str, List[str]]) -> Dict[str, Any]:
        """从systemctl输出中提取服务状态"""
        metadata = {}
        cmd_str = ' '.join(cmd) if isinstance(cmd, list) else cmd
        
        # 提取服务状态
        if 'status' in cmd_str and result:
            for line in result.output_lines():
                if 'Active:' in line:
                    status = line.split('Active:')[1].strip()
                    metadata['service_status'] = status
                    if 'active' in status:
                        metadata['is_active'] = True
                    else:
                        metadata['is_active'] = False
        
        return metadata


# ========== 使用示例 ==========
if __name__ == "__main__":
    
    # 示例1：使用基础执行器
    print("=== 基础执行器 ===")
    executor = ShellExecutor()
    result = executor.execute("ls -la")
    print(f"命令成功: {bool(result)}")
    print(f"输出行数: {len(result.output_lines())}")
    print(f"前3行输出:\n{chr(10).join(result.output_lines()[:3])}")
    print("-" * 50)
    
    # 示例2：使用dnf执行器（如果有dnf命令）
    print("=== DNF执行器 ===")
    dnf = DnfExecutor()
    # 搜索命令示例（不会实际修改系统）
    result = dnf.execute("dnf search python3", show_animation=False)  # 测试时关闭动画
    print(f"搜索完成: {bool(result)}")
    if result.get_metadata('package_count', 0) > 0:
        print(f"找到 {result.get_metadata('package_count')} 个包")
    
    # 示例3：使用rpm执行器
    print("=== RPM执行器 ===")
    rpm = RpmExecutor(query_format="%{NAME} %{VERSION}")
    # 查询已安装的包（前5个）
    result = rpm.execute("rpm -qa | head -5", show_animation=False)
    print(f"查询完成: {bool(result)}")
    print("前5个包:")
    for line in result.output_lines()[:5]:
        print(f"  {line}")
    
    # 示例4：使用systemd执行器
    print("=== Systemd执行器 ===")
    systemd = SystemdExecutor()
    result = systemd.execute("systemctl status sshd", show_animation=False)
    print(f"服务状态: {result.get_metadata('service_status', 'unknown')}")
    print(f"服务活跃: {result.get_metadata('is_active', False)}")
    
    # 示例5：带测试命令的示例
    print("=== 带测试命令 ===")
    executor = ShellExecutor()
    result = executor.execute("ls -la", t_cmd="grep README")
    print(f"包含README文件: {bool(result)}")
    if not result:
        print(f"未找到README文件")
    
    # 示例6：如何扩展自己的专用执行器
    print("=== 自定义扩展示例 ===")
    
    class GitExecutor(ShellExecutor):
        """Git命令专用执行器"""
        
        def get_display_message(self, cmd: Union[str, List[str]]) -> str:
            cmd_str = ' '.join(cmd) if isinstance(cmd, list) else cmd
            if 'status' in cmd_str:
                return "Checking git status..."
            elif 'pull' in cmd_str:
                return "Pulling from remote..."
            elif 'push' in cmd_str:
                return "Pushing to remote..."
            elif 'commit' in cmd_str:
                return "Committing changes..."
            else:
                return "Git Running..."
        
        def parse_output_for_metadata(self, result: ShellResult, cmd: Union[str, List[str]]) -> Dict[str, Any]:
            """提取git信息"""
            metadata = {}
            cmd_str = ' '.join(cmd) if isinstance(cmd, list) else cmd
            
            if 'status' in cmd_str:
                # 统计未跟踪、修改、新增的文件
                untracked = 0
                modified = 0
                for line in result.output_lines():
                    if 'Untracked files:' in line:
                        untracked = 1
                    elif 'modified:' in line:
                        modified += 1
                metadata['untracked'] = untracked > 0
                metadata['modified_count'] = modified
            
            return metadata
    
    git = GitExecutor()
    result = git.execute("git status", show_animation=False)
    if result:
        print(f"有未跟踪文件: {result.get_metadata('untracked', False)}")
        print(f"修改文件数: {result.get_metadata('modified_count', 0)}")