/**
 * 聊天业务服务
 *
 * 功能描述：
 * - 实现聊天相关的业务逻辑
 * - 协调会话管理、消息过滤等子服务
 * - 实现游戏内实时聊天 + Zulip 异步同步
 *
 * 架构层级：Business Layer（业务层）
 *
 * 核心优化：
 * - 🚀 游戏内实时广播：后端直接广播给同区域用户
 * - 🔄 Zulip异步同步：消息异步存储到Zulip
 * - ⚡ 低延迟聊天体验
 *
 * 最近修改：
 * - 2026-01-15: 功能完善 - WebSocket登录时自动初始化用户Zulip客户端 (修改者: AI)
 * - 2026-01-14: 代码规范优化 - 提取魔法数字为常量 (修改者: moyin)
 * - 2026-01-14: 代码规范优化 - 补充类级别JSDoc注释 (修改者: moyin)
 * - 2026-01-14: 代码规范优化 - 补充接口定义的JSDoc注释 (修改者: moyin)
 * - 2026-01-14: 代码规范优化 - 完善文件头注释和方法注释规范 (修改者: moyin)
 *
 * @author moyin
 * @version 1.1.0
 * @since 2026-01-14
 * @lastModified 2026-01-15
 */

import { Injectable, Logger, Inject } from '@nestjs/common';
import { randomUUID } from 'crypto';
import { ChatSessionService } from './services/chat_session.service';
import type { ChatFriendInfo, ChatFriendRequestInfo, MapPlayerPresence } from './services/chat_session.service';
import { ChatFilterService } from './services/chat_filter.service';
import {
  IZulipClientPoolService,
  IApiKeySecurityService,
} from '../../core/zulip_core/zulip_core.interfaces';
import type { IPlayerAppearance, ICafeCompanionPresence } from '../../core/session_core/session_core.interfaces';
import { LoginCoreService } from '../../core/login_core/login_core.service';
import { ZulipAccountsService } from '../../core/db/zulip_accounts/zulip_accounts.service';
import { ZulipAccountsMemoryService } from '../../core/db/zulip_accounts/zulip_accounts_memory.service';
import { AccountProfileService } from '../auth/account_profile.service';
import { TaskService } from '../tasks/task.service';

// ========== 接口定义 ==========

/**
 * 聊天消息请求接口
 */
export interface ChatMessageRequest {
  /** WebSocket连接ID */
  socketId: string;
  /** 消息内容 */
  content: string;
  /** 消息范围：local(本地)、global(全局) */
  scope: string;
  /** 目标地图ID（可选，不传则使用会话当前地图） */
  mapId?: string;
  /** 私聊目标用户ID */
  targetUserId?: string;
  /** 私聊目标用户名（客户端展示用） */
  targetUsername?: string;
  /** 私聊来源上下文：whisper / friends */
  privateContext?: string;
  /** 是否同步显示角色气泡 */
  bubble?: boolean;
}

/**
 * 聊天消息响应接口
 */
export interface ChatMessageResponse {
  /** 是否成功 */
  success: boolean;
  /** 消息ID（成功时返回） */
  messageId?: string;
  /** 错误信息（失败时返回） */
  error?: string;
}

/**
 * 玩家登录请求接口
 */
export interface PlayerLoginRequest {
  /** 认证Token */
  token: string;
  /** WebSocket连接ID */
  socketId: string;
}

/**
 * 登录响应接口
 */
export interface LoginResponse {
  /** 是否成功 */
  success: boolean;
  /** 会话ID（成功时返回） */
  sessionId?: string;
  /** 用户ID（成功时返回） */
  userId?: string;
  /** 用户名（成功时返回） */
  username?: string;
  /** 当前地图ID（成功时返回） */
  currentMap?: string;
  /** 错误信息（失败时返回） */
  error?: string;
}

/**
 * 位置更新请求接口
 */
export interface PositionUpdateRequest {
  /** WebSocket连接ID */
  socketId: string;
  /** X坐标 */
  x: number;
  /** Y坐标 */
  y: number;
  /** 地图ID */
  mapId: string;
  /** 外观同步信息 */
  appearance?: IPlayerAppearance;
}

export interface PlayerPresenceStateUpdateRequest {
  userId: string;
  mapId?: string;
  x?: number;
  y?: number;
  cafeCompanion?: ICafeCompanionPresence | null;
  movementLocked?: boolean;
}

/**
 * 地图在线玩家快照项
 */
