/**
 * Redis 服务 - 管理会议状态
 * 用于维护跨 WebSocket 连接的用户状态，支持用户重新加入时恢复状态
 */

import { Injectable } from '@nestjs/common';
import { RedisService } from '../../plugins/redis/redis.service';
import type { Redis } from 'ioredis';
import type { BlacklistUser, JoinMode, UserPermissionState } from './types';

@Injectable()
export class MeetingRedisService {
  /** Redis 客户端（会议数据库 DB 0） */
  private redisClient!: Redis;

  /**
   * Redis Key 前缀配置
   *
   * 设计原则：
   * 1. 使用冒号(:)作为分隔符，符合 Redis Key 命名规范
   * 2. 按功能模块分组，便于管理和排查问题
   * 3. 所有 Key 都设置了 24 小时过期时间，自动清理
   *
   * 数据结构选择：
   * - Hash：适合存储对象类型的数据（如用户状态、黑名单）
   * - Set：适合存储需要去重的集合（如房间用户列表、Socket ID 列表）
   */
  private readonly KEY_PREFIX = {
    /**
     * 用户状态 Key
     * 用途：存储用户在会议中的权限状态（禁麦/禁视频）和 Socket 连接列表
     * 数据结构：Hash
     * Key 格式：meeting:user:{roomId}:{shortUid}
     * Hash 字段：
     *   - isAudioMuted: 是否被禁麦（'1' 或 '0'）
     *   - isVideoMuted: 是否被禁视频（'1' 或 '0'）
     *   - socketIds: JSON 字符串数组，用户的所有 Socket 连接 ID
     * 适用场景：用户断线重连、教师禁麦/禁视频后用户重新加入、多设备登录检测
     * 过期时间：24 小时
     */
    USER_STATE: 'meeting:user:',

    /**
     * 黑名单 Key
     * 用途：存储被踢出房间的用户列表，防止用户再次加入
     * 数据结构：Hash
     * Key 格式：meeting:blacklist:{roomId}
     * Hash 字段：
     *   - field: shortUid（用户短 UID）
     *   - value: JSON 字符串 { shortUid, userName }
     * 适用场景：用户被踢出后记录，下次用户尝试加入时检查
     * 过期时间：24 小时
     * 注意：黑名单在课程结束后不会被清理，需要手动调用 clearRoomAll 或单独清理
     */
    BLACKLIST: 'meeting:blacklist:',

    /**
     * 房间状态 Key
     * 用途：存储房间的全局状态信息
     * 数据结构：Hash
     * Key 格式：meeting:room:{roomId}
     * Hash 字段：
     *   - classStatus: 课堂状态（not_started | in_class | finished）
     *   - speakerUid: 当前主讲人短 UID（可选）
     *   - teacherUid: 老师短 UID（可选）
     *   - endTimestamp: 房间结束时间（毫秒时间戳，可选）
     * 适用场景：同步课堂状态、切换主讲人、判断课程是否在进行中
     * 过期时间：24 小时
     */
    ROOM_STATE: 'meeting:room:',
  };

  constructor(private readonly redisService: RedisService) {
    // ✅ 不在构造函数中初始化，改为懒加载
  }

  /**
   * 懒加载获取 Redis 客户端（第一次使用时才初始化）
   */
  private getClient(): Redis {
    if (!this.redisClient) {
      this.redisClient = this.redisService.getMeetingClient();
    }
    return this.redisClient;
  }

  /**
   * 获取用户状态 Key
   */
  private getUserStateKey(roomId: string, shortUid: number): string {
    return `${this.KEY_PREFIX.USER_STATE}${roomId}:${shortUid}`;
  }

  /**
   * 设置用户状态（Hash 结构）
   */
  async setUserState(roomId: string, shortUid: number, state: Partial<UserPermissionState>): Promise<void> {
    const key = this.getUserStateKey(roomId, shortUid);
    const hm: Record<string, string> = {};
    if (typeof state.isAudioMuted === 'boolean') {
      hm.isAudioMuted = state.isAudioMuted ? '1' : '0';
    }
    if (typeof state.isVideoMuted === 'boolean') {
      hm.isVideoMuted = state.isVideoMuted ? '1' : '0';
    }
    if (Object.keys(hm).length > 0) {
      await this.getClient().hset(key, hm);
      // 设置过期时间：24 小时（会议结束后自动清理）
      await this.getClient().expire(key, 24 * 60 * 60);
    }
  }

