import os
import threading
import time
import uuid

import select
import socketio

# 检查平台并导入相应的模块
if os.name == 'nt':  # Windows
    import subprocess

    PTY_AVAILABLE = False
else:  # Unix/Linux/MacOS
    try:
        import pty
        import termios
        import tty

        PTY_AVAILABLE = True
    except ImportError:
        PTY_AVAILABLE = False


class TerminalClient:
    def __init__(self, server_url, client_name=None):
        self.sio = socketio.Client()
        self.server_url = server_url
        # 在初始化时就生成client_id，确保一致性
        self.client_id = str(uuid.uuid4())
        self.client_name = client_name or f"{self.get_hostname()}"
        self.master_fd = None
        self.process = None
        self.running = False
        self.connected = False

        self.sessions = {}
        self.os = os.name

        print(f"初始化客户端: {self.client_name}")
        print(f"系统平台: {os.name}")
        print(f"PTY 可用: {PTY_AVAILABLE}")

        self.setup_handlers()

    def get_hostname(self):
        """获取主机名 - 跨平台兼容"""
        try:
            import socket
            return socket.gethostname()
        except:
            return "unknown-host"

    def setup_handlers(self):
        @self.sio.event
        def connect():
            print(f"✓ 连接到服务器: {self.server_url}")
            self.connected = True
            # 立即注册客户端
            self.sio.emit('client_register', {
                'client_id': self.client_id,
                'name': self.client_name,
                'os': self.os
            })
            self.sessions[self.client_id] = {'input_buffer': ''}
            print(f"✓ 客户端注册信息已发送: {self.client_name} (ID: {self.client_id})")

        @self.sio.event
        def disconnect():
            print("✗ 与服务器断开连接")
            self.connected = False
            self.stop_terminal()

        @self.sio.on('registration_success')
        def handle_registration_success(data):
            print("✓ 客户端注册成功")

        @self.sio.on('terminal_command')
        def handle_terminal_command(data):
            """处理来自服务器的终端命令"""
            if data.get('client_id') != self.client_id:
                return

            command = data.get('command', '')
            print(f"收到终端输入: {repr(command)}")

            # Windows 和 Unix 不同的处理方式
            if os.name == 'nt':  # Windows
                session = self.sessions[self.client_id]
                if self.process and self.process.poll() is None:
                    try:
                        # 如果是回车键，发送整个命令并清空缓冲区
                        if command == '\r' or command == '\n':
                            command = session['input_buffer'] + '\r\n'
                            self.process.stdin.write(command)
                            self.process.stdin.flush()
                            session['input_buffer'] = ''  # 清空缓冲区
                        else:
                            # 将字符添加到缓冲区
                            session['input_buffer'] += command
                        print(f"✓ 成功写入命令到 Windows 进程")
                    except Exception as e:
                        print(f"✗ 写入 Windows 进程失败: {e}")
            else:  # Unix/Linux/MacOS
                if self.master_fd and self.running:
                    try:
                        os.write(self.master_fd, command.encode('utf-8'))
                        print(f"✓ 成功写入命令到伪终端")
                    except Exception as e:
                        print(f"✗ 写入伪终端失败: {e}")

    def find_available_shell(self):
        """查找可用的 shell - 跨平台兼容"""
        if os.name == 'nt':  # Windows
            # Windows 使用 cmd.exe 或 PowerShell
            shells = ['cmd.exe', 'powershell.exe']
            for shell in shells:
                # 检查是否在 PATH 中
                if self.which(shell):
                    print(f"找到可用 shell: {shell}")
                    return shell
            return 'cmd.exe'  # 默认回退
        else:  # Unix/Linux/MacOS
            shells = [
                '/bin/bash', '/usr/bin/bash',
                '/bin/zsh', '/usr/bin/zsh',
                '/bin/sh', '/usr/bin/sh',
                '/bin/ksh', '/usr/bin/ksh',
                '/bin/tcsh', '/usr/bin/tcsh'
            ]

            for shell in shells:
                if os.path.exists(shell):
                    print(f"找到可用 shell: {shell}")
                    return shell

            # 如果找不到任何标准 shell，尝试使用环境变量中的 SHELL
            fallback_shell = os.environ.get('SHELL')
            if fallback_shell and os.path.exists(fallback_shell):
                print(f"使用环境变量中的 shell: {fallback_shell}")
                return fallback_shell

            print("警告: 未找到可用的 shell，将尝试使用 /bin/sh")
            return '/bin/sh'  # 最后尝试

    def which(self, program):
        """查找可执行程序 - 类似 Unix which 命令的 Windows 实现"""

        def is_exe(fpath):
            return os.path.isfile(fpath) and os.access(fpath, os.X_OK)

        fpath, fname = os.path.split(program)
        if fpath:
            if is_exe(program):
                return program
        else:
            for path in os.environ["PATH"].split(os.pathsep):
                exe_file = os.path.join(path, program)
                if is_exe(exe_file):
                    return exe_file
        return None

    def start_terminal(self):
        """启动终端 - 跨平台兼容版本"""
        try:
            print("正在启动终端...")

            if os.name == 'nt':  # Windows
                return self.start_windows_terminal()
            else:  # Unix/Linux/MacOS
                return self.start_unix_terminal()

        except Exception as e:
            print(f"✗ 启动终端失败: {e}")
            import traceback
            traceback.print_exc()
            return False

    def start_windows_terminal(self):
        """Windows 终端实现"""
        try:
            shell_path = self.find_available_shell()
            print(f"使用 Windows shell: {shell_path}")

            # 启动进程
            self.process = subprocess.Popen(
                [shell_path],
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,
                stderr=subprocess.STDOUT,
                text=True,
                bufsize=1,
                universal_newlines=True,
                cwd=os.getcwd()
            )

            self.running = True

            # 启动线程读取输出
            output_thread = threading.Thread(target=self.read_windows_output)
            output_thread.daemon = True
            output_thread.start()

            print("✓ Windows 终端启动成功")
            return True

        except Exception as e:
            print(f"✗ 启动 Windows 终端失败: {e}")
            return False

    def start_unix_terminal(self):
        """Unix/Linux/MacOS 终端实现"""
        if not PTY_AVAILABLE:
            print("✗ pty 模块不可用，无法创建伪终端")
            return False

        try:
            # 查找可用的 shell
            shell_path = self.find_available_shell()
            shell_name = os.path.basename(shell_path)

            print(f"使用 Unix shell: {shell_path}")

            # 创建伪终端
            self.master_fd, slave_fd = pty.openpty()

            # 启动shell进程
            pid = os.fork()

            if pid == 0:  # 子进程
                os.close(self.master_fd)
                os.setsid()
                os.dup2(slave_fd, 0)  # stdin
                os.dup2(slave_fd, 1)  # stdout
                os.dup2(slave_fd, 2)  # stderr
                os.close(slave_fd)

                # 设置环境变量
                env = os.environ.copy()
                env['TERM'] = 'xterm-256color'
                env['COLORTERM'] = 'truecolor'

                # 启动shell
                if shell_name in ['bash', 'zsh', 'ksh']:
                    os.execve(shell_path, [shell_name, '-i'], env)
                else:
                    os.execve(shell_path, [shell_name], env)
            else:
                os.close(slave_fd)
                self.running = True

                # 启动线程读取终端输出
                output_thread = threading.Thread(target=self.read_unix_output)
                output_thread.daemon = True
                output_thread.start()

                print("✓ Unix 终端启动成功")
                return True

        except Exception as e:
            print(f"✗ 启动 Unix 终端失败: {e}")
            return False

    def read_windows_output(self):
        """读取 Windows 进程输出"""
        print("开始读取 Windows 进程输出...")
        while self.running and self.process and self.process.poll() is None:
            try:
                output = self.process.stdout.readline()
                if output and self.connected and output != '\n':
                    self.sio.emit('terminal_data', {
                        'client_id': self.client_id,
                        'data': '\n' + output[:-1]
                    })
                    print(f"发送 Windows 输出: {repr(output[:50])}")
            except Exception as e:
                if self.running:
                    print(f"读取 Windows 输出时出错: {e}")
                break
        print("Windows 输出读取线程结束")

    def read_unix_output(self):
        """读取 Unix 伪终端输出"""
        print("开始读取 Unix 终端输出...")

        while self.running and self.master_fd:
            try:
                # 使用select检查是否有数据可读
                r, w, e = select.select([self.master_fd], [], [], 0.1)
                if self.master_fd in r:
                    output = os.read(self.master_fd, 1024).decode('utf-8', errors='ignore')
                    if output and self.connected:
                        # 发送终端输出到服务器
                        self.sio.emit('terminal_data', {
                            'client_id': self.client_id,
                            'data': output
                        })
                        print(f"发送 Unix 输出: {repr(output[:50])}")
            except (OSError, ValueError) as e:
                if self.running:  # 只在运行状态下打印错误
                    print(f"读取 Unix 终端输出错误: {e}")
                break

        print("Unix 输出读取线程结束")

    def stop_terminal(self):
        """停止终端 - 跨平台兼容"""
        print("正在停止终端...")
        self.running = False

        # Windows 进程处理
        if self.process:
            try:
                if os.name == 'nt':
                    # Windows 发送 Ctrl+C 信号
                    self.process.send_signal(subprocess.signal.CTRL_C_EVENT)
                else:
                    self.process.terminate()

                self.process.wait(timeout=3)
            except:
                try:
                    self.process.kill()
                except:
                    pass
            self.process = None

        # Unix 伪终端处理
        if self.master_fd:
            try:
                os.close(self.master_fd)
            except:
                pass
            self.master_fd = None

        print("终端已停止")

    def connect_with_retry(self, max_retries=5, retry_interval=3):
        """带重试的连接"""
        for attempt in range(max_retries):
            try:
                print(f"尝试连接到服务器 ({attempt + 1}/{max_retries})...")
                self.sio.connect(self.server_url)
                self.start_terminal()
                return True
            except Exception as e:
                print(f"连接失败: {e}")
                if attempt < max_retries - 1:
                    print(f"{retry_interval}秒后重试...")
                    time.sleep(retry_interval)

        return False

    def run(self):
        """运行客户端"""
        if self.connect_with_retry():
            try:
                print("客户端运行中，按 Ctrl+C 退出...")
                self.sio.wait()
            except KeyboardInterrupt:
                print("\n客户端正在关闭...")
            except Exception as e:
                print(f"客户端运行错误: {e}")
            finally:
                self.stop_terminal()
                if self.sio.connected:
                    self.sio.disconnect()
        else:
            print("无法连接到服务器，客户端退出")


def start():
    import argparse

    parser = argparse.ArgumentParser(description='终端客户端')
    parser.add_argument('--name', type=str, help='客户端名称', default=os.getenv("NAME"))
    parser.add_argument('--server', type=str, help='服务器地址',
                        default=os.getenv("SERVER_URL", 'http://localhost:5000'))

    args = parser.parse_args()

    client = TerminalClient(
        server_url=args.server,
        client_name=args.name
    )

    client.run()


if __name__ == '__main__':
    start()