export interface MapPlayerSnapshotItem {
  /** 用户ID */
  userId: string;
  /** 用户名 */
  username: string;
  /** 地图ID */
  mapId: string;
  /** X坐标 */
  x: number;
  /** Y坐标 */
  y: number;
  /** 外观同步信息 */
  appearance?: IPlayerAppearance;
  /** 皮肤ID（兼容前端实时位置协议） */
  skinId?: string;
  /** 头像ID（兼容前端实时位置协议） */
  avatarId?: string;
  /** 咖啡店陪伴服务状态 */
  cafeCompanion?: ICafeCompanionPresence | null;
  /** 是否锁定移动 */
  movementLocked?: boolean;
}

/**
 * 好友操作请求接口
 */
export interface FriendActionRequest {
  /** WebSocket连接ID */
  socketId: string;
  /** 好友用户ID */
  friendUserId: string;
  /** 好友用户名 */
  friendUsername?: string;
}

/**
 * 游戏聊天消息格式（用于WebSocket广播）
 */
interface GameChatMessage {
  /** 消息类型标识 */
  t: 'chat_render';
  /** 发送者用户名 */
  from: string;
  /** 发送者用户ID */
  fromUserId: string;
  /** 消息文本内容 */
  txt: string;
  /** 是否显示气泡 */
  bubble: boolean;
  /** 时间戳（ISO格式） */
  timestamp: string;
  /** 消息ID */
  messageId: string;
  /** 地图ID */
  mapId: string;
  /** 消息范围 */
  scope: string;
  /** 私聊目标用户ID */
  toUserId?: string;
  /** 私聊目标用户名 */
  toUsername?: string;
  /** 私聊来源上下文：whisper / friends */
  privateContext?: string;
}

/**
 * 聊天WebSocket网关接口
 */
interface IChatWebSocketGateway {
  /**
   * 向指定地图广播消息
   * @param mapId 地图ID
   * @param data 广播数据
   * @param excludeId 排除的socketId（可选）
   */
  broadcastToMap(mapId: string, data: any, excludeId?: string): void;
  /**
   * 向所有已认证玩家广播消息
   * @param data 广播数据
   * @param excludeId 排除的socketId（可选）
   */
  broadcastToAll(data: any, excludeId?: string): void;
  /**
   * 向指定玩家发送消息
   * @param socketId WebSocket连接ID
   * @param data 发送数据
   */
  sendToPlayer(socketId: string, data: any): void;
}

/**
 * 聊天业务服务类
 *
 * 职责：
 * - 处理玩家登录/登出的会话管理
 * - 协调消息过滤和验证流程
 * - 实现游戏内实时广播和Zulip异步同步
 *
 * 主要方法：
 * - handlePlayerLogin() - 处理玩家登录认证和会话创建
 * - handlePlayerLogout() - 处理玩家登出和资源清理
 * - sendChatMessage() - 发送聊天消息并广播
 * - updatePlayerPosition() - 更新玩家位置信息
 *
 * 使用场景：
 * - 游戏客户端通过WebSocket连接后的聊天功能
 * - 需要实时广播和持久化存储的聊天场景
 */
@Injectable()
export class ChatService {
  private readonly logger = new Logger(ChatService.name);
  private readonly DEFAULT_MAP = 'whale_port';
  private readonly DEFAULT_POSITION = { x: 400, y: 300 };
  private readonly DEFAULT_PAGE_SIZE = 50;
  private readonly MAX_HISTORY_MESSAGES = 200;
  private readonly inMemoryHistory = new Map<string, GameChatMessage[]>();
  private websocketGateway: IChatWebSocketGateway;

  constructor(
    @Inject('ZULIP_CLIENT_POOL_SERVICE')
    private readonly zulipClientPool: IZulipClientPoolService,
    private readonly sessionService: ChatSessionService,
    private readonly filterService: ChatFilterService,
    @Inject('API_KEY_SECURITY_SERVICE')
    private readonly apiKeySecurityService: IApiKeySecurityService,
    private readonly loginCoreService: LoginCoreService,
    @Inject('ZulipAccountsService')
    private readonly zulipAccountsService: ZulipAccountsService | ZulipAccountsMemoryService,
    private readonly accountProfileService: AccountProfileService,
    private readonly taskService: TaskService,
  ) {
    this.logger.log('ChatService初始化完成');
  }

  /**
   * 设置WebSocket网关引用
   * @param gateway WebSocket网关实例
   */
  setWebSocketGateway(gateway: IChatWebSocketGateway): void {
    this.websocketGateway = gateway;
    this.logger.log('WebSocket网关引用设置完成');
  }