  /**
   * 获取用户状态（Hash 结构）
   */
  async getUserState(roomId: string, shortUid: number): Promise<UserPermissionState> {
    const key = this.getUserStateKey(roomId, shortUid);
    const map = await this.getClient().hgetall(key);
    let socketEntries: Array<{ socketId: string; joinMode: JoinMode }> = [];
    if (map?.socketEntries) {
      try {
        socketEntries = JSON.parse(map.socketEntries);
      } catch {
        socketEntries = [];
      }
    }

    let socketIds: string[] = [];
    if (socketEntries.length > 0) {
      socketIds = [...new Set(socketEntries.map((e) => e.socketId).filter(Boolean))];
    } else if (map?.socketIds) {
      try {
        socketIds = JSON.parse(map.socketIds);
        socketEntries = socketIds.map((socketId) => ({ socketId, joinMode: 'classroom' }));
      } catch {
        socketIds = [];
        socketEntries = [];
      }
    }
    return {
      isAudioMuted: map?.isAudioMuted === '1',
      isVideoMuted: map?.isVideoMuted === '1',
      socketIds,
      socketEntries,
    };
  }

  /**
   * 清理用户状态（下课或离开时调用）
   */
  async clearUserState(roomId: string, shortUid: number): Promise<void> {
    const key = this.getUserStateKey(roomId, shortUid);
    await this.getClient().del(key);
  }

  // ==================== 黑名单管理 ====================

  /**
   * 检查用户是否被踢出（黑名单中）
   * @param roomId - 房间 ID
   * @param shortUid - 短 UID
   */
  async isUserKicked(roomId: string, shortUid: number): Promise<boolean> {
    const key = `${this.KEY_PREFIX.BLACKLIST}${roomId}`;
    const exists = await this.getClient().hexists(key, String(shortUid));
    return exists === 1;
  }

  /**
   * 将用户加入黑名单
   * @param roomId - 房间 ID
   * @param shortUid - 短 UID
   * @param userName - 用户名称
   */
  async addToBlacklist(roomId: string, shortUid: number, userName: string): Promise<void> {
    const key = `${this.KEY_PREFIX.BLACKLIST}${roomId}`;
    const userData: BlacklistUser = { shortUid, userName };
    await this.getClient().hset(key, String(shortUid), JSON.stringify(userData));
    await this.getClient().expire(key, 24 * 60 * 60);
  }

  /**
   * 从黑名单移除用户（按短 UID）
   */
  async removeFromBlacklist(roomId: string, shortUid: number): Promise<void> {
    const key = `${this.KEY_PREFIX.BLACKLIST}${roomId}`;
    await this.getClient().hdel(key, String(shortUid));
  }

  /**
   * 获取黑名单中的所有用户
   * @returns 黑名单用户列表（包含短 UID 和名称）
   */
  async getBlacklist(roomId: string): Promise<BlacklistUser[]> {
    const key = `${this.KEY_PREFIX.BLACKLIST}${roomId}`;
    const values = await this.getClient().hvals(key);
    return values
      .map((v) => {
        try {
          return JSON.parse(v) as BlacklistUser;
        } catch {
          return null;
        }
      })
      .filter((u): u is BlacklistUser => u !== null);
  }

  // ==================== 房间状态管理 ====================

  /**
   * 设置课堂状态
   * @param roomId - 房间 ID
   * @param status - 状态：not_started（未开始）/ in_class（上课中）/ finished（已下课）
   */
  async setClassStatus(roomId: string, status: 'finished' | 'in_class' | 'not_started'): Promise<void> {
    const key = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`;
    await this.getClient().hset(key, { classStatus: status });
    await this.getClient().expire(key, 24 * 60 * 60);
  }

  /**
   * 设置主讲人
   * @param roomId - 房间 ID
   * @param speakerUid - 主讲人短 UID，null 表示取消主讲
   */
  async setSpeaker(roomId: string, speakerUid: number | null): Promise<void> {
    const key = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`;
    if (speakerUid === null) {
      await this.getClient().hdel(key, 'speakerUid');
    } else {
      await this.getClient().hset(key, { speakerUid: String(speakerUid) });
      await this.getClient().expire(key, 24 * 60 * 60);
    }
  }

