/**
 * Star Wars Hearthstone - Game Server
 * PvP over LAN (Radmin VPN). 2-4 players.
 */
const express = require('express');
const http = require('http');
const { Server } = require('socket.io');
const path = require('path');
const bcrypt = require('bcrypt');
const session = require('express-session');
const redisClient = require('./redis-client');

const app = express();
const server = http.createServer(app);
const io = new Server(server);

// Парсинг JSON для API
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Настройка сессий (будет настроена после инициализации Redis)
// Временная конфигурация сессий, будет обновлена после инициализации Redis
app.use(session({
  secret: process.env.SESSION_SECRET || 'star-wars-hearthstone-secret-key-change-in-production',
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 30 * 24 * 60 * 60 * 1000 // 30 дней
  }
}));

app.use(express.static(path.join(__dirname, 'public')));

// API для авторизации и регистрации
app.post('/api/register', async (req, res) => {
  try {
    const { username, password, email } = req.body;
    
    if (!username || !password) {
      return res.status(400).json({ error: 'Имя пользователя и пароль обязательны' });
    }
    
    if (username.length < 3 || username.length > 20) {
      return res.status(400).json({ error: 'Имя пользователя должно быть от 3 до 20 символов' });
    }
    
    if (password.length < 6) {
      return res.status(400).json({ error: 'Пароль должен быть не менее 6 символов' });
    }
    
    // Проверяем, существует ли пользователь
    if (redisClient.isRedisAvailable()) {
      const existingUser = await redisClient.get(`user:${username}`);
      if (existingUser) {
        return res.status(400).json({ error: 'Пользователь с таким именем уже существует' });
      }
      
      // Хешируем пароль
      const hashedPassword = await bcrypt.hash(password, 10);
      
      // Сохраняем пользователя
      const userData = {
        username,
        password: hashedPassword,
        email: email || '',
        createdAt: Date.now(),
        gamesPlayed: 0,
        gamesWon: 0,
        totalDamage: 0,
        totalHealing: 0
      };
      
      await redisClient.set(`user:${username}`, JSON.stringify(userData));
      if (email) {
        await redisClient.set(`user:email:${email}`, username);
      }
      
      // Создаем сессию
      req.session.userId = username;
      req.session.save();
      
      res.json({ success: true, user: { username, email: userData.email } });
    } else {
      // Без Redis - простая проверка (только для тестов)
      req.session.userId = username;
      req.session.save();
      res.json({ success: true, user: { username, email: email || '' }, warning: 'Работаем без БД' });
    }
  } catch (error) {
    console.error('Ошибка регистрации:', error);
    res.status(500).json({ error: 'Ошибка сервера при регистрации' });
  }
});

app.post('/api/login', async (req, res) => {
  try {
    const { username, password } = req.body;
    
    if (!username || !password) {
      return res.status(400).json({ error: 'Имя пользователя и пароль обязательны' });
    }
    
    if (redisClient.isRedisAvailable()) {
      const userDataStr = await redisClient.get(`user:${username}`);
      if (!userDataStr) {
        return res.status(401).json({ error: 'Неверное имя пользователя или пароль' });
      }
      
      const userData = JSON.parse(userDataStr);
      const passwordMatch = await bcrypt.compare(password, userData.password);
      
      if (!passwordMatch) {
        return res.status(401).json({ error: 'Неверное имя пользователя или пароль' });
      }
      
      // Создаем сессию
      req.session.userId = username;
      req.session.save();
      
      res.json({ 
        success: true, 
        user: { 
          username: userData.username, 
          email: userData.email,
          gamesPlayed: userData.gamesPlayed || 0,
          gamesWon: userData.gamesWon || 0
        } 
      });
    } else {
      // Без Redis - простая авторизация (только для тестов)
      req.session.userId = username;
      req.session.save();
      res.json({ success: true, user: { username }, warning: 'Работаем без БД' });
    }
  } catch (error) {
    console.error('Ошибка входа:', error);
    res.status(500).json({ error: 'Ошибка сервера при входе' });
  }
});

app.post('/api/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).json({ error: 'Ошибка при выходе' });
    }
    res.json({ success: true });
  });
});

app.get('/api/user', async (req, res) => {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'Не авторизован' });
  }
  
  if (redisClient.isRedisAvailable()) {
    const userDataStr = await redisClient.get(`user:${req.session.userId}`);
    if (userDataStr) {
      const userData = JSON.parse(userDataStr);
      res.json({ 
        user: { 
          username: userData.username, 
          email: userData.email,
          gamesPlayed: userData.gamesPlayed || 0,
          gamesWon: userData.gamesWon || 0,
          totalDamage: userData.totalDamage || 0,
          totalHealing: userData.totalHealing || 0
        } 
      });
    } else {
      res.status(404).json({ error: 'Пользователь не найден' });
    }
  } else {
    res.json({ user: { username: req.session.userId }, warning: 'Работаем без БД' });
  }
});

app.get('/api/leaderboard', async (req, res) => {
  try {
    if (!redisClient.isRedisAvailable()) {
      return res.json({ leaderboard: [], warning: 'Работаем без БД' });
    }
    
    // Получаем всех пользователей
    const userKeys = await redisClient.keys('user:*');
    const leaderboard = [];
    
    for (const key of userKeys) {
      if (key.startsWith('user:email:')) continue; // Пропускаем email индексы
      const userDataStr = await redisClient.get(key);
      if (userDataStr) {
        const userData = JSON.parse(userDataStr);
        leaderboard.push({
          username: userData.username,
          gamesPlayed: userData.gamesPlayed || 0,
          gamesWon: userData.gamesWon || 0,
          winRate: userData.gamesPlayed > 0 ? ((userData.gamesWon || 0) / userData.gamesPlayed * 100).toFixed(1) : 0,
          totalDamage: userData.totalDamage || 0,
          totalHealing: userData.totalHealing || 0
        });
      }
    }
    
    // Сортируем по победам, затем по количеству игр
    leaderboard.sort((a, b) => {
      if (b.gamesWon !== a.gamesWon) return b.gamesWon - a.gamesWon;
      return b.gamesPlayed - a.gamesPlayed;
    });
    
    res.json({ leaderboard: leaderboard.slice(0, 100) }); // Топ 100
  } catch (error) {
    console.error('Ошибка получения leaderboard:', error);
    res.status(500).json({ error: 'Ошибка сервера' });
  }
});

app.get('/api/cards', (req, res) => {
  res.json(cardDb);
});

const PORT = process.env.PORT || 3542;
const MAX_PLAYERS = 4;
const MIN_PLAYERS = 2;

const cardDb = require('./cards');

function getCard(room, cardId) {
  return cardDb[cardId];
}

const rooms = new Map(); // code -> { lobby, gameState, gameStarted, faction, turnTimerInterval, turnTimeLeft, turnTimerWarnFired, spectators: [] }
const socketToUsername = new Map(); // socket.id -> username (для обновления статистики)
const TURN_SECONDS = 90;
const TURN_WARN_AT = 15;

function generateRoomCode() {
  const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
  let code = '';
  for (let i = 0; i < 5; i++) {
    code += chars[Math.floor(Math.random() * chars.length)];
  }
  return code;
}

function cleanupEmptyRooms() {
  for (const [code, room] of rooms.entries()) {
    if (!room.gameStarted && (!room.lobby || room.lobby.length === 0)) {
      clearTurnTimer(room);
      rooms.delete(code);
    }
  }
}

// Периодическая очистка пустых комнат
setInterval(cleanupEmptyRooms, 30000); // каждые 30 секунд

// Функция для применения урона с учетом брони
function applyDamageToPlayer(player, damage) {
  if (damage <= 0) return;
  
  // Проверка на Droid Armor - удваиваем броню для мех-юнитов
  // (это обрабатывается при создании карты, но можно добавить проверку здесь)
  
  // Сначала урон идет на броню
  if (player.armor > 0) {
    const armorDamage = Math.min(player.armor, damage);
    player.armor -= armorDamage;
    damage -= armorDamage;
  }
  
  // Оставшийся урон идет на HP
  if (damage > 0) {
    player.health = Math.max(0, player.health - damage);
  }
}

// Функция для лечения игрока
function healPlayer(player, amount) {
  if (amount <= 0) return;
  player.health = Math.min(30, player.health + amount);
}

function shuffle(a) {
  for (let i = a.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [a[i], a[j]] = [a[j], a[i]];
  }
  return a;
}

function createDeck(faction) {
  const minionIds = Object.keys(cardDb).filter((id) => {
    const c = cardDb[id];
    if (c.type !== 'minion') return false;
    if (faction && c.faction !== faction && c.faction !== 'neutral' && c.faction !== 'animal') return false;
    return true;
  });
  const spellIds = Object.keys(cardDb).filter((id) => {
    const c = cardDb[id];
    if (c.type !== 'spell') return false;
    if (faction && c.faction !== faction && c.faction !== 'neutral') return false;
    return true;
  });
  const deck = [];
  const legendaryUsed = new Set();
  for (let i = 0; i < 15; i++) {
    const pool = minionIds.filter((id) => {
      const c = cardDb[id];
      if (c.legendary && legendaryUsed.has(id)) return false;
      return true;
    });
    if (!pool.length) break;
    const id = pool[Math.floor(Math.random() * pool.length)];
    deck.push(id);
    if (cardDb[id].legendary) legendaryUsed.add(id);
  }
  for (let i = 0; i < 5 && spellIds.length; i++) {
    deck.push(spellIds[Math.floor(Math.random() * spellIds.length)]);
  }
  return shuffle(deck);
}

function drawCards(deck, n) {
  const drawn = [];
  for (let i = 0; i < n && deck.length; i++) {
    drawn.push(deck.pop());
  }
  return drawn;
}

function initGame(room) {
  const lobby = room.lobby;
  const playerCount = lobby.length;
  const factionChoice = room.faction || null;
  const players = lobby.map((p, i) => ({
    id: p.id,
    name: p.name || `Player ${i + 1}`,
    deck: createDeck(factionChoice),
    hand: [],
    board: [],
    mana: 0,
    maxMana: 0,
    health: 30,
    armor: 0, // Система брони
    hero: (p.hero || ['luke', 'vader', 'yoda', 'leia', 'rey', 'kylo', 'mace'][i % 7]),
    manualDrawUsed: false,
    fatigueCounter: 0,
    heroAbilityUsed: false,
    isAI: false,
  }));

  // Если режим ИИ, добавляем ИИ игрока
  if (room.aiMode) {
    const starWarsHeroes = [
      'Darth Maul', 'Count Dooku', 'General Grievous', 'Grand Moff Tarkin',
      'Admiral Thrawn', 'Kylo Ren', 'Snoke', 'General Hux',
      'Captain Phasma', 'Moff Gideon', 'Cad Bane', 'Asajj Ventress',
      'Savage Opress', 'Darth Sidious', 'Darth Tyranus', 'Darth Plagueis',
      'Jango Fett', 'Boba Fett', 'IG-88', 'Bossk',
      'Dengar', 'Zuckuss', '4-LOM', 'Aurra Sing'
    ];
    const randomHero = starWarsHeroes[Math.floor(Math.random() * starWarsHeroes.length)];
    players.push({
      id: 'AI_' + Date.now(),
      name: randomHero,
      deck: createDeck(factionChoice),
      hand: [],
      board: [],
      mana: 0,
      maxMana: 0,
      health: 30,
      armor: 0, // Система брони
      hero: 'vader',
      manualDrawUsed: false,
      fatigueCounter: 0,
      heroAbilityUsed: false,
      isAI: true,
    });
  }

  players.forEach((p, i) => {
    p.hand = drawCards(p.deck, i < 2 ? 3 : 4);
  });
  players[0].maxMana = 1;
  players[0].mana = 1;

  room.gameState = {
    phase: 'playing',
    turn: 1,
    players,
    currentPlayerIndex: 0,
    log: [],
  };
  room.gameStarted = true;
  startTurnTimer(room);
  broadcastGameState(room);
  
  // Если первый ход ИИ, делаем его ход
  if (room.aiMode && players[0].isAI) {
    setTimeout(() => {
      if (room.gameState && room.gameState.phase === 'playing') {
        makeAITurn(room);
      }
    }, 1000);
  }
}