  /**
   * 处理玩家登录
   * @param request 登录请求，包含token和socketId
   * @returns 登录响应，包含会话信息或错误信息
   */
  async handlePlayerLogin(request: PlayerLoginRequest): Promise<LoginResponse> {
    const startTime = Date.now();

    this.logger.log('开始处理玩家登录', {
      operation: 'handlePlayerLogin',
      socketId: request.socketId,
    });

    try {
      // 1. 验证参数
      if (!request.token?.trim() || !request.socketId?.trim()) {
        return { success: false, error: 'Token或socketId不能为空' };
      }

      // 2. 验证Token
      const userInfo = await this.validateGameToken(request.token);
      if (!userInfo) {
        return { success: false, error: 'Token验证失败' };
      }

      // 3. 初始化用户的Zulip客户端（从数据库获取Zulip账号信息）
      await this.initializeZulipClientForUser(userInfo.userId);

      // 4. 创建会话
      const sessionResult = await this.createUserSession(request.socketId, userInfo);

      this.logger.log('玩家登录成功', {
        operation: 'handlePlayerLogin',
        socketId: request.socketId,
        userId: userInfo.userId,
        duration: Date.now() - startTime,
      });

      return {
        success: true,
        sessionId: sessionResult.sessionId,
        userId: userInfo.userId,
        username: userInfo.username,
        currentMap: sessionResult.currentMap,
      };

    } catch (error) {
      const err = error as Error;
      this.logger.error('玩家登录失败', { error: err.message });
      return { success: false, error: '登录失败，请稍后重试' };
    }
  }

  /**
   * 处理玩家登出
   * @param socketId WebSocket连接ID
   * @param reason 登出原因：manual(手动)、timeout(超时)、disconnect(断开)
   */
  async handlePlayerLogout(socketId: string, reason: 'manual' | 'timeout' | 'disconnect' = 'manual'): Promise<void> {
    this.logger.log('开始处理玩家登出', { socketId, reason });

    try {
      const session = await this.sessionService.getSession(socketId);
      if (!session) return;

      const userId = session.userId;

      // 清理Zulip客户端（注意：不删除Redis中的API Key，保持持久化）
      if (userId) {
        try {
          await this.zulipClientPool.destroyUserClient(userId);
        } catch (e) {
          this.logger.warn('Zulip客户端清理失败', { error: (e as Error).message });
        }
      }

      // 销毁会话
      await this.sessionService.destroySession(socketId);

      this.logger.log('玩家登出完成', { socketId, userId, reason });

    } catch (error) {
      this.logger.error('玩家登出失败', { error: (error as Error).message });
    }
  }

  /**
   * 发送聊天消息
   * @param request 聊天消息请求，包含socketId、content和scope
   * @returns 发送结果，包含messageId或错误信息
   */
  async sendChatMessage(request: ChatMessageRequest): Promise<ChatMessageResponse> {
    const startTime = Date.now();

    this.logger.log('开始处理聊天消息', {
      operation: 'sendChatMessage',
      socketId: request.socketId,
      contentLength: request.content.length,
    });

    try {
      // 1. 获取会话
      const session = await this.sessionService.getSession(request.socketId);
      if (!session) {
        return { success: false, error: '会话不存在，请重新登录' };
      }

      const normalizedScope = this.normalizeChatScope(request.scope);

      if (normalizedScope === 'private' && !request.targetUserId?.trim()) {
        return { success: false, error: '请选择悄悄话对象' };
      }

      // 2. 确定目标地图（优先使用请求中的mapId，否则使用会话当前地图）
      const targetMapId = request.mapId || session.currentMap;

      // 3. 获取上下文
      const context = await this.sessionService.injectContext(request.socketId, targetMapId);
      const targetStream = context.stream;
      const targetTopic = context.topic || 'General';

      // 4. 消息验证
      const validationResult = await this.filterService.validateMessage(
        session.userId,
        request.content,
        targetStream,
        targetMapId,
      );

      if (!validationResult.allowed) {
        return { success: false, error: validationResult.reason || '消息发送失败' };
      }

      const messageContent = validationResult.filteredContent || request.content;
      const messageId = `game_${Date.now()}_${session.userId}`;

      // 5. 🚀 立即广播给游戏内玩家（根据scope决定广播范围）
      const gameMessage: GameChatMessage = {
        t: 'chat_render',
        from: session.username,
        fromUserId: session.userId,
        txt: messageContent,
        bubble: Boolean(request.bubble),
        timestamp: new Date().toISOString(),
        messageId,
        mapId: targetMapId,
        scope: normalizedScope,
      };

      if (normalizedScope === 'private') {
        gameMessage.toUserId = request.targetUserId?.trim();
        gameMessage.toUsername = request.targetUsername?.trim() || undefined;
        gameMessage.privateContext = request.privateContext?.trim() || undefined;
      }

      // local: 当前地图；global: 所有在线玩家；private: 仅发送者与目标玩家。
      try {
        await this.dispatchGameChatMessage(gameMessage, request.socketId);
        this.recordChatHistory(gameMessage);
      } catch (dispatchError) {
        const message = (dispatchError as Error).message || '消息发送失败';
        return { success: false, error: message };
      }

      // 6. 🔄 异步同步到Zulip
      if (normalizedScope !== 'private') {
        this.syncToZulipAsync(session.userId, targetStream, targetTopic, messageContent, messageId)
          .catch(e => this.logger.warn('Zulip同步失败', { error: (e as Error).message }));
      }

      if (normalizedScope === 'global') {
        await this.taskService.recordActivity(BigInt(session.userId), 'public_message_sent')
          .catch((error: unknown) => this.logger.warn('记录公共聊天任务失败', { error: error instanceof Error ? error.message : String(error) }));
      }

      this.logger.log('聊天消息发送完成', {
        operation: 'sendChatMessage',
        messageId,
        duration: Date.now() - startTime,
      });

      return { success: true, messageId };

    } catch (error) {
      this.logger.error('聊天消息发送失败', { error: (error as Error).message });
      return { success: false, error: '消息发送失败，请稍后重试' };
    }
  }

