const prisma = require('../prisma/client');

const TEAM_NAME_MAP = {
  A: '深耕队',
  B: '致远队',
};

function getTeamDisplayName(team) {
  return TEAM_NAME_MAP[team] || team;
}

/**
 * 内存等待表：roomId → { totalSeats, joined: Set<ws>, players: Array }
 * 房间满员或游戏开始后从表中移除
 */
const waitingRooms = new Map();

/**
 * 游戏结束分数收集表：roomId → Map<playerId, {score, nickname}>
 * 用于收集房间内所有玩家的最终分数
 */
const gameOverScores = new Map();

/** 房间玩家计数：roomId → number，用于分配递增的 playerId */
const roomPlayerCounter = new Map();

/**
 * 房间倒计时表：roomId → { timer, duration, startTime }
 * allReady 后启动，时间到广播 room:timeUp
 */
const roomTimers = new Map();

/**
 * 开始前倒计时表：roomId → { timer }
 * allReady 后启动3秒倒计时，每秒广播 room:countdown，结束后广播 room:gameStart
 */
const countdownTimers = new Map();

/**
 * 注册房间相关 WebSocket 事件处理
 *
 * 事件列表（客户端 → 服务器）：
 *   room:create    创建房间（房主专用），携带 roomId + totalSeats
 *   room:join      加入已有等待中的房间
 *   room:state     实时状态帧，转发给大屏
 *   room:gameOver  游戏结束，保存分数
 *
 * 事件列表（服务器 → 客户端）：
 *   room:created         创建成功确认
 *   room:joined          加入成功确认
 *   room:playerJoined    有新玩家加入，广播当前人数
 *   room:allReady        所有人到齐，通知各端进入倒计时
 *   room:countdown       服务端统一倒计时（每秒广播 value: 3/2/1）
 *   room:gameStart       倒计时结束，通知各端正式开始游戏
 *   room:state           转发给大屏
 *   room:gameOver        转发给大屏
 *   room:playerDisconnected  小游戏异常断开通知大屏
 *   error                错误通知
 *
 * @param {import('ws').WebSocket} ws
 * @param {{ broadcastToRoom, joinRoom, leaveAllRooms }} helpers
 * @returns {Function} cleanup 函数，ws 关闭时调用
 */