  /**
   * 设置投屏状态
   * @param roomId - 房间 ID
   * @param screenShareUid - 投屏人短 UID，null 表示停止投屏
   */
  async setScreenSharing(roomId: string, screenShareUid: number | null, screenShareOwnerUid?: number | null): Promise<void> {
    const key = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`;
    if (screenShareUid === null) {
      await this.getClient().hdel(key, 'screenShareUid');
      await this.getClient().hdel(key, 'screenShareOwnerUid');
    } else {
      const hm: Record<string, string> = { screenShareUid: String(screenShareUid) };
      if (typeof screenShareOwnerUid === 'number' && Number.isFinite(screenShareOwnerUid) && screenShareOwnerUid > 0) {
        hm.screenShareOwnerUid = String(screenShareOwnerUid);
      }
      await this.getClient().hset(key, hm);
      await this.getClient().expire(key, 24 * 60 * 60);
    }
  }

  /**
   * 设置老师短 UID
   * @param roomId - 房间 ID
   * @param teacherUid - 老师短 UID
   */
  async setTeacherUid(roomId: string, teacherUid: number): Promise<void> {
    const key = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`;
    await this.getClient().hset(key, { teacherUid: String(teacherUid) });
    await this.getClient().expire(key, 24 * 60 * 60);
  }

  /**
   * 获取老师短 UID
   * @param roomId - 房间 ID
   * @returns 老师短 UID，不存在返回 null
   */
  async getTeacherUid(roomId: string): Promise<number | null> {
    const key = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`;
    const v = await this.getClient().hget(key, 'teacherUid');
    const uid = v ? Number(v) : NaN;
    if (!Number.isFinite(uid) || uid <= 0) {
      return null;
    }
    return uid;
  }

  /**
   * 获取房间状态
   * @returns 房间状态（包含课堂状态、主讲人、投屏状态）
   */
  async getRoomState(roomId: string): Promise<{
    classStatus: 'finished' | 'in_class' | 'not_started';
    speakerUid?: number;
    screenShareUid?: number;
    screenShareOwnerUid?: number;
    teacherUid?: number;
    endTimestamp?: number;
  }> {
    const key = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`;
    const map = await this.getClient().hgetall(key);
    const status = (map?.classStatus as any) || 'not_started';
    const speakerUid = map?.speakerUid ? Number(map.speakerUid) : undefined;
    const screenShareUid = map?.screenShareUid ? Number(map.screenShareUid) : undefined;
    const screenShareOwnerUid = map?.screenShareOwnerUid ? Number(map.screenShareOwnerUid) : undefined;
    const teacherUid = map?.teacherUid ? Number(map.teacherUid) : undefined;
    const endTimestamp = map?.endTimestamp ? Number(map.endTimestamp) : undefined;
    return {
      classStatus: status,
      ...(Number.isFinite(speakerUid) ? { speakerUid } : {}),
      ...(Number.isFinite(screenShareUid) ? { screenShareUid } : {}),
      ...(Number.isFinite(screenShareOwnerUid) ? { screenShareOwnerUid } : {}),
      ...(Number.isFinite(teacherUid) ? { teacherUid } : {}),
      ...(Number.isFinite(endTimestamp) ? { endTimestamp } : {}),
    };
  }

  /**
   * 设置房间结束时间
   * @param roomId - 房间 ID
   * @param endTimestamp - 房间结束时间（毫秒时间戳）
   * @param mode - 写入模式：max 表示只允许延长，不允许缩短
   */
  async setRoomEndTimestamp(roomId: string, endTimestamp: number, mode: 'max' | 'overwrite' = 'max'): Promise<void> {
    const key = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`;
    const next = Number(endTimestamp);
    if (!Number.isFinite(next) || next <= 0) {
      return;
    }

    if (mode === 'max') {
      const current = await this.getClient().hget(key, 'endTimestamp');
      const currentNum = current ? Number(current) : NaN;
      if (Number.isFinite(currentNum) && currentNum > 0 && currentNum >= next) {
        return;
      }
    }

    await this.getClient().hset(key, { endTimestamp: String(next) });
    await this.getClient().expire(key, 24 * 60 * 60);
  }

  /**
   * 获取房间结束时间
   * @param roomId - 房间 ID
   */
  async getRoomEndTimestamp(roomId: string): Promise<number | null> {
    const key = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`;
    const v = await this.getClient().hget(key, 'endTimestamp');
    const t = v ? Number(v) : NaN;
    if (!Number.isFinite(t) || t <= 0) {
      return null;
    }
    return t;
  }