  /**
   * 更新玩家位置
   * @param request 位置更新请求，包含socketId、坐标和mapId
   * @returns 更新是否成功
   */
  async updatePlayerPosition(request: PositionUpdateRequest): Promise<boolean> {
    try {
      if (!request.socketId?.trim() || !request.mapId?.trim()) {
        return false;
      }

      return await this.sessionService.updatePlayerPosition(
        request.socketId,
        request.mapId,
        request.x,
        request.y,
        {
          appearance: request.appearance,
        },
      );
    } catch (error) {
      this.logger.error('更新位置失败', { error: (error as Error).message });
      return false;
    }
  }

  async updatePlayerPresenceState(
    request: PlayerPresenceStateUpdateRequest,
  ): Promise<{ success: boolean; presence?: MapPlayerSnapshotItem; socketId?: string; error?: string }> {
    try {
      const normalizedUserId = request.userId?.trim();
      if (!normalizedUserId) {
        return { success: false, error: '用户ID不能为空' };
      }

      const position = Number.isFinite(request.x) && Number.isFinite(request.y)
        ? { x: Number(request.x), y: Number(request.y) }
        : undefined;
      const presence = await this.sessionService.updateBusinessPresenceByUserId(normalizedUserId, {
        mapId: request.mapId,
        position,
        cafeCompanion: request.cafeCompanion,
        movementLocked: request.movementLocked,
      });

      if (!presence) {
        return { success: false, error: '玩家当前不在线' };
      }

      const payload = this.toMapPlayerSnapshotItem(presence);
      this.broadcastPlayerPresence(payload);
      return { success: true, presence: payload, socketId: presence.socketId };
    } catch (error) {
      this.logger.error('更新玩家业务状态失败', { userId: request.userId, error: (error as Error).message });
      return { success: false, error: '更新玩家业务状态失败' };
    }
  }

  /**
   * 获取指定地图的在线玩家快照
   * @param mapId 地图ID
   * @param excludeSocketId 排除的WebSocket连接ID
   * @returns 在线玩家列表
   */
  async getMapPlayerSnapshot(mapId: string, excludeSocketId?: string): Promise<MapPlayerSnapshotItem[]> {
    const players = await this.sessionService.getPlayersInMap(mapId);
    return players
      .filter((player: MapPlayerPresence) => player.socketId !== excludeSocketId)
      .map((player: MapPlayerPresence) => this.toMapPlayerSnapshotItem(player));
  }

  async refreshPlayerAppearance(socketId: string): Promise<MapPlayerSnapshotItem | null> {
    const session = await this.sessionService.getSession(socketId);
    if (!session) return null;

    const appearance = await this.resolveAccountAppearance(session.userId);
    await this.sessionService.updatePlayerPosition(
      socketId,
      session.currentMap,
      Number(session.position?.x ?? 0),
      Number(session.position?.y ?? 0),
      { appearance },
    );

    const updatedSession = await this.sessionService.getSession(socketId);
    if (!updatedSession) return null;
    return this.toMapPlayerSnapshotItem({
      socketId: updatedSession.socketId,
      userId: updatedSession.userId,
      username: updatedSession.username,
      mapId: updatedSession.currentMap,
      x: Number(updatedSession.position?.x ?? 0),
      y: Number(updatedSession.position?.y ?? 0),
      appearance: updatedSession.appearance,
      cafeCompanion: updatedSession.cafeCompanion ?? null,
      movementLocked: Boolean(updatedSession.movementLocked),
    });
  }

