package com.qqchen.deploy.backend.framework.ssh.websocket;

import com.qqchen.deploy.backend.framework.enums.AuthTypeEnum;
import com.qqchen.deploy.backend.framework.enums.ResponseCode;
import com.qqchen.deploy.backend.framework.enums.SSHDisconnectReason;
import com.qqchen.deploy.backend.framework.enums.SSHEvent;
import com.qqchen.deploy.backend.framework.enums.SSHMessageType;
import com.qqchen.deploy.backend.framework.enums.SSHStatusEnum;
import com.qqchen.deploy.backend.framework.enums.SSHTargetType;
import com.qqchen.deploy.backend.framework.exception.BusinessException;
import com.qqchen.deploy.backend.framework.ssh.ISSHCommandService;
import com.qqchen.deploy.backend.framework.ssh.SSHCommandServiceFactory;
import com.qqchen.deploy.backend.framework.utils.JsonUtils;
import com.qqchen.deploy.backend.framework.utils.SessionIdGenerator;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.connection.channel.direct.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;

/**
 * SSH WebSocket 抽象处理器（Framework层）
 * 提供通用的SSH WebSocket能力
 * 
 * 子类需要实现5个方法：
 * 1. getSSHTarget(session) - 获取连接目标信息
 * 2. checkPermission(userId, target) - 权限验证
 * 3. getMaxSessions() - 最大并发连接数
 * 4. getPathPattern() - WebSocket路径模式
 * 5. onEvent(event, data) - 事件钩子（可选）
 * 
 * @author Framework
 */
@Slf4j
public abstract class AbstractSSHWebSocketHandler extends TextWebSocketHandler {
    
    private static final Logger log = LoggerFactory.getLogger(AbstractSSHWebSocketHandler.class);
    
    // ========== 会话存储 ==========
    
    protected final Map<String, WebSocketSession> webSocketSessions = new ConcurrentHashMap<>();
    protected final Map<String, SSHClient> sshClients = new ConcurrentHashMap<>();
    protected final Map<String, Session.Shell> sshShells = new ConcurrentHashMap<>();
    protected final Map<String, Future<?>> outputTasks = new ConcurrentHashMap<>();
    
    // ========== 依赖注入 ==========
    
    /**
     * SSH命令服务工厂（由子类注入）
     */
    private final SSHCommandServiceFactory sshCommandServiceFactory;
    
    /**
     * SSH输出监听线程池（Framework自动注入）
     */
    @Resource(name = "sshOutputExecutor")
    private AsyncTaskExecutor asyncTaskExecutor;
    
    /**
     * SSH会话管理器（由子类注入）
     */
    private final SSHSessionManager sessionManager;
    
    /**
     * 会话目标信息存储：sessionId → SSHTarget
     * 用于在事件中访问连接目标信息
     */
    private final Map<String, SSHTarget> sessionTargets = new ConcurrentHashMap<>();
    
    /**
     * 构造函数（子类必须调用）
     * 
     * @param sshCommandServiceFactory SSH命令服务工厂
     * @param sessionManager SSH会话管理器
     */
    protected AbstractSSHWebSocketHandler(
        SSHCommandServiceFactory sshCommandServiceFactory,
        SSHSessionManager sessionManager) {
        this.sshCommandServiceFactory = sshCommandServiceFactory;
        this.sessionManager = sessionManager;
    }
    
    // ========== 子类必须实现的抽象方法 ==========
    
    /**
     * 获取SSH连接目标信息（由子类实现）
     * 
     * @param session WebSocket会话
     * @return SSH目标信息
     * @throws Exception 获取失败时抛出
     */
    protected abstract SSHTarget getSSHTarget(WebSocketSession session) throws Exception;
    
    /**
     * 检查用户权限（由子类实现）
     * 
     * @param userId 用户ID
     * @param target SSH目标
     * @return 是否有权限
     */
    protected abstract boolean checkPermission(Long userId, SSHTarget target);
    
    /**
     * 获取最大并发连接数（由子类实现）
     * 
     * @return 最大连接数
     */
    protected abstract int getMaxSessions();
    