function registerRoomHandlers(ws, { broadcastToRoom, joinRoom, leaveAllRooms, roomSubscribers }) {
  if (!ws._handlers) ws._handlers = {};

  // ── room:create ────────────────────────────────────────────────────────────
  // 房主创建房间，写库并进入等待状态
  ws._handlers['room:create'] = async ({ roomId, totalSeats, gameDuration, nickname, team } = {}) => {
    if (!roomId || !totalSeats) {
      ws.sendEvent('error', { message: '缺少 roomId 或 totalSeats' });
      return;
    }

    const rid      = String(roomId);
    const seats    = Number(totalSeats);
    const duration = Number(gameDuration) || 0; // 0 表示不限时

    try {
      // 同一个连接重新建房前，先退出旧房间，避免旧订阅残留到新局。
      leaveAllRooms(ws);

      // 写库：waiting 状态
      await prisma.room.upsert({
        where: { roomId: rid },
        update: { status: 'waiting', totalSeats: seats },
        create: { roomId: rid, status: 'waiting', totalSeats: seats },
      });

      // 房主信息
      const playerInfo = {
        playerId: 1,
        nickname: nickname || '玩家1',
        team: team || 'A'
      };

      // 加入内存等待表（含游戏时长和玩家列表）
      waitingRooms.set(rid, {
        totalSeats: seats,
        joined: new Set([ws]),
        players: [playerInfo],
        gameDuration: duration
      });

      // 订阅房间广播频道
      joinRoom(ws, rid);
      ws.ctx.roomId   = rid;
      ws.ctx.role     = 'minigame';
      ws.ctx.playerId = 1;
      ws.ctx.nickname = playerInfo.nickname;
      ws.ctx.team     = playerInfo.team;
      roomPlayerCounter.set(rid, 1);

      console.log(`[Room] 创建房间 ${rid}，总座位: ${seats}，房主: ${playerInfo.nickname} (${getTeamDisplayName(playerInfo.team)})`);
      ws.sendEvent('room:created', {
        roomId: rid,
        totalSeats: seats,
        joinedCount: 1,
        playerId: 1,
        players: [playerInfo]
      });
    } catch (err) {
      console.error('[room:create] 错误:', err);
      ws.sendEvent('error', { message: '创建房间失败' });
    }
  };

  // ── room:join ──────────────────────────────────────────────────────────────
  // 其他玩家凭房间号加入等待中的房间
  ws._handlers['room:join'] = async ({ roomId, nickname, team } = {}) => {
    if (!roomId) {
      ws.sendEvent('error', { message: '缺少 roomId' });
      return;
    }

    const rid = String(roomId);
    const waiting = waitingRooms.get(rid);

    // 如果等待表里没有，说明是旧流程（游戏中直接 join），兼容处理
    if (!waiting) {
      try {
        // 同一个连接重新进房前，先退出旧房间，避免旧订阅残留到新局。
        leaveAllRooms(ws);

        await prisma.room.upsert({
          where: { roomId: rid },
          update: { status: 'playing' },
          create: { roomId: rid, status: 'playing' },
        });
        joinRoom(ws, rid);
        ws.ctx.roomId = rid;
        ws.ctx.role   = 'minigame';

        const session = await prisma.gameSession.create({
          data: { roomId: rid, status: 'playing' },
        });
        ws.ctx.sessionId = session.id;

        console.log(`[Room] 小游戏加入房间(兼容) ${rid}，sessionId: ${session.id}`);
        ws.sendEvent('room:joined', { roomId: rid, sessionId: session.id });
      } catch (err) {
        console.error('[room:join] 错误:', err);
        ws.sendEvent('error', { message: '加入房间失败' });
      }
      return;
    }

    // 同一个连接重新进房前，先退出旧房间，避免旧订阅残留到新局。
    leaveAllRooms(ws);

    // 校验：房间是否已满
    if (waiting.joined.size >= waiting.totalSeats) {
      ws.sendEvent('error', { message: '房间已满' });
      return;
    }

    // 服务端强制校验队伍人数，防止并发导致某队超员
    const perTeamSeats = Math.floor(waiting.totalSeats / 2);
    let finalTeam = team || 'A';
    const teamACount = waiting.players.filter(p => p.team === 'A').length;
    const teamBCount = waiting.players.filter(p => p.team === 'B').length;

    if (finalTeam === 'A' && teamACount >= perTeamSeats) {
      ws.sendEvent('error', { message: `${getTeamDisplayName('A')}已满，请选择其他队伍`, code: 'TEAM_FULL' });
      return;
    } else if (finalTeam === 'B' && teamBCount >= perTeamSeats) {
      ws.sendEvent('error', { message: `${getTeamDisplayName('B')}已满，请选择其他队伍`, code: 'TEAM_FULL' });
      return;
    }

    // 分配递增 playerId
    const nextId = (roomPlayerCounter.get(rid) || 0) + 1;
    roomPlayerCounter.set(rid, nextId);

    // 玩家信息
    const playerInfo = {
      playerId: nextId,
      nickname: nickname || `玩家${nextId}`,
      team: finalTeam
    };

    // 加入等待表
    waiting.joined.add(ws);
    waiting.players.push(playerInfo);
    joinRoom(ws, rid);
    ws.ctx.roomId   = rid;
    ws.ctx.role     = 'minigame';
    ws.ctx.playerId = nextId;
    ws.ctx.nickname = playerInfo.nickname;
    ws.ctx.team     = playerInfo.team;

    const joinedCount = waiting.joined.size;
    const { totalSeats, players } = waiting;

    console.log(`[Room] 玩家加入房间 ${rid}，playerId=${nextId}，team=${getTeamDisplayName(playerInfo.team)}，当前 ${joinedCount}/${totalSeats}`);

    // 通知自己加入成功（含最终分配的队伍，可能因满员被调整）
    ws.sendEvent('room:joined', { roomId: rid, joinedCount, totalSeats, playerId: nextId, players, myPlayerId: nextId, team: finalTeam });

    // 广播给房间内所有人（含房主，broadcastToRoom 排除自身，自己单独 send）
    broadcastToRoom(rid, 'room:playerJoined', { roomId: rid, joinedCount, totalSeats, players });
    ws.sendEvent('room:playerJoined', { roomId: rid, joinedCount, totalSeats, players });

    // 人数到齐 → 通知所有人可以开始
    if (joinedCount >= totalSeats) {
      console.log(`[Room] 房间 ${rid} 人数到齐，通知开始游戏`);

      try {
        await prisma.room.update({
          where: { roomId: rid },
          data: { status: 'playing' },
        });
        const session = await prisma.gameSession.create({
          data: { roomId: rid, status: 'playing' },
        });

        for (const playerWs of waiting.joined) {
          playerWs.ctx.sessionId = session.id;
        }

        const { gameDuration = 0 } = waiting;
        const allReadyPayload = { roomId: rid, sessionId: session.id, gameDuration };
        broadcastToRoom(rid, 'room:allReady', allReadyPayload);
        ws.sendEvent('room:allReady', allReadyPayload);

        // 启动服务端统一倒计时 3→2→1→开始
        _startGameCountdown(rid, gameDuration, session.id, broadcastToRoom);
      } catch (err) {
        console.error('[room:join allReady] 错误:', err);
      }

      waitingRooms.delete(rid);
    }
  };

  // ── room:state ─────────────────────────────────────────────────────────────
  ws._handlers['room:state'] = (stateData) => {
    const { roomId, playerId, nickname } = ws.ctx;
    if (!roomId) return;
    broadcastToRoom(roomId, 'room:state', { ...stateData, playerId, nickname }, ws);
  };

  // ── room:gameOver ──────────────────────────────────────────────────────────
  ws._handlers['room:gameOver'] = async ({ score } = {}) => {
    const { roomId, sessionId, playerId, nickname } = ws.ctx;
    if (!roomId || !sessionId) return;

    try {
      // 停掉服务端倒计时（避免重复触发）
      _clearRoomTimer(roomId);

      await _finishSession(ws, roomId, sessionId, score);

      // 收集该玩家的分数和队伍
      if (!gameOverScores.has(roomId)) {
        gameOverScores.set(roomId, new Map());
      }
      const roomScores = gameOverScores.get(roomId);
      roomScores.set(playerId, { score: score ?? 0, nickname, team: ws.ctx.team });

      // 完成总人数以本局房间座位数为准，避免旧连接残留导致 1v1 被算成 4 人局。
      const room = await prisma.room.findUnique({
        where: { roomId },
        select: { totalSeats: true },
      });
      const totalPlayers = Number(room?.totalSeats) || roomScores.size;
      const reportedPlayers = roomScores.size;

      console.log(`[Room] 游戏结束分数收集 roomId=${roomId} playerId=${playerId} score=${score ?? 0} (${reportedPlayers}/${totalPlayers})`);

      // 广播当前收集到的分数给其他人（排除发送者，避免覆盖本地快照分数）
      const scoresObj = {};
      const nicknamesObj = {};
      const teamsObj = {};
      for (const [pid, data] of roomScores) {
        scoresObj[pid] = data.score;
        nicknamesObj[pid] = data.nickname;
        teamsObj[pid] = data.team;
      }
      broadcastToRoom(roomId, 'room:gameOver', { roomId, scores: scoresObj, nicknames: nicknamesObj, teams: teamsObj, playerId, nickname }, ws);

      // 广播玩家完成进度给所有人（broadcastToRoom 不传 excludeWs 时包含发送者）
      const progressPayload = { roomId, finishedCount: reportedPlayers, totalCount: totalPlayers, playerId };
      broadcastToRoom(roomId, 'room:playerFinished', progressPayload);

      // 如果所有玩家都已上报分数，广播 allFinished 并清理
      if (reportedPlayers >= totalPlayers) {
        console.log(`[Room] 所有玩家分数收集完成 roomId=${roomId}`);

        // 广播全员完成事件（含最终分数），通知客户端显示结果
        const allFinishedPayload = { roomId, scores: scoresObj, nicknames: nicknamesObj, teams: teamsObj };
        broadcastToRoom(roomId, 'room:allFinished', allFinishedPayload);

        // 房间状态标记为 finished（所有玩家都完成后才更新）
        await prisma.room.update({
          where: { roomId },
          data: { status: 'finished' },
        });

        gameOverScores.delete(roomId);
      }
    } catch (err) {
      console.error('[room:gameOver] 错误:', err);
    }
  };

  // ── cleanup（ws close 时调用）──────────────────────────────────────────────
  const cleanup = async () => {
    const { role, roomId, sessionId } = ws.ctx;
    if (role !== 'minigame' || !roomId) return;

    // 如果还在等待中，从等待表移除并通知其他人
    const waiting = waitingRooms.get(String(roomId));
    if (waiting) {
      waiting.joined.delete(ws);
      const joinedCount = waiting.joined.size;
      console.log(`[Room] 等待中玩家断开 roomId=${roomId}，剩余 ${joinedCount}/${waiting.totalSeats}`);
      broadcastToRoom(roomId, 'room:playerJoined', {
        roomId,
        joinedCount,
        totalSeats: waiting.totalSeats,
      });
      if (joinedCount === 0) {
        waitingRooms.delete(roomId);
        roomPlayerCounter.delete(roomId);
      }
      return;
    }

    // 游戏进行中断开
    if (!sessionId) return;
    try {
      const session = await prisma.gameSession.findUnique({ where: { id: sessionId } });
      if (session && session.status === 'playing') {
        await _finishSession(ws, roomId, sessionId, session.score);
        broadcastToRoom(roomId, 'room:playerDisconnected', { roomId });
        console.log(`[Room] 小游戏异常断开，已结束游戏局 roomId=${roomId} sessionId=${sessionId}`);
      }
    } catch (err) {
      console.error('[disconnect] 清理游戏局错误:', err);
    }
  };

  return cleanup;
}