  /**
   * 添加好友
   * @param request 好友操作请求
   * @returns 好友信息
   */
  async addFriend(request: FriendActionRequest): Promise<{ success: boolean; friend?: ChatFriendInfo; error?: string }> {
    try {
      const session = await this.sessionService.getSession(request.socketId);
      if (!session) {
        return { success: false, error: '会话不存在，请重新登录' };
      }

      const friend = await this.sessionService.addFriend(
        session.userId,
        request.friendUserId,
        request.friendUsername,
      );
      return { success: true, friend };
    } catch (error) {
      return { success: false, error: (error as Error).message || '添加好友失败' };
    }
  }

  /**
   * 发送好友请求
   * @param request 好友操作请求
   * @returns 好友请求信息
   */
  async requestFriend(
    request: FriendActionRequest,
  ): Promise<{ success: boolean; friendRequest?: ChatFriendRequestInfo; targetSocketId?: string | null; error?: string }> {
    try {
      const session = await this.sessionService.getSession(request.socketId);
      if (!session) {
        return { success: false, error: '会话不存在，请重新登录' };
      }

      const friendRequest = await this.sessionService.createFriendRequest(
        session.userId,
        session.username,
        request.friendUserId,
      );
      const targetSocketId = await this.sessionService.getSocketIdByUserId(request.friendUserId);
      if (targetSocketId && this.websocketGateway) {
        this.websocketGateway.sendToPlayer(targetSocketId, {
          t: 'friend_request_received',
          request: friendRequest,
        });
      }

      return { success: true, friendRequest, targetSocketId };
    } catch (error) {
      return { success: false, error: (error as Error).message || '好友请求发送失败' };
    }
  }

  /**
   * 接受好友请求
   * @param request 好友操作请求
   * @returns 添加后的好友信息
   */
  async acceptFriendRequest(
    request: FriendActionRequest,
  ): Promise<{ success: boolean; friend?: ChatFriendInfo; reciprocalFriend?: ChatFriendInfo; error?: string }> {
    try {
      const session = await this.sessionService.getSession(request.socketId);
      if (!session) {
        return { success: false, error: '会话不存在，请重新登录' };
      }

      const result = await this.sessionService.acceptFriendRequest(
        session.userId,
        request.friendUserId,
        session.username,
      );
      const requesterSocketId = await this.sessionService.getSocketIdByUserId(request.friendUserId);
      if (requesterSocketId && this.websocketGateway) {
        this.websocketGateway.sendToPlayer(requesterSocketId, {
          t: 'friend_request_accepted',
          friend: result.reciprocalFriend,
        });
      }

      return { success: true, friend: result.friend, reciprocalFriend: result.reciprocalFriend };
    } catch (error) {
      return { success: false, error: (error as Error).message || '接受好友请求失败' };
    }
  }

  /**
   * 拒绝好友请求
   * @param request 好友操作请求
   * @returns 操作结果
   */
  async rejectFriendRequest(request: FriendActionRequest): Promise<{ success: boolean; error?: string }> {
    try {
      const session = await this.sessionService.getSession(request.socketId);
      if (!session) {
        return { success: false, error: '会话不存在，请重新登录' };
      }

      await this.sessionService.rejectFriendRequest(session.userId, request.friendUserId);
      const requesterSocketId = await this.sessionService.getSocketIdByUserId(request.friendUserId);
      if (requesterSocketId && this.websocketGateway) {
        this.websocketGateway.sendToPlayer(requesterSocketId, {
          t: 'friend_request_rejected',
          userId: session.userId,
          username: session.username,
        });
      }

      return { success: true };
    } catch (error) {
      return { success: false, error: (error as Error).message || '拒绝好友请求失败' };
    }
  }

  /**
   * 移除好友
   * @param request 好友操作请求
   * @returns 操作结果
   */
  async removeFriend(request: FriendActionRequest): Promise<{ success: boolean; error?: string }> {
    try {
      const session = await this.sessionService.getSession(request.socketId);
      if (!session) {
        return { success: false, error: '会话不存在，请重新登录' };
      }

      await this.sessionService.removeFriend(session.userId, request.friendUserId);
      return { success: true };
    } catch (error) {
      return { success: false, error: (error as Error).message || '移除好友失败' };
    }
  }