    /**
     * 获取WebSocket路径模式（由子类实现）
     * 
     * @return 路径模式，如 "/api/v1/server-ssh/connect/*"
     */
    protected abstract String getPathPattern();
    
    /**
     * 事件钩子（可选，子类可以重写）
     * 用于审计日志、统计等业务逻辑
     * 
     * @param event 事件类型
     * @param eventData 事件数据（强类型）
     */
    protected void onEvent(SSHEvent event, SSHEventData eventData) {
        // 默认空实现
    }
    
    /**
     * 从WebSocketSession中获取SSH SessionId（懒加载模式）
     * 
     * 如果session.attributes中没有sshSessionId，会自动生成并存储
     * 这是获取sessionId的唯一标准方法，保证sessionId的一致性
     * 
     * @param session WebSocketSession
     * @return SSH SessionId（增强后的唯一ID）
     */
    private String getSessionId(WebSocketSession session) {
        String sessionId = (String) session.getAttributes().get("sshSessionId");
        if (sessionId == null) {
            // 懒加载：如果没有，就生成并存储（保证一致性）
            sessionId = SessionIdGenerator.enhanceWebSocketSessionId(session.getId());
            session.getAttributes().put("sshSessionId", sessionId);
        }
        return sessionId;
    }
    
    // ========== Framework 提供的核心能力 ==========
    
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // ⚠️ 关键：使用统一的getSessionId方法获取线程安全的唯一SessionId
        // 这会自动生成增强型SessionId：WebSocket原始ID + 时间戳 + 序列号
        String sessionId = getSessionId(session);
        
        log.info("WebSocket连接建立: webSocketId={}, sshSessionId={}", session.getId(), sessionId);
        