/**
 * 将进行中的游戏局标记为已结束，并更新房间状态
 */
/**
 * 结束单个 session 记录。
 * 只更新 session 本身，不动 room 状态——
 * room 状态由 onRoomEmpty（所有人断开）统一处理。
 */
async function _finishSession(ws, roomId, sessionId, score) {
  const endedAt = new Date();

  const session = await prisma.gameSession.findUnique({ where: { id: sessionId } });
  const duration = session
    ? Math.round((endedAt.getTime() - session.startedAt.getTime()) / 1000)
    : null;

  await prisma.gameSession.update({
    where: { id: sessionId },
    data: { score: score ?? 0, status: 'finished', endedAt, duration },
  });

  ws.ctx.sessionId = null;

  console.log(`[Room] Session ${sessionId} 结束 roomId=${roomId} score=${score ?? 0} duration=${duration}s`);
}

/**
 * 当某房间所有 WebSocket 连接都断开时由 index.js 调用。
 * 若房间仍处于 waiting / playing 状态，将其标记为 finished，
 * 并同步结束未完成的 gameSession。
 */
async function onRoomEmpty(roomId) {
  console.log(`[Room] 房间 ${roomId} 所有连接已断开，检查状态...`);

  const room = await prisma.room.findUnique({
    where: { roomId },
    select: { status: true },
  });

  // 已是 finished 或根本不存在，无需处理
  if (!room || room.status === 'finished') return;

  // 结束所有未完成的 session
  const activeSessions = await prisma.gameSession.findMany({
    where: { roomId, status: 'playing' },
  });

  const endedAt = new Date();
  for (const session of activeSessions) {
    const duration = Math.round((endedAt.getTime() - session.startedAt.getTime()) / 1000);
    await prisma.gameSession.update({
      where: { id: session.id },
      data: { status: 'finished', endedAt, duration },
    });
    console.log(`[Room] Session ${session.id} 已标记为 finished，时长 ${duration}s`);
  }

  // 房间状态改为 finished
  await prisma.room.update({
    where: { roomId },
    data: { status: 'finished' },
  });

  _clearRoomTimer(roomId);
  _clearGameCountdown(roomId);
  console.log(`[Room] 房间 ${roomId} 已标记为 finished（连接归零触发）`);
}