// Функция для хода ИИ
function makeAITurn(room) {
  const gameState = room.gameState;
  if (!gameState || gameState.phase !== 'playing') return;
  
  const aiPlayerIndex = gameState.currentPlayerIndex;
  const aiPlayer = gameState.players[aiPlayerIndex];
  
  if (!aiPlayer || !aiPlayer.isAI || aiPlayer.health <= 0) return;
  if (gameState.currentPlayerIndex !== aiPlayerIndex) return;
  
  // Применяем синергии перед ходом
  applySynergies(room);
  
  // Небольшая задержка для визуализации
  setTimeout(() => {
    let actionsDone = 0;
    const maxActions = 5; // Максимум действий за ход
    
    // 1. Играем карты (приоритет дешёвым и эффективным)
    const playableCards = aiPlayer.hand
      .map((cardId, index) => {
        const card = getCard(room, cardId);
        if (!card) return null;
        const cost = card.cost || 0;
        if (cost > aiPlayer.mana) return null;
        if (card.type === 'minion' && aiPlayer.board.length >= 7) return null;
        
        // Оценка карты (чем выше, тем лучше)
        let value = 0;
        if (card.type === 'minion') {
          value = (card.attack || 0) + (card.health || 0) - cost * 2;
          if (card.battlecryId) value += 2;
          if (card.legendary) value += 1;
        } else if (card.type === 'spell') {
          value = 3 - cost;
          if (card.spellEffect === 'deal_3_minion' || card.spellEffect === 'deal_3_any') value += 2;
          if (card.spellEffect === 'buff_3_2') value += 1;
        }
        
        return { index, card, cost, value };
      })
      .filter(Boolean)
      .sort((a, b) => b.value - a.value);
    
    // Играем лучшие карты
    let cardIndex = 0;
    const playNextCard = () => {
      if (cardIndex >= playableCards.length || actionsDone >= 3) {
        // Переходим к атакам
        setTimeout(() => {
          performAIAttacks(room, aiPlayerIndex);
        }, 500);
        return;
      }
      
      const playable = playableCards[cardIndex];
      const currentPlayer = gameState.players[aiPlayerIndex];
      
      if (!currentPlayer || playable.cost > currentPlayer.mana) {
        cardIndex++;
        playNextCard();
        return;
      }
      
      if (playable.card.type === 'minion') {
        // Играем миньона (пропускаем карты с выбором цели для упрощения)
        if (playable.card.battlecryId === 'return_hand_enemy') {
          cardIndex++;
          playNextCard();
          return;
        }
        const boardPos = currentPlayer.board.length;
        playCard(room, aiPlayer.id, playable.index, boardPos);
        actionsDone++;
        cardIndex++;
        setTimeout(playNextCard, 600);
      } else if (playable.card.type === 'spell') {
        // Используем заклинание
        const enemies = gameState.players.filter((p, i) => i !== aiPlayerIndex && p.health > 0);
        if (enemies.length > 0) {
          // Выбираем врага: 70% шанс выбрать самого слабого, 30% - случайно среди всех
          let targetEnemy;
          if (Math.random() < 0.7 && enemies.length > 1) {
            const weakest = enemies.reduce((min, p) => (p.health < min.health ? p : min), enemies[0]);
            const weakEnemies = enemies.filter(p => p.health === weakest.health);
            targetEnemy = weakEnemies[Math.floor(Math.random() * weakEnemies.length)];
          } else {
            targetEnemy = enemies[Math.floor(Math.random() * enemies.length)];
          }
          let targetBoardIndex = -1;
          
          if (playable.card.spellTarget === 'enemy_minion' || playable.card.spellTarget === 'any_minion') {
            if (targetEnemy.board && targetEnemy.board.length > 0) {
              targetBoardIndex = 0;
            } else {
              targetBoardIndex = -1;
            }
          } else if (playable.card.spellTarget === 'friendly_minion') {
            if (currentPlayer.board && currentPlayer.board.length > 0) {
              targetBoardIndex = 0;
            } else {
              cardIndex++;
              playNextCard();
              return;
            }
          } else if (playable.card.spellTarget === 'enemy_player') {
            // Для заклинаний типа "Грабеж" пропускаем (требует выбор карт)
            if (playable.card.spellEffect === 'steal_cards') {
              cardIndex++;
              playNextCard();
              return;
            }
            targetBoardIndex = -1;
          }
          
          const targetPlayerIndex = gameState.players.findIndex((p, i) => i !== aiPlayerIndex && p === targetEnemy);
          playSpell(room, aiPlayer.id, playable.index, targetPlayerIndex, targetBoardIndex);
          actionsDone++;
          cardIndex++;
          setTimeout(playNextCard, 600);
        } else {
          cardIndex++;
          playNextCard();
        }
      } else {
        cardIndex++;
        playNextCard();
      }
    };
    
    playNextCard();
  }, 800);
}

// Функция для атак ИИ
function performAIAttacks(room, aiPlayerIndex) {
  const gameState = room.gameState;
  if (!gameState || gameState.phase !== 'playing') {
    endTurn(room);
    return;
  }
  
  const aiPlayer = gameState.players[aiPlayerIndex];
  if (!aiPlayer || aiPlayer.health <= 0) {
    endTurn(room);
    return;
  }
  
  const attackableMinions = aiPlayer.board.filter(m => m.canAttack && !m.frozen);
  const enemies = gameState.players.filter((p, i) => i !== aiPlayerIndex && p.health > 0);
  
  if (attackableMinions.length === 0 || enemies.length === 0) {
    setTimeout(() => endTurn(room), 500);
    return;
  }
  
  let attackIndex = 0;
  const performNextAttack = () => {
    if (attackIndex >= attackableMinions.length) {
      setTimeout(() => endTurn(room), 500);
      return;
    }
    
    const minion = attackableMinions[attackIndex];
    const boardIndex = aiPlayer.board.indexOf(minion);
    // Выбираем врага: 70% шанс выбрать самого слабого, 30% - случайно среди всех
    let targetEnemy;
    if (Math.random() < 0.7 && enemies.length > 1) {
      const weakest = enemies.reduce((min, p) => (p.health < min.health ? p : min), enemies[0]);
      const weakEnemies = enemies.filter(p => p.health === weakest.health);
      targetEnemy = weakEnemies[Math.floor(Math.random() * weakEnemies.length)];
    } else {
      targetEnemy = enemies[Math.floor(Math.random() * enemies.length)];
    }
    const targetPlayerIndex = gameState.players.findIndex((p, i) => i !== aiPlayerIndex && p === targetEnemy);
    let targetBoardIndex = -1;
    
    // Выбираем цель: сначала слабые миньоны, потом герой
    if (targetEnemy.board && targetEnemy.board.length > 0) {
      const weakMinion = targetEnemy.board.find(m => m.health <= minion.attack) ||
                         targetEnemy.board[0];
      targetBoardIndex = targetEnemy.board.indexOf(weakMinion);
    } else {
      targetBoardIndex = -1; // Атакуем героя
    }
    
    attack(room, aiPlayer.id, boardIndex, targetPlayerIndex, targetBoardIndex);
    attackIndex++;
    setTimeout(performNextAttack, 800);
  };
  
  performNextAttack();
}

function clearTurnTimer(room) {
  if (room.turnTimerInterval) {
    clearInterval(room.turnTimerInterval);
    room.turnTimerInterval = null;
  }
  room.turnTimeLeft = TURN_SECONDS;
  room.turnTimerWarnFired = false;
}

function startTurnTimer(room) {
  clearTurnTimer(room);
  if (!room.gameState || room.gameState.phase !== 'playing') return;
  room.turnTimeLeft = TURN_SECONDS;
  room.turnTimerWarnFired = false;
  io.to(room.code).emit('turnTime', {
    left: room.turnTimeLeft,
    currentPlayerIndex: room.gameState.currentPlayerIndex,
    warn: false,
    ended: false,
  });
  room.turnTimerInterval = setInterval(() => {
    if (!room.gameState || room.gameState.phase !== 'playing') {
      clearTurnTimer(room);
      return;
    }
    room.turnTimeLeft--;
    io.to(room.code).emit('turnTime', {
      left: room.turnTimeLeft,
      currentPlayerIndex: room.gameState.currentPlayerIndex,
      warn: room.turnTimeLeft === TURN_WARN_AT,
      ended: room.turnTimeLeft <= 0,
    });
    if (room.turnTimeLeft === TURN_WARN_AT) room.turnTimerWarnFired = true;
    if (room.turnTimeLeft <= 0) {
      clearTurnTimer(room);
      endTurn(room);
    }
  }, 1000);
}

function broadcastGameState(room) {
  if (!room.gameState) return;
  room.gameState.players.forEach((p, i) => {
    const socket = io.sockets.sockets.get(p.id);
    if (socket) {
      const yourView = {
        ...room.gameState,
        yourIndex: i,
        yourHand: [...p.hand],
        yourDeck: [...p.deck],
        yourDeckCount: p.deck.length,
        yourManualDrawUsed: !!p.manualDrawUsed,
        yourHeroAbilityUsed: !!p.heroAbilityUsed,
        yourArmor: p.armor || 0,
        cardDb: cardDb,
      };
      socket.emit('gameState', yourView);
    }
  });
}

function findPlayerIndex(room, socketId) {
  return room.gameState.players.findIndex((p) => p.id === socketId);
}

function getRoomByCode(code) {
  return rooms.get(code?.toUpperCase());
}

function getRoomBySocket(socketId) {
  for (const room of rooms.values()) {
    if (room.lobby?.some((p) => p.id === socketId)) return room;
    if (room.gameState?.players?.some((p) => p.id === socketId)) return room;
    if (room.spectators?.some((s) => s.id === socketId)) return room;
  }
  return null;
}