  // ==================== Socket 连接管理 ====================

  /**
   * 添加用户 Socket 连接（存储在 USER_STATE Hash 的 socketIds 字段中）
   * @param roomId - 房间 ID
   * @param shortUid - 短 UID
   * @param socketId - Socket.IO 连接 ID
   * @param joinMode - 加入方式（课程/监控/学生）
   */
  async addSocket(roomId: string, shortUid: number, socketId: string, joinMode: JoinMode): Promise<void> {
    const key = this.getUserStateKey(roomId, shortUid);
    const currentState = await this.getUserState(roomId, shortUid);
    const socketEntries = Array.isArray(currentState.socketEntries) ? currentState.socketEntries : [];
    const existed = socketEntries.some((e) => e.socketId === socketId);
    if (!existed) {
      socketEntries.push({ socketId, joinMode });
    }
    const socketIds = [...new Set(socketEntries.map((e) => e.socketId).filter(Boolean))];
    await this.getClient().hset(key, { socketIds: JSON.stringify(socketIds), socketEntries: JSON.stringify(socketEntries) });
    await this.getClient().expire(key, 24 * 60 * 60);
  }

  /**
   * 移除用户 Socket 连接
   * @param roomId - 房间 ID
   * @param shortUid - 短 UID
   * @param socketId - Socket.IO 连接 ID
   */
  async removeSocket(roomId: string, shortUid: number, socketId: string): Promise<void> {
    const key = this.getUserStateKey(roomId, shortUid);
    const currentState = await this.getUserState(roomId, shortUid);
    const socketEntries = Array.isArray(currentState.socketEntries) ? currentState.socketEntries : [];
    const nextEntries = socketEntries.length > 0 ? socketEntries.filter((e) => e.socketId !== socketId) : [];
    const nextSocketIds =
      nextEntries.length > 0 ? [...new Set(nextEntries.map((e) => e.socketId).filter(Boolean))] : (currentState.socketIds || []).filter((id) => id !== socketId);

    if (nextSocketIds.length > 0) {
      const hm: Record<string, string> = { socketIds: JSON.stringify(nextSocketIds) };
      if (socketEntries.length > 0) {
        hm.socketEntries = JSON.stringify(nextEntries);
      }
      await this.getClient().hset(key, hm);
    } else {
      await this.getClient().del(key);
    }
  }

  /**
   * 获取用户的所有 Socket 连接 ID
   * @param roomId - 房间 ID
   * @param shortUid - 短 UID
   * @param joinMode - 加入方式（可选；用于多端共存的场景）
   * @returns Socket 连接 ID 数组
   */
  async getSocketIds(roomId: string, shortUid: number, joinMode?: JoinMode): Promise<string[]> {
    const state = await this.getUserState(roomId, shortUid);
    if (joinMode && Array.isArray(state.socketEntries) && state.socketEntries.length > 0) {
      return state.socketEntries.filter((e) => e.joinMode === joinMode).map((e) => e.socketId);
    }
    return state.socketIds || [];
  }

  // ==================== 房间用户管理 ====================

  /**
   * 添加用户到房间（通过设置用户状态来标记用户在线）
   * @param roomId - 房间 ID
   * @param shortUid - 短 UID
   */
  async addUserToRoom(roomId: string, shortUid: number): Promise<void> {
    // 通过设置用户状态来标记用户在线（设置一个占位状态）
    await this.setUserState(roomId, shortUid, {});
  }