/**
 * 启动游戏开始前的统一倒计时（3→2→1→开始）
 * 由服务端控制节奏，每秒广播 room:countdown，结束后广播 room:gameStart
 * 解决各客户端本地 setInterval 不同步、跳帧的问题
 */
function _startGameCountdown(roomId, gameDuration, sessionId, broadcastToRoom) {
  // 清理可能存在的旧倒计时
  _clearGameCountdown(roomId);

  let remaining = 3;

  // 立即广播第一个倒计时值（3）
  broadcastToRoom(roomId, 'room:countdown', { roomId, value: remaining });
  console.log(`[Room] 房间 ${roomId} 倒计时: ${remaining}`);

  const timer = setInterval(() => {
    remaining--;
    if (remaining > 0) {
      // 广播 2, 1
      broadcastToRoom(roomId, 'room:countdown', { roomId, value: remaining });
      console.log(`[Room] 房间 ${roomId} 倒计时: ${remaining}`);
    } else {
      // 倒计时结束，广播 room:gameStart
      clearInterval(timer);
      countdownTimers.delete(roomId);
      const startAtMs = Date.now();
      broadcastToRoom(roomId, 'room:gameStart', { roomId, sessionId, gameDuration, startAtMs });
      console.log(`[Room] 房间 ${roomId} 倒计时结束，游戏开始`);

      // 游戏正式开始后才启动游戏时长计时器
      if (gameDuration > 0) {
        _startRoomTimer(roomId, gameDuration, sessionId, broadcastToRoom, startAtMs);
      }
    }
  }, 1000);

  countdownTimers.set(roomId, { timer });
}