  /**
   * 获取好友列表
   * @param socketId WebSocket连接ID
   * @returns 好友列表
   */
  async getFriends(socketId: string): Promise<{ success: boolean; friends?: ChatFriendInfo[]; requests?: ChatFriendRequestInfo[]; error?: string }> {
    try {
      const session = await this.sessionService.getSession(socketId);
      if (!session) {
        return { success: false, error: '会话不存在，请重新登录' };
      }

      const friends = await this.sessionService.getFriends(session.userId);
      const requests = await this.sessionService.getFriendRequests(session.userId);
      return { success: true, friends, requests };
    } catch (error) {
      return { success: false, error: (error as Error).message || '获取好友列表失败' };
    }
  }

  /**
   * 获取聊天历史
   * @param query 查询参数，包含mapId、limit和offset
   * @returns 聊天历史记录列表
   */
  async getChatHistory(query: { mapId?: string; limit?: number; offset?: number }) {
    const mapId = (query.mapId || this.DEFAULT_MAP).trim();
    const limit = Math.max(1, Math.min(Number(query.limit || this.DEFAULT_PAGE_SIZE), this.DEFAULT_PAGE_SIZE));
    const offset = Math.max(0, Number(query.offset || 0));
    const messages = (this.inMemoryHistory.get(mapId) || []).slice().reverse().map((message, index) => ({
      id: offset + index + 1,
      messageId: message.messageId,
      sender: message.from,
      fromUserId: message.fromUserId,
      content: message.txt,
      scope: message.scope,
      mapId: message.mapId,
      timestamp: message.timestamp,
      streamName: message.mapId,
      topicName: 'Game Chat',
      bubble: message.bubble,
      toUserId: message.toUserId,
      toUsername: message.toUsername,
      privateContext: message.privateContext,
    }));

    return {
      success: true,
      messages: messages.slice(offset, offset + limit),
      total: messages.length,
      count: Math.min(Math.max(messages.length - offset, 0), limit),
    };
  }

  /**
   * 获取会话信息
   * @param socketId WebSocket连接ID
   * @returns 会话信息或null
   */
  async getSession(socketId: string) {
    return this.sessionService.getSession(socketId);
  }

  private recordChatHistory(message: GameChatMessage): void {
    if (message.scope === 'private') {
      return;
    }
    const mapId = message.mapId || this.DEFAULT_MAP;
    const history = this.inMemoryHistory.get(mapId) || [];
    history.push(message);
    if (history.length > this.MAX_HISTORY_MESSAGES) {
      history.splice(0, history.length - this.MAX_HISTORY_MESSAGES);
    }
    this.inMemoryHistory.set(mapId, history);
  }

  // ========== 私有方法 ==========

  /**
   * 初始化用户的Zulip客户端
   *
   * 功能描述：
   * 1. 从数据库获取用户的Zulip账号信息
   * 2. 检查Redis中是否已有API Key缓存
   * 3. 如果Redis中没有，从数据库标记判断是否需要重新获取
   * 4. 创建Zulip客户端实例
   *
   * @param userId 用户ID
   */
  private async initializeZulipClientForUser(userId: string): Promise<void> {
    this.logger.log('开始初始化用户Zulip客户端', {
      operation: 'initializeZulipClientForUser',
      userId,
    });

    try {
      // 1. 从数据库获取用户的Zulip账号信息
      const zulipAccount = await this.zulipAccountsService.findByGameUserId(userId);

      if (!zulipAccount) {
        this.logger.debug('用户没有关联的Zulip账号，跳过Zulip客户端初始化', {
          operation: 'initializeZulipClientForUser',
          userId,
        });
        return;
      }

      if (zulipAccount.status !== 'active') {
        this.logger.warn('用户Zulip账号状态异常，跳过初始化', {
          operation: 'initializeZulipClientForUser',
          userId,
          status: zulipAccount.status,
        });
        return;
      }

      // 2. 检查Redis中是否已有API Key
      const existingApiKey = await this.apiKeySecurityService.getApiKey(userId);

      if (existingApiKey.success && existingApiKey.apiKey) {
        this.logger.log('Redis中已有API Key缓存，直接创建Zulip客户端', {
          operation: 'initializeZulipClientForUser',
          userId,
          zulipEmail: zulipAccount.zulipEmail,
        });

        // 创建Zulip客户端
        await this.createZulipClientWithApiKey(
          userId,
          zulipAccount.zulipEmail,
          existingApiKey.apiKey
        );
        return;
      }

      // 3. Redis中没有API Key，记录警告
      // 注意：由于登录时没有用户密码，无法重新生成API Key
      // API Key应该在用户注册时存储到Redis，如果丢失需要用户重新绑定Zulip账号
      this.logger.warn('Redis中没有用户的Zulip API Key缓存，无法创建Zulip客户端', {
        operation: 'initializeZulipClientForUser',
        userId,
        zulipEmail: zulipAccount.zulipEmail,
        hint: '用户可能需要重新绑定Zulip账号',
      });

    } catch (error) {
      const err = error as Error;
      this.logger.error('初始化用户Zulip客户端失败', {
        operation: 'initializeZulipClientForUser',
        userId,
        error: err.message,
      });
      // 不抛出异常，允许用户继续登录（只是没有Zulip功能）
    }
  }

