/**
 * 会话管理服务
 * 
 * 功能描述：
 * - 维护WebSocket连接ID与Zulip队列ID的映射关系
 * - 管理玩家位置跟踪和上下文注入
 * - 提供空间过滤和会话查询功能
 * - 支持会话状态的序列化和反序列化
 * - 支持服务重启后的状态恢复
 * 
 * 职责分离：
 * - 会话存储：管理会话数据在Redis中的存储和检索
 * - 位置跟踪：维护玩家在游戏世界中的位置信息
 * - 上下文注入：根据玩家位置确定消息的目标Stream和Topic
 * - 空间过滤：根据地图ID筛选相关的玩家会话
 * - 资源清理：定期清理过期会话和释放相关资源
 * 
 * 主要方法：
 * - createSession(): 创建会话并绑定Socket_ID与Zulip_Queue_ID
 * - getSession(): 获取会话信息
 * - injectContext(): 上下文注入，根据位置确定Stream/Topic
 * - getSocketsInMap(): 空间过滤，获取指定地图的所有Socket
 * - updatePlayerPosition(): 更新玩家位置
 * - destroySession(): 销毁会话
 * - cleanupExpiredSessions(): 清理过期会话
 * 
 * Redis存储结构：
 * - 会话数据: zulip:session:{socketId} -> JSON(GameSession)
 * - 地图玩家列表: zulip:map_players:{mapId} -> Set<socketId>
 * - 用户会话映射: zulip:user_session:{userId} -> socketId
 * 
 * 使用场景：
 * - 玩家登录时创建会话映射
 * - 消息路由时进行上下文注入
 * - 消息分发时进行空间过滤
 * - 玩家登出时清理会话数据
 * 
 * 最近修改：
 * - 2026-01-12: 代码规范优化 - 处理TODO项，实现玩家位置确定Topic逻辑，从配置获取地图ID列表 (修改者: moyin)
 * - 2026-01-07: 代码规范优化 - 完善文件头注释和修改记录 (修改者: moyin)
 * 
 * @author angjustinl
 * @version 1.1.0
 * @since 2025-12-25
 * @lastModified 2026-01-12
 */

import { Injectable, Logger, Inject } from '@nestjs/common';
import { IRedisService } from '../../../core/redis/redis.interface';
import { IZulipConfigService } from '../../../core/zulip_core/zulip_core.interfaces';
import { Internal, Constants } from '../../../core/zulip_core/zulip.interfaces';

// 常量定义
const DEFAULT_MAP_IDS = ['novice_village', 'tavern', 'market'] as const;
const SESSION_TIMEOUT_MINUTES = 30;
const CLEANUP_INTERVAL_MINUTES = 5;

/**
 * 游戏会话接口 - 重新导出以保持向后兼容
 */
export type GameSession = Internal.GameSession;

/**
 * 位置信息接口 - 重新导出以保持向后兼容
 */
export type Position = Internal.Position;

/**
 * 上下文信息接口
 */
export interface ContextInfo {
  stream: string;
  topic?: string;
}

/**
 * 创建会话请求接口
 */
export interface CreateSessionRequest {
  socketId: string;
  userId: string;
  username?: string;
  zulipQueueId: string;
  initialMap?: string;
  initialPosition?: Position;
}

/**
 * 会话统计信息接口
 */
export interface SessionStats {
  totalSessions: number;
  mapDistribution: Record<string, number>;
  oldestSession?: Date;
  newestSession?: Date;
}

/**
 * 会话管理服务类
 * 
 * 职责：
 * - 维护WebSocket连接ID与Zulip队列ID的映射关系
 * - 管理玩家位置跟踪和上下文注入
 * - 提供空间过滤和会话查询功能
 * - 支持会话状态的序列化和反序列化
 * 
 * 主要方法：
 * - createSession(): 创建会话并绑定Socket_ID与Zulip_Queue_ID
 * - getSession(): 获取会话信息
 * - injectContext(): 上下文注入，根据位置确定Stream/Topic
 * - getSocketsInMap(): 空间过滤，获取指定地图的所有Socket
 * - updatePlayerPosition(): 更新玩家位置
 * - destroySession(): 销毁会话
 * 
 * 使用场景：
 * - 玩家登录时创建会话映射
 * - 消息路由时进行上下文注入
 * - 消息分发时进行空间过滤
 * - 玩家登出时清理会话数据
 */