function endTurn(room) {
  const gameState = room.gameState;
  const prev = gameState.currentPlayerIndex;
  gameState.currentPlayerIndex = (gameState.currentPlayerIndex + 1) % gameState.players.length;
  const next = gameState.currentPlayerIndex;
  const np = gameState.players[next];
  np.maxMana = Math.min(10, np.maxMana + 1);
  np.mana = np.maxMana;
  
  // Эффекты структур в конце хода предыдущего игрока
  const prevPlayer = gameState.players[prev];
  if (prevPlayer) {
    prevPlayer.board.forEach((m) => {
      const card = getCard(room, m.cardId);
      if (!card) return;
      
      if (card.name === 'Храм джедаев') {
        prevPlayer.board.forEach((minion) => {
          if (minion.id !== m.id && minion.health < (minion.maxHealth || minion.health)) {
            minion.health = Math.min(minion.maxHealth || minion.health, minion.health + 1);
          }
        });
      } else if (card.name === 'Станция Звезды Смерти') {
        const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
        if (enemies.length) {
          const target = enemies[Math.floor(Math.random() * enemies.length)];
          applyDamageToPlayer(target, 3);
          gameState.log.push({ type: 'structure', effect: 'death_star_damage', fromPlayer: prev, toPlayer: gameState.players.indexOf(target), damage: 3 });
        }
      } else if (card.name === 'Кантина Мос-Эйсли') {
        if (prevPlayer.deck.length > 0) {
          const drawn = drawCards(prevPlayer.deck, 1);
          if (drawn.length) prevPlayer.hand.push(...drawn);
          gameState.log.push({ type: 'structure', effect: 'cantina_draw', playerIndex: prev });
        }
      } else if (card.name === 'Сарлак') {
        const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
        if (enemies.length) {
          const enemy = enemies[Math.floor(Math.random() * enemies.length)];
          if (enemy.board && enemy.board.length > 0) {
            const weak = enemy.board.filter(min => min.attack <= 3);
            if (weak.length > 0) {
              const target = weak[Math.floor(Math.random() * weak.length)];
              const boardIdx = enemy.board.indexOf(target);
              const targetCard = getCard(room, target.cardId);
              enemy.board.splice(boardIdx, 1);
              gameState.log.push({ type: 'structure', effect: 'sarlacc_consume', fromPlayer: prev, toPlayer: gameState.players.indexOf(enemy), toIdx: boardIdx });
              if (targetCard && targetCard.deathrattleId) {
                runDeathrattle(room, targetCard, gameState.players.indexOf(enemy));
              }
            }
          }
        }
      }
      // Обработка планет
      else if (card.name === 'Korriban') {
        const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
        if (enemies.length) {
          const target = enemies[Math.floor(Math.random() * enemies.length)];
          applyDamageToPlayer(target, 2);
          gameState.log.push({ type: 'planet', effect: 'korriban_damage', fromPlayer: prev, toPlayer: gameState.players.indexOf(target), damage: 2 });
        }
      } else if (card.name === 'Tatooine') {
        if (prevPlayer.deck.length > 0) {
          const drawn = drawCards(prevPlayer.deck, 1);
          if (drawn.length) prevPlayer.hand.push(...drawn);
          gameState.log.push({ type: 'planet', effect: 'tatooine_draw', playerIndex: prev });
        }
      } else if (card.name === 'Coruscant') {
        prevPlayer.board.forEach((minion) => {
          if (minion.id !== m.id && minion.health < (minion.maxHealth || minion.health)) {
            minion.health = Math.min(minion.maxHealth || minion.health, minion.health + 1);
          }
        });
        gameState.log.push({ type: 'planet', effect: 'coruscant_heal', playerIndex: prev });
      } else if (card.name === 'Zakuul') {
        const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
        enemies.forEach((enemy) => {
          applyDamageToPlayer(enemy, 3);
        });
        gameState.log.push({ type: 'planet', effect: 'zakuul_damage', fromPlayer: prev });
      } else if (card.name === 'Kamino') {
        if (prevPlayer.hand.length < 10) {
          prevPlayer.hand.push('clone_trooper');
          gameState.log.push({ type: 'planet', effect: 'kamino_summon', playerIndex: prev });
        }
      } else if (card.name === 'Mustafar') {
        const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
        enemies.forEach((enemy) => {
          applyDamageToPlayer(enemy, 1);
          if (enemy.board) {
            enemy.board.forEach((min) => {
              min.health = Math.max(0, min.health - 1);
            });
            enemy.board = enemy.board.filter((m) => m.health > 0);
          }
        });
        gameState.log.push({ type: 'planet', effect: 'mustafar_damage', fromPlayer: prev });
      } else if (card.name === 'Naboo') {
        healPlayer(prevPlayer, 2);
        gameState.log.push({ type: 'planet', effect: 'naboo_heal', playerIndex: prev });
      }
      // Обработка эффектов карт с хилом и броней
      else if (card.fieldEffect === 'heal_1_per_turn') {
        healPlayer(prevPlayer, 1);
        gameState.log.push({ type: 'fieldEffect', effect: 'heal_1_per_turn', playerIndex: prev });
      } else if (card.fieldEffect === 'armor_regen_2_if_jedi') {
        // Проверяем, есть ли джедай на поле
        const hasJedi = prevPlayer.board.some(min => {
          const minCard = getCard(room, min.cardId);
          return minCard && (minCard.name?.includes('Jedi') || 
            minCard.id === 'luke' || minCard.id === 'obiwan' || 
            minCard.id === 'ahsoka' || minCard.id === 'mace' ||
            minCard.id === 'yoda' || minCard.id === 'anakin');
        });
        if (hasJedi) {
          prevPlayer.armor = (prevPlayer.armor || 0) + 2;
          gameState.log.push({ type: 'fieldEffect', effect: 'armor_regen_2_if_jedi', playerIndex: prev });
        }
      } else if (card.fieldEffect === 'armor_regen_5_every_2_turns') {
        // Каждые 2 хода восстанавливаем +5 брони
        const turnCount = gameState.turn || 0;
        if (turnCount % 2 === 0) {
          prevPlayer.armor = (prevPlayer.armor || 0) + 5;
          gameState.log.push({ type: 'fieldEffect', effect: 'armor_regen_5_every_2_turns', playerIndex: prev });
        }
      } else if (card.name === 'Endor') {
        if (prevPlayer.hand.length < 10) {
          prevPlayer.hand.push('ewok');
          gameState.log.push({ type: 'planet', effect: 'endor_summon', playerIndex: prev });
        }
      } else if (card.name === 'Hoth') {
        const enemies = gameState.players.filter((pl, i) => i !== prev && pl.health > 0);
        enemies.forEach((enemy) => {
          if (enemy.board) {
            enemy.board.forEach((min) => {
              min.frozen = true;
            });
          }
        });
        gameState.log.push({ type: 'planet', effect: 'hoth_freeze', fromPlayer: prev });
      } else if (card.name === 'Dagobah') {
        if (prevPlayer.deck.length > 0) {
          const drawn = drawCards(prevPlayer.deck, 1);
          if (drawn.length) prevPlayer.hand.push(...drawn);
          gameState.log.push({ type: 'planet', effect: 'dagobah_draw', playerIndex: prev });
        }
      }
    });
  }
  
  np.board.forEach((m) => {
    if (m.frozen) { m.frozen = false; return; }
    const card = getCard(room, m.cardId);
    if (card && card.attack > 0) {
      m.canAttack = true;
      if (card.canAttackTwice) {
        m.attacksUsed = 0; // Сбрасываем счётчик атак для двойной атаки
      }
    }
    // Сбрасываем флаг использования Energy Shield
    if (card && card.shieldEffect === 'absorb_first_damage') {
      m.shieldUsedThisTurn = false;
    }
  });
  np.manualDrawUsed = false;
  np.heroAbilityUsed = false;
  if (np.deck.length === 0) {
    np.fatigueCounter = (np.fatigueCounter || 0) + 1;
    applyDamageToPlayer(np, np.fatigueCounter);
    gameState.log.push({ type: 'fatigue', playerIndex: next, damage: np.fatigueCounter });
  } else {
    const drawn = drawCards(np.deck, 1);
    if (drawn.length) np.hand.push(...drawn);
  }
  gameState.turn++;
  gameState.log.push({ type: 'turn', from: prev, to: next });
  
  // Применяем синергии в начале каждого хода
  applySynergies(room);
  
  checkGameOver(room);
  startTurnTimer(room);
  broadcastGameState(room);
  
  // Если следующий игрок - ИИ, делаем его ход
  if (np.isAI && gameState.phase === 'playing') {
    setTimeout(() => {
      if (room.gameState && room.gameState.phase === 'playing') {
        makeAITurn(room);
      }
    }, 1500);
  }
}

function manualDraw(room, socketId) {
  const gameState = room.gameState;
  const pi = findPlayerIndex(room, socketId);
  if (pi < 0 || gameState.currentPlayerIndex !== pi) return;
  const p = gameState.players[pi];
  if (p.manualDrawUsed || p.hand.length >= 10) return;
  if (p.deck.length === 0) {
    p.manualDrawUsed = true;
    p.fatigueCounter = (p.fatigueCounter || 0) + 1;
    const dmg = p.fatigueCounter;
    applyDamageToPlayer(p, dmg);
    gameState.log.push({ type: 'fatigue', playerIndex: pi, damage: dmg });
    checkGameOver(room);
    broadcastGameState(room);
    return;
  }
  p.manualDrawUsed = true;
  const drawn = drawCards(p.deck, 1);
  if (drawn.length) p.hand.push(...drawn);
  gameState.log.push({ type: 'draw', playerIndex: pi });
  broadcastGameState(room);
}

function runBattlecry(room, card, playerIndex) {
  const gameState = room.gameState;
  const p = gameState.players[playerIndex];
  const enemies = gameState.players.filter((pl, i) => i !== playerIndex && pl.health > 0);
  const id = card.battlecryId;
  if (id === 'deal_1_hero' && enemies.length) {
    const t = enemies[Math.floor(Math.random() * enemies.length)];
    const idx = gameState.players.indexOf(t);
    applyDamageToPlayer(t, 1);
    gameState.log.push({ type: 'battlecry', effect: 'deal_1_hero', fromPlayer: playerIndex, toPlayer: idx, damage: 1 });
  } else if (id === 'deal_2_hero' && enemies.length) {
    const t = enemies[Math.floor(Math.random() * enemies.length)];
    const idx = gameState.players.indexOf(t);
    applyDamageToPlayer(t, 2);
    gameState.log.push({ type: 'battlecry', effect: 'deal_2_hero', fromPlayer: playerIndex, toPlayer: idx, damage: 2 });
  } else if (id === 'draw_1') {
    const drawn = drawCards(p.deck, 1);
    if (drawn.length) p.hand.push(...drawn);
    gameState.log.push({ type: 'battlecry', effect: 'draw_1', playerIndex });
  } else if (id === 'draw_2') {
    const drawn = drawCards(p.deck, 2);
    if (drawn.length) p.hand.push(...drawn);
    gameState.log.push({ type: 'battlecry', effect: 'draw_2', playerIndex });
  } else if (id === 'deal_2_minion' && enemies.length) {
    const enemy = enemies[Math.floor(Math.random() * enemies.length)];
    const enemyIdx = gameState.players.indexOf(enemy);
    if (enemy.board && enemy.board.length > 0) {
      const target = enemy.board[Math.floor(Math.random() * enemy.board.length)];
      target.health = Math.max(0, target.health - 2);
      const boardIdx = enemy.board.indexOf(target);
      gameState.log.push({ type: 'battlecry', effect: 'deal_2_minion', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx, damage: 2 });
      if (target.health <= 0) {
        enemy.board.splice(boardIdx, 1);
        runDeathrattle(room, getCard(room, target.cardId), enemyIdx);
      }
    }
  } else if (id === 'destroy_weak_minion' && enemies.length) {
    const enemy = enemies[Math.floor(Math.random() * enemies.length)];
    const enemyIdx = gameState.players.indexOf(enemy);
    if (enemy.board && enemy.board.length > 0) {
      const weak = enemy.board.filter(m => m.attack <= 3);
      if (weak.length > 0) {
        const target = weak[Math.floor(Math.random() * weak.length)];
        const boardIdx = enemy.board.indexOf(target);
        const targetCard = getCard(room, target.cardId);
        enemy.board.splice(boardIdx, 1);
        gameState.log.push({ type: 'battlecry', effect: 'destroy_weak_minion', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx });
        if (targetCard && targetCard.deathrattleId) {
          runDeathrattle(room, targetCard, enemyIdx);
        }
      }
    }
  } else if (id === 'heal_hero_2') {
    healPlayer(p, 2);
    gameState.log.push({ type: 'battlecry', effect: 'heal_hero_2', playerIndex });
  } else if (id === 'heal_hero_3') {
    healPlayer(p, 3);
    gameState.log.push({ type: 'battlecry', effect: 'heal_hero_3', playerIndex });
  } else if (id === 'buff_all_friendly_attack') {
    p.board.forEach((m) => {
      m.attack += 1;
    });
    gameState.log.push({ type: 'battlecry', effect: 'buff_all_friendly_attack', playerIndex });
  } else if (id === 'deal_3_all_enemies') {
    enemies.forEach((e) => {
      applyDamageToPlayer(e, 3);
    });
    gameState.log.push({ type: 'battlecry', effect: 'deal_3_all_enemies', fromPlayer: playerIndex });
  } else if (id === 'deal_3_any') {
    if (enemies.length) {
      const t = enemies[Math.floor(Math.random() * enemies.length)];
      const idx = gameState.players.indexOf(t);
      if (t.board && t.board.length > 0) {
        const target = t.board[Math.floor(Math.random() * t.board.length)];
        target.health = Math.max(0, target.health - 3);
        const boardIdx = t.board.indexOf(target);
        gameState.log.push({ type: 'battlecry', effect: 'deal_3_any', fromPlayer: playerIndex, toPlayer: idx, toIdx: boardIdx, damage: 3 });
        if (target.health <= 0) {
          t.board.splice(boardIdx, 1);
          runDeathrattle(room, getCard(room, target.cardId), idx);
        }
      } else {
        applyDamageToPlayer(t, 3);
        gameState.log.push({ type: 'battlecry', effect: 'deal_3_any', fromPlayer: playerIndex, toPlayer: idx, damage: 3 });
      }
    }
  } else if (id === 'draw_1_gain_mana') {
    const drawn = drawCards(p.deck, 1);
    if (drawn.length) p.hand.push(...drawn);
    p.maxMana = Math.min(10, p.maxMana + 1);
    p.mana = Math.min(10, p.mana + 1);
    gameState.log.push({ type: 'battlecry', effect: 'draw_1_gain_mana', playerIndex });
  } else if (id === 'destroy_medium_minion' && enemies.length) {
    const enemy = enemies[Math.floor(Math.random() * enemies.length)];
    const enemyIdx = gameState.players.indexOf(enemy);
    if (enemy.board && enemy.board.length > 0) {
      const weak = enemy.board.filter(m => m.attack <= 4);
      if (weak.length > 0) {
        const target = weak[Math.floor(Math.random() * weak.length)];
        const boardIdx = enemy.board.indexOf(target);
        const targetCard = getCard(room, target.cardId);
        enemy.board.splice(boardIdx, 1);
        gameState.log.push({ type: 'battlecry', effect: 'destroy_medium_minion', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx });
        if (targetCard && targetCard.deathrattleId) {
          runDeathrattle(room, targetCard, enemyIdx);
        }
      }
    }
  } else if (id === 'buff_friendly_2_1') {
    if (p.board && p.board.length > 0) {
      const target = p.board[Math.floor(Math.random() * p.board.length)];
      target.attack += 2;
      target.health += 1;
      target.maxHealth = (target.maxHealth || target.health) + 1;
      const boardIdx = p.board.indexOf(target);
      gameState.log.push({ type: 'battlecry', effect: 'buff_friendly_2_1', playerIndex, toIdx: boardIdx });
    }
  } else if (id === 'buff_2_2') {
    if (p.board && p.board.length > 0) {
      const target = p.board[Math.floor(Math.random() * p.board.length)];
      target.attack += 2;
      target.health += 2;
      target.maxHealth = (target.maxHealth || target.health) + 2;
      const boardIdx = p.board.indexOf(target);
      gameState.log.push({ type: 'battlecry', effect: 'buff_2_2', playerIndex, toIdx: boardIdx });
    }
  } else if (id === 'heal_hero_5') {
    healPlayer(p, 5);
    gameState.log.push({ type: 'battlecry', effect: 'heal_hero_5', playerIndex });
  } else if (id === 'return_hand_enemy') {
    // Для Ezra Bridger требуется выбор конкретного игрока
    // Отправляем запрос клиенту для выбора цели
    const socket = io.sockets.sockets.get(p.id);
    if (socket && enemies.length > 0) {
      socket.emit('battlecryTargetRequest', {
        battlecryId: 'return_hand_enemy',
        cardId: card.id || Object.keys(cardDb).find(k => cardDb[k] === card),
        availableTargets: enemies.map((e, idx) => ({
          playerIndex: gameState.players.indexOf(e),
          playerName: e.name || `Игрок ${gameState.players.indexOf(e) + 1}`,
          hasMinions: e.board && e.board.length > 0
        }))
      });
    }
    return; // Не выполняем сразу, ждём выбора цели
  } else if (id === 'destroy_strongest_enemy' && enemies.length) {
    const enemy = enemies[Math.floor(Math.random() * enemies.length)];
    const enemyIdx = gameState.players.indexOf(enemy);
    if (enemy.board && enemy.board.length > 0) {
      const strongest = enemy.board.reduce((max, m) => (m.attack > (max?.attack || 0) ? m : max), null);
      if (strongest) {
        const boardIdx = enemy.board.indexOf(strongest);
        const targetCard = getCard(room, strongest.cardId);
        enemy.board.splice(boardIdx, 1);
        gameState.log.push({ type: 'battlecry', effect: 'destroy_strongest_enemy', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx });
        if (targetCard && targetCard.deathrattleId) {
          runDeathrattle(room, targetCard, enemyIdx);
        }
      }
    }
  } else if (id === 'freeze_all_enemies') {
    enemies.forEach((enemy) => {
      if (enemy.board) {
        enemy.board.forEach((m) => {
          m.frozen = true;
        });
      }
    });
    gameState.log.push({ type: 'battlecry', effect: 'freeze_all_enemies', fromPlayer: playerIndex });
  } else if (id === 'random_effect') {
    const effects = [
      () => { const drawn = drawCards(p.deck, 1); if (drawn.length) p.hand.push(...drawn); gameState.log.push({ type: 'battlecry', effect: 'random_draw', playerIndex }); },
      () => { if (enemies.length) { const t = enemies[Math.floor(Math.random() * enemies.length)]; applyDamageToPlayer(t, 1); gameState.log.push({ type: 'battlecry', effect: 'random_damage', fromPlayer: playerIndex, toPlayer: gameState.players.indexOf(t), damage: 1 }); } },
      () => { healPlayer(p, 1); gameState.log.push({ type: 'battlecry', effect: 'random_heal', playerIndex }); }
    ];
    const randomEffect = effects[Math.floor(Math.random() * effects.length)];
    randomEffect();
  } else if (id === 'deal_2_or_4_hero' && enemies.length) {
    const enemy = enemies[Math.floor(Math.random() * enemies.length)];
    const enemyIdx = gameState.players.indexOf(enemy);
    if (enemy.board && enemy.board.length > 0) {
      const target = enemy.board[Math.floor(Math.random() * enemy.board.length)];
      target.health = Math.max(0, target.health - 2);
      const boardIdx = enemy.board.indexOf(target);
      applyDamageToPlayer(enemy, 2);
      gameState.log.push({ type: 'battlecry', effect: 'deal_2_or_4_hero', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx, damage: 2 });
      if (target.health <= 0) {
        enemy.board.splice(boardIdx, 1);
        runDeathrattle(room, getCard(room, target.cardId), enemyIdx);
      }
    } else {
      enemy.health = Math.max(0, enemy.health - 4);
      applyDamageToPlayer(enemy, 4);
      gameState.log.push({ type: 'battlecry', effect: 'deal_2_or_4_hero', fromPlayer: playerIndex, toPlayer: enemyIdx, damage: 4 });
    }
  } else if (id === 'deal_2_all_enemy_minions') {
    enemies.forEach((enemy) => {
      if (enemy.board) {
        enemy.board.forEach((m) => {
          m.health = Math.max(0, m.health - 2);
          if (m.health <= 0) {
            const dc = getCard(room, m.cardId);
            if (dc && dc.deathrattleId) runDeathrattle(room, dc, gameState.players.indexOf(enemy));
          }
        });
        enemy.board = enemy.board.filter((m) => m.health > 0);
      }
    });
    gameState.log.push({ type: 'battlecry', effect: 'deal_2_all_enemy_minions', fromPlayer: playerIndex });
  } else if (id === 'heal_all_friendly_2') {
    p.board.forEach((m) => {
      m.health = Math.min(m.maxHealth || m.health, m.health + 2);
    });
    gameState.log.push({ type: 'battlecry', effect: 'heal_all_friendly_2', playerIndex });
  } else if (id === 'heal_all_5') {
    p.health = Math.min(30, p.health + 5);
    p.board.forEach((m) => {
      m.health = Math.min(m.maxHealth || m.health, m.health + 5);
    });
    gameState.log.push({ type: 'battlecry', effect: 'heal_all_5', playerIndex });
  } else if (id === 'destroy_strong_minion' && enemies.length) {
    const enemy = enemies[Math.floor(Math.random() * enemies.length)];
    const enemyIdx = gameState.players.indexOf(enemy);
    if (enemy.board && enemy.board.length > 0) {
      const strong = enemy.board.filter(m => m.attack <= 5);
      if (strong.length > 0) {
        const target = strong[Math.floor(Math.random() * strong.length)];
        const boardIdx = enemy.board.indexOf(target);
        const targetCard = getCard(room, target.cardId);
        enemy.board.splice(boardIdx, 1);
        gameState.log.push({ type: 'battlecry', effect: 'destroy_strong_minion', fromPlayer: playerIndex, toPlayer: enemyIdx, toIdx: boardIdx });
        if (targetCard && targetCard.deathrattleId) {
          runDeathrattle(room, targetCard, enemyIdx);
        }
      }
    }
  } else if (id === 'buff_all_friendly_1_1') {
    p.board.forEach((m) => {
      m.attack += 1;
      m.health += 1;
      m.maxHealth = (m.maxHealth || m.health) + 1;
    });
    gameState.log.push({ type: 'battlecry', effect: 'buff_all_friendly_1_1', playerIndex });
  }
}