  /**
   * 使用API Key创建Zulip客户端
   *
   * @param userId 用户ID
   * @param zulipEmail Zulip邮箱
   * @param apiKey API Key
   */
  private async createZulipClientWithApiKey(
    userId: string,
    zulipEmail: string,
    apiKey: string
  ): Promise<void> {
    try {
      const clientInstance = await this.zulipClientPool.createUserClient(userId, {
        username: zulipEmail,
        apiKey: apiKey,
        realm: process.env.ZULIP_SERVER_URL || 'https://zulip.xinghangee.icu/',
      });

      this.logger.log('Zulip客户端创建成功', {
        operation: 'createZulipClientWithApiKey',
        userId,
        zulipEmail,
        queueId: clientInstance.queueId,
      });
    } catch (error) {
      const err = error as Error;
      this.logger.error('创建Zulip客户端失败', {
        operation: 'createZulipClientWithApiKey',
        userId,
        zulipEmail,
        error: err.message,
      });
      throw error;
    }
  }

  private async validateGameToken(token: string) {
    try {
      const payload = await this.loginCoreService.verifyToken(token, 'access');
      if (!payload?.sub) return null;

      return {
        userId: payload.sub,
        username: payload.username || `user_${payload.sub}`,
        email: payload.email || `${payload.sub}@example.com`,
        zulipEmail: undefined,
        zulipApiKey: undefined,
      };
    } catch (error) {
      this.logger.warn('Token验证失败', { error: (error as Error).message });
      return null;
    }
  }

  private async createUserSession(socketId: string, userInfo: any) {
    const sessionId = randomUUID();
    const appearance = await this.resolveAccountAppearance(userInfo.userId);

    // 尝试获取已创建的Zulip客户端的队列ID
    let zulipQueueId = `queue_${sessionId}`;
    try {
      const existingClient = await this.zulipClientPool.getUserClient(userInfo.userId);
      if (existingClient?.queueId) {
        zulipQueueId = existingClient.queueId;
      }
    } catch (e) {
      this.logger.debug('获取Zulip客户端队列ID失败，使用默认值', {
        error: (e as Error).message
      });
    }

    const session = await this.sessionService.createSession(
      socketId,
      userInfo.userId,
      zulipQueueId,
      userInfo.username,
      this.DEFAULT_MAP,
      this.DEFAULT_POSITION,
      appearance,
    );

    return { sessionId, currentMap: session.currentMap };
  }

  private async resolveAccountAppearance(userId: string): Promise<IPlayerAppearance | undefined> {
    const normalizedUserId = String(userId || '').trim();
    if (!/^\d+$/.test(normalizedUserId)) {
      return undefined;
    }

    try {
      const accountProfile = await this.accountProfileService.getAccountProfile(BigInt(normalizedUserId));
      const profile = accountProfile.profile;
      const skinId = String(profile.skin_id || '').trim();
      const avatarId = String(profile.avatar_id || '').trim();
      const appearance: IPlayerAppearance = {};
      if (skinId) {
        appearance.skinId = skinId;
        const skinAsset = profile.owned_skins.find((skin: any) => {
          const assetSkinId = String(skin?.id ?? skin?.skin_id ?? '').trim();
          return assetSkinId === skinId;
        });
        if (skinAsset) {
          appearance.skinAsset = this.toRealtimeSkinAsset(skinAsset as Record<string, any>);
        }
      }
      if (avatarId) {
        appearance.avatarId = avatarId;
      }
      return Object.keys(appearance).length > 0 ? appearance : undefined;
    } catch (error) {
      this.logger.warn('读取账号外观失败，在线会话将等待客户端位置包补充', {
        userId: normalizedUserId,
        error: (error as Error).message,
      });
      return undefined;
    }
  }