@Injectable()
export class SessionManagerService {
  private readonly SESSION_PREFIX = 'zulip:session:';
  private readonly MAP_PLAYERS_PREFIX = 'zulip:map_players:';
  private readonly USER_SESSION_PREFIX = 'zulip:user_session:';
  private readonly SESSION_TIMEOUT = 3600; // 1小时
  private readonly DEFAULT_MAP = 'novice_village';
  private readonly DEFAULT_POSITION: Position = { x: 400, y: 300 };
  private readonly logger = new Logger(SessionManagerService.name);

  constructor(
    @Inject('REDIS_SERVICE')
    private readonly redisService: IRedisService,
    @Inject('ZULIP_CONFIG_SERVICE')
    private readonly configManager: IZulipConfigService,
  ) {
    this.logger.log('SessionManagerService初始化完成');
  }

  /**
   * 序列化会话对象为JSON字符串
   * 
   * 功能描述：
   * 将GameSession对象转换为可存储在Redis中的JSON字符串
   * 
   * @param session 会话对象
   * @returns string JSON字符串
   * @private
   */
  private serializeSession(session: GameSession): string {
    const serialized: Internal.GameSessionSerialized = {
      socketId: session.socketId,
      userId: session.userId,
      username: session.username,
      zulipQueueId: session.zulipQueueId,
      currentMap: session.currentMap,
      position: session.position,
      lastActivity: session.lastActivity instanceof Date 
        ? session.lastActivity.toISOString() 
        : session.lastActivity,
      createdAt: session.createdAt instanceof Date 
        ? session.createdAt.toISOString() 
        : session.createdAt,
    };
    return JSON.stringify(serialized);
  }

  /**
   * 反序列化JSON字符串为会话对象
   * 
   * 功能描述：
   * 将Redis中存储的JSON字符串转换回GameSession对象
   * 
   * @param data JSON字符串
   * @returns GameSession 会话对象
   * @private
   */
  private deserializeSession(data: string): GameSession {
    const parsed: Internal.GameSessionSerialized = JSON.parse(data);
    return {
      socketId: parsed.socketId,
      userId: parsed.userId,
      username: parsed.username,
      zulipQueueId: parsed.zulipQueueId,
      currentMap: parsed.currentMap,
      position: parsed.position,
      lastActivity: new Date(parsed.lastActivity),
      createdAt: new Date(parsed.createdAt),
    };
  }