function runDeathrattle(room, card, ownerIndex) {
  if (!card) return;
  
  // Проверка на превращение при смерти
  if (card.transformOnDeath) {
    handleTransformOnDeath(room, card, ownerIndex);
  }
  
  if (!card.deathrattleId) return;
  const gameState = room.gameState;
  const owner = gameState.players[ownerIndex];
  const enemies = gameState.players.filter((pl, i) => i !== ownerIndex && pl.health > 0);
  const id = card.deathrattleId;
  if (id === 'draw_1') {
    const drawn = drawCards(owner.deck, 1);
    if (drawn.length) owner.hand.push(...drawn);
    gameState.log.push({ type: 'deathrattle', effect: 'draw_1', playerIndex: ownerIndex });
  } else if (id === 'deal_2_all_enemies') {
    enemies.forEach((e) => {
      applyDamageToPlayer(e, 2);
    });
    gameState.log.push({ type: 'deathrattle', effect: 'deal_2_all_enemies', fromPlayer: ownerIndex });
  } else if (id === 'deal_1_random_enemy' && enemies.length) {
    const t = enemies[Math.floor(Math.random() * enemies.length)];
    applyDamageToPlayer(t, 1);
    gameState.log.push({ type: 'deathrattle', effect: 'deal_1_random_enemy', fromPlayer: ownerIndex });
  } else if (id === 'heal_hero_2') {
    healPlayer(owner, 2);
    gameState.log.push({ type: 'deathrattle', effect: 'heal_hero_2', playerIndex: ownerIndex });
  } else if (id === 'draw_3') {
    const drawn = drawCards(owner.deck, 3);
    if (drawn.length) owner.hand.push(...drawn);
    gameState.log.push({ type: 'deathrattle', effect: 'draw_3', playerIndex: ownerIndex });
  } else if (id === 'deal_4_random' && enemies.length) {
    const t = enemies[Math.floor(Math.random() * enemies.length)];
    applyDamageToPlayer(t, 4);
    gameState.log.push({ type: 'deathrattle', effect: 'deal_4_random', fromPlayer: ownerIndex, toPlayer: gameState.players.indexOf(t), damage: 4 });
  } else if (id === 'deal_3_random' && enemies.length) {
    const t = enemies[Math.floor(Math.random() * enemies.length)];
    applyDamageToPlayer(t, 3);
    gameState.log.push({ type: 'deathrattle', effect: 'deal_3_random', fromPlayer: ownerIndex, toPlayer: gameState.players.indexOf(t), damage: 3 });
  }
}

function handleTransformOnDeath(room, card, ownerIndex) {
  if (card.transformOnDeath) {
    const owner = room.gameState.players[ownerIndex];
    const minionOnBoard = owner.board.find(m => {
      const mCard = cardDb[m.cardId];
      return mCard && (mCard.id === card.id || mCard.name === card.name);
    });
    if (minionOnBoard) {
      minionOnBoard.cardId = card.transformOnDeath;
      minionOnBoard.health = Math.max(1, minionOnBoard.health);
      const newCard = cardDb[card.transformOnDeath];
      if (newCard) {
        minionOnBoard.maxHealth = newCard.health;
        minionOnBoard.attack = newCard.attack;
        room.gameState.log.push({ type: 'transform', fromCard: card.name, toCard: newCard.name, playerIndex: ownerIndex });
      }
    }
  }
}

function handleTransformOnDeath(room, card, ownerIndex) {
  if (card.transformOnDeath) {
    const owner = room.gameState.players[ownerIndex];
    const minionOnBoard = owner.board.find(m => m.cardId === card.id || m.cardId === Object.keys(cardDb).find(k => cardDb[k] === card));
    if (minionOnBoard) {
      minionOnBoard.cardId = card.transformOnDeath;
      minionOnBoard.health = Math.max(1, minionOnBoard.health);
      const newCard = cardDb[card.transformOnDeath];
      if (newCard) {
        minionOnBoard.maxHealth = newCard.health;
        minionOnBoard.attack = newCard.attack;
        room.gameState.log.push({ type: 'transform', fromCard: card.id || card.name, toCard: card.transformOnDeath, playerIndex: ownerIndex });
      }
    }
  }
}

async function updatePlayerStats(playerName, isWinner, stats = {}) {
  if (!redisClient.isRedisAvailable() || !playerName) return;
  
  try {
    // playerName может быть именем пользователя или socket.id
    // Пытаемся найти по имени пользователя
    const userDataStr = await redisClient.get(`user:${playerName}`);
    if (userDataStr) {
      const userData = JSON.parse(userDataStr);
      userData.gamesPlayed = (userData.gamesPlayed || 0) + 1;
      if (isWinner) {
        userData.gamesWon = (userData.gamesWon || 0) + 1;
      }
      if (stats.totalDamage) {
        userData.totalDamage = (userData.totalDamage || 0) + stats.totalDamage;
      }
      if (stats.totalHealing) {
        userData.totalHealing = (userData.totalHealing || 0) + stats.totalHealing;
      }
      await redisClient.set(`user:${playerName}`, JSON.stringify(userData));
    }
  } catch (error) {
    console.error('Ошибка обновления статистики для', playerName, ':', error);
  }
}

function checkGameOver(room) {
  const gameState = room.gameState;
  const alive = gameState.players.filter((pp) => pp.health > 0);
  const dead = gameState.players.filter((pp) => pp.health <= 0);
  
  // Помечаем мёртвых игроков
  dead.forEach((p) => {
    if (p.health <= 0 && !p.isDead) {
      p.isDead = true;
      gameState.log.push({ type: 'playerDefeated', playerIndex: gameState.players.indexOf(p), playerName: p.name });
    }
  });
  
  // Если остался один или меньше живых - игра окончена
  if (alive.length <= 1) {
    gameState.phase = 'ended';
    gameState.winner = alive.length === 1 ? alive[0] : null;
    
    // Обновляем статистику игроков
    if (gameState.winner) {
      gameState.players.forEach((player) => {
        const isWinner = player.id === gameState.winner.id;
        // Подсчитываем статистику из лога
        const totalDamage = gameState.log
          .filter(log => {
            const playerIdx = gameState.players.indexOf(player);
            return (log.fromPlayer === playerIdx || log.fromPlayerIndex === playerIdx) && log.damage;
          })
          .reduce((sum, log) => sum + (log.damage || 0), 0);
        
        // Обновляем асинхронно, не блокируя основной поток
        // Используем имя пользователя из маппинга или имя игрока
        const username = socketToUsername.get(player.id) || player.name;
        updatePlayerStats(username, isWinner, { totalDamage }).catch(err => {
          console.error('Ошибка обновления статистики для', username, err);
        });
      });
    }
  }
  
  // Если текущий игрок мёртв, переключаем ход
  const currentPlayer = gameState.players[gameState.currentPlayerIndex];
  if (currentPlayer && currentPlayer.health <= 0 && alive.length > 1) {
    // Пропускаем ход мёртвого игрока
    gameState.log.push({ type: 'skipTurn', playerIndex: gameState.currentPlayerIndex, reason: 'defeated' });
    endTurn(room);
  }
}