  private toRealtimeSkinAsset(skinAsset: Record<string, any>): Record<string, any> {
    return {
      id: skinAsset.id ?? skinAsset.skin_id,
      name: skinAsset.name,
      texture_url: skinAsset.texture_url,
      mime_type: skinAsset.mime_type,
      hframes: skinAsset.hframes,
      vframes: skinAsset.vframes,
      source: skinAsset.source,
    };
  }

  private normalizeChatScope(scope: string): 'local' | 'global' | 'private' {
    const normalizedScope = scope?.trim().toLowerCase();
    if (normalizedScope === 'global' || normalizedScope === 'world') {
      return 'global';
    }
    if (normalizedScope === 'private' || normalizedScope === 'whisper' || normalizedScope === 'dm') {
      return 'private';
    }
    return 'local';
  }

  private async dispatchGameChatMessage(message: GameChatMessage, senderSocketId: string): Promise<void> {
    if (message.scope === 'global') {
      this.broadcastToAllGamePlayers(message, senderSocketId);
      return;
    }

    if (message.scope === 'private') {
      await this.sendPrivateGameMessage(message, senderSocketId);
      return;
    }

    await this.broadcastToGamePlayers(message.mapId, message, senderSocketId);
  }

  private broadcastToAllGamePlayers(message: GameChatMessage, excludeSocketId?: string): void {
    if (!this.websocketGateway) {
      throw new Error('WebSocket网关未设置');
    }

    this.websocketGateway.broadcastToAll(message, excludeSocketId);
  }

  private async sendPrivateGameMessage(message: GameChatMessage, senderSocketId: string): Promise<void> {
    if (!this.websocketGateway) {
      throw new Error('WebSocket网关未设置');
    }

    const targetUserId = message.toUserId?.trim();
    if (!targetUserId) {
      throw new Error('私聊目标用户ID不能为空');
    }

    const targetSocketId = await this.sessionService.getSocketIdByUserId(targetUserId);
    if (!targetSocketId) {
      throw new Error('悄悄话对象不在线');
    }

    this.websocketGateway.sendToPlayer(senderSocketId, message);
    if (targetSocketId !== senderSocketId) {
      this.websocketGateway.sendToPlayer(targetSocketId, message);
    }
  }

  private async broadcastToGamePlayers(mapId: string, message: GameChatMessage, excludeSocketId?: string) {
    if (!this.websocketGateway) {
      throw new Error('WebSocket网关未设置');
    }

    const sockets = await this.sessionService.getSocketsInMap(mapId);
    const targetSockets = sockets.filter(id => id !== excludeSocketId);

    for (const socketId of targetSockets) {
      try {
        this.websocketGateway.sendToPlayer(socketId, message);
      } catch (e) {
        this.logger.warn('发送消息失败', { socketId, error: (e as Error).message });
      }
    }
  }

  private toMapPlayerSnapshotItem(player: MapPlayerPresence): MapPlayerSnapshotItem {
    return {
      userId: player.userId,
      username: player.username,
      mapId: player.mapId,
      x: player.x,
      y: player.y,
      appearance: player.appearance,
      skinId: player.appearance?.skinId,
      avatarId: player.appearance?.avatarId,
      cafeCompanion: player.cafeCompanion ?? null,
      movementLocked: Boolean(player.movementLocked),
    };
  }

  private broadcastPlayerPresence(presence: MapPlayerSnapshotItem): void {
    if (!this.websocketGateway) {
      this.logger.warn('WebSocket网关未设置，跳过玩家业务状态广播', { userId: presence.userId });
      return;
    }

    this.websocketGateway.broadcastToMap(presence.mapId, {
      t: 'position_update',
      userId: presence.userId,
      username: presence.username,
      x: presence.x,
      y: presence.y,
      mapId: presence.mapId,
      appearance: presence.appearance,
      skinId: presence.skinId,
      avatarId: presence.avatarId,
      cafeCompanion: presence.cafeCompanion ?? null,
      movementLocked: Boolean(presence.movementLocked),
    });
  }

  private async syncToZulipAsync(userId: string, stream: string, topic: string, content: string, gameMessageId: string) {
    try {
      const apiKeyResult = await this.apiKeySecurityService.getApiKey(userId);
      if (!apiKeyResult.success || !apiKeyResult.apiKey) return;

      const zulipContent = `${content}\n\n*[游戏消息ID: ${gameMessageId}]*`;
      await this.zulipClientPool.sendMessage(userId, stream, topic, zulipContent);
    } catch (error) {
      this.logger.warn('Zulip同步异常', { error: (error as Error).message });
    }
  }
}