  /**
   * 创建会话并绑定Socket_ID与Zulip_Queue_ID
   * 
   * 功能描述：
   * 创建新的游戏会话，建立WebSocket连接与Zulip队列的映射关系
   * 
   * 业务逻辑：
   * 1. 验证输入参数
   * 2. 检查用户是否已有会话（如有则先清理）
   * 3. 创建会话对象
   * 4. 存储到Redis缓存
   * 5. 添加到地图玩家列表
   * 6. 建立用户到会话的映射
   * 7. 设置过期时间
   * 
   * @param socketId WebSocket连接ID
   * @param userId 用户ID
   * @param zulipQueueId Zulip事件队列ID
   * @param username 用户名（可选）
   * @param initialMap 初始地图（可选）
   * @param initialPosition 初始位置（可选）
   * @returns Promise<GameSession> 创建的会话对象
   * 
   * @throws Error 当参数验证失败时
   * @throws Error 当Redis操作失败时
   */
  async createSession(
    socketId: string, 
    userId: string, 
    zulipQueueId: string,
    username?: string,
    initialMap?: string,
    initialPosition?: Position,
  ): Promise<GameSession> {
    const startTime = Date.now();
    
    this.logger.log('开始创建游戏会话', {
      operation: 'createSession',
      socketId,
      userId,
      zulipQueueId,
      timestamp: new Date().toISOString(),
    });

    try {
      // 1. 参数验证
      if (!socketId || !socketId.trim()) {
        throw new Error('socketId不能为空');
      }
      if (!userId || !userId.trim()) {
        throw new Error('userId不能为空');
      }
      if (!zulipQueueId || !zulipQueueId.trim()) {
        throw new Error('zulipQueueId不能为空');
      }

      // 2. 检查用户是否已有会话，如有则先清理
      const existingSocketId = await this.redisService.get(`${this.USER_SESSION_PREFIX}${userId}`);
      if (existingSocketId) {
        this.logger.log('用户已有会话，先清理旧会话', {
          operation: 'createSession',
          userId,
          existingSocketId,
        });
        await this.destroySession(existingSocketId);
      }

      // 3. 创建会话对象
      const now = new Date();
      const session: GameSession = {
        socketId,
        userId,
        username: username || `user_${userId}`,
        zulipQueueId,
        currentMap: initialMap || this.DEFAULT_MAP,
        position: initialPosition || { ...this.DEFAULT_POSITION },
        lastActivity: now,
        createdAt: now,
      };

      // 4. 存储会话到Redis
      const sessionKey = `${this.SESSION_PREFIX}${socketId}`;
      await this.redisService.setex(sessionKey, this.SESSION_TIMEOUT, this.serializeSession(session));

      // 5. 添加到地图玩家列表
      const mapKey = `${this.MAP_PLAYERS_PREFIX}${session.currentMap}`;
      await this.redisService.sadd(mapKey, socketId);
      await this.redisService.expire(mapKey, this.SESSION_TIMEOUT);

      // 6. 建立用户到会话的映射
      const userSessionKey = `${this.USER_SESSION_PREFIX}${userId}`;
      await this.redisService.setex(userSessionKey, this.SESSION_TIMEOUT, socketId);

      const duration = Date.now() - startTime;
      
      this.logger.log('游戏会话创建成功', {
        operation: 'createSession',
        socketId,
        userId,
        zulipQueueId,
        currentMap: session.currentMap,
        duration,
        timestamp: new Date().toISOString(),
      });

      return session;

    } catch (error) {
      const err = error as Error;
      const duration = Date.now() - startTime;
      
      this.logger.error('创建游戏会话失败', {
        operation: 'createSession',
        socketId,
        userId,
        zulipQueueId,
        error: err.message,
        duration,
        timestamp: new Date().toISOString(),
      }, err.stack);

      throw error;
    }
  }

  /**
   * 获取会话信息
   * 
   * 功能描述：
   * 根据socketId获取会话信息，并更新最后活动时间
   * 
   * @param socketId WebSocket连接ID
   * @returns Promise<GameSession | null> 会话信息，不存在时返回null
   */
  async getSession(socketId: string): Promise<GameSession | null> {
    try {
      if (!socketId || !socketId.trim()) {
        this.logger.warn('获取会话失败：socketId为空', {
          operation: 'getSession',
        });
        return null;
      }

      const sessionKey = `${this.SESSION_PREFIX}${socketId}`;
      const sessionData = await this.redisService.get(sessionKey);

      if (!sessionData) {
        this.logger.debug('会话不存在', {
          operation: 'getSession',
          socketId,
        });
        return null;
      }

      const session = this.deserializeSession(sessionData);
      
      // 更新最后活动时间
      session.lastActivity = new Date();
      await this.redisService.setex(sessionKey, this.SESSION_TIMEOUT, this.serializeSession(session));

      this.logger.debug('获取会话信息成功', {
        operation: 'getSession',
        socketId,
        userId: session.userId,
        currentMap: session.currentMap,
      });

      return session;

    } catch (error) {
      const err = error as Error;
      this.logger.error('获取会话信息失败', {
        operation: 'getSession',
        socketId,
        error: err.message,
        timestamp: new Date().toISOString(),
      }, err.stack);

      return null;
    }
  }

  /**
   * 根据用户ID获取会话信息
   * 
   * 功能描述：
   * 根据userId查找对应的会话信息
   * 
   * @param userId 用户ID
   * @returns Promise<GameSession | null> 会话信息，不存在时返回null
   */
  async getSessionByUserId(userId: string): Promise<GameSession | null> {
    try {
      if (!userId || !userId.trim()) {
        return null;
      }

      const userSessionKey = `${this.USER_SESSION_PREFIX}${userId}`;
      const socketId = await this.redisService.get(userSessionKey);

      if (!socketId) {
        return null;
      }

      return this.getSession(socketId);

    } catch (error) {
      const err = error as Error;
      this.logger.error('根据用户ID获取会话失败', {
        operation: 'getSessionByUserId',
        userId,
        error: err.message,
      }, err.stack);

      return null;
    }
  }