function playCard(room, socketId, handIndex, boardPos) {
  const gameState = room.gameState;
  const pi = findPlayerIndex(room, socketId);
  if (pi < 0 || gameState.currentPlayerIndex !== pi) return;
  const p = gameState.players[pi];
  if (p.health <= 0 || p.isDead) return; // Мёртвый игрок не может играть
  const cid = p.hand[handIndex];
  if (!cid) return;
  const card = getCard(room, cid);
  if (!card || card.type !== 'minion' || p.board.length >= 7) return;
  const cost = card.cost || 0;
  if (p.mana < cost) return;

  p.mana -= cost;
  p.hand.splice(handIndex, 1);
  const minion = {
    id: `${Date.now()}-${Math.random().toString(36).slice(2)}`,
    cardId: cid,
    attack: card.attack,
    health: card.health,
    maxHealth: card.health,
    canAttack: card.attack > 0,
    attacksUsed: 0, // Для двойной атаки
  };
  p.board.splice(typeof boardPos === 'number' ? boardPos : p.board.length, 0, minion);
  gameState.log.push({ type: 'play', playerIndex: pi, cardId: cid, minionId: minion.id });
  
  // Обработка battlecry для карт с броней
  if (card.battlecry === 'add_armor_15') {
    p.armor = (p.armor || 0) + 15;
    gameState.log.push({ type: 'battlecry', effect: 'add_armor_15', playerIndex: pi });
  }
  
  // Для battlecry, требующих выбора цели (Ezra Bridger), отправляем запрос клиенту
  if (card.battlecryId === 'return_hand_enemy') {
    const enemies = gameState.players.filter((pl, i) => i !== pi && pl.health > 0);
    if (enemies.length > 0) {
      const socket = io.sockets.sockets.get(p.id);
      if (socket) {
        // Сохраняем информацию о миньоне для последующего выполнения battlecry
        minion.pendingBattlecry = { battlecryId: card.battlecryId, cardId: cid };
        socket.emit('battlecryTargetRequest', {
          battlecryId: 'return_hand_enemy',
          cardId: cid,
          minionId: minion.id,
          availableTargets: enemies.flatMap(enemy => {
            const enemyIdx = gameState.players.indexOf(enemy);
            if (!enemy.board || enemy.board.length === 0) return [];
            return enemy.board.map((m, boardIdx) => ({
              playerIndex: enemyIdx,
              boardIndex: boardIdx,
              playerName: enemy.name || `Игрок ${enemyIdx + 1}`,
              minionName: getCard(room, m.cardId)?.name || m.cardId
            }));
          })
        });
        broadcastGameState(room);
        return; // Не применяем синергии пока, ждём выбора цели
      }
    }
  } else if (card.battlecryId) {
    runBattlecry(room, card, pi);
  }
  
  // Применяем синергии после размещения карты
  applySynergies(room);
  
  checkGameOver(room);
  broadcastGameState(room);
}

// Функция для применения синергий между картами
function applySynergies(room) {
  const gameState = room.gameState;
  const cardDb = require('./cards');
  
  gameState.players.forEach((player, playerIndex) => {
    if (!player.board || player.board.length === 0) return;
    
    // Сбрасываем все бонусы синергий перед пересчётом
    player.board.forEach(m => {
      m.synergyAttackBonus = 0;
      m.synergyHealthBonus = 0;
    });
    
    player.board.forEach((minion, idx) => {
      const card = cardDb[minion.cardId];
      if (!card) return;
      
      // Дарт Вейдер даёт +1/+1 всем штурмовикам и клонам
      if (card.id === 'vader' || card.name === 'Darth Vader') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'stormtrooper' || otherCard.name === 'Stormtrooper' || 
                otherCard.id === 'clone_trooper' || otherCard.name === 'Clone Trooper' ||
                otherCard.id === 'clone_commando' || otherCard.name === 'Clone Commando' ||
                otherCard.id === 'arc_trooper' || otherCard.name === 'ARC Trooper')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
        
        // Аура: Пока Дарт Вейдер на поле, все джедаи противника получают -1/-1
        if (card.aura === 'jedi_debuff_enemy') {
          gameState.players.forEach((enemyPlayer, enemyPlayerIndex) => {
            if (enemyPlayerIndex !== playerIndex && enemyPlayer.board) {
              enemyPlayer.board.forEach((enemyMinion) => {
                const enemyCard = cardDb[enemyMinion.cardId];
                if (enemyCard && (enemyCard.name?.includes('Jedi') || 
                    enemyCard.id === 'luke' || enemyCard.id === 'obiwan' || 
                    enemyCard.id === 'ahsoka' || enemyCard.id === 'mace' ||
                    enemyCard.id === 'quigon' || enemyCard.id === 'plo_koon' ||
                    enemyCard.id === 'ki_adi' || enemyCard.id === 'aayla' ||
                    enemyCard.id === 'shaak_ti' || enemyCard.id === 'kanan' ||
                    enemyCard.id === 'ezra' || enemyCard.id === 'cal_kestis' ||
                    enemyCard.id === 'yoda' || enemyCard.id === 'anakin')) {
                  enemyMinion.synergyAttackBonus = (enemyMinion.synergyAttackBonus || 0) - 1;
                  enemyMinion.synergyHealthBonus = (enemyMinion.synergyHealthBonus || 0) - 1;
                }
              });
            }
          });
        }
      }
      
      // Люк Скайуокер даёт +1/+1 всем повстанцам
      if (card.id === 'luke' || card.name === 'Luke Skywalker') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && otherCard.faction === 'rebellion' && otherCard.type === 'minion') {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Император Палпатин даёт +1/+1 всем ситхам и имперским картам
      if (card.id === 'palpatine' || card.name === 'Emperor Palpatine') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.faction === 'empire' || 
                otherCard.name?.includes('Darth') || otherCard.id === 'maul' || 
                otherCard.id === 'vader' || otherCard.id === 'dooku' || otherCard.id === 'kylo')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Хан Соло и Чубакка дают друг другу +1/+1
      if (card.id === 'han' || card.name === 'Han Solo') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'chewie' || otherCard.name === 'Chewbacca')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      if (card.id === 'chewie' || card.name === 'Chewbacca') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'han' || otherCard.name === 'Han Solo')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // R2-D2 и C-3PO дают друг другу +1/+1
      if (card.id === 'r2d2' || card.name === 'R2-D2') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'c3po' || otherCard.name === 'C-3PO')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      if (card.id === 'c3po' || card.name === 'C-3PO') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'r2d2' || otherCard.name === 'R2-D2')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Лея даёт +1/+1 Люку
      if (card.id === 'leia' || card.name === 'Princess Leia') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'luke' || otherCard.name === 'Luke Skywalker')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Люк даёт +1/+1 Лее
      if (card.id === 'luke' || card.name === 'Luke Skywalker') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'leia' || otherCard.name === 'Princess Leia')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Оби-Ван даёт +1/+1 Энакину/Анакину
      if (card.id === 'obiwan' || card.name === 'Obi-Wan Kenobi') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'anakin' || otherCard.name === 'Anakin Skywalker')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Энакин даёт +1/+1 Оби-Вану, Падме и Асоке
      if (card.id === 'anakin' || card.name === 'Anakin Skywalker') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'obiwan' || otherCard.name === 'Obi-Wan Kenobi' ||
                otherCard.id === 'padme' || otherCard.name === 'Padmé Amidala' ||
                otherCard.id === 'ahsoka' || otherCard.name === 'Ahsoka Tano')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
        
        // Аура: Пока Анакин на поле, все джедаи получают +1/+1
        if (card.aura === 'jedi_buff_all') {
          player.board.forEach((other, otherIdx) => {
            if (idx !== otherIdx) {
              const otherCard = cardDb[other.cardId];
              if (otherCard && (otherCard.name?.includes('Jedi') || 
                  otherCard.id === 'luke' || otherCard.id === 'obiwan' || 
                  otherCard.id === 'ahsoka' || otherCard.id === 'mace' ||
                  otherCard.id === 'quigon' || otherCard.id === 'plo_koon' ||
                  otherCard.id === 'ki_adi' || otherCard.id === 'aayla' ||
                  otherCard.id === 'shaak_ti' || otherCard.id === 'kanan' ||
                  otherCard.id === 'ezra' || otherCard.id === 'cal_kestis' ||
                  otherCard.id === 'yoda')) {
                other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
                other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
              }
            }
          });
        }
      }
      
      // Аура: Droid Armor - мех-юниты имеют удвоенную броню
      if (card.aura === 'double_armor_mechs') {
        // Удваиваем броню игрока, если есть мех-юниты на поле
        const hasMechs = player.board.some(other => {
          const otherCard = cardDb[other.cardId];
          return otherCard && (otherCard.id === 'r2d2' || otherCard.id === 'c3po' || 
            otherCard.id === 'bb8' || otherCard.id === 'droid' ||
            otherCard.name?.includes('Droid') || otherCard.name?.includes('дроид'));
        });
        if (hasMechs) {
          // Броня уже применена, но можно добавить бонус
          // Это будет обрабатываться при применении урона
        }
      }
      
      // Йода даёт +1/+1 всем джедаям
      if (card.id === 'yoda' || card.name === 'Yoda') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && otherCard.faction === 'rebellion' && 
                (otherCard.name?.includes('Jedi') || otherCard.id === 'luke' || 
                 otherCard.id === 'obiwan' || otherCard.id === 'anakin' || 
                 otherCard.id === 'ahsoka' || otherCard.id === 'mace' ||
                 otherCard.id === 'quigon' || otherCard.id === 'plo_koon' ||
                 otherCard.id === 'ki_adi' || otherCard.id === 'aayla' ||
                 otherCard.id === 'shaak_ti' || otherCard.id === 'kanan' ||
                 otherCard.id === 'ezra' || otherCard.id === 'cal_kestis')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Боба Фетт даёт +1/+1 Джанго Фетту
      if (card.id === 'boba' || card.name === 'Boba Fett') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'jango' || otherCard.name === 'Jango Fett')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Джанго Фетт даёт +1/+1 Бобе Фетту
      if (card.id === 'jango' || card.name === 'Jango Fett') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'boba' || otherCard.name === 'Boba Fett')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Эйсока даёт +1/+1 Рексу
      if (card.id === 'ahsoka' || card.name === 'Ahsoka Tano') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'rex' || otherCard.name === 'Captain Rex')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Рекс даёт +1/+1 Эйсоке
      if (card.id === 'rex' || card.name === 'Captain Rex') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'ahsoka' || otherCard.name === 'Ahsoka Tano')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Мандалорец даёт +1/+1 Грогу
      if (card.id === 'mando' || card.name === 'Din Djarin') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'grogu' || otherCard.name === 'Grogu')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Грогу даёт +1/+1 Мандалорцу
      if (card.id === 'grogu' || card.name === 'Grogu') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'mando' || otherCard.name === 'Din Djarin')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Таркин даёт +1/+1 имперским картам
      if (card.id === 'tarkin' || card.name === 'Grand Moff Tarkin') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && otherCard.faction === 'empire' && otherCard.type === 'minion') {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Дарт Мол даёт +1/+1 Сэвиджу Оппрессу
      if (card.id === 'maul' || card.name === 'Darth Maul') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'savage' || otherCard.name === 'Savage Opress')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Сэвидж Оппресс даёт +1/+1 Дарту Молу
      if (card.id === 'savage' || card.name === 'Savage Opress') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'maul' || otherCard.name === 'Darth Maul')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Падме даёт +1/+1 Энакину
      if (card.id === 'padme' || card.name === 'Padmé Amidala') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'anakin' || otherCard.name === 'Anakin Skywalker')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
      
      // Энакин даёт +1/+1 Падме
      if (card.id === 'anakin' || card.name === 'Anakin Skywalker') {
        player.board.forEach((other, otherIdx) => {
          if (idx !== otherIdx) {
            const otherCard = cardDb[other.cardId];
            if (otherCard && (otherCard.id === 'padme' || otherCard.name === 'Padmé Amidala')) {
              other.synergyAttackBonus = (other.synergyAttackBonus || 0) + 1;
              other.synergyHealthBonus = (other.synergyHealthBonus || 0) + 1;
            }
          }
        });
      }
    });
  });
}