/**
 * 清理游戏开始前倒计时
 */
function _clearGameCountdown(roomId) {
  const entry = countdownTimers.get(roomId);
  if (entry) {
    clearInterval(entry.timer);
    countdownTimers.delete(roomId);
  }
}

/**
 * 启动房间服务端倒计时
 * 时间到后广播 room:timeUp 给房间内所有人（小游戏 + 大屏）
 */
function _startRoomTimer(roomId, durationSec, sessionId, broadcastToRoom, startAtMs = Date.now()) {
  // 清理旧计时器
  _clearRoomTimer(roomId);

  const startTime = startAtMs;
  const timer = setTimeout(async () => {
    console.log(`[Room] 房间 ${roomId} 时间到，广播 room:timeUp`);

    // 广播给所有人（不排除任何人，broadcastToRoom excludeWs=null）
    broadcastToRoom(roomId, 'room:timeUp', { roomId, durationSec });

    // 写库：批量结束未完成的 session
    try {
      const endedAt = new Date();
      const activeSessions = await prisma.gameSession.findMany({
        where: { roomId, status: 'playing' },
      });
      for (const s of activeSessions) {
        const duration = Math.round((endedAt.getTime() - s.startedAt.getTime()) / 1000);
        await prisma.gameSession.update({
          where: { id: s.id },
          data: { status: 'finished', endedAt, duration },
        });
      }
      await prisma.room.update({
        where: { roomId },
        data: { status: 'finished' },
      });
      console.log(`[Room] 房间 ${roomId} 因超时已标记 finished`);
    } catch (err) {
      console.error('[timeUp] 写库错误:', err);
    }

    roomTimers.delete(roomId);
  }, durationSec * 1000);

  roomTimers.set(roomId, { timer, durationSec, startTime });
  console.log(`[Room] 房间 ${roomId} 倒计时启动，时长 ${durationSec}s`);
}

/**
 * 清理房间倒计时
 */
function _clearRoomTimer(roomId) {
  const entry = roomTimers.get(roomId);
  if (entry) {
    clearTimeout(entry.timer);
    roomTimers.delete(roomId);
  }
}

function getRoomTimerSnapshot(roomId, now = Date.now()) {
  const entry = roomTimers.get(String(roomId));
  if (!entry) return null;

  const durationSec = Number(entry.durationSec) || 0;
  const startAtMs = Number(entry.startTime) || 0;
  if (durationSec <= 0 || startAtMs <= 0) return null;

  const elapsedSec = Math.max(0, Math.floor((now - startAtMs) / 1000));
  const remainingSec = Math.max(0, durationSec - elapsedSec);
  if (remainingSec <= 0) return null;

  return {
    roomId: String(roomId),
    durationSec,
    startAtMs,
    remainingSec,
  };
}

/**
 * 查询等待中房间的队伍人数情况
 * @param {string} roomId
 * @returns {{ teamA: number, teamB: number, totalSeats: number, perTeamSeats: number } | null}
 *   返回 null 表示房间不在等待表中（未创建或已开始）
 */
function getWaitingRoomTeamInfo(roomId) {
  const waiting = waitingRooms.get(String(roomId));
  if (!waiting) return null;

  const teamA = waiting.players.filter(p => p.team === 'A').length;
  const teamB = waiting.players.filter(p => p.team === 'B').length;
  const perTeamSeats = Math.floor(waiting.totalSeats / 2);

  return { teamA, teamB, totalSeats: waiting.totalSeats, perTeamSeats };
}

module.exports = { registerRoomHandlers, onRoomEmpty, getWaitingRoomTeamInfo, getRoomTimerSnapshot };