  /**
   * 上下文注入：根据位置确定Stream/Topic
   * 
   * 功能描述：
   * 根据玩家当前位置和地图信息，确定消息应该发送到的Zulip Stream和Topic
   * 
   * 业务逻辑：
   * 1. 获取玩家会话信息
   * 2. 根据地图ID查找对应的Stream
   * 3. 根据玩家位置确定Topic（如果有交互对象）
   * 4. 返回上下文信息
   * 
   * @param socketId WebSocket连接ID
   * @param mapId 地图ID（可选，用于覆盖当前地图）
   * @returns Promise<ContextInfo> 上下文信息
   * 
   * @throws Error 当会话不存在时
   */
  async injectContext(socketId: string, mapId?: string): Promise<ContextInfo> {
    this.logger.debug('开始上下文注入', {
      operation: 'injectContext',
      socketId,
      mapId,
      timestamp: new Date().toISOString(),
    });

    try {
      const session = await this.getSession(socketId);
      if (!session) {
        throw new Error('会话不存在');
      }

      const targetMapId = mapId || session.currentMap;

      // 从ConfigManager获取地图对应的Stream
      const stream = this.configManager.getStreamByMap(targetMapId) || 'General';

      // 根据玩家位置确定Topic（基础实现）
      // 检查是否靠近交互对象，如果没有则使用默认Topic
      let topic = 'General';
      
      // 尝试根据位置查找附近的交互对象
      if (session.position) {
        const nearbyObject = this.configManager.findNearbyObject(
          targetMapId,
          session.position.x,
          session.position.y,
          50 // 50像素范围内
        );
        
        if (nearbyObject) {
          topic = nearbyObject.zulipTopic;
        }
      }
      
      const context: ContextInfo = {
        stream,
        topic,
      };

      this.logger.debug('上下文注入完成', {
        operation: 'injectContext',
        socketId,
        targetMapId,
        stream: context.stream,
        topic: context.topic,
      });

      return context;

    } catch (error) {
      const err = error as Error;
      this.logger.error('上下文注入失败', {
        operation: 'injectContext',
        socketId,
        mapId,
        error: err.message,
        timestamp: new Date().toISOString(),
      }, err.stack);

      // 返回默认上下文
      return {
        stream: 'General',
      };
    }
  }

  /**
   * 空间过滤：获取指定地图的所有Socket
   * 
   * 功能描述：
   * 获取指定地图中所有在线玩家的Socket ID列表，用于消息分发
   * 
   * @param mapId 地图ID
   * @returns Promise<string[]> Socket ID列表
   */
  async getSocketsInMap(mapId: string): Promise<string[]> {
    try {
      const mapKey = `${this.MAP_PLAYERS_PREFIX}${mapId}`;
      const socketIds = await this.redisService.smembers(mapKey);

      this.logger.debug('获取地图玩家列表', {
        operation: 'getSocketsInMap',
        mapId,
        playerCount: socketIds.length,
      });

      return socketIds;

    } catch (error) {
      const err = error as Error;
      this.logger.error('获取地图玩家列表失败', {
        operation: 'getSocketsInMap',
        mapId,
        error: err.message,
        timestamp: new Date().toISOString(),
      }, err.stack);

      return [];
    }
  }