function playSpell(room, socketId, handIndex, targetPlayerIndex, targetBoardIndex) {
  const gameState = room.gameState;
  const pi = findPlayerIndex(room, socketId);
  if (pi < 0 || gameState.currentPlayerIndex !== pi) return;
  const p = gameState.players[pi];
  if (p.health <= 0 || p.isDead) return; // Мёртвый игрок не может играть
  const cid = p.hand[handIndex];
  if (!cid) return;
  const card = getCard(room, cid);
  if (!card || card.type !== 'spell') return;
  const cost = card.cost || 0;
  if (p.mana < cost) return;

  const eff = card.spellEffect;
  const needTarget = card.spellTarget && card.spellTarget !== 'none';
  
  // Специальная обработка для steal_cards - требует выбор противника и карт с его доски
  // Если targetPlayerIndex указан, значит клиент уже выбрал противника и нужно отправить запрос на выбор карт
  if (eff === 'steal_cards') {
    if (targetPlayerIndex == null || targetPlayerIndex === pi) {
      // Если противник не выбран, просто возвращаемся (клиент должен выбрать противника)
      return;
    }
    const targetPlayer = gameState.players[targetPlayerIndex];
    if (!targetPlayer || targetPlayer.health <= 0 || !targetPlayer.board || targetPlayer.board.length === 0) {
      return;
    }
    
    // Отправляем запрос на выбор карт для кражи
    const socket = io.sockets.sockets.get(p.id);
    if (socket) {
      socket.emit('stealCardsRequest', {
        targetPlayerIndex: targetPlayerIndex,
        targetPlayerName: targetPlayer.name || `Игрок ${targetPlayerIndex + 1}`,
        targetBoardSize: targetPlayer.board.length,
        maxCards: Math.min(2, targetPlayer.board.length)
      });
    }
    // Не тратим ману и не удаляем карту пока - это сделаем в stealCardsFromDeck после выбора карт
    return;
  }

  // Для других заклинаний проверяем targetBoardIndex только если это не выбор игрока
  if (needTarget && card.spellTarget !== 'enemy_player' && (targetPlayerIndex == null || targetBoardIndex == null)) return;
  if (needTarget && card.spellTarget === 'enemy_player' && targetPlayerIndex == null) return;
  const enemies = gameState.players.filter((pl, i) => i !== pi && pl.health > 0);

  if (eff === 'deal_2') {
    if (targetBoardIndex === -1) {
      const t = gameState.players[targetPlayerIndex];
      if (!t || t.health <= 0) return;
      applyDamageToPlayer(t, 2);
      gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'deal_2_hero', damage: 2 });
    } else {
      const t = gameState.players[targetPlayerIndex];
      const m = t?.board?.[targetBoardIndex];
      if (!m) return;
      m.health -= 2;
      gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex, effect: 'deal_2_minion', damage: 2 });
      if (m.health <= 0) {
        const dc = getCard(room, m.cardId);
        if (dc && dc.deathrattleId) runDeathrattle(room, dc, targetPlayerIndex);
      }
    }
  } else if (eff === 'draw_2') {
    const drawn = drawCards(p.deck, 2);
    if (drawn.length) p.hand.push(...drawn);
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'draw_2' });
  } else if (eff === 'buff_2_2') {
    const m = p.board[targetBoardIndex];
    if (!m) return;
    m.attack += 2;
    m.health += 2;
    m.maxHealth = (m.maxHealth || m.health) + 2;
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toIdx: targetBoardIndex, effect: 'buff_2_2' });
  } else if (eff === 'deal_1_all_enemies') {
    gameState.players.forEach((pl, i) => {
      if (i === pi) return;
      applyDamageToPlayer(pl, 1);
      pl.board.forEach((m) => {
        m.health -= 1;
        if (m.health <= 0) {
          const dc = getCard(room, m.cardId);
          if (dc && dc.deathrattleId) runDeathrattle(room, dc, i);
        }
      });
      pl.board = pl.board.filter((m) => m.health > 0);
    });
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'deal_1_all_enemies' });
  } else if (eff === 'freeze') {
    const t = gameState.players[targetPlayerIndex];
    const m = t?.board?.[targetBoardIndex];
    if (!m) return;
    m.frozen = true;
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex, effect: 'freeze' });
  } else if (eff === 'return_hand') {
    const t = gameState.players[targetPlayerIndex];
    const m = t?.board?.[targetBoardIndex];
    if (!m) return;
    t.board.splice(targetBoardIndex, 1);
    t.hand.push(m.cardId);
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex, effect: 'return_hand' });
  } else if (eff === 'deal_4') {
    if (targetBoardIndex === -1) {
      const t = gameState.players[targetPlayerIndex];
      if (!t || t.health <= 0) return;
      applyDamageToPlayer(t, 4);
      gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'deal_4_hero', damage: 4 });
    } else {
      const t = gameState.players[targetPlayerIndex];
      const m = t?.board?.[targetBoardIndex];
      if (!m) return;
      m.health = Math.max(0, m.health - 4);
      gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex, effect: 'deal_4_minion', damage: 4 });
      if (m.health <= 0) {
        t.board.splice(targetBoardIndex, 1);
        const dc = getCard(room, m.cardId);
        if (dc && dc.deathrattleId) runDeathrattle(room, dc, targetPlayerIndex);
      }
    }
  } else if (eff === 'heal_4') {
    if (targetBoardIndex === -1) {
      const t = gameState.players[targetPlayerIndex];
      if (!t) return;
      healPlayer(t, 4);
      gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'heal_4_hero' });
    } else {
      const t = gameState.players[targetPlayerIndex];
      const m = t?.board?.[targetBoardIndex];
      if (!m) return;
      m.health = Math.min(m.maxHealth || m.health, m.health + 4);
      gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex, effect: 'heal_4_minion' });
    }
  } else if (eff === 'deal_3_minion') {
    const t = gameState.players[targetPlayerIndex];
    const m = t?.board?.[targetBoardIndex];
    if (!m) return;
    m.health = Math.max(0, m.health - 3);
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex, effect: 'deal_3_minion', damage: 3 });
    if (m.health <= 0) {
      t.board.splice(targetBoardIndex, 1);
      const dc = getCard(room, m.cardId);
      if (dc && dc.deathrattleId) runDeathrattle(room, dc, targetPlayerIndex);
    }
  } else if (eff === 'freeze_damage') {
    const t = gameState.players[targetPlayerIndex];
    const m = t?.board?.[targetBoardIndex];
    if (!m) return;
    m.frozen = true;
    m.health = Math.max(0, m.health - 2);
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex, effect: 'freeze_damage', damage: 2 });
    if (m.health <= 0) {
      t.board.splice(targetBoardIndex, 1);
      const dc = getCard(room, m.cardId);
      if (dc && dc.deathrattleId) runDeathrattle(room, dc, targetPlayerIndex);
    }
  } else if (eff === 'buff_all_friendly') {
    p.board.forEach((m) => {
      m.attack += 1;
      m.health += 1;
      m.maxHealth = (m.maxHealth || m.health) + 1;
    });
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'buff_all_friendly' });
  } else if (eff === 'heal_hero_5') {
    healPlayer(p, 5);
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'heal_hero_5' });
  } else if (eff === 'heal_hero_30pct') {
    const healAmount = Math.floor(p.health * 0.3);
    healPlayer(p, healAmount);
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'heal_hero_30pct', amount: healAmount });
  } else if (eff === 'add_armor_10') {
    p.armor = (p.armor || 0) + 10;
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'add_armor_10' });
  } else if (eff === 'destroy_weak_2') {
    enemies.forEach((enemy) => {
      if (enemy.board && enemy.board.length > 0) {
        const weak = enemy.board.filter(m => m.attack <= 2);
        weak.forEach((target) => {
          const boardIdx = enemy.board.indexOf(target);
          const targetCard = getCard(room, target.cardId);
          enemy.board.splice(boardIdx, 1);
          if (targetCard && targetCard.deathrattleId) {
            runDeathrattle(room, targetCard, gameState.players.indexOf(enemy));
          }
        });
      }
    });
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, effect: 'destroy_weak_2' });
  } else if (eff === 'buff_3_2') {
    const m = p.board[targetBoardIndex];
    if (!m) return;
    m.attack += 3;
    m.health += 2;
    m.maxHealth = (m.maxHealth || m.health) + 2;
    gameState.log.push({ type: 'spell', spell: cid, fromPlayer: pi, toIdx: targetBoardIndex, effect: 'buff_3_2' });
  } else return;

  p.mana -= cost;
  p.hand.splice(handIndex, 1);
  // Применяем синергии после размещения карты
  applySynergies(room);
  
  checkGameOver(room);
  broadcastGameState(room);
}

function stealCardsFromDeck(room, socketId, handIndex, targetPlayerIndex, cardIndices) {
  const gameState = room.gameState;
  const pi = findPlayerIndex(room, socketId);
  if (pi < 0 || gameState.currentPlayerIndex !== pi) return;
  const p = gameState.players[pi];
  if (p.health <= 0 || p.isDead) return;
  
  const cid = p.hand[handIndex];
  if (!cid) return;
  const card = getCard(room, cid);
  if (!card || card.type !== 'spell' || card.spellEffect !== 'steal_cards') return;
  const cost = card.cost || 0;
  if (p.mana < cost) return;
  
  const targetPlayer = gameState.players[targetPlayerIndex];
  if (!targetPlayer || targetPlayerIndex === pi || targetPlayer.health <= 0) return;
  if (!targetPlayer.board || targetPlayer.board.length === 0) return;
  
  // Проверяем индексы карт на доске
  if (!Array.isArray(cardIndices) || cardIndices.length === 0 || cardIndices.length > 2) return;
  const validIndices = cardIndices.filter(idx => idx >= 0 && idx < targetPlayer.board.length);
  if (validIndices.length === 0) return;
  
  // Убираем дубликаты и сортируем по убыванию (чтобы удалять с конца)
  const uniqueIndices = [...new Set(validIndices)].sort((a, b) => b - a);
  
  // Крадём карты с доски противника
  const stolenCards = [];
  uniqueIndices.forEach(boardIdx => {
    if (boardIdx >= 0 && boardIdx < targetPlayer.board.length) {
      const minion = targetPlayer.board[boardIdx];
      if (minion && minion.cardId) {
        stolenCards.push(minion.cardId);
        targetPlayer.board.splice(boardIdx, 1);
      }
    }
  });
  
  // Добавляем украденные карты в руку игрока
  stolenCards.forEach(cardId => {
    if (p.hand.length < 10) {
      p.hand.push(cardId);
    }
  });
  
  // Тратим ману и удаляем заклинание
  p.mana -= cost;
  p.hand.splice(handIndex, 1);
  
  gameState.log.push({
    type: 'spell',
    spell: cid,
    fromPlayer: pi,
    toPlayer: targetPlayerIndex,
    effect: 'steal_cards',
    stolenCount: stolenCards.length
  });
  
  // Применяем синергии после изменений на доске
  applySynergies(room);
  
  // Применяем синергии после боя
  applySynergies(room);
  
  // Применяем синергии после изменений на доске
  applySynergies(room);
  
  checkGameOver(room);
  broadcastGameState(room);
}

/** Выбор существующей карты из БД по правилу «лучше по характеристикам» (выше стоимость, та же фракция). */
function pickForgeResultCard(cardIds) {
  const ids = Array.isArray(cardIds) ? cardIds.slice() : [];
  if (ids.length < 2 || ids.length > 3) return null;
  const cards = ids.map((id) => cardDb[id]).filter(Boolean);
  if (cards.length !== ids.length || cards.some((c) => c.type !== 'minion')) return null;
  const base = cards.reduce((a, b) => ((a.cost || 0) >= (b.cost || 0) ? a : b));
  const baseCost = base.cost || 0;
  const faction = base.faction || 'neutral';
  const targetCost = Math.min(10, baseCost + 1);
  const exclude = new Set(ids);

  function pool(filter) {
    return Object.keys(cardDb).filter((id) => {
      const c = cardDb[id];
      if (!c || c.type !== 'minion' || exclude.has(id)) return false;
      return filter(c, id);
    });
  }

  let list = pool((c) => (c.faction || 'neutral') === faction && (c.cost || 0) === targetCost);
  if (list.length === 0) list = pool((c) => (c.faction || 'neutral') === faction && (c.cost || 0) >= targetCost);
  if (list.length === 0) list = pool((c) => (c.faction || 'neutral') === 'neutral' && (c.cost || 0) >= targetCost);
  if (list.length === 0) list = pool((c) => (c.cost || 0) >= targetCost);
  if (list.length === 0) return null;
  list.sort();
  const resultCardId = list[0];
  return { resultCardId };
}

function forgeCard(room, socketId, cardIds) {
  const gameState = room.gameState;
  const pi = findPlayerIndex(room, socketId);
  if (pi < 0 || gameState.currentPlayerIndex !== pi) return;
  const p = gameState.players[pi];
  if (p.health <= 0 || p.isDead) return;
  if ((p.mana || 0) < 2) return;

  const ids = Array.isArray(cardIds) ? cardIds : [];
  if (ids.length < 2 || ids.length > 3) return;

  const hand = p.hand || [];
  const toRemove = ids.filter((id) => hand.includes(id));
  if (toRemove.length !== ids.length) return;

  const cards = toRemove.map((id) => cardDb[id]).filter(Boolean);
  if (cards.length !== toRemove.length || cards.some((c) => c.type !== 'minion')) return;

  const result = pickForgeResultCard(ids);
  if (!result) return;

  toRemove.forEach((id) => {
    const idx = p.hand.indexOf(id);
    if (idx >= 0) p.hand.splice(idx, 1);
  });
  if (p.hand.length < 10) p.hand.push(result.resultCardId);
  p.mana = Math.max(0, (p.mana || 0) - 2);

  gameState.log.push({ type: 'forge', fromPlayer: pi, resultCardId: result.resultCardId });

  applySynergies(room);
  checkGameOver(room);
  broadcastGameState(room);
}