  /**
   * 从房间移除用户（清理用户状态）
   * @param roomId - 房间 ID
   * @param shortUid - 短 UID
   */
  async removeUserFromRoom(roomId: string, shortUid: number): Promise<void> {
    // 清理用户状态即表示用户离开房间
    await this.clearUserState(roomId, shortUid);
  }

  /**
   * 获取房间中的所有用户短 UID 列表
   * 通过扫描 USER_STATE 模式来获取房间内所有用户
   * @param roomId - 房间 ID
   * @returns 短 UID 数组
   */
  async getUsersInRoom(roomId: string): Promise<number[]> {
    const pattern = `${this.KEY_PREFIX.USER_STATE}${roomId}:*`;
    const client = this.getClient();
    const userUids: number[] = [];
    let cursor = '0';

    do {
      // eslint-disable-next-line no-await-in-loop
      const res = await client.scan(cursor, 'MATCH', pattern, 'COUNT', 200);
      cursor = res[0];
      const keys = res[1] ?? [];
      for (const key of keys) {
        // 从 key 中提取 shortUid：meeting:user:{roomId}:{shortUid}
        const parts = key.split(':');
        const shortUid = Number(parts[parts.length - 1]);
        if (Number.isFinite(shortUid)) {
          userUids.push(shortUid);
        }
      }
    } while (cursor !== '0');

    return userUids;
  }

  /**
   * 检查房间是否为空（没有任何用户连接）
   * 通过检查 Redis 中是否有任何用户的 socketIds 来判断
   * 注意：这是检查 Redis 状态，不依赖 Socket.IO 的 rooms
   * @param roomId - 房间 ID
   * @returns true 表示房间为空，false 表示还有用户
   */
  async isRoomEmpty(roomId: string): Promise<boolean> {
    const users = await this.getUsersInRoom(roomId);
    // 如果没有用户，直接返回 true
    if (users.length === 0) {
      return true;
    }
    // 并行检查所有用户是否还有有效的 socket 连接
    const socketChecks = await Promise.all(
      users.map(async (shortUid) => {
        const socketIds = await this.getSocketIds(roomId, shortUid);
        return socketIds.length > 0;
      })
    );
    // 如果任何一个用户还有 socketIds，说明房间不为空
    return !socketChecks.includes(true);
  }

  // ==================== 清理 ====================

  /**
   * 清理课程状态（下课时调用，不清理黑名单）
   * 清理：房间状态、用户状态（包含 socketIds）
   * 注意：用户状态通过 scan 模式匹配清理，socketIds 存储在用户状态 Hash 中一起清理
   * @param roomId - 房间 ID
   */
  async clearClassData(roomId: string): Promise<void> {
    const client = this.getClient();
    const userStatePattern = `${this.KEY_PREFIX.USER_STATE}${roomId}:*`;
    const roomStateKey = `${this.KEY_PREFIX.ROOM_STATE}${roomId}`;

    // 使用 SCAN 遍历所有匹配的用户状态 key
    let cursor: string | null = '0';
    const keysToDelete: string[] = [];

    // 遍历直到 cursor 回到 '0' 或者返回 null
    while (cursor !== null && cursor !== '0') {
      // eslint-disable-next-line no-await-in-loop
      const res: [string, string[]] = await client.scan(cursor, 'MATCH', userStatePattern, 'COUNT', 200);
      cursor = res[0] === '0' ? null : res[0];
      const keys = res[1] ?? [];
      if (keys.length > 0) {
        keysToDelete.push(...keys);
      }
    }

    // 使用 pipeline 批量删除，减少网络往返
    if (keysToDelete.length > 0) {
      const pipeline = client.pipeline();
      for (const key of keysToDelete) {
        pipeline.del(key);
      }
      await pipeline.exec();
    }

    // 清理房间状态
    await client.del(roomStateKey);
  }

  /**
   * 清理房间所有状态（包括黑名单）
   * 用于房间彻底无人时使用
   * @param roomId - 房间 ID
   */
  async clearRoomAll(roomId: string): Promise<void> {
    // 先清理课程数据
    await this.clearClassData(roomId);
    // 再清理黑名单
    const blacklistKey = `${this.KEY_PREFIX.BLACKLIST}${roomId}`;
    await this.getClient().del(blacklistKey);
  }
}