        try {
            // 1. 获取用户信息
            Long userId = (Long) session.getAttributes().get("userId");
            String username = (String) session.getAttributes().get("username");
            String clientIp = (String) session.getAttributes().get("clientIp");
            String userAgent = (String) session.getAttributes().get("userAgent");
            
            if (userId == null) {
                log.error("无法获取用户信息: sessionId={}", sessionId);
                sendError(session, "认证失败");
                session.close(CloseStatus.POLICY_VIOLATION);
                return;
            }
            
            // 2. 获取连接目标
            SSHTarget target = getSSHTarget(session);
            validateSSHTarget(target);
            
            // 保存target信息，供后续事件使用
            sessionTargets.put(sessionId, target);
            
            log.info("获取SSH目标成功: targetType={}, target={}", target.getTargetType(), target.getMetadata());
            
            // 3. 触发连接前事件
            SSHEventData eventData = SSHEventData.builder()
                .sessionId(sessionId)
                .userId(userId)
                .username(username)
                .target(target)
                .clientIp(clientIp)
                .userAgent(userAgent)
                .build();
            onEvent(SSHEvent.BEFORE_CONNECT, eventData);
            
            // 4. 权限验证
            if (!checkPermission(userId, target)) {
                log.warn("用户无权访问目标: userId={}, target={}:{}", 
                    userId, target.getTargetType(), target.getMetadata());
                sendError(session, "无权访问此目标");
                session.close(CloseStatus.POLICY_VIOLATION);
                return;
            }
            
            // 5. 提前注册会话（解决并发竞态问题）
            sessionManager.registerSession(sessionId, userId, target.getTargetType(), target.getMetadata());
            boolean sessionRegistered = true;
            
            try {
                // 6. 检查并发连接数（注册后立即检查）
                long activeCount = sessionManager.countActiveSessions(userId, target.getTargetType(), target.getMetadata());
                if (activeCount > getMaxSessions()) {
                    log.warn("用户连接数超过限制: userId={}, target={}:{}, current={}, max={}", 
                        userId, target.getTargetType(), target.getMetadata(), activeCount, getMaxSessions());
                    sendError(session, "连接数超过限制（最多" + getMaxSessions() + "个）");
                    sessionManager.removeSession(sessionId);
                    sessionRegistered = false;
                    session.close(CloseStatus.POLICY_VIOLATION);
                    return;
                }
                
                // 7. 发送连接中状态
                sendStatus(session, SSHStatusEnum.CONNECTING);
                
                // 8. 建立SSH连接
                SSHClient sshClient = createSSHConnection(target);
                sshClients.put(sessionId, sshClient);
                
                // 9. 打开Shell通道并分配PTY（伪终端）
                Session sshSession = sshClient.startSession();
                
                // ⚠️ 关键：分配PTY，启用交互式Shell、回显、提示符
                // 参数：终端类型, 列数, 行数, 宽度(像素), 高度(像素), 终端模式
                sshSession.allocatePTY("xterm", 80, 24, 0, 0, java.util.Collections.emptyMap());
                
                Session.Shell shell = sshSession.startShell();
                
                // 保存会话信息
                webSocketSessions.put(sessionId, session);
                sshShells.put(sessionId, shell);
                
                // 10. ⚠️ 优化：先启动输出监听线程，确保不错过任何SSH输出
                // 直接传递session和shell，sessionId从session.attributes中获取
                Future<?> stdoutTask = asyncTaskExecutor.submit(() -> readSSHOutput(session, shell));
                outputTasks.put(sessionId, stdoutTask);
                
                // 同时启动错误流监听（某些SSH服务器会将输出发送到错误流）
                Future<?> stderrTask = asyncTaskExecutor.submit(() -> readSSHError(session, shell));
                outputTasks.put(sessionId + "_stderr", stderrTask);
                
                // 11. 发送连接成功状态
                sendStatus(session, SSHStatusEnum.CONNECTED);
                log.info("SSH连接成功: sessionId={}, userId={}, target={}:{}", 
                    sessionId, userId, target.getTargetType(), target.getMetadata());
                
                // 12. ⚠️ 异步创建审计日志，不阻塞主线程
                // 使用CompletableFuture异步执行，避免数据库操作延迟影响SSH输出接收
                java.util.concurrent.CompletableFuture.runAsync(() -> {
                    try {
                        onEvent(SSHEvent.AFTER_CONNECT, eventData);
                    } catch (Exception ex) {
                        log.error("AFTER_CONNECT事件处理失败: sessionId={}", sessionId, ex);
                    }
                });
                
            } catch (Exception ex) {
                // 如果会话已注册，需要清理
                if (sessionRegistered) {
                    sessionManager.removeSession(sessionId);
                }
                throw ex;  // 重新抛出，由外层catch处理
            }
            
        } catch (Exception e) {
            log.error("建立SSH连接失败: sessionId={}", sessionId, e);
            sendError(session, "连接失败: " + e.getMessage());
            
            // 构建失败事件数据
            SSHEventData failureEventData = SSHEventData.builder()
                .sessionId(sessionId)
                .errorMessage(e.getMessage())
                .exception(e)
                .build();
            cleanupSession(sessionId, failureEventData);
            
            try {
                session.close(CloseStatus.SERVER_ERROR);
            } catch (IOException ex) {
                log.error("关闭WebSocket会话失败: sessionId={}", sessionId, ex);
            }
        }
    }
    
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String sessionId = getSessionId(session);
        
        try {
            SSHWebSocketMessage msg = JsonUtils.fromJson(message.getPayload(), SSHWebSocketMessage.class);
            
            if (msg.getType() == SSHMessageType.INPUT) {
                // 用户输入命令（使用强类型Request）
                com.qqchen.deploy.backend.framework.ssh.websocket.request.SSHInputRequest request = 
                    msg.getRequest(com.qqchen.deploy.backend.framework.ssh.websocket.request.SSHInputRequest.class);
                
                if (request == null || !request.isValid()) {
                    log.warn("INPUT消息格式错误或为空: sessionId={}", sessionId);
                    return;
                }
                
                Session.Shell shell = sshShells.get(sessionId);
                if (shell != null) {
                    OutputStream outputStream = shell.getOutputStream();
                    outputStream.write(request.getCommand().getBytes(StandardCharsets.UTF_8));
                    outputStream.flush();
                    
                    // 触发命令事件
                    SSHTarget target = sessionTargets.get(sessionId);
                    SSHEventData eventData = SSHEventData.builder()
                        .sessionId(sessionId)
                        .command(request.getCommand())
                        .target(target)
                        .build();
                    onEvent(SSHEvent.ON_COMMAND, eventData);
                }
            } else if (msg.getType() == SSHMessageType.RESIZE) {
                // 终端尺寸调整
                handleResizeMessage(sessionId, msg);
            }
        } catch (Exception e) {
            log.error("处理WebSocket消息失败: sessionId={}", sessionId, e);
        }
    }
    
    /**
     * 处理终端尺寸调整消息（使用强类型Request）
     * 
     * @param sessionId 会话ID
     * @param msg 消息对象
     */
    private void handleResizeMessage(String sessionId, SSHWebSocketMessage msg) {
        try {
            // 使用强类型提取request
            com.qqchen.deploy.backend.framework.ssh.websocket.request.SSHResizeRequest request = 
                msg.getRequest(com.qqchen.deploy.backend.framework.ssh.websocket.request.SSHResizeRequest.class);
            
            if (request == null) {
                log.warn("RESIZE消息缺少request或格式错误: sessionId={}", sessionId);
                return;
            }
            
            // 验证参数
            if (!request.isValid()) {
                log.warn("RESIZE消息参数无效: sessionId={}, rows={}, cols={}", 
                    sessionId, request.getRows(), request.getCols());
                return;
            }
            
            // 调整SSH PTY尺寸
            Session.Shell shell = sshShells.get(sessionId);
            if (shell != null) {
                // 计算像素尺寸（标准字体：8x16像素）
                int widthPixels = request.getCols() * 8;
                int heightPixels = request.getRows() * 16;
                
                shell.changeWindowDimensions(request.getCols(), request.getRows(), 
                    widthPixels, heightPixels);
            } else {
                log.warn("未找到SSH Shell，无法调整尺寸: sessionId={}", sessionId);
            }
            
        } catch (Exception e) {
            log.error("处理RESIZE消息失败: sessionId={}", sessionId, e);
        }
    }
    
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String sessionId = getSessionId(session);
        log.info("WebSocket连接关闭: sessionId={}, status={}", sessionId, status);
        
        SSHTarget target = sessionTargets.get(sessionId);
        SSHEventData eventData = SSHEventData.builder()
            .sessionId(sessionId)
            .target(target)
            .closeStatus(status)
            .build();
        
        cleanupSession(sessionId, eventData);
    }
    
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        String sessionId = getSessionId(session);
        log.error("WebSocket传输错误: sessionId={}", sessionId, exception);
        
        SSHTarget target = sessionTargets.get(sessionId);
        SSHEventData eventData = SSHEventData.builder()
            .sessionId(sessionId)
            .target(target)
            .errorMessage(exception.getMessage())
            .exception(exception)
            .build();
        onEvent(SSHEvent.ON_ERROR, eventData);
        
        sendError(session, "传输错误: " + exception.getMessage());
        cleanupSession(sessionId, eventData);
        
        try {
            session.close(CloseStatus.SERVER_ERROR);
        } catch (IOException e) {
            log.error("关闭WebSocket会话失败: sessionId={}", sessionId, e);
        }
    }
    
    // ========== 私有方法 ==========
    
    /**
     * 创建SSH连接
     */
    private SSHClient createSSHConnection(SSHTarget target) throws Exception {
        ISSHCommandService sshService = sshCommandServiceFactory.getService(target.getOsType());
        
        String password = null, privateKey = null, passphrase = null;
        
        switch (target.getAuthType()) {
            case PASSWORD:
                if (target.getPassword() == null || target.getPassword().isEmpty()) {
                    throw new BusinessException(ResponseCode.INVALID_PARAM,
                        new Object[]{"SSH密码不能为空"});
                }
                password = target.getPassword();
                break;
                
            case KEY:
                if (target.getPrivateKey() == null || target.getPrivateKey().isEmpty()) {
                    throw new BusinessException(ResponseCode.INVALID_PARAM,
                        new Object[]{"SSH私钥不能为空"});
                }
                privateKey = target.getPrivateKey();
                passphrase = target.getPassphrase();
                break;
                
            default:
                throw new BusinessException(ResponseCode.INVALID_PARAM,
                    new Object[]{"不支持的认证类型: " + target.getAuthType()});
        }
        
        return sshService.createConnection(target.getHost(), target.getPort(), target.getUsername(),
            password, privateKey, passphrase);
    }
    
    /**
     * 验证SSH目标参数
     */
    private void validateSSHTarget(SSHTarget target) {
        if (target == null) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"SSH目标不能为空"});
        }
        if (target.getTargetType() == null) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"目标类型不能为空"});
        }
        if (target.getHost() == null || target.getHost().isEmpty()) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"主机地址不能为空"});
        }
        if (target.getUsername() == null || target.getUsername().isEmpty()) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"用户名不能为空"});
        }
        if (target.getAuthType() == null) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"认证方式不能为空"});
        }
        if (target.getOsType() == null) {
            throw new BusinessException(ResponseCode.INVALID_PARAM, new Object[]{"操作系统类型不能为空"});
        }
    }
    
    /**
     * 读取SSH输出并发送到前端
     * 
     * @param session WebSocket会话
     * @param shell SSH Shell
     */
    private void readSSHOutput(WebSocketSession session, Session.Shell shell) {
        String sessionId = getSessionId(session);
        
        try {
            InputStream inputStream = shell.getInputStream();
            byte[] buffer = new byte[1024];
            int len;
            
            while (session.isOpen() && (len = inputStream.read(buffer)) > 0) {
                String output = new String(buffer, 0, len, StandardCharsets.UTF_8);
                sendOutput(session, output);
            }
            
        } catch (java.io.InterruptedIOException e) {
            if (session.isOpen()) {
                log.warn("SSH输出监听线程被中断: sessionId={}", sessionId);
                try {
                    sendError(session, "SSH连接被中断");
                } catch (Exception ex) {
                    // 忽略
                }
            }
        } catch (IOException e) {
            if (session.isOpen()) {
                log.error("读取SSH输出失败: sessionId={}", sessionId, e);
                try {
                    sendError(session, "读取SSH输出失败: " + e.getMessage());
                } catch (Exception ex) {
                    // 忽略
                }
            }
        }
    }
    
    /**
     * 读取SSH错误流并发送到前端
     * 某些SSH服务器可能将输出发送到标准错误流
     * 
     * @param session WebSocket会话
     * @param shell SSH Shell
     */
    private void readSSHError(WebSocketSession session, Session.Shell shell) {
        String sessionId = getSessionId(session);
        
        try {
            InputStream errorStream = shell.getErrorStream();
            byte[] buffer = new byte[1024];
            int len;
            
            while (session.isOpen() && (len = errorStream.read(buffer)) > 0) {
                String output = new String(buffer, 0, len, StandardCharsets.UTF_8);
                sendOutput(session, output);
            }
            
        } catch (java.io.InterruptedIOException e) {
            if (session.isOpen()) {
                log.warn("SSH错误流监听线程被中断: sessionId={}", sessionId);
            }
        } catch (IOException e) {
            if (session.isOpen()) {
                log.error("读取SSH错误流失败: sessionId={}", sessionId, e);
            }
        }
    }
    
    /**
     * 清理单个会话（供子类调用，如gracefulShutdown）
     * 
     * @param sessionId 会话ID
     */
    protected void cleanupSingleSession(String sessionId) {
        SSHTarget target = sessionTargets.get(sessionId);
        SSHEventData eventData = SSHEventData.builder()
            .sessionId(sessionId)
            .target(target)
            .build();
        cleanupSession(sessionId, eventData);
    }
    
    /**
     * 清理会话资源
     * 
     * @param sessionId 会话ID
     * @param eventData 事件数据
     */
    private void cleanupSession(String sessionId, SSHEventData eventData) {
        
        try {
            // 1. 触发断开前事件（异步，不阻塞清理）
            asyncTaskExecutor.submit(() -> {
                try {
                    onEvent(SSHEvent.BEFORE_DISCONNECT, eventData);
                } catch (Exception e) {
                    log.error("BEFORE_DISCONNECT事件处理失败: sessionId={}", sessionId, e);
                }
            });
            
            // 2. 移除WebSocketSession
            webSocketSessions.remove(sessionId);
            
            // 3. 取消输出监听任务
            Future<?> stdoutTask = outputTasks.remove(sessionId);
            if (stdoutTask != null && !stdoutTask.isDone()) {
                stdoutTask.cancel(true);
            }
            
            Future<?> stderrTask = outputTasks.remove(sessionId + "_stderr");
            if (stderrTask != null && !stderrTask.isDone()) {
                stderrTask.cancel(true);
            }
            
            // 4. 关闭SSH Shell
            Session.Shell shell = sshShells.remove(sessionId);
            if (shell != null) {
                try {
                    shell.close();
                } catch (IOException e) {
                    log.error("关闭SSH Shell失败: sessionId={}", sessionId, e);
                }
            }
            
            // 5. 关闭SSH客户端
            SSHClient sshClient = sshClients.remove(sessionId);
            if (sshClient != null) {
                try {
                    if (sshClient.isConnected()) {
                        sshClient.disconnect();
                    }
                } catch (IOException e) {
                    log.error("关闭SSH客户端失败: sessionId={}", sessionId, e);
                }
            }
            
            // 6. 从会话管理器移除（最重要，必须执行）
            sessionManager.removeSession(sessionId);
            
            // 7. 移除target信息
            sessionTargets.remove(sessionId);
            
            log.info("会话资源清理完成: sessionId={}", sessionId);
            
        } finally {
            // 8. 触发断开后事件（异步，即使清理失败也要触发）
            asyncTaskExecutor.submit(() -> {
                try {
                    onEvent(SSHEvent.AFTER_DISCONNECT, eventData);
                } catch (Exception e) {
                    log.error("AFTER_DISCONNECT事件处理失败: sessionId={}", sessionId, e);
                }
            });
        }
    }
    
    /**
     * 发送输出消息到前端（使用强类型Response）
     * 
     * @param session WebSocket会话
     * @param output 输出内容
     */
    protected void sendOutput(WebSocketSession session, String output) {
        String sessionId = getSessionId(session);
        
        try {
            if (!session.isOpen()) {
                return;
            }
            
            // 创建强类型Response对象
            com.qqchen.deploy.backend.framework.ssh.websocket.response.SSHOutputResponse response = 
                new com.qqchen.deploy.backend.framework.ssh.websocket.response.SSHOutputResponse(output);
            
            // 包装成 {"response": SSHOutputResponse}
            Map<String, Object> data = new HashMap<>();
            data.put("response", response);
            
            // 创建消息
            SSHWebSocketMessage msg = new SSHWebSocketMessage(SSHMessageType.OUTPUT, data);
            session.sendMessage(new TextMessage(JsonUtils.toJson(msg)));
        } catch (IOException e) {
            log.error("发送输出消息失败(IOException): sessionId={}", sessionId, e);
        } catch (Exception e) {
            log.error("发送输出消息失败(Exception): sessionId={}", sessionId, e);
        }
    }
    
    /**
     * 发送状态消息到前端（使用强类型Response）
     */
    protected void sendStatus(WebSocketSession session, SSHStatusEnum status) {
        try {
            if (!session.isOpen()) {
                return;
            }
            
            // 创建强类型Response对象
            com.qqchen.deploy.backend.framework.ssh.websocket.response.SSHStatusResponse response = 
                new com.qqchen.deploy.backend.framework.ssh.websocket.response.SSHStatusResponse(status);
            
            // 包装成 {"response": SSHStatusResponse}
            Map<String, Object> data = new HashMap<>();
            data.put("response", response);
            
            // 创建消息
            SSHWebSocketMessage msg = new SSHWebSocketMessage(SSHMessageType.STATUS, data);
            session.sendMessage(new TextMessage(JsonUtils.toJson(msg)));
        } catch (IOException e) {
            log.error("发送状态消息失败: sessionId={}", session.getId(), e);
        }
    }
    
    /**
     * 发送错误消息到前端（使用强类型Response）
     */
    protected void sendError(WebSocketSession session, String error) {
        try {
            if (!session.isOpen()) {
                return;
            }
            
            // 创建强类型Response对象
            com.qqchen.deploy.backend.framework.ssh.websocket.response.SSHErrorResponse response = 
                new com.qqchen.deploy.backend.framework.ssh.websocket.response.SSHErrorResponse(error);
            
            // 包装成 {"response": SSHErrorResponse}
            Map<String, Object> data = new HashMap<>();
            data.put("response", response);
            
            // 创建消息
            SSHWebSocketMessage msg = new SSHWebSocketMessage(SSHMessageType.ERROR, data);
            session.sendMessage(new TextMessage(JsonUtils.toJson(msg)));
        } catch (IOException e) {
            if (session.isOpen()) {
                log.error("发送错误消息失败: sessionId={}", session.getId(), e);
            } else {
                log.debug("发送错误消息失败（session已关闭）: sessionId={}", session.getId());
            }
        }
    }
    
    /**
     * 优雅下线：应用关闭时清理所有活跃的SSH会话（Framework统一实现）
     * 使用 @PreDestroy 注解，确保在Spring容器销毁前执行
     */
    @jakarta.annotation.PreDestroy
    public void gracefulShutdown() {
        log.warn("====== SSH Handler准备关闭，开始优雅下线所有SSH会话 ======");
        log.warn("当前活跃SSH会话数: {}", webSocketSessions.size());
        
        if (webSocketSessions.isEmpty()) {
            log.info("没有活跃的SSH会话，跳过优雅下线");
            return;
        }
        
        // 记录开始时间
        long startTime = System.currentTimeMillis();
        int successCount = 0;
        int failureCount = 0;
        
        // 遍历所有活跃会话
        for (Map.Entry<String, WebSocketSession> entry : webSocketSessions.entrySet()) {
            String sessionId = entry.getKey();
            WebSocketSession session = entry.getValue();
            
            try {
                log.info("关闭SSH会话: sessionId={}", sessionId);
                
                // 1. 尝试向前端发送服务器下线通知
                try {
                    if (session.isOpen()) {
                        sendError(session, "服务器正在重启，连接即将关闭");
                        // 给前端一点时间接收消息
                        Thread.sleep(100);
                    }
                } catch (Exception e) {
                    log.debug("发送下线通知失败: sessionId={}", sessionId, e);
                }
                
                // 2. 触发BEFORE_SHUTDOWN事件（异步，但等待完成）
                SSHTarget target = sessionTargets.get(sessionId);
                Future<?> shutdownEventTask = asyncTaskExecutor.submit(() -> {
                    try {
                        SSHEventData eventData = SSHEventData.builder()
                            .sessionId(sessionId)
                            .target(target)
                            .disconnectReason(SSHDisconnectReason.SERVER_SHUTDOWN.getCode())
                            .disconnectReasonDesc(SSHDisconnectReason.SERVER_SHUTDOWN.getDescription())
                            .build();
                        onEvent(SSHEvent.BEFORE_SHUTDOWN, eventData);
                    } catch (Exception e) {
                        log.error("BEFORE_SHUTDOWN事件处理失败: sessionId={}", sessionId, e);
                    }
                });
                
                // 等待审计日志写入完成（最多1秒）
                try {
                    shutdownEventTask.get(1, java.util.concurrent.TimeUnit.SECONDS);
                } catch (Exception e) {
                    log.warn("等待BEFORE_SHUTDOWN事件超时: sessionId={}", sessionId);
                }
                
                // 3. 清理SSH资源
                try {
                    cleanupSingleSession(sessionId);
                } catch (Exception e) {
                    log.error("清理SSH资源失败: sessionId={}", sessionId, e);
                }
                
                // 4. 关闭WebSocket连接
                try {
                    if (session.isOpen()) {
                        session.close(new CloseStatus(1001, "服务器正在重启"));
                    }
                } catch (Exception e) {
                    log.debug("关闭WebSocket失败: sessionId={}", sessionId, e);
                }
                
                successCount++;
                log.info("SSH会话关闭成功: sessionId={}", sessionId);
                
            } catch (Exception e) {
                failureCount++;
                log.error("关闭SSH会话失败: sessionId={}", sessionId, e);
            }
        }
        
        long duration = System.currentTimeMillis() - startTime;
        log.warn("====== 优雅下线完成 ======");
        log.warn("总会话数: {}, 成功: {}, 失败: {}, 耗时: {}ms", 
                 successCount + failureCount, successCount, failureCount, duration);
    }
}