function heroAbility(room, socketId, targetPlayerIndex, targetBoardIndex) {
  const gameState = room.gameState;
  const pi = findPlayerIndex(room, socketId);
  if (pi < 0 || gameState.currentPlayerIndex !== pi) return;
  const p = gameState.players[pi];
  if (p.health <= 0 || p.isDead) return; // Мёртвый игрок не может играть
  if (p.heroAbilityUsed || p.mana < 2) return;
  if (targetPlayerIndex == null || targetBoardIndex == null) return;
  const targetPlayer = gameState.players[targetPlayerIndex];
  if (!targetPlayer) return;
  if (targetBoardIndex === -1) {
    applyDamageToPlayer(targetPlayer, 1);
    gameState.log.push({ type: 'heroAbility', fromPlayer: pi, toPlayer: targetPlayerIndex, effect: 'deal_1_hero' });
  } else {
    const m = targetPlayer.board[targetBoardIndex];
    if (!m) return;
    m.health -= 1;
    gameState.log.push({ type: 'heroAbility', fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex, effect: 'deal_1_minion' });
  }
  p.mana -= 2;
  p.heroAbilityUsed = true;
  if (targetBoardIndex !== -1) {
    const m = targetPlayer.board[targetBoardIndex];
    if (m && m.health <= 0) {
      const card = getCard(room, m.cardId);
      if (card && card.deathrattleId) runDeathrattle(room, card, targetPlayerIndex);
    }
  }
  gameState.players.forEach((pl) => {
    pl.board = pl.board.filter((min) => min.health > 0);
  });
  // Применяем синергии после изменений на доске
  applySynergies(room);
  
  // Применяем синергии после изменений на доске
  applySynergies(room);
  
  checkGameOver(room);
  broadcastGameState(room);
}

function attack(room, socketId, attackerBoardIndex, targetPlayerIndex, targetBoardIndex) {
  const gameState = room.gameState;
  const pi = findPlayerIndex(room, socketId);
  if (pi < 0 || gameState.currentPlayerIndex !== pi) return;
  const p = gameState.players[pi];
  if (p.health <= 0 || p.isDead) return; // Мёртвый игрок не может атаковать
  const attacker = p.board[attackerBoardIndex];
  if (!attacker || !attacker.canAttack) return;

  const targetPlayer = gameState.players[targetPlayerIndex];
  if (!targetPlayer) return;

  // Применяем синергии перед расчётом урона
  applySynergies(room);
  
  if (targetBoardIndex === -1) {
    let attackerAttack = attacker.attack + (attacker.synergyAttackBonus || 0);
    const attackerCard = getCard(room, attacker.cardId);
    
    // Если карта делит урон при двойной атаке
    if (attackerCard && attackerCard.divideDamage && attackerCard.canAttackTwice) {
      const attacksUsed = attacker.attacksUsed || 0;
      if (attacksUsed > 0) {
        // Второй удар - урон делится
        attackerAttack = Math.floor(attackerAttack / 2);
      }
    }
    
    // Проверка на уменьшение урона (Имперский Щитогенератор)
    const shieldGen = targetPlayer.board.find(m => {
      const card = getCard(room, m.cardId);
      return card && card.damageReduction;
    });
    if (shieldGen) {
      const card = getCard(room, shieldGen.cardId);
      if (card && card.damageReduction) {
        attackerAttack = Math.floor(attackerAttack * (1 - card.damageReduction));
      }
    }
    
    // Проверка на Energy Shield - поглощает первый урон каждого хода
    const energyShield = targetPlayer.board.find(m => {
      const card = getCard(room, m.cardId);
      return card && card.shieldEffect === 'absorb_first_damage';
    });
    if (energyShield && !energyShield.shieldUsedThisTurn) {
      energyShield.shieldUsedThisTurn = true;
      attackerAttack = 0; // Полностью поглощается
      gameState.log.push({ type: 'shieldAbsorb', playerIndex: targetPlayerIndex, minionId: energyShield.id });
    }
    
    const oldArmor = targetPlayer.armor || 0;
    applyDamageToPlayer(targetPlayer, attackerAttack);
    gameState.log.push({
      type: 'attackHero',
      fromPlayer: pi,
      toPlayer: targetPlayerIndex,
      attackerMinionId: attacker.id,
      damage: attackerAttack,
      armorBlocked: oldArmor - (targetPlayer.armor || 0),
    });
    // Применяем синергии перед расчётом урона
  applySynergies(room);
  
  checkGameOver(room); // Проверяем после атаки по герою
  } else {
    const target = targetPlayer.board[targetBoardIndex];
    if (!target) return;
    
  // Применяем синергии перед расчётом урона
  applySynergies(room);
  
  // Учитываем бонусы синергий при расчёте урона (бонусы уже применены в applySynergies)
  const attackerAttack = attacker.attack + (attacker.synergyAttackBonus || 0);
  const targetAttack = target.attack + (target.synergyAttackBonus || 0);
    
    target.health -= attackerAttack;
    attacker.health -= targetAttack;
    const attackerDied = attacker.health <= 0;
    const targetDied = target.health <= 0;
    gameState.log.push({
      type: 'attack',
      fromPlayer: pi,
      toPlayer: targetPlayerIndex,
      fromIdx: attackerBoardIndex,
      toIdx: targetBoardIndex,
      attackerMinionId: attacker.id,
      targetMinionId: target.id,
      attackerDied,
      targetDied,
      damage: attacker.attack,
      reverseDamage: target.attack,
    });
    const targetCard = getCard(room, target.cardId);
    const attackerCard = getCard(room, attacker.cardId);
    
    // Обработка Soul Heal - хил за каждую убитую карту противника
    if (targetDied) {
      // Проверяем, есть ли у атакующего игрока карта Soul Heal
      p.board.forEach((minion) => {
        const minionCard = getCard(room, minion.cardId);
        if (minionCard && minionCard.onEnemyDeath === 'heal_1_per_kill') {
          healPlayer(p, 1);
          gameState.log.push({ type: 'soulHeal', playerIndex: pi, healed: 1 });
        }
      });
      
      if (targetCard && targetCard.deathrattleId) {
        runDeathrattle(room, targetCard, targetPlayerIndex);
      }
    }
    if (attackerDied && attackerCard && attackerCard.deathrattleId) {
      runDeathrattle(room, attackerCard, pi);
    }
  }

  // Механика двойной атаки
  const attackerCard = getCard(room, attacker.cardId);
  if (attackerCard && attackerCard.canAttackTwice) {
    attacker.attacksUsed = (attacker.attacksUsed || 0) + 1;
    // Если карта умирает после определенного количества атак
    if (attackerCard.diesAfterAttacks && attacker.attacksUsed >= attackerCard.diesAfterAttacks) {
      attacker.health = 0; // Убиваем карту
      gameState.log.push({ type: 'minionDeath', minionId: attacker.id, reason: 'diesAfterAttacks' });
    } else if (attacker.attacksUsed >= 2) {
      attacker.canAttack = false;
      attacker.attacksUsed = 0;
    } else {
      // Может атаковать еще раз
      attacker.canAttack = true;
    }
  } else {
    attacker.canAttack = false;
    attacker.attacksUsed = 0;
  }

  [...gameState.players].forEach((pl) => {
    pl.board = pl.board.filter((m) => m.health > 0);
  });

  checkGameOver(room);
  broadcastGameState(room);
}