  /**
   * 更新玩家位置
   * 
   * 功能描述：
   * 更新玩家在游戏世界中的位置信息，如果切换地图则更新地图玩家列表
   * 
   * 业务逻辑：
   * 1. 获取当前会话
   * 2. 检查是否切换地图
   * 3. 更新会话位置信息
   * 4. 如果切换地图，更新地图玩家列表
   * 5. 保存更新后的会话
   * 
   * @param socketId WebSocket连接ID
   * @param mapId 地图ID
   * @param x X坐标
   * @param y Y坐标
   * @returns Promise<boolean> 是否更新成功
   */
  async updatePlayerPosition(socketId: string, mapId: string, x: number, y: number): Promise<boolean> {
    this.logger.debug('开始更新玩家位置', {
      operation: 'updatePlayerPosition',
      socketId,
      mapId,
      position: { x, y },
      timestamp: new Date().toISOString(),
    });

    try {
      // 参数验证
      if (!socketId || !socketId.trim()) {
        this.logger.warn('更新位置失败：socketId为空', {
          operation: 'updatePlayerPosition',
        });
        return false;
      }

      if (!mapId || !mapId.trim()) {
        this.logger.warn('更新位置失败：mapId为空', {
          operation: 'updatePlayerPosition',
          socketId,
        });
        return false;
      }

      const sessionKey = `${this.SESSION_PREFIX}${socketId}`;
      const sessionData = await this.redisService.get(sessionKey);
      
      if (!sessionData) {
        this.logger.warn('更新位置失败：会话不存在', {
          operation: 'updatePlayerPosition',
          socketId,
        });
        return false;
      }

      const session = this.deserializeSession(sessionData);
      const oldMapId = session.currentMap;
      const mapChanged = oldMapId !== mapId;

      // 更新会话信息
      session.currentMap = mapId;
      session.position = { x, y };
      session.lastActivity = new Date();

      await this.redisService.setex(sessionKey, this.SESSION_TIMEOUT, this.serializeSession(session));

      // 如果切换了地图，更新地图玩家列表
      if (mapChanged) {
        // 从旧地图移除
        const oldMapKey = `${this.MAP_PLAYERS_PREFIX}${oldMapId}`;
        await this.redisService.srem(oldMapKey, socketId);

        // 添加到新地图
        const newMapKey = `${this.MAP_PLAYERS_PREFIX}${mapId}`;
        await this.redisService.sadd(newMapKey, socketId);
        await this.redisService.expire(newMapKey, this.SESSION_TIMEOUT);

        this.logger.log('玩家切换地图', {
          operation: 'updatePlayerPosition',
          socketId,
          userId: session.userId,
          oldMapId,
          newMapId: mapId,
          position: { x, y },
        });
      }

      this.logger.debug('玩家位置更新成功', {
        operation: 'updatePlayerPosition',
        socketId,
        mapId,
        position: { x, y },
        mapChanged,
      });

      return true;

    } catch (error) {
      const err = error as Error;
      this.logger.error('更新玩家位置失败', {
        operation: 'updatePlayerPosition',
        socketId,
        mapId,
        position: { x, y },
        error: err.message,
        timestamp: new Date().toISOString(),
      }, err.stack);

      return false;
    }
  }

  /**
   * 销毁会话
   * 
   * 功能描述：
   * 清理玩家会话数据，从地图玩家列表中移除，释放相关资源
   * 
   * 业务逻辑：
   * 1. 获取会话信息
   * 2. 从地图玩家列表中移除
   * 3. 删除用户会话映射
   * 4. 删除会话数据
   * 
   * @param socketId WebSocket连接ID
   * @returns Promise<boolean> 是否销毁成功
   */
  async destroySession(socketId: string): Promise<boolean> {
    this.logger.log('开始销毁游戏会话', {
      operation: 'destroySession',
      socketId,
      timestamp: new Date().toISOString(),
    });

    try {
      if (!socketId || !socketId.trim()) {
        this.logger.warn('销毁会话失败：socketId为空', {
          operation: 'destroySession',
        });
        return false;
      }

      // 获取会话信息
      const sessionKey = `${this.SESSION_PREFIX}${socketId}`;
      const sessionData = await this.redisService.get(sessionKey);
      
      if (!sessionData) {
        this.logger.log('会话不存在，跳过销毁', {
          operation: 'destroySession',
          socketId,
        });
        return true;
      }

      const session = this.deserializeSession(sessionData);

      // 从地图玩家列表中移除
      const mapKey = `${this.MAP_PLAYERS_PREFIX}${session.currentMap}`;
      await this.redisService.srem(mapKey, socketId);

      // 删除用户会话映射
      const userSessionKey = `${this.USER_SESSION_PREFIX}${session.userId}`;
      await this.redisService.del(userSessionKey);

      // 删除会话数据
      await this.redisService.del(sessionKey);

      this.logger.log('游戏会话销毁成功', {
        operation: 'destroySession',
        socketId,
        userId: session.userId,
        currentMap: session.currentMap,
        timestamp: new Date().toISOString(),
      });

      return true;

    } catch (error) {
      const err = error as Error;
      this.logger.error('销毁游戏会话失败', {
        operation: 'destroySession',
        socketId,
        error: err.message,
        timestamp: new Date().toISOString(),
      }, err.stack);

      // 即使失败也要尝试清理会话数据
      try {
        const sessionKey = `${this.SESSION_PREFIX}${socketId}`;
        await this.redisService.del(sessionKey);
      } catch (cleanupError) {
        const cleanupErr = cleanupError as Error;
        this.logger.error('会话清理失败', {
          operation: 'destroySession',
          socketId,
          error: cleanupErr.message,
        });
      }

      return false;
    }
  }

  /**
   * 清理过期会话
   * 
   * 功能描述：
   * 定时任务，清理超时的会话数据和相关资源
   * 
   * 业务逻辑：
   * 1. 获取所有地图的玩家列表
   * 2. 检查每个会话的最后活动时间
   * 3. 清理超过30分钟未活动的会话
   * 4. 返回需要注销的Zulip队列ID列表
   * 
   * @param timeoutMinutes 超时时间（分钟），默认30分钟
   * @returns Promise<{cleanedCount: number, zulipQueueIds: string[]}> 清理结果
   */
  async cleanupExpiredSessions(timeoutMinutes: number = 30): Promise<{
    cleanedCount: number;
    zulipQueueIds: string[];
  }> {
    const startTime = Date.now();
    
    this.logger.log('开始清理过期会话', {
      operation: 'cleanupExpiredSessions',
      timeoutMinutes,
      timestamp: new Date().toISOString(),
    });

    const expiredSessions: GameSession[] = [];
    const zulipQueueIds: string[] = [];
    const timeoutMs = timeoutMinutes * 60 * 1000;
    const now = Date.now();

    try {
      // 获取所有地图的玩家列表
      const mapIds = this.configManager.getAllMapIds().length > 0 
        ? this.configManager.getAllMapIds() 
        : DEFAULT_MAP_IDS;
      
      for (const mapId of mapIds) {
        const socketIds = await this.getSocketsInMap(mapId);
        
        for (const socketId of socketIds) {
          try {
            const sessionKey = `${this.SESSION_PREFIX}${socketId}`;
            const sessionData = await this.redisService.get(sessionKey);
            
            if (!sessionData) {
              // 会话数据不存在，从地图列表中移除
              await this.redisService.srem(`${this.MAP_PLAYERS_PREFIX}${mapId}`, socketId);
              continue;
            }

            const session = this.deserializeSession(sessionData);
            const lastActivityTime = session.lastActivity instanceof Date 
              ? session.lastActivity.getTime() 
              : new Date(session.lastActivity).getTime();
            
            // 检查是否超时
            if (now - lastActivityTime > timeoutMs) {
              expiredSessions.push(session);
              zulipQueueIds.push(session.zulipQueueId);
              
              this.logger.log('发现过期会话', {
                operation: 'cleanupExpiredSessions',
                socketId: session.socketId,
                userId: session.userId,
                lastActivity: session.lastActivity,
                idleMinutes: Math.round((now - lastActivityTime) / 60000),
              });
            }
          } catch (sessionError) {
            const err = sessionError as Error;
            this.logger.warn('检查会话时出错', {
              operation: 'cleanupExpiredSessions',
              socketId,
              error: err.message,
            });
          }
        }
      }

      // 清理过期会话
      for (const session of expiredSessions) {
        try {
          await this.destroySession(session.socketId);
        } catch (destroyError) {
          const err = destroyError as Error;
          this.logger.error('清理过期会话失败', {
            operation: 'cleanupExpiredSessions',
            socketId: session.socketId,
            error: err.message,
          });
        }
      }

      const duration = Date.now() - startTime;
      
      this.logger.log('过期会话清理完成', {
        operation: 'cleanupExpiredSessions',
        cleanedCount: expiredSessions.length,
        zulipQueueCount: zulipQueueIds.length,
        duration,
        timestamp: new Date().toISOString(),
      });

      return {
        cleanedCount: expiredSessions.length,
        zulipQueueIds,
      };

    } catch (error) {
      const err = error as Error;
      this.logger.error('清理过期会话失败', {
        operation: 'cleanupExpiredSessions',
        error: err.message,
        timestamp: new Date().toISOString(),
      }, err.stack);

      return {
        cleanedCount: 0,
        zulipQueueIds: [],
      };
    }
  }