io.on('connection', (socket) => {
  // Получаем имя пользователя от клиента
  socket.on('setUsername', (username) => {
    if (username) {
      socketToUsername.set(socket.id, username);
    }
  });
  
  socket.on('createRoom', (data) => {
    // Используем имя из маппинга или переданное имя
    const username = socketToUsername.get(socket.id);
    const name = username || (typeof data === 'string' ? data : (data?.name || 'Host'));
    const aiMode = typeof data === 'object' && data?.aiMode === true;
    const code = generateRoomCode();
    const room = {
      code,
      lobby: [{ id: socket.id, name: name }],
      gameState: null,
      gameStarted: false,
      faction: null,
      aiMode: aiMode,
      turnTimerInterval: null,
      turnTimeLeft: TURN_SECONDS,
      turnTimerWarnFired: false,
      spectators: [],
    };
    rooms.set(code, room);
    socket.join(code);
    socket.emit('roomCreated', {
      code,
      you: room.lobby[0],
      lobby: room.lobby,
      serverIP: serverIP,
      serverPort: serverPort,
      aiMode: aiMode,
    });
    cleanupEmptyRooms();
  });

  socket.on('getRoomsList', () => {
    const roomsList = Array.from(rooms.entries()).map(([code, room]) => {
      if (room.gameStarted && room.gameState) {
        // Для запущенных игр используем данные из gameState
        return {
          code,
          playerCount: room.gameState.players?.length || 0,
          maxPlayers: MAX_PLAYERS,
          gameStarted: true,
          hasAI: room.aiMode || false,
          players: room.gameState.players?.map(p => p.name) || [],
          spectators: (room.spectators || []).length
        };
      } else {
        // Для лобби используем данные из lobby
        return {
          code,
          playerCount: room.lobby?.length || 0,
          maxPlayers: MAX_PLAYERS,
          gameStarted: false,
          hasAI: room.aiMode || false,
          players: room.lobby?.map(p => p.name) || [],
          spectators: (room.spectators || []).length
        };
      }
    });
    socket.emit('roomsList', roomsList);
  });

  socket.on('joinRoom', (data) => {
    const { code, name } = data || {};
    if (!code) {
      socket.emit('error', 'Код комнаты не указан');
      return;
    }
    const room = getRoomByCode(code);
    if (!room) {
      socket.emit('error', 'Комната не найдена');
      return;
    }
    
    // Используем имя из маппинга или переданное имя
    const username = socketToUsername.get(socket.id);
    const playerName = username || name || `Player ${room.lobby.length + 1}`;
    
    if (room.gameStarted) {
      // Игра уже началась - подключаемся как наблюдатель
      if (!room.spectators) room.spectators = [];
      room.spectators.push({ id: socket.id, name: playerName });
      socket.join(code);
      socket.emit('joinedAsSpectator', { code, gameState: room.gameState });
      broadcastGameState(room); // Отправляем текущее состояние игры
      return;
    }
    
    // Игра не началась - подключаемся как игрок
    if (room.lobby.length >= MAX_PLAYERS) {
      socket.emit('error', 'Комната заполнена');
      return;
    }
    room.lobby.push({ id: socket.id, name: playerName });
    socket.join(code);
    io.to(code).emit('lobbyUpdate', room.lobby);
    socket.emit('roomJoined', { code, you: room.lobby[room.lobby.length - 1], lobby: room.lobby });
    cleanupEmptyRooms();
  });

  socket.on('leaveLobby', () => {
    const room = getRoomBySocket(socket.id);
    if (!room) return;
    room.lobby = room.lobby.filter((p) => p.id !== socket.id);
    socket.leave(room.code);
    if (room.lobby.length === 0 && !room.gameStarted) {
      clearTurnTimer(room);
      rooms.delete(room.code);
    } else {
      io.to(room.code).emit('lobbyUpdate', room.lobby);
    }
    cleanupEmptyRooms();
  });

  socket.on('startGame', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room) {
      socket.emit('error', 'Комната не найдена');
      return;
    }
    if (room.lobby[0]?.id !== socket.id) {
      socket.emit('error', 'Только хост может начать игру');
      return;
    }
    // В режиме ИИ можно начать игру с одним игроком
    if (!room.aiMode && room.lobby.length < MIN_PLAYERS) {
      socket.emit('error', `Нужно минимум ${MIN_PLAYERS} игроков`);
      return;
    }
    const validFactions = ['rebellion', 'empire', 'pirates', 'mandalorians'];
    const faction = data?.faction && validFactions.includes(data.faction) ? data.faction : null;
    room.faction = faction;
    initGame(room);
  });

  socket.on('playCard', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    playCard(room, socket.id, data.handIndex, data.boardPos);
  });

  socket.on('playSpell', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    playSpell(room, socket.id, data.handIndex, data.targetPlayerIndex, data.targetBoardIndex);
  });

  socket.on('heroAbility', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    heroAbility(room, socket.id, data.targetPlayerIndex, data.targetBoardIndex);
  });

  socket.on('attack', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    attack(
      room,
      socket.id,
      data.attackerBoardIndex,
      data.targetPlayerIndex,
      data.targetBoardIndex ?? -1
    );
  });

  socket.on('endTurn', () => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    if (findPlayerIndex(room, socket.id) !== room.gameState.currentPlayerIndex) return;
    endTurn(room);
  });

  socket.on('drawCard', () => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    manualDraw(room, socket.id);
  });

  socket.on('forgePreview', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    const pi = findPlayerIndex(room, socket.id);
    if (pi < 0) return;
    const p = room.gameState.players[pi];
    const ids = Array.isArray(data?.cardIds) ? data.cardIds : [];
    const requestId = data?.requestId;
    if (ids.length < 2 || ids.length > 3) {
      socket.emit('forgePreviewResult', { error: true, requestId });
      return;
    }
    const hand = p.hand || [];
    const ok = ids.every((id) => hand.includes(id));
    if (!ok) {
      socket.emit('forgePreviewResult', { error: true, requestId });
      return;
    }
    const cards = ids.map((id) => cardDb[id]).filter(Boolean);
    if (cards.length !== ids.length || cards.some((c) => c.type !== 'minion')) {
      socket.emit('forgePreviewResult', { error: true, requestId });
      return;
    }
    const result = pickForgeResultCard(ids);
    if (!result) {
      socket.emit('forgePreviewResult', { error: true, requestId });
      return;
    }
    socket.emit('forgePreviewResult', { resultCardId: result.resultCardId, requestId });
  });

  socket.on('forgeCard', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    forgeCard(room, socket.id, data.cardIds);
  });

  socket.on('stealCards', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    stealCardsFromDeck(room, socket.id, data.handIndex, data.targetPlayerIndex, data.cardIndices);
  });
  
  socket.on('chatMessage', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room) return;
    
    const message = (data?.message || '').trim();
    if (message.length === 0 || message.length > 200) return;
    
    // Находим имя игрока
    let playerName = 'Игрок';
    if (room.gameState && room.gameState.players) {
      const player = room.gameState.players.find(p => p.id === socket.id);
      if (player) {
        playerName = player.name || 'Игрок';
      }
    } else if (room.lobby) {
      const lobbyPlayer = room.lobby.find(p => p.id === socket.id);
      if (lobbyPlayer) {
        playerName = lobbyPlayer.name || 'Игрок';
      }
    }
    
    // Отправляем сообщение всем в комнате
    io.to(room.code).emit('chatMessage', {
      playerName: playerName,
      message: message
    });
  });
  
  socket.on('battlecryTarget', (data) => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || room.gameState.phase !== 'playing') return;
    const gameState = room.gameState;
    const pi = findPlayerIndex(room, socket.id);
    if (pi < 0) return;
    const p = gameState.players[pi];
    if (p.health <= 0 || p.isDead) return;
    
    if (data.battlecryId === 'return_hand_enemy') {
      const targetPlayerIndex = data.targetPlayerIndex;
      const targetBoardIndex = data.targetBoardIndex;
      const targetPlayer = gameState.players[targetPlayerIndex];
      
      if (!targetPlayer || targetPlayerIndex === pi || targetPlayer.health <= 0) return;
      if (!targetPlayer.board || targetPlayer.board.length === 0) return;
      if (targetBoardIndex == null || targetBoardIndex < 0 || targetBoardIndex >= targetPlayer.board.length) return;
      
      const target = targetPlayer.board[targetBoardIndex];
      targetPlayer.board.splice(targetBoardIndex, 1);
      if (targetPlayer.hand.length < 10) {
        targetPlayer.hand.push(target.cardId);
      }
      
      // Удаляем pendingBattlecry с миньона
      const playedMinion = p.board?.find(m => m.pendingBattlecry && m.pendingBattlecry.battlecryId === 'return_hand_enemy');
      if (playedMinion) {
        delete playedMinion.pendingBattlecry;
      }
      
      gameState.log.push({ type: 'battlecry', effect: 'return_hand_enemy', fromPlayer: pi, toPlayer: targetPlayerIndex, toIdx: targetBoardIndex });
      
      // Применяем синергии после изменений на доске
      applySynergies(room);
      
      checkGameOver(room);
      broadcastGameState(room);
    }
  });

  socket.on('resetToLobby', () => {
    const room = getRoomBySocket(socket.id);
    if (!room || !room.gameState || !room.gameState.players?.length) return;
    const hostId = room.gameState.players[0].id;
    if (socket.id !== hostId) return;
    clearTurnTimer(room);
    // Сохраняем список игроков для новой игры
    room.lobby = room.gameState.players.map((p) => ({ id: p.id, name: p.name }));
    // Сохраняем настройки комнаты
    const savedFaction = room.faction;
    const savedAIMode = room.aiMode;
    room.gameState = null;
    room.gameStarted = false;
    room.faction = savedFaction;
    room.aiMode = savedAIMode;
    // Очищаем наблюдателей
    if (room.spectators) room.spectators = [];
    io.to(room.code).emit('backToLobby', { lobby: room.lobby });
  });

  socket.on('disconnect', () => {
    // Удаляем маппинг socket.id -> username
    socketToUsername.delete(socket.id);
    const room = getRoomBySocket(socket.id);
    if (!room) return;
    
    // Проверяем, был ли это наблюдатель
    if (room.spectators) {
      const specIdx = room.spectators.findIndex(s => s.id === socket.id);
      if (specIdx >= 0) {
        room.spectators.splice(specIdx, 1);
        socket.leave(room.code);
        return;
      }
    }
    
    if (!room.gameStarted) {
      room.lobby = room.lobby.filter((p) => p.id !== socket.id);
      socket.leave(room.code);
      if (room.lobby.length === 0) {
        clearTurnTimer(room);
        rooms.delete(room.code);
      } else {
        io.to(room.code).emit('lobbyUpdate', room.lobby);
      }
      cleanupEmptyRooms();
      return;
    }
    if (room.gameState) {
      const idx = findPlayerIndex(room, socket.id);
      if (idx >= 0) {
        room.gameState.players[idx].health = 0;
        const alive = room.gameState.players.filter((p) => p.health > 0);
        if (alive.length <= 1) {
          room.gameState.phase = 'ended';
          room.gameState.winner = alive[0] || null;
        }
        broadcastGameState(room);
      }
    }
  });
});

let serverIP = 'localhost';
let serverPort = PORT;

// API для авторизации и регистрации
app.post('/api/register', async (req, res) => {
  try {
    const { username, password, email } = req.body;
    
    if (!username || !password) {
      return res.status(400).json({ error: 'Имя пользователя и пароль обязательны' });
    }
    
    if (username.length < 3 || username.length > 20) {
      return res.status(400).json({ error: 'Имя пользователя должно быть от 3 до 20 символов' });
    }
    
    if (password.length < 6) {
      return res.status(400).json({ error: 'Пароль должен быть не менее 6 символов' });
    }
    
    // Проверяем, существует ли пользователь
    if (redisClient.isRedisAvailable()) {
      const existingUser = await redisClient.get(`user:${username}`);
      if (existingUser) {
        return res.status(400).json({ error: 'Пользователь с таким именем уже существует' });
      }
      
      // Хешируем пароль
      const hashedPassword = await bcrypt.hash(password, 10);
      
      // Сохраняем пользователя
      const userData = {
        username,
        password: hashedPassword,
        email: email || '',
        createdAt: Date.now(),
        gamesPlayed: 0,
        gamesWon: 0,
        totalDamage: 0,
        totalHealing: 0
      };
      
      await redisClient.set(`user:${username}`, JSON.stringify(userData));
      await redisClient.set(`user:email:${email || ''}`, username); // Для поиска по email
      
      // Создаем сессию
      req.session.userId = username;
      req.session.save();
      
      res.json({ success: true, user: { username, email: userData.email } });
    } else {
      // Без Redis - простая проверка (только для тестов)
      req.session.userId = username;
      req.session.save();
      res.json({ success: true, user: { username, email: email || '' }, warning: 'Работаем без БД' });
    }
  } catch (error) {
    console.error('Ошибка регистрации:', error);
    res.status(500).json({ error: 'Ошибка сервера при регистрации' });
  }
});

app.post('/api/login', async (req, res) => {
  try {
    const { username, password } = req.body;
    
    if (!username || !password) {
      return res.status(400).json({ error: 'Имя пользователя и пароль обязательны' });
    }
    
    if (redisClient.isRedisAvailable()) {
      const userDataStr = await redisClient.get(`user:${username}`);
      if (!userDataStr) {
        return res.status(401).json({ error: 'Неверное имя пользователя или пароль' });
      }
      
      const userData = JSON.parse(userDataStr);
      const passwordMatch = await bcrypt.compare(password, userData.password);
      
      if (!passwordMatch) {
        return res.status(401).json({ error: 'Неверное имя пользователя или пароль' });
      }
      
      // Создаем сессию
      req.session.userId = username;
      req.session.save();
      
      res.json({ 
        success: true, 
        user: { 
          username: userData.username, 
          email: userData.email,
          gamesPlayed: userData.gamesPlayed || 0,
          gamesWon: userData.gamesWon || 0
        } 
      });
    } else {
      // Без Redis - простая авторизация (только для тестов)
      req.session.userId = username;
      req.session.save();
      res.json({ success: true, user: { username }, warning: 'Работаем без БД' });
    }
  } catch (error) {
    console.error('Ошибка входа:', error);
    res.status(500).json({ error: 'Ошибка сервера при входе' });
  }
});

app.post('/api/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).json({ error: 'Ошибка при выходе' });
    }
    res.json({ success: true });
  });
});

app.get('/api/user', async (req, res) => {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'Не авторизован' });
  }
  
  if (redisClient.isRedisAvailable()) {
    const userDataStr = await redisClient.get(`user:${req.session.userId}`);
    if (userDataStr) {
      const userData = JSON.parse(userDataStr);
      res.json({ 
        user: { 
          username: userData.username, 
          email: userData.email,
          gamesPlayed: userData.gamesPlayed || 0,
          gamesWon: userData.gamesWon || 0,
          totalDamage: userData.totalDamage || 0,
          totalHealing: userData.totalHealing || 0
        } 
      });
    } else {
      res.status(404).json({ error: 'Пользователь не найден' });
    }
  } else {
    res.json({ user: { username: req.session.userId }, warning: 'Работаем без БД' });
  }
});

app.get('/api/leaderboard', async (req, res) => {
  try {
    if (!redisClient.isRedisAvailable()) {
      return res.json({ leaderboard: [], warning: 'Работаем без БД' });
    }
    
    // Получаем всех пользователей
    const userKeys = await redisClient.keys('user:*');
    const leaderboard = [];
    
    for (const key of userKeys) {
      if (key.startsWith('user:email:')) continue; // Пропускаем email индексы
      const userDataStr = await redisClient.get(key);
      if (userDataStr) {
        const userData = JSON.parse(userDataStr);
        leaderboard.push({
          username: userData.username,
          gamesPlayed: userData.gamesPlayed || 0,
          gamesWon: userData.gamesWon || 0,
          winRate: userData.gamesPlayed > 0 ? ((userData.gamesWon || 0) / userData.gamesPlayed * 100).toFixed(1) : 0,
          totalDamage: userData.totalDamage || 0,
          totalHealing: userData.totalHealing || 0
        });
      }
    }
    
    // Сортируем по победам, затем по количеству игр
    leaderboard.sort((a, b) => {
      if (b.gamesWon !== a.gamesWon) return b.gamesWon - a.gamesWon;
      return b.gamesPlayed - a.gamesPlayed;
    });
    
    res.json({ leaderboard: leaderboard.slice(0, 100) }); // Топ 100
  } catch (error) {
    console.error('Ошибка получения leaderboard:', error);
    res.status(500).json({ error: 'Ошибка сервера' });
  }
});

// Middleware для проверки авторизации
function requireAuth(req, res, next) {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'Требуется авторизация' });
  }
  next();
}

// Инициализация Redis перед запуском сервера
(async () => {
  await redisClient.initRedis();
  
  // Настраиваем session store после инициализации Redis
  // Сессии уже настроены выше, Redis используется только для данных пользователей
  if (redisClient.isRedisAvailable()) {
    console.log('💾 Redis доступен для хранения данных пользователей');
  } else {
    console.log('💾 Работаем без БД (Redis недоступен)');
  }
  
  server.listen(PORT, '0.0.0.0', () => {
    const os = require('os');
    const nets = os.networkInterfaces();
    let lan = 'localhost';
    for (const name of Object.keys(nets)) {
      for (const n of nets[name]) {
        if (n.family === 'IPv4' && !n.internal) {
          lan = n.address;
          break;
        }
      }
    }
    serverIP = lan;
    serverPort = PORT;
    console.log(`
  ╔══════════════════════════════════════════════════════════╗
  ║   STAR WARS HEARTHSTONE - Server running                ║
  ║   Local:   http://localhost:${PORT}                        ║
  ║   LAN:     http://${lan}:${PORT}     (use for Radmin VPN)   ║
  ║   Redis:   ${redisClient.isRedisAvailable() ? '✅ подключен' : '❌ отключен (работаем без БД)'}   ║
  ╚══════════════════════════════════════════════════════════╝
  `);
  });
})();