  /**
   * 检查会话是否过期
   * 
   * @param socketId WebSocket连接ID
   * @param timeoutMinutes 超时时间（分钟）
   * @returns Promise<boolean> 是否过期
   */
  async isSessionExpired(socketId: string, timeoutMinutes: number = 30): Promise<boolean> {
    try {
      const sessionKey = `${this.SESSION_PREFIX}${socketId}`;
      const sessionData = await this.redisService.get(sessionKey);
      
      if (!sessionData) {
        return true; // 会话不存在视为过期
      }

      const session = this.deserializeSession(sessionData);
      const lastActivityTime = session.lastActivity instanceof Date 
        ? session.lastActivity.getTime() 
        : new Date(session.lastActivity).getTime();
      
      const timeoutMs = timeoutMinutes * 60 * 1000;
      return Date.now() - lastActivityTime > timeoutMs;

    } catch (error) {
      return true; // 出错时视为过期
    }
  }

  /**
   * 刷新会话活动时间
   * 
   * @param socketId WebSocket连接ID
   * @returns Promise<boolean> 是否刷新成功
   */
  async refreshSession(socketId: string): Promise<boolean> {
    try {
      const sessionKey = `${this.SESSION_PREFIX}${socketId}`;
      const sessionData = await this.redisService.get(sessionKey);
      
      if (!sessionData) {
        return false;
      }

      const session = this.deserializeSession(sessionData);
      session.lastActivity = new Date();
      
      await this.redisService.setex(sessionKey, this.SESSION_TIMEOUT, this.serializeSession(session));
      
      // 同时刷新用户会话映射的过期时间
      const userSessionKey = `${this.USER_SESSION_PREFIX}${session.userId}`;
      await this.redisService.expire(userSessionKey, this.SESSION_TIMEOUT);

      return true;

    } catch (error) {
      const err = error as Error;
      this.logger.error('刷新会话失败', {
        operation: 'refreshSession',
        socketId,
        error: err.message,
      });
      return false;
    }
  }

  /**
   * 获取会话统计信息
   * 
   * 功能描述：
   * 获取当前系统中的会话统计信息，包括总会话数和地图分布
   * 
   * @returns Promise<SessionStats> 会话统计信息
   */
  async getSessionStats(): Promise<SessionStats> {
    try {
      // 获取所有地图的玩家列表
      const mapIds = this.configManager.getAllMapIds().length > 0 
        ? this.configManager.getAllMapIds() 
        : DEFAULT_MAP_IDS;
      const mapDistribution: Record<string, number> = {};
      let totalSessions = 0;

      for (const mapId of mapIds) {
        const mapKey = `${this.MAP_PLAYERS_PREFIX}${mapId}`;
        const players = await this.redisService.smembers(mapKey);
        mapDistribution[mapId] = players.length;
        totalSessions += players.length;
      }

      this.logger.debug('获取会话统计信息', {
        operation: 'getSessionStats',
        totalSessions,
        mapDistribution,
      });

      return {
        totalSessions,
        mapDistribution,
      };

    } catch (error) {
      const err = error as Error;
      this.logger.error('获取会话统计失败', {
        operation: 'getSessionStats',
        error: err.message,
      });

      return {
        totalSessions: 0,
        mapDistribution: {},
      };
    }
  }

  /**
   * 获取所有活跃会话
   * 
   * 功能描述：
   * 获取指定地图中所有活跃会话的详细信息
   * 
   * @param mapId 地图ID（可选，不传则获取所有地图）
   * @returns Promise<GameSession[]> 会话列表
   */
  async getAllSessions(mapId?: string): Promise<GameSession[]> {
    try {
      const sessions: GameSession[] = [];
      
      if (mapId) {
        // 获取指定地图的会话
        const socketIds = await this.getSocketsInMap(mapId);
        for (const socketId of socketIds) {
          const session = await this.getSession(socketId);
          if (session) {
            sessions.push(session);
          }
        }
      } else {
        // 获取所有地图的会话
        const mapIds = this.configManager.getAllMapIds().length > 0 
          ? this.configManager.getAllMapIds() 
          : DEFAULT_MAP_IDS;
        for (const map of mapIds) {
          const socketIds = await this.getSocketsInMap(map);
          for (const socketId of socketIds) {
            const session = await this.getSession(socketId);
            if (session) {
              sessions.push(session);
            }
          }
        }
      }

      return sessions;

    } catch (error) {
      const err = error as Error;
      this.logger.error('获取所有会话失败', {
        operation: 'getAllSessions',
        mapId,
        error: err.message,
      });

      return [];
    }
  }
}

