<?php
// =============================================
//           ПОЛНЫЙ functions.php
//    (версия с поддержкой множественных статов предметов)
// =============================================

require_once __DIR__ . '/db.php';

// ---------- БАЗОВЫЕ ФУНКЦИИ ----------
function isLoggedIn() {
    return isset($_SESSION['user_id']);
}

function getUserById($id) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

function getUserByUsername($username) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?");
    $stmt->execute([$username]);
    return $stmt->fetch();
}

function getTotalPlayers() {
    global $pdo;
    $result = $pdo->query("SELECT COUNT(*) FROM users");
    return $result->fetchColumn();
}

// ---------- АВАТАРЫ ----------
function getAvatarUrl($user) {
    if ($user['avatar_type'] === 'shop' && !empty($user['custom_avatar'])) {
        return $user['custom_avatar'];
    }
    if ($user['avatar_type'] === 'custom' && !empty($user['custom_avatar'])) {
        return $user['custom_avatar'];
    }
    // стандартный аватар по полу
    return ($user['gender'] === 'Мужской') ? 'images/avatars/male.png' : 'images/avatars/female.png';
}

function uploadCustomAvatar($userId, $file) {
    global $pdo;
    $targetDir = 'uploads/avatars/';
    if (!is_dir($targetDir)) {
        mkdir($targetDir, 0755, true);
    }

    $check = getimagesize($file['tmp_name']);
    if ($check === false) {
        return ['success' => false, 'message' => 'Файл не является изображением.'];
    }

    if ($file['size'] > 4096) { // 4 КБ
        return ['success' => false, 'message' => 'Файл слишком большой. Максимум 4 КБ.'];
    }

    $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
    $allowed = ['jpg', 'jpeg', 'png', 'gif'];
    if (!in_array($ext, $allowed)) {
        return ['success' => false, 'message' => 'Допустимые форматы: JPG, PNG, GIF.'];
    }

    $newFileName = 'avatar_' . $userId . '_' . time() . '.' . $ext;
    $targetFile = $targetDir . $newFileName;

    if (!move_uploaded_file($file['tmp_name'], $targetFile)) {
        return ['success' => false, 'message' => 'Ошибка при сохранении файла.'];
    }

    $stmt = $pdo->prepare("UPDATE users SET custom_avatar = ?, avatar_type = 'custom' WHERE id = ?");
    $stmt->execute([$targetFile, $userId]);

    return ['success' => true, 'path' => $targetFile];
}

function resetAvatarToGender($userId) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE users SET avatar_type = 'gender' WHERE id = ?");
    $stmt->execute([$userId]);
}

function getRoleColor($role) {
    switch ($role) {
        case 'admin':
        case 'support':
            return 'blue';
        case 'moderator':
            return 'green';
        default:
            return 'gray';
    }
}

function addChatMessage($userId, $message) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO chat_messages (user_id, message) VALUES (?, ?)");
    $stmt->execute([$userId, $message]);
}

function getChatMessages($limit = 50) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT cm.*, u.username, u.role, u.side, u.last_activity
        FROM chat_messages cm
        JOIN users u ON cm.user_id = u.id
        ORDER BY cm.created_at DESC
        LIMIT :limit
    ");
    $stmt->bindValue(':limit', (int)$limit, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

function safeGet($array, $key, $default = 0) {
    return isset($array[$key]) ? $array[$key] : $default;
}

function regenerateEnergy($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT energy, max_energy, last_activity FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    $user = $stmt->fetch();
    if (!$user) return;

    $lastActivity = strtotime($user['last_activity']);
    $now = time();
    $diffSeconds = $now - $lastActivity;
    $regen = floor($diffSeconds / 60);
    if ($regen > 0) {
        $newEnergy = min($user['energy'] + $regen, $user['max_energy']);
        $stmt = $pdo->prepare("UPDATE users SET energy = ? WHERE id = ?");
        $stmt->execute([$newEnergy, $userId]);
    }
}

function updateLastActivity($userId) {
    global $pdo;
    regenerateAllStats($userId);
    $stmt = $pdo->prepare("UPDATE users SET last_activity = NOW() WHERE id = ?");
    $stmt->execute([$userId]);
}

// ---------- ОНЛАЙН-СПИСОК ----------
function getOnlineUsers($filter, $limit, $offset) {
    global $pdo;
    $timeLimit = date('Y-m-d H:i:s', time() - 300);
    $sql = "SELECT id, username, in_chat FROM users WHERE last_activity > :timeLimit";
    if ($filter === 'noclan') $sql .= " AND (clan_id IS NULL OR clan_id = 0)";
    $sql .= " ORDER BY last_activity DESC LIMIT :limit OFFSET :offset";
    $stmt = $pdo->prepare($sql);
    $stmt->bindValue(':timeLimit', $timeLimit);
    $stmt->bindValue(':limit', (int)$limit, PDO::PARAM_INT);
    $stmt->bindValue(':offset', (int)$offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

function getTotalOnlineUsers($filter = 'all') {
    global $pdo;
    $timeLimit = date('Y-m-d H:i:s', time() - 300);
    $sql = "SELECT COUNT(*) FROM users WHERE last_activity > :timeLimit";
    if ($filter === 'noclan') $sql .= " AND (clan_id IS NULL OR clan_id = 0)";
    $stmt = $pdo->prepare($sql);
    $stmt->bindValue(':timeLimit', $timeLimit);
    $stmt->execute();
    return $stmt->fetchColumn();
}

// ---------- НАСТРОЙКИ ----------
function saveUserSettings($userId, $settings) {
    global $pdo;
    $stmt = $pdo->prepare("REPLACE INTO user_settings (user_id, show_drop, show_smiles, log_lines, receive_newsletter, show_in_noclan, color_scheme) 
                          VALUES (?, ?, ?, ?, ?, ?, ?)");
    return $stmt->execute([$userId, $settings['show_drop'], $settings['show_smiles'], $settings['log_lines'], $settings['receive_newsletter'], $settings['show_in_noclan'], $settings['color_scheme']]);
}

function getUserSettings($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM user_settings WHERE user_id = ?");
    $stmt->execute([$userId]);
    $row = $stmt->fetch();
    if (!$row) {
        return ['show_drop' => 1, 'show_smiles' => 1, 'log_lines' => 10, 'receive_newsletter' => 0, 'show_in_noclan' => 0, 'color_scheme' => 'blue'];
    }
    return $row;
}

// ---------- ЧЁРНЫЙ СПИСОК ----------
function addToBlacklist($userId, $blockedUserId) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO blacklist (user_id, blocked_user_id) VALUES (?, ?)");
    return $stmt->execute([$userId, $blockedUserId]);
}
function removeFromBlacklist($userId, $blockedUserId) {
    global $pdo;
    $stmt = $pdo->prepare("DELETE FROM blacklist WHERE user_id = ? AND blocked_user_id = ?");
    return $stmt->execute([$userId, $blockedUserId]);
}
function isInBlacklist($userId, $blockedUserId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT 1 FROM blacklist WHERE user_id = ? AND blocked_user_id = ?");
    $stmt->execute([$userId, $blockedUserId]);
    return $stmt->fetchColumn() ? true : false;
}
function getBlacklist($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT u.id, u.username FROM blacklist b JOIN users u ON b.blocked_user_id = u.id WHERE b.user_id = ?");
    $stmt->execute([$userId]);
    return $stmt->fetchAll();
}

// ---------- СМЕНА ДАННЫХ ----------
function isUsernameAvailable($username, $excludeUserId = null) {
    global $pdo;
    $sql = "SELECT 1 FROM users WHERE username = ?";
    $params = [$username];
    if ($excludeUserId) { $sql .= " AND id != ?"; $params[] = $excludeUserId; }
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return !$stmt->fetchColumn();
}
function updateUsername($userId, $newUsername) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE users SET username = ? WHERE id = ?");
    return $stmt->execute([$newUsername, $userId]);
}
function updateSide($userId, $newSide) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE users SET side = ? WHERE id = ?");
    return $stmt->execute([$newSide, $userId]);
}
function updateUserGold($userId, $newGold) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE users SET gold = ? WHERE id = ?");
    return $stmt->execute([$newGold, $userId]);
}
function updateUserPassword($userId, $newHash) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE users SET password_hash = ? WHERE id = ?");
    return $stmt->execute([$newHash, $userId]);
}

// ---------- КЛАН (ЗАГЛУШКА) ----------
function getClanByUserId($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT c.*, cm.rank FROM clans c JOIN clan_members cm ON c.id = cm.clan_id WHERE cm.user_id = ?");
    $stmt->execute([$userId]);
    return $stmt->fetch();
}
function getLoyaltySkill($userId) { return null; }

// ---------- УРОВНИ ----------
function getExpForLevel($level) {
    $expTable = [
        1 => 0,
        2 => 100,
        3 => 250,
        4 => 475,
        5 => 812,
        6 => 1300,
        7 => 2000,
        8 => 3200,
        9 => 4900,
        10 => 7400,
        11 => 11300,
        12 => 17000,
        13 => 25700,
        14 => 38700,
        15 => 58100,
        16 => 83400,
        17 => 116300,
        18 => 159000,
        19 => 214600,
        20 => 286800,
        21 => 380700,
        22 => 502800,
        23 => 661500,
        24 => 867800,
        25 => 1130000,
        26 => 1510000,
        27 => 2030000,
        28 => 2770000,
        29 => 3800000,
        30 => 5240000,
        31 => 7260000,
        32 => 10090000,
        33 => 14050000,
        34 => 20000000,
        35 => 28000000,
        36 => 39200000,
        37 => 54880000,
        38 => 76832000,
        39 => 107564800,
        40 => 150590720,
        41 => 196290000,
        42 => 256380000,
        43 => 328480000,
        44 => 414990000,
        45 => 518820000,
    ];
    return $expTable[$level] ?? 999999999;
}
function getLevelByExp($exp) {
    $level = 1;
    while (getExpForLevel($level + 1) <= $exp) $level++;
    return $level;
}
function getExpPercent($exp) {
    $currentLevel = getLevelByExp($exp);
    $expCurrent = getExpForLevel($currentLevel);
    $expNext = getExpForLevel($currentLevel + 1);
    if ($expNext <= $expCurrent) return 0;
    $percent = ($exp - $expCurrent) / ($expNext - $expCurrent) * 100;
    return round($percent, 1);
}

// ---------- БАЗОВЫЕ СТАТЫ ----------
function getBaseStatsForLevel($level) {
    return [
        'attack' => 10 + ($level - 1) * 3,
        'health' => 50 + ($level - 1) * 8,
        'defense' => 5 + ($level - 1) * 2,
        'stamina' => 20 + ($level - 1) * 2,
        'accuracy' => 5 + ($level - 1) * 0.5,
        'recovery' => 2 + ($level - 1) * 0.2,
    ];
}

// ---------- ПЕРЕСЧЁТ СТАТОВ ----------

function recalcUserStats($userId) {
    global $pdo;
    error_log("recalcUserStats: начало для пользователя $userId");

    $user = getUserById($userId);
    if (!$user) {
        error_log("recalcUserStats: пользователь не найден");
        return;
    }

    $base = getBaseStatsForLevel($user['level']);
    $equipped = getEquippedItems($userId);
    error_log("recalcUserStats: надетых предметов: " . count($equipped));

    $bonus = ['attack'=>0, 'health'=>0, 'defense'=>0, 'stamina'=>0, 'accuracy'=>0, 'recovery'=>0];
    foreach ($equipped as $item) {
        $itemBonuses = getItemBonusValues($item);
        foreach ($bonus as $stat => &$value) {
            $value += $itemBonuses[$stat];
        }
    }
    error_log("recalcUserStats: бонусы: " . json_encode($bonus));

    $attack = $base['attack'] + $bonus['attack'];
    $health = $base['health'] + $bonus['health'];
    $defense = $base['defense'] + $bonus['defense'];
    $stamina = $base['stamina'] + $bonus['stamina'];
    $accuracy = calculateAccuracy($stamina) + $bonus['accuracy'];
    $recovery = $base['recovery'] + $bonus['recovery'];
    $defense_percent = calculateDefensePercent($defense);

    // Мастерство
    $mastery = $user['mastery'];
    if (isPremiumActive($userId)) {
        $mastery += 1;
    }
    $mult = 1 + $mastery / 100;
    $attack = round($attack * $mult);
    $health = round($health * $mult);
    $defense = round($defense * $mult);
    $stamina = round($stamina * $mult);
    $accuracy = round($accuracy * $mult);
    $recovery = round($recovery * $mult);

    // Бонусы от достижений
    $attack += $user['bonus_attack'];
    $health += $user['bonus_health'];
    $defense += $user['bonus_defense'];
    $stamina += $user['bonus_stamina'];
    $recovery += $user['bonus_recovery'];

    $total_stats = $attack + $health + $defense + $stamina + $accuracy + $recovery;
    $max_stamina = $stamina;

    error_log("recalcUserStats: итоговые статы: attack=$attack, health=$health, defense=$defense, stamina=$stamina, accuracy=$accuracy, recovery=$recovery");

    $sql = "UPDATE users SET 
        base_attack=?, base_health=?, base_defense=?, base_stamina=?, base_accuracy=?, base_recovery=?,
        attack=?, health=?, defense=?, stamina=?, accuracy=?, recovery=?, defense_percent=?, total_stats=?, max_stamina=?
        WHERE id=?";
    $stmt = $pdo->prepare($sql);
    $result = $stmt->execute([
        $base['attack'], $base['health'], $base['defense'], $base['stamina'], $base['accuracy'], $base['recovery'],
        $attack, $health, $defense, $stamina, $accuracy, $recovery, $defense_percent, $total_stats, $max_stamina,
        $userId
    ]);
    if ($result) {
        error_log("recalcUserStats: статы обновлены успешно");
    } else {
        error_log("recalcUserStats: ошибка: " . print_r($stmt->errorInfo(), true));
    }
}
function addExp($userId, $expGained) {
    global $pdo;
    // Премиум даёт +300% опыта (x4)
    if (isPremiumActive($userId)) {
        $expGained = $expGained * 4;
    }
    $user = getUserById($userId);
    $newExp = $user['exp'] + $expGained;
    $newLevel = getLevelByExp($newExp);
    $oldLevel = $user['level'];
    $stmt = $pdo->prepare("UPDATE users SET exp = ?, level = ? WHERE id = ?");
    $stmt->execute([$newExp, $newLevel, $userId]);
    if ($newLevel > $oldLevel) {
        recalcUserStats($userId);
        // Начисляем очко таланта, если новый уровень >= 11
        if ($newLevel >= 11) {
            grantTalentPointForLevel($userId);
        }
    }
    return $newLevel;
}

// ---------- ПРЕДМЕТЫ (МНОГОПАРАМЕТРИЧЕСКИЕ) ----------

function getItemById($itemId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM items WHERE id = ?");
    $stmt->execute([$itemId]);
    return $stmt->fetch();
}

function getUserItemById($userItemId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT 
            ui.*,
            i.name as item_name,
            i.slot,
            i.level_required,
            i.image,
            i.base_attack,
            i.base_health,
            i.base_defense,
            i.base_stamina,
            i.base_accuracy,
            i.base_recovery,
            q.name as quality_name,
            q.icon as quality_icon,
            q.color as quality_color
        FROM user_items ui 
        JOIN items i ON ui.item_id = i.id 
        JOIN item_qualities q ON ui.quality_id = q.id 
        WHERE ui.id = ?
    ");
    $stmt->execute([$userItemId]);
    return $stmt->fetch();
}


function getUserBagItems($userId, $offset = 0, $limit = 20) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT 
            ui.*,
            i.name as item_name,
            i.slot,
            i.level_required,
            i.image,
            i.base_attack,
            i.base_health,
            i.base_defense,
            i.base_stamina,
            i.base_accuracy,
            i.base_recovery,
            q.name as quality_name,
            q.icon as quality_icon,
            q.color as quality_color
        FROM user_items ui 
        JOIN items i ON ui.item_id = i.id 
        JOIN item_qualities q ON ui.quality_id = q.id 
        WHERE ui.user_id = ? AND ui.is_equipped = 0 
        ORDER BY ui.created_at DESC 
        LIMIT ? OFFSET ?
    ");
    $stmt->bindValue(1, $userId, PDO::PARAM_INT);
    $stmt->bindValue(2, (int)$limit, PDO::PARAM_INT);
    $stmt->bindValue(3, (int)$offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

function countUserBagItems($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM user_items WHERE user_id = ? AND is_equipped = 0");
    $stmt->execute([$userId]);
    return $stmt->fetchColumn();
}

function getEquippedItems($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT 
            ui.*,
            i.name as item_name,
            i.slot,
            i.level_required,
            i.image,
            i.base_attack,
            i.base_health,
            i.base_defense,
            i.base_stamina,
            i.base_accuracy,
            i.base_recovery,
            q.name as quality_name,
            q.icon as quality_icon,
            q.color as quality_color
        FROM user_items ui 
        JOIN items i ON ui.item_id = i.id 
        JOIN item_qualities q ON ui.quality_id = q.id 
        WHERE ui.user_id = ? AND ui.is_equipped = 1 
        ORDER BY FIELD(ui.equipped_slot, 'head','neck','body','pants','belt','bracers','gloves','ring','weapon','boots')
    ");
    $stmt->execute([$userId]);
    return $stmt->fetchAll();
}


/**
 * Возвращает массив бонусных значений предмета с учётом процента качества.
 * Ключи: attack, health, defense, stamina, accuracy, recovery
 */
function getItemBonusValues($item) {
    $stats = ['attack', 'health', 'defense', 'stamina', 'accuracy', 'recovery'];
    $result = array_fill_keys($stats, 0);
    $multiplier = (100 + ($item['bonus_percent'] ?? 0)) / 100;

    foreach ($stats as $stat) {
        $baseKey = 'base_' . $stat;
        if (!empty($item[$baseKey])) {
            $result[$stat] = round($item[$baseKey] * $multiplier);
        }
    }
    return $result;
}
// ---------- ИСПРАВЛЕННЫЕ ФУНКЦИИ ЭКИПИРОВКИ ----------


function canEquipItem($userId, $userItemId) {
    $item = getUserItemById($userItemId);
    if (!$item) {
        error_log("canEquipItem: предмет не найден. ID: $userItemId");
        return false;
    }
    if ($item['user_id'] != $userId) {
        error_log("canEquipItem: предмет принадлежит другому пользователю.");
        return false;
    }
    if ($item['is_equipped']) {
        error_log("canEquipItem: предмет уже надет.");
        return false;
    }
    $user = getUserById($userId);
    if (!$user) {
        error_log("canEquipItem: пользователь не найден.");
        return false;
    }
    if ($user['level'] < $item['level_required']) {
        error_log("canEquipItem: недостаточный уровень.");
        return false;
    }
    return true;
}

function equipItem($userId, $userItemId) {
    global $pdo;

    if (!canEquipItem($userId, $userItemId)) {
        error_log("equipItem: canEquipItem вернул false");
        return false;
    }

    $item = getUserItemById($userItemId);
    if (!$item) {
        error_log("equipItem: не удалось получить данные предмета");
        return false;
    }
    $slot = $item['slot'];
    if (empty($slot)) {
        error_log("equipItem: пустой слот");
        return false;
    }

    $needCommit = !$pdo->inTransaction();
    if ($needCommit) {
        $pdo->beginTransaction();
        error_log("equipItem: транзакция начата");
    }

    try {
        // Снимаем текущий предмет в этом слоте
        $stmt = $pdo->prepare("UPDATE user_items SET is_equipped = 0, equipped_slot = NULL, is_bound = 0 WHERE user_id = ? AND equipped_slot = ? AND is_equipped = 1");
        $stmt->execute([$userId, $slot]);
        error_log("equipItem: снято предметов: " . $stmt->rowCount());

        // Надеваем новый
        $stmt = $pdo->prepare("UPDATE user_items SET is_equipped = 1, equipped_slot = ?, is_bound = 1 WHERE id = ?");
        $stmt->execute([$slot, $userItemId]);
        if ($stmt->rowCount() === 0) {
            throw new Exception("Не удалось обновить предмет");
        }
        error_log("equipItem: предмет $userItemId надет в слот $slot");

        if ($needCommit) {
            $pdo->commit();
            error_log("equipItem: транзакция закоммичена");
        }

        // Пересчитываем статы
        recalcUserStats($userId);
        return true;
    } catch (Exception $e) {
        if ($needCommit && $pdo->inTransaction()) {
            $pdo->rollBack();
            error_log("equipItem: транзакция откачена");
        }
        error_log("equipItem: ошибка: " . $e->getMessage());
        return false;
    }
}

function unequipItem($userId, $userItemId) {
    global $pdo;
    $item = getUserItemById($userItemId);
    if (!$item || $item['user_id'] != $userId || !$item['is_equipped']) return false;
    $stmt = $pdo->prepare("UPDATE user_items SET is_equipped = 0, equipped_slot = NULL WHERE id = ?");
    $result = $stmt->execute([$userItemId]);
    if ($result) recalcUserStats($userId);
    return $result;
}

function sellItem($userId, $userItemId) {
    global $pdo;
    $item = getUserItemById($userItemId);
    if (!$item) {
        error_log("sellItem: предмет с ID $userItemId не найден");
        return false;
    }
    if ($item['user_id'] != $userId) {
        error_log("sellItem: предмет принадлежит пользователю {$item['user_id']}, а не $userId");
        return false;
    }
    if ($item['is_equipped']) {
        error_log("sellItem: предмет надет, продажа невозможна");
        return false;
    }
    $bonusValues = getItemBonusValues($item);
    $totalStats = array_sum($bonusValues);
    $price = round($totalStats * 2);
    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("DELETE FROM user_items WHERE id = ?");
        $stmt->execute([$userItemId]);
        $stmt = $pdo->prepare("UPDATE users SET bucks = bucks + ? WHERE id = ?");
        $stmt->execute([$price, $userId]);
        $pdo->commit();
        return $price;
    } catch (Exception $e) {
        $pdo->rollBack();
        error_log("sellItem: исключение: " . $e->getMessage());
        return false;
    }
}

function discardItem($userId, $userItemId) {
    global $pdo;
    $item = getUserItemById($userItemId);
    if (!$item) {
        error_log("discardItem: предмет с ID $userItemId не найден");
        return false;
    }
    if ($item['user_id'] != $userId) {
        error_log("discardItem: предмет принадлежит пользователю {$item['user_id']}, а не $userId");
        return false;
    }
    if ($item['is_equipped']) {
        error_log("discardItem: предмет надет, выбросить нельзя");
        return false;
    }
    $stmt = $pdo->prepare("DELETE FROM user_items WHERE id = ?");
    $result = $stmt->execute([$userItemId]);
    if (!$result) {
        error_log("discardItem: ошибка выполнения DELETE");
    }
    return $result;
}

// ---------- СХВАТКИ ----------
function getOpponentsForBrawl($userId, $difficulty, $limit = 3) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT total_stats, level FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    $player = $stmt->fetch();
    $myStats = (int)$player['total_stats'];
    $myLevel = (int)$player['level'];
    switch ($difficulty) {
        case 'weaker':   $min = 0; $max = $myStats - 1; break;
        case 'equal':    $min = $myStats; $max = $myStats; break;
        case 'stronger': $min = $myStats + 1; $max = 999999; break;
        default: $min = 0; $max = 999999;
    }
    // Добавлены поля avatar_type, custom_avatar
    $sql = "SELECT id, username, level, total_stats, attack, health, defense, gender, avatar_type, custom_avatar 
            FROM users 
            WHERE id != ? AND total_stats BETWEEN ? AND ? 
            ORDER BY RAND() LIMIT ?";
    $stmt = $pdo->prepare($sql);
    $stmt->bindValue(1, $userId, PDO::PARAM_INT);
    $stmt->bindValue(2, $min, PDO::PARAM_INT);
    $stmt->bindValue(3, $max, PDO::PARAM_INT);
    $stmt->bindValue(4, (int)$limit, PDO::PARAM_INT);
    $stmt->execute();
    $realOpponents = $stmt->fetchAll();
    if (count($realOpponents) >= $limit) return $realOpponents;
    $needed = $limit - count($realOpponents);
    $bots = generateBots($difficulty, $myStats, $myLevel, $needed);
    return array_merge($realOpponents, $bots);
}
function generateBots($difficulty, $playerStats, $playerLevel, $count = 1) {
    $bots = [];
    switch ($difficulty) {
        case 'weaker':   $statsBase = max(1, $playerStats - rand(50,200)); $levelBase = max(1, $playerLevel - rand(1,3)); $namePrefix = 'Новичок'; break;
        case 'equal':    $statsBase = $playerStats + rand(-20,20); $levelBase = $playerLevel + rand(-1,1); $namePrefix = 'Боец'; break;
        case 'stronger': $statsBase = $playerStats + rand(100,300); $levelBase = $playerLevel + rand(2,5); $namePrefix = 'Ветеран'; break;
        default: $statsBase = $playerStats; $levelBase = $playerLevel; $namePrefix = 'Сталкер';
    }
    for ($i = 0; $i < $count; $i++) {
        $botStats = max(1, $statsBase + rand(-30,30));
        $botLevel = max(1, $levelBase + rand(-1,1));
        $bots[] = [
            'id' => -($i + 1),
            'username' => $namePrefix . ' ' . rand(100,999),
            'level' => $botLevel,
            'total_stats' => $botStats,
            'attack' => round($botStats * 0.4),
            'health' => round($botStats * 0.3),
            'defense' => round($botStats * 0.2),
            'gender' => rand(0,1) ? 'Мужской' : 'Женский',
            'is_bot' => true,
            // Добавленные поля для аватара
            'avatar_type' => 'gender',
            'custom_avatar' => null
        ];
    }
    return $bots;
}
function fight($attackerId, $defender) {
    global $pdo;
    $attacker = getUserById($attackerId);
    $isBot = isset($defender['is_bot']) && $defender['is_bot'];
    $defenderId = $defender['id'];

    // *** Убрана проверка и трата выносливости ***

    $attackPower = $attacker['attack'];
    $defensePower = $defender['defense'] + $defender['health'] / 2;
    $result = ($attackPower >= $defensePower) ? 'win' : 'loss';

    if ($result == 'win') { $bucksEarned = rand(10,50); $expEarned = rand(20,100); }
    else { $bucksEarned = rand(1,10); $expEarned = 5; }

    $pdo->beginTransaction();
    try {
        // *** Убрано списание энергии и выносливости ***
        // $stmt = $pdo->prepare("UPDATE users SET energy = energy - 10, stamina = stamina - ?, bucks = bucks + ? WHERE id = ?");
        // $stmt->execute([$staminaCost, $bucksEarned, $attackerId]);

        // Оставляем только начисление наград
        $stmt = $pdo->prepare("UPDATE users SET bucks = bucks + ? WHERE id = ?");
        $stmt->execute([$bucksEarned, $attackerId]);

        addExp($attackerId, $expEarned);
        addUserParticipation($attackerId, $expEarned);

        if (!$isBot) {
            $stmt = $pdo->prepare("INSERT INTO battle_logs (attacker_id, defender_id, attacker_stats, defender_stats, result, bucks_earned, exp_earned) VALUES (?, ?, ?, ?, ?, ?, ?)");
            $stmt->execute([$attackerId, $defenderId, $attacker['total_stats'], $defender['total_stats'], $result, $bucksEarned, $expEarned]);
        }

        $pdo->commit();
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['result'=>'error', 'message'=>'Ошибка: '.$e->getMessage()];
    }

    return ['result'=>$result,'bucks'=>$bucksEarned,'exp'=>$expEarned,'attacker'=>$attacker,'defender'=>$defender,'is_bot'=>$isBot];
}

// ---------- УМЕНИЯ ----------
function getAllAbilities() {
    global $pdo;
    $stmt = $pdo->query("SELECT a.*, MIN(at.price_gold) as min_price, MAX(at.effect_value) as max_effect FROM abilities a LEFT JOIN ability_tiers at ON a.id = at.ability_id GROUP BY a.id");
    return $stmt->fetchAll();
}
function getAbility($abilityId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM abilities WHERE id = ?");
    $stmt->execute([$abilityId]);
    return $stmt->fetch();
}
function getAbilityTiers($abilityId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM ability_tiers WHERE ability_id = ? ORDER BY tier_level ASC");
    $stmt->execute([$abilityId]);
    return $stmt->fetchAll();
}
function getAbilityTier($abilityId, $tierLevel) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM ability_tiers WHERE ability_id = ? AND tier_level = ?");
    $stmt->execute([$abilityId, $tierLevel]);
    return $stmt->fetch();
}
function getUserAbility($userId, $abilityId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM user_abilities WHERE user_id = ? AND ability_id = ?");
    $stmt->execute([$userId, $abilityId]);
    return $stmt->fetch();
}
function purchaseAbility($userId, $abilityId, $tierLevel) {
    global $pdo;
    $tier = getAbilityTier($abilityId, $tierLevel);
    if (!$tier) return ['success'=>false,'message'=>'Уровень не найден'];
    $stmt = $pdo->prepare("SELECT gold FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    $gold = $stmt->fetchColumn();
    if ($gold < $tier['price_gold']) return ['success'=>false,'message'=>'Недостаточно золота'];
    $stmt = $pdo->prepare("UPDATE users SET gold = gold - ? WHERE id = ?");
    $stmt->execute([$tier['price_gold'], $userId]);
    $stmt = $pdo->prepare("REPLACE INTO user_abilities (user_id, ability_id, tier_level, purchased_at) VALUES (?, ?, ?, NOW())");
    $stmt->execute([$userId, $abilityId, $tierLevel]);
    return ['success'=>true,'message'=>'Умение успешно куплено/улучшено!'];
}

function formatNumber($num) {
    if ($num >= 1000000) return round($num / 1000000, 2) . 'M';
    elseif ($num >= 1000) return round($num / 1000, 1) . 'K';
    return $num;
}

// ---------- ВЕНДОРЫ ----------
function getVendorSets() {
    global $pdo;
    $stmt = $pdo->query("SELECT * FROM vendor_sets ORDER BY id");
    return $stmt->fetchAll();
}
function getVendorSetById($setId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM vendor_sets WHERE id = ?");
    $stmt->execute([$setId]);
    return $stmt->fetch();
}
function getVendorItemsBySet($setId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM vendor_items WHERE set_id = ? ORDER BY sort_order");
    $stmt->execute([$setId]);
    return $stmt->fetchAll();
}
function getVendorItemById($itemId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM vendor_items WHERE id = ?");
    $stmt->execute([$itemId]);
    return $stmt->fetch();
}
function purchaseVendorItem($userId, $vendorItemId, $bonusPercent = 200) {
    global $pdo;
    $vendorItem = getVendorItemById($vendorItemId);
    if (!$vendorItem) return ['success'=>false,'message'=>'Предмет не найден.'];

    $user = getUserById($userId);
    if ($user['level'] < $vendorItem['level_required'])
        return ['success'=>false,'message'=>'Недостаточный уровень.'];

    // Цена зависит от бонуса
    switch ($bonusPercent) {
        case 250: $price = 10; break;
        case 275: $price = 40; break;
        default:  $price = 5;  break;
    }
    if ($user['gold'] < $price)
        return ['success'=>false,'message'=>'Недостаточно золота.'];

    $pdo->beginTransaction();
    try {
        // Списание золота
        $stmt = $pdo->prepare("UPDATE users SET gold = gold - ? WHERE id = ?");
        $stmt->execute([$price, $userId]);

        // Создаём новый предмет в items с актуальными характеристиками
        $stmt = $pdo->prepare("
            INSERT INTO items 
            (name, slot, level_required, image, 
             base_attack, base_health, base_defense, base_stamina, base_accuracy, base_recovery)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        ");
        $stmt->execute([
            $vendorItem['item_name'],
            $vendorItem['slot'],
            $vendorItem['level_required'],
            $vendorItem['image'],
            $vendorItem['base_attack'],
            $vendorItem['base_health'],
            $vendorItem['base_defense'],
            $vendorItem['base_stamina'],
            $vendorItem['base_accuracy'],
            $vendorItem['base_recovery']
        ]);
        $itemId = $pdo->lastInsertId();

        // quality_id = 1 (обычное качество) – убедитесь, что такая запись есть в item_qualities
        $qualityId = 1;
        $stmt = $pdo->prepare("
            INSERT INTO user_items 
            (user_id, item_id, quality_id, bonus_percent, is_equipped, is_bound, created_at)
            VALUES (?, ?, ?, ?, 0, 0, NOW())
        ");
        $stmt->execute([$userId, $itemId, $qualityId, $bonusPercent]);

        $pdo->commit();
        return ['success'=>true,'message'=>'Предмет успешно куплен!','item_id'=>$pdo->lastInsertId()];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success'=>false,'message'=>'Ошибка при покупке: '.$e->getMessage()];
    }
}

function purchaseAllVendorSet($userId, $setId, $bonusPercent = 200) {
    global $pdo;
    $items = getVendorItemsBySet($setId);
    if (count($items) != 10) return ['success'=>false,'message'=>'Комплект должен содержать 10 предметов.'];
    $user = getUserById($userId);
    $totalPrice = 40;
    if ($user['gold'] < $totalPrice) return ['success'=>false,'message'=>'Недостаточно золота.'];
    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE users SET gold = gold - ? WHERE id = ?");
        $stmt->execute([$totalPrice, $userId]);

        foreach ($items as $vendorItem) {
            // Создаём новый item
            $stmt = $pdo->prepare("
                INSERT INTO items 
                (name, slot, level_required, image, 
                 base_attack, base_health, base_defense, base_stamina, base_accuracy, base_recovery)
                VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            ");
            $stmt->execute([
                $vendorItem['item_name'],
                $vendorItem['slot'],
                $vendorItem['level_required'],
                $vendorItem['image'],
                $vendorItem['base_attack'],
                $vendorItem['base_health'],
                $vendorItem['base_defense'],
                $vendorItem['base_stamina'],
                $vendorItem['base_accuracy'],
                $vendorItem['base_recovery']
            ]);
            $itemId = $pdo->lastInsertId();

            $qualityId = 1;
            $stmt = $pdo->prepare("INSERT INTO user_items (user_id, item_id, quality_id, bonus_percent, is_equipped, is_bound, created_at) VALUES (?, ?, ?, ?, 0, 0, NOW())");
            $stmt->execute([$userId, $itemId, $qualityId, $bonusPercent]);
        }
        $pdo->commit();
        return ['success'=>true,'message'=>'Все предметы комплекта успешно куплены!'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success'=>false,'message'=>'Ошибка при покупке комплекта: '.$e->getMessage()];
    }
}
// ---------- БАН / МУТ ----------
function isBanned($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT is_banned, banned_until FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    $row = $stmt->fetch();
    if (!$row) return false;
    if ($row['is_banned'] == 0) return false;
    if ($row['banned_until'] && strtotime($row['banned_until']) < time()) {
        $stmt = $pdo->prepare("UPDATE users SET is_banned = 0, ban_reason = NULL, banned_at = NULL, banned_until = NULL WHERE id = ?");
        $stmt->execute([$userId]);
        return false;
    }
    return true;
}
function isMuted($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT is_muted, muted_until FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    $row = $stmt->fetch();
    if (!$row) return false;
    if ($row['is_muted'] == 0) return false;
    if ($row['muted_until'] && strtotime($row['muted_until']) < time()) {
        $stmt = $pdo->prepare("UPDATE users SET is_muted = 0, muted_until = NULL WHERE id = ?");
        $stmt->execute([$userId]);
        return false;
    }
    return true;
}
function addBanHistory($userId, $adminId, $action, $reason = null, $bannedUntil = null, $mutedUntil = null) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO ban_history (user_id, admin_id, action, reason, banned_until, muted_until) VALUES (?, ?, ?, ?, ?, ?)");
    $stmt->execute([$userId, $adminId, $action, $reason, $bannedUntil, $mutedUntil]);
}
function getUserBanHistory($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT bh.*, admin.username as admin_name FROM ban_history bh JOIN users admin ON bh.admin_id = admin.id WHERE bh.user_id = ? ORDER BY bh.created_at DESC");
    $stmt->execute([$userId]);
    return $stmt->fetchAll();
}
function deleteChatMessage($messageId, $moderatorId) {
    global $pdo;
    $stmt = $pdo->prepare("DELETE FROM chat_messages WHERE id = ?");
    return $stmt->execute([$messageId]);
}
function clearAllChatMessages() {
    global $pdo;
    return $pdo->query("TRUNCATE TABLE chat_messages");
}
function getAllBattleLogs($limit = 100, $offset = 0) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT bl.*, a.username as attacker_name, d.username as defender_name FROM battle_logs bl LEFT JOIN users a ON bl.attacker_id = a.id LEFT JOIN users d ON bl.defender_id = d.id ORDER BY bl.created_at DESC LIMIT ? OFFSET ?");
    $stmt->bindValue(1, (int)$limit, PDO::PARAM_INT);
    $stmt->bindValue(2, (int)$offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}
function updateUserFull($userId, $data) {
    global $pdo;
    $allowed = ['username','side','gender','role','clan_id','attack','health','stamina','accuracy','recovery','defense','defense_percent','total_stats','mastery','gold','bucks','medkits','energy','max_energy','bag_max','exp','level','exp_percent','achievements','skills_count','participation'];
    $set = []; $params = [];
    foreach ($data as $key => $value) if (in_array($key, $allowed)) { $set[] = "$key = ?"; $params[] = $value; }
    if (empty($set)) return false;
    $params[] = $userId;
    $sql = "UPDATE users SET " . implode(', ', $set) . " WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    return $stmt->execute($params);
}

// ==================== НОВЫЙ БЛОК: РАЙОНЫ (без bot_id) ====================

function isInDistrict($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT 1 FROM district_players WHERE user_id = ?");
    $stmt->execute([$userId]);
    return (bool)$stmt->fetchColumn();
}

function getDistrictById($id) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT d.*, ds.controlling_side, ds.progress,
               COALESCE(ds.controlling_side, d.initial_side) as current_owner
        FROM districts d
        LEFT JOIN district_state ds ON d.id = ds.district_id
        WHERE d.id = ?
    ");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

function getDistrictsForPlayer($userId) {
    global $pdo;
    $user = getUserById($userId);
    $level = $user['level'];
    $stmt = $pdo->prepare("
        SELECT d.*, ds.controlling_side, ds.progress,
               COALESCE(ds.controlling_side, d.initial_side) as current_owner
        FROM districts d
        LEFT JOIN district_state ds ON d.id = ds.district_id
        WHERE d.level_min <= ? AND d.level_max >= ?
        ORDER BY d.level_min ASC, d.id ASC
    ");
    $stmt->execute([$level, $level]);
    return $stmt->fetchAll();
}

function ensureDistrictState($districtId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT 1 FROM district_state WHERE district_id = ?");
    $stmt->execute([$districtId]);
    if (!$stmt->fetch()) {
        $district = getDistrictById($districtId);
        $initial = ($district['initial_side'] === 'нейтральная') ? null : $district['initial_side'];
        $stmt = $pdo->prepare("INSERT INTO district_state (district_id, controlling_side, progress) VALUES (?, ?, 0)");
        $stmt->execute([$districtId, $initial]);
    }
}

function enterDistrict($userId, $districtId) {
    global $pdo;
    $district = getDistrictById($districtId);
    if (!$district) return ['success' => false, 'message' => 'Район не найден'];
    $user = getUserById($userId);
    if ($user['level'] < $district['level_min'] || $user['level'] > $district['level_max'])
        return ['success' => false, 'message' => 'Ваш уровень не подходит для этого района'];
    if (isInDistrict($userId)) exitDistrict($userId);
    $stmt = $pdo->prepare("INSERT INTO district_players (user_id, district_id, current_hp, current_stamina) VALUES (?, ?, ?, ?)");
    $stmt->execute([$userId, $districtId, $user['health'], $user['stamina']]);
    $stmt = $pdo->prepare("UPDATE users SET in_chat = 1 WHERE id = ?");
    $stmt->execute([$userId]);
    return ['success' => true, 'message' => 'Вы вошли в район'];
}

function exitDistrict($userId) {
    global $pdo;
    $stmt = $pdo->prepare("DELETE FROM district_players WHERE user_id = ?");
    $stmt->execute([$userId]);
    $stmt = $pdo->prepare("UPDATE users SET in_chat = 0 WHERE id = ?");
    $stmt->execute([$userId]);
    return true;
}

function getOpponentsInDistrict($userId, $districtId) {
    global $pdo;
    $user = getUserById($userId);
    $mySide = $user['side'];
    // Реальные игроки (user_id > 0)
    $stmt = $pdo->prepare("
        SELECT u.id, u.username, u.side, u.level, u.attack, u.health, u.defense, u.stamina, u.accuracy, u.recovery,
               dp.current_hp, dp.current_stamina, u.defense_percent, u.gender,
               0 as is_bot
        FROM district_players dp
        JOIN users u ON dp.user_id = u.id
        WHERE dp.district_id = ? 
          AND u.id != ? 
          AND u.side != ?
          AND u.id NOT IN (SELECT blocked_user_id FROM blacklist WHERE user_id = ?)
    ");
    $stmt->execute([$districtId, $userId, $mySide, $userId]);
    $players = $stmt->fetchAll();
    // Боты (user_id < 0)
    $stmt2 = $pdo->prepare("
        SELECT 
            dp.user_id as id,
            dp.username,
            dp.level,
            dp.total_stats,
            dp.attack,
            dp.health,
            dp.defense,
            dp.stamina,
            dp.accuracy,
            dp.recovery,
            dp.defense_percent,
            dp.gender,
            dp.current_hp,
            dp.current_stamina,
            1 as is_bot
        FROM district_players dp
        WHERE dp.district_id = ? 
          AND dp.user_id < 0 
          AND dp.current_hp > 0
    ");
    $stmt2->execute([$districtId]);
    $bots = $stmt2->fetchAll();
    return array_merge($players, $bots);
}

function regenerateInBattle($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT dp.*, u.recovery, u.health as max_hp, u.stamina as max_stamina 
        FROM district_players dp
        JOIN users u ON dp.user_id = u.id
        WHERE dp.user_id = ?
    ");
    $stmt->execute([$userId]);
    $state = $stmt->fetch();
    if (!$state) return;
    $lastAction = strtotime($state['last_action']);
    $now = time();
    $diff = $now - $lastAction;
    $tick = 10;
    $ticksPassed = floor($diff / $tick);
    if ($ticksPassed > 0) {
        $regenAmount = round($state['recovery'] / 10) * $ticksPassed;
        $newHp = min($state['current_hp'] + $regenAmount, $state['max_hp']);
        $newStam = min($state['current_stamina'] + $regenAmount, $state['max_stamina']);
        $stmt = $pdo->prepare("UPDATE district_players SET current_hp = ?, current_stamina = ?, last_action = NOW() WHERE user_id = ?");
        $stmt->execute([$newHp, $newStam, $userId]);
    }
}

function generateBotForDistrict($districtId, $playerLevel, $playerStats, $enemySide) {
    global $pdo;
    $botLevel = max(1, $playerLevel + rand(-2, 2));
    $botStats = max(1, $playerStats + rand(-100, 100));
    $baseStats = getBaseStatsForLevel($botLevel);
    $attack   = $baseStats['attack']   + rand(-10, 20);
    $defense  = $baseStats['defense']  + rand(-5, 10);
    $health   = $baseStats['health']   + rand(-20, 30);
    $stamina  = $baseStats['stamina']  + rand(-5, 10);
    $accuracy = max(0.1, $baseStats['accuracy'] + rand(-1, 2));
    $recovery = max(1, $baseStats['recovery'] + rand(-1, 3));
    $defense_percent = round($defense / 10, 1);
    $total_stats = $attack + $health + $defense + $stamina + $accuracy + $recovery;
    $gender = rand(0,1) ? 'Мужской' : 'Женский';
    $username = ($enemySide == 'Якудза' ? 'Якудза ' : 'Мафия ') . rand(100, 999);
    $botId = -rand(10000, 99999);
    $stmt = $pdo->prepare("
        INSERT INTO district_players 
        (user_id, district_id, is_bot, username, level, total_stats, 
         attack, defense, health, stamina, accuracy, recovery, defense_percent, 
         gender, current_hp, current_stamina, last_action, last_attack_time)
        VALUES (?, ?, 1, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW(), NULL)
    ");
    $stmt->execute([
        $botId, $districtId, $username, $botLevel, $total_stats,
        $attack, $defense, $health, $stamina, $accuracy, $recovery, $defense_percent,
        $gender, $health, $stamina
    ]);
    return $botId;
}

function ensureBotsInDistrict($districtId, $userId) {
    global $pdo;
    $user = getUserById($userId);
    $mySide = $user['side'];
    $enemySide = ($mySide == 'Коза Ностра') ? 'Якудза' : 'Коза Ностра';
    $stmt = $pdo->prepare("DELETE FROM district_players WHERE district_id = ? AND is_bot = 1 AND current_hp <= 0");
    $stmt->execute([$districtId]);
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM district_players WHERE district_id = ? AND is_bot = 1 AND current_hp > 0");
    $stmt->execute([$districtId]);
    $botsCount = $stmt->fetchColumn();
    $neededBots = 3 - $botsCount;
    for ($i = 0; $i < $neededBots; $i++) generateBotForDistrict($districtId, $user['level'], $user['total_stats'], $enemySide);
}

function canAttackPlayer($attackerId, $defenderId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT last_attack FROM district_attack_cooldown WHERE attacker_id = ? AND defender_id = ?");
    $stmt->execute([$attackerId, $defenderId]);
    $row = $stmt->fetch();
    if (!$row) return true;
    return (time() - strtotime($row['last_attack'])) >= 5;
}
function updateAttackCooldown($attackerId, $defenderId) {
    global $pdo;
    $stmt = $pdo->prepare("REPLACE INTO district_attack_cooldown (attacker_id, defender_id, last_attack) VALUES (?, ?, NOW())");
    $stmt->execute([$attackerId, $defenderId]);
}

function botAttackPlayer($botId, $playerId) {
    global $pdo;
    
    $stmt = $pdo->prepare("SELECT * FROM district_players WHERE user_id = ? AND is_bot = 1");
    $stmt->execute([$botId]);
    $bot = $stmt->fetch();
    if (!$bot || $bot['current_hp'] <= 0) return null;

    $player = getUserById($playerId);
    $stmt = $pdo->prepare("SELECT * FROM district_players WHERE user_id = ?");
    $stmt->execute([$playerId]);
    $playerDp = $stmt->fetch();
    if (!$playerDp) return null;

    $staminaCost = round(3.5 * sqrt($bot['attack']));
    if ($bot['current_stamina'] < $staminaCost) return null;

    $defenderEffects = getActiveEffects($playerId);

    $damage = $bot['attack'];
    $isCrit = false;
    if (mt_rand(1, 100) <= $bot['accuracy']) { 
        $damage = round($damage * 1.5); 
        $isCrit = true; 
    }

    $defensePercent = $player['defense_percent'];
    $damageBlocked = round($damage * $defensePercent / 100);
    $damageBlocked = applyDefensiveEffects($damage, $damageBlocked, $playerId, $botId);
    $damageTaken = $damage - $damageBlocked;
    if ($damageTaken < 0) $damageTaken = 0;

    $newHp = $playerDp['current_hp'] - $damageTaken;
    $newStam = $playerDp['current_stamina'] - $damage;
    if ($newStam < 0) $newStam = 0;

    $playerKilled = false;
    if ($newHp <= 0) {
        $newHp = 0;
        $playerKilled = true;
        exitDistrict($playerId);
    } else {
        $stmt = $pdo->prepare("UPDATE district_players SET current_hp = ?, current_stamina = ?, last_action = NOW() WHERE user_id = ?");
        $stmt->execute([$newHp, $newStam, $playerId]);
    }

    $playerExp = $damageBlocked;
    $playerParticipation = $damageBlocked;
    if ($isCrit) { 
        $playerExp = round($playerExp * 1.5); 
        $playerParticipation = round($playerParticipation * 1.5); 
    }
    addExp($playerId, $playerExp);
    addUserParticipation($playerId, $playerParticipation);

    $newBotStam = $bot['current_stamina'] - $staminaCost;
    $stmt = $pdo->prepare("UPDATE district_players SET current_stamina = ?, last_action = NOW(), last_attack_time = NOW() WHERE user_id = ?");
    $stmt->execute([$newBotStam, $botId]);

    // --- ДОБАВЛЯЕМ ЛОГ ---
    $districtId = $bot['district_id'];
    $logMessage = sprintf("%s атаковал %s и нанёс %d урона%s",
        $bot['username'],
        $player['username'],
        $damageTaken,
        $isCrit ? ' (крит)' : ''
    );
    addDistrictBattleLog($districtId, $botId, $playerId, $logMessage);
    // --- КОНЕЦ ЛОГА ---

    return [
        'bot_name' => $bot['username'],
        'damage' => $damage,
        'damage_taken' => $damageTaken,
        'damage_blocked' => $damageBlocked,
        'is_crit' => $isCrit,
        'player_exp' => $playerExp,
        'player_participation' => $playerParticipation,
        'player_new_hp' => $newHp,
        'player_killed' => $playerKilled
    ];
}

function attackOpponent($attackerId, $defenderId) {
    global $pdo;
    
    // Проверка кулдауна
    if (!canAttackPlayer($attackerId, $defenderId)) {
        return ['success' => false, 'miss' => true, 'message' => 'Промах! Атаковать можно раз в 5 секунд.'];
    }

    $attackerUser = getUserById($attackerId);
    $stmt = $pdo->prepare("SELECT * FROM district_players WHERE user_id = ?");
    $stmt->execute([$attackerId]);
    $attackerDp = $stmt->fetch();
    if (!$attackerDp) return ['success' => false, 'message' => 'Вы не в районе'];

    // Стоимость выносливости
    $staminaCost = floor($attackerUser['attack'] / 1) + 1;
    if ($attackerDp['current_stamina'] < $staminaCost) {
        return ['success' => false, 'message' => 'Недостаточно выносливости'];
    }

    $isBotDefender = ($defenderId < 0);
    if ($isBotDefender) {
        $stmt = $pdo->prepare("SELECT * FROM district_players WHERE user_id = ? AND is_bot = 1 AND current_hp > 0");
        $stmt->execute([$defenderId]);
        $defenderDp = $stmt->fetch();
        if (!$defenderDp) return ['success' => false, 'message' => 'Противник не найден или уже мёртв'];
        $defenderUser = $defenderDp;
        $defenderName = $defenderDp['username'];
        $defenderGender = $defenderDp['gender'];
        $defenderRealId = null;
    } else {
        $stmt = $pdo->prepare("SELECT * FROM district_players WHERE user_id = ?");
        $stmt->execute([$defenderId]);
        $defenderDp = $stmt->fetch();
        if (!$defenderDp) return ['success' => false, 'message' => 'Противник не в районе'];
        $defenderUser = getUserById($defenderId);
        $defenderName = $defenderUser['username'];
        $defenderGender = $defenderUser['gender'];
        $defenderRealId = $defenderId;
    }

    if ($attackerDp['district_id'] != $defenderDp['district_id']) {
        return ['success' => false, 'message' => 'Противник в другом районе'];
    }

    regenerateInBattle($attackerId);
    if (!$isBotDefender) regenerateInBattle($defenderId);

    $attackerEffects = getActiveEffects($attackerId);
    $defenderEffects = getActiveEffects($defenderId);

    $damage = $attackerUser['attack'];
    $damage = applyOffensiveEffects($damage, $attackerEffects);

    $critChance = $attackerUser['accuracy'];
    foreach ($attackerEffects as $eff) {
        if ($eff['effect_type'] === 'crit_chance') {
            $critChance += $eff['effect_value'];
        }
    }
    $isCrit = (mt_rand(1, 100) <= $critChance);
    if ($isCrit) $damage = round($damage * 1.5);

    if ($isBotDefender) {
        $defensePercent = $defenderDp['defense_percent'];
        $defenderHealth = $defenderDp['health'];
    } else {
        $defensePercent = $defenderUser['defense_percent'];
        $defenderHealth = $defenderUser['health'];
    }

    $damageBlocked = round($damage * $defensePercent / 100);
    $damageBlocked = applyDefensiveEffects($damage, $damageBlocked, $defenderId, $attackerId);
    $damageTaken = max(0, $damage - $damageBlocked);

    $oldHp = $defenderDp['current_hp'];
    $newHp = $oldHp - $damageTaken;
    $newStam = $defenderDp['current_stamina'] - $damage;
    if ($newStam < 0) $newStam = 0;

    $defenderKilled = false;
    if ($newHp <= 0) {
        $newHp = 0; $defenderKilled = true;
        if ($isBotDefender) {
            $stmt = $pdo->prepare("UPDATE district_players SET current_hp = 0 WHERE user_id = ?");
            $stmt->execute([$defenderId]);
        } else {
            exitDistrict($defenderId);
        }
    } else {
        $stmt = $pdo->prepare("UPDATE district_players SET current_hp = ?, current_stamina = ?, last_action = NOW() WHERE user_id = ?");
        $stmt->execute([$newHp, $newStam, $defenderId]);
    }

    $attackerExp = $damage;
    $attackerParticipation = $damage;
    $defenderExp = $damageBlocked;
    $defenderParticipation = $damageBlocked;
    if ($isCrit) {
        $attackerExp = round($attackerExp * 1.5);
        $attackerParticipation = round($attackerParticipation * 1.5);
        if (!$defenderKilled) {
            $defenderExp = round($defenderExp * 1.5);
            $defenderParticipation = round($defenderParticipation * 1.5);
        }
    }

    $pdo->beginTransaction();
    try {
        $newAttackerStam = $attackerDp['current_stamina'] - $staminaCost;
        $stmt = $pdo->prepare("UPDATE district_players SET current_stamina = ?, last_action = NOW() WHERE user_id = ?");
        $stmt->execute([$newAttackerStam, $attackerId]);

        addExp($attackerId, $attackerExp);
        addUserParticipation($attackerId, $attackerParticipation);

        if (!$isBotDefender && !$defenderKilled) {
            addExp($defenderId, $defenderExp);
            addUserParticipation($defenderId, $defenderParticipation);
        }

        $districtId = $attackerDp['district_id'];
        ensureDistrictState($districtId);
        $stmt = $pdo->prepare("UPDATE district_state SET progress = progress + ? WHERE district_id = ?");
        $stmt->execute([$damage, $districtId]);

        $stmt = $pdo->prepare("SELECT progress FROM district_state WHERE district_id = ?");
        $stmt->execute([$districtId]);
        $progress = $stmt->fetchColumn();

        $district = getDistrictById($districtId);
        $captured = false; $reward = null;
        if ($progress >= $district['capture_threshold']) {
            $newOwner = $attackerUser['side'];
            $stmt = $pdo->prepare("UPDATE district_state SET controlling_side = ?, progress = 0 WHERE district_id = ?");
            $stmt->execute([$newOwner, $districtId]);
            $stmt = $pdo->prepare("INSERT INTO district_captures (district_id, captured_by_side, captured_by_user_id) VALUES (?, ?, ?)");
            $stmt->execute([$districtId, $newOwner, $attackerId]);

            // Начисляем 50 баксов
            $stmt = $pdo->prepare("UPDATE users SET bucks = bucks + 50 WHERE id = ?");
            $stmt->execute([$attackerId]);
            $reward = ['item_name' => '50 bucks', 'bonus' => 0];

            $enemySide = ($newOwner == 'Коза Ностра') ? 'Якудза' : 'Коза Ностра';
            $stmt = $pdo->prepare("DELETE FROM district_players WHERE district_id = ? AND user_id IN (SELECT id FROM users WHERE side = ?)");
            $stmt->execute([$districtId, $enemySide]);
            $captured = true;
        }

        updateAttackCooldown($attackerId, $defenderId);

        // --- ДОБАВЛЯЕМ ЛОГ ---
        $logMessage = sprintf("%s атаковал %s и нанёс %d урона%s",
            $attackerUser['username'],
            $defenderName,
            $damageTaken,
            $isCrit ? ' (крит)' : ''
        );
        addDistrictBattleLog($districtId, $attackerId, $defenderRealId, $logMessage);
        // --- КОНЕЦ ЛОГА ---

        $pdo->commit();

        return [
            'success' => true,
            'damage_dealt' => $damage,
            'damage_taken' => $damageTaken,
            'damage_blocked' => $damageBlocked,
            'is_crit' => $isCrit,
            'attacker_exp' => $attackerExp,
            'attacker_participation' => $attackerParticipation,
            'defender_exp' => $defenderExp,
            'defender_participation' => $defenderParticipation,
            'defender_killed' => $defenderKilled,
            'defender_new_hp' => $newHp,
            'defender_old_hp' => $oldHp,
            'defender_new_stam' => $newStam,
            'defender_name' => $defenderName,
            'defender_gender' => $defenderGender,
            'defender_id' => $defenderRealId,
            'is_bot_defender' => $isBotDefender,
            'district_progress' => $progress,
            'district_captured' => $captured,
            'reward' => $reward
        ];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

function giveRandomDistrictReward($userId, $districtId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT i.id as item_id, i.name as item_name, 7 as quality_id, 
               FLOOR(200 + RAND() * 51) as bonus_percent
        FROM items i
        JOIN district_loot dl ON i.id = dl.item_id
        WHERE dl.district_id = ? OR dl.district_id IS NULL
        ORDER BY RAND()
        LIMIT 1
    ");
    $stmt->execute([$districtId]);
    $item = $stmt->fetch();
    if (!$item) {
        $stmt = $pdo->prepare("
            SELECT i.id as item_id, vi.item_name, 7 as quality_id,
                   FLOOR(200 + RAND() * 51) as bonus_percent
            FROM vendor_items vi
            JOIN items i ON i.name = vi.item_name
            ORDER BY RAND()
            LIMIT 1
        ");
        $stmt->execute();
        $item = $stmt->fetch();
    }
    if (!$item) return null;
    $stmt = $pdo->prepare("INSERT INTO user_items (user_id, item_id, quality_id, bonus_percent, is_equipped, is_bound, created_at) VALUES (?, ?, ?, ?, 0, 1, NOW())");
    $stmt->execute([$userId, $item['item_id'], $item['quality_id'], $item['bonus_percent']]);
    return [
        'item_name' => $item['item_name'],
        'bonus' => $item['bonus_percent'],
        'user_item_id' => $pdo->lastInsertId()
    ];
}

// ---------- АДМИНИСТРИРОВАНИЕ РАЙОНОВ ----------
function adminGetAllDistricts() {
    global $pdo;
    $stmt = $pdo->query("SELECT * FROM districts ORDER BY level_min ASC, id ASC");
    return $stmt->fetchAll();
}
function adminCreateDistrict($data) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO districts (name, description, level_min, level_max, capture_threshold, initial_side, icon) VALUES (?, ?, ?, ?, ?, ?, ?)");
    return $stmt->execute([$data['name'], $data['description'] ?? null, $data['level_min'], $data['level_max'], $data['capture_threshold'], $data['initial_side'], $data['icon'] ?? '/images/icons/blue_tower.png']);
}
function adminUpdateDistrict($id, $data) {
    global $pdo;
    $set = []; $params = [];
    foreach ($data as $key => $value) { $set[] = "$key = ?"; $params[] = $value; }
    $params[] = $id;
    $sql = "UPDATE districts SET " . implode(', ', $set) . " WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    return $stmt->execute($params);
}
function adminDeleteDistrict($id) {
    global $pdo;
    $stmt = $pdo->prepare("DELETE FROM districts WHERE id = ?");
    return $stmt->execute([$id]);
}

// ---------- ПОЛУЧЕНИЕ ВСЕХ УМЕНИЙ ИГРОКА ----------
function getUserAbilities($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT ua.*, a.name, a.description, a.icon, a.effect_type,
               at.tier_level, at.tier_name, at.effect_value, 
               at.duration_seconds, at.cooldown_seconds, at.price_gold, at.icon_star
        FROM user_abilities ua
        JOIN abilities a ON ua.ability_id = a.id
        JOIN ability_tiers at ON ua.ability_id = at.ability_id AND ua.tier_level = at.tier_level
        WHERE ua.user_id = ?
        ORDER BY ua.chosen DESC, ua.tier_level DESC, ua.purchased_at DESC
    ");
    $stmt->execute([$userId]);
    return $stmt->fetchAll();
}

// ---------- ПОЛУЧЕНИЕ АКТИВНОГО УМЕНИЯ (chosen = 3) ----------
function getActiveAbility($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT ua.*, a.name, a.effect_type, at.effect_value, 
               at.duration_seconds, at.cooldown_seconds, at.icon_star
        FROM user_abilities ua
        JOIN abilities a ON ua.ability_id = a.id
        JOIN ability_tiers at ON ua.ability_id = at.ability_id AND ua.tier_level = at.tier_level
        WHERE ua.user_id = ? AND ua.chosen = 3
        LIMIT 1
    ");
    $stmt->execute([$userId]);
    return $stmt->fetch();
}

// ---------- УСТАНОВКА АКТИВНОГО УМЕНИЯ ----------
function setActiveAbility($userId, $userAbilityId) {
    global $pdo;
    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE user_abilities SET chosen = 0 WHERE user_id = ?");
        $stmt->execute([$userId]);
        $stmt = $pdo->prepare("UPDATE user_abilities SET chosen = 3 WHERE id = ? AND user_id = ?");
        $stmt->execute([$userAbilityId, $userId]);
        $stmt = $pdo->prepare("
            UPDATE user_abilities 
            SET chosen = 2 
            WHERE user_id = ? AND id != ? AND chosen = 0 
            ORDER BY purchased_at DESC 
            LIMIT 1
        ");
        $stmt->execute([$userId, $userAbilityId]);
        $stmt = $pdo->prepare("
            UPDATE user_abilities 
            SET chosen = 1 
            WHERE user_id = ? AND id != ? AND chosen = 0 
            ORDER BY purchased_at DESC 
            LIMIT 1
        ");
        $stmt->execute([$userId, $userAbilityId]);
        $pdo->commit();
        return true;
    } catch (Exception $e) {
        $pdo->rollBack();
        return false;
    }
}

// ---------- ПРОВЕРКА, ГОТОВО ЛИ УМЕНИЕ К ИСПОЛЬЗОВАНИЮ ----------
function canUseAbility($userId, $abilityId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT cooldown_until 
        FROM user_abilities 
        WHERE user_id = ? AND ability_id = ? AND chosen = 3
    ");
    $stmt->execute([$userId, $abilityId]);
    $row = $stmt->fetch();
    if (!$row) return false;
    if (!$row['cooldown_until']) return true;
    return time() >= strtotime($row['cooldown_until']);
}

// ---------- АКТИВАЦИЯ УМЕНИЯ ----------
function activateAbility($userId, $abilityId = null) {
    global $pdo;

    if ($abilityId === null) {
        $ability = getActiveAbility($userId);
        if (!$ability) return ['success' => false, 'message' => 'Активное умение не выбрано.'];
        $abilityId = $ability['ability_id'];
    } else {
        $stmt = $pdo->prepare("
            SELECT ua.*, a.effect_type, at.effect_value, at.duration_seconds, at.cooldown_seconds 
            FROM user_abilities ua
            JOIN abilities a ON ua.ability_id = a.id
            JOIN ability_tiers at ON ua.ability_id = at.ability_id AND ua.tier_level = at.tier_level
            WHERE ua.user_id = ? AND ua.ability_id = ?
        ");
        $stmt->execute([$userId, $abilityId]);
        $ability = $stmt->fetch();
        if (!$ability) return ['success' => false, 'message' => 'Умение не найдено.'];
    }

    if ($ability['cooldown_until'] && strtotime($ability['cooldown_until']) > time()) {
        return ['success' => false, 'message' => 'Умение ещё на перезарядке.'];
    }

    $now = time();
    $activeUntil = ($ability['duration_seconds'] > 0) ? date('Y-m-d H:i:s', $now + $ability['duration_seconds']) : null;
    $cooldownUntil = date('Y-m-d H:i:s', $now + $ability['cooldown_seconds']);

    $stmt = $pdo->prepare("UPDATE user_abilities SET active_until = ?, cooldown_until = ? WHERE user_id = ? AND ability_id = ?");
    $result = $stmt->execute([$activeUntil, $cooldownUntil, $userId, $abilityId]);

    if ($result) {
        return ['success' => true, 'message' => 'Умение активировано!', 'ability' => $ability];
    }
    return ['success' => false, 'message' => 'Ошибка активации.'];
}

// ---------- ПОЛУЧЕНИЕ ВСЕХ АКТИВНЫХ ЭФФЕКТОВ ИГРОКА (для боя) ----------
function getActiveEffects($userId) {
    global $pdo;
    $now = date('Y-m-d H:i:s');
    $stmt = $pdo->prepare("
        SELECT ua.*, a.effect_type, at.effect_value
        FROM user_abilities ua
        JOIN abilities a ON ua.ability_id = a.id
        JOIN ability_tiers at ON ua.ability_id = at.ability_id AND ua.tier_level = at.tier_level
        WHERE ua.user_id = ? 
          AND ua.active_until IS NOT NULL 
          AND ua.active_until > ?
    ");
    $stmt->execute([$userId, $now]);
    return $stmt->fetchAll();
}

// ---------- ПРИМЕНЕНИЕ ЭФФЕКТОВ К УРОНУ (АТАКУЮЩИЙ) ----------
function applyOffensiveEffects($damage, $effects) {
    foreach ($effects as $eff) {
        if ($eff['effect_type'] === 'attack_multiplier') {
            $damage = round($damage * (1 + $eff['effect_value'] / 100));
        }
    }
    return $damage;
}

// ---------- ПРИМЕНЕНИЕ ЭФФЕКТОВ К ЗАЩИТЕ (ЗАЩИТНИК) ----------
function applyDefensiveEffects($damage, $damageBlocked, $defenderId, $attackerId) {
    $effects = getActiveEffects($defenderId);
    foreach ($effects as $eff) {
        if ($eff['effect_type'] === 'defense_percent') {
            $damageBlocked = round($damageBlocked * (1 + $eff['effect_value'] / 100));
        }
        if ($eff['effect_type'] === 'evasion') {
            if (mt_rand(1, 100) <= $eff['effect_value']) {
                $damageBlocked = $damage; // полный блок
            }
        }
    }
    return min($damageBlocked, $damage);
}

// ==================== УЛИЧНЫЕ ПЕРЕСТРЕЛКИ (АРЕНА) ====================

// Получить рейтинг арены пользователя
function getArenaRating($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT rating, weekly_score, multiplier_until FROM arena_rating WHERE user_id = ?");
    $stmt->execute([$userId]);
    $row = $stmt->fetch();
    if (!$row) {
        $stmt = $pdo->prepare("INSERT INTO arena_rating (user_id) VALUES (?)");
        $stmt->execute([$userId]);
        return ['rating' => 1000, 'weekly_score' => 0, 'multiplier_until' => null];
    }
    return $row;
}

function updateArenaRating($userId, $delta) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE arena_rating SET rating = GREATEST(0, rating + ?) WHERE user_id = ?");
    $stmt->execute([$delta, $userId]);
}

function updateWeeklyScore($userId, $delta) {
    global $pdo;
    $today = date('Y-m-d');
    $stmt = $pdo->prepare("SELECT battles_count FROM arena_daily WHERE user_id = ? AND battle_date = ?");
    $stmt->execute([$userId, $today]);
    $daily = $stmt->fetch();
    $count = $daily ? $daily['battles_count'] : 0;
    if ($count < 10) {
        $stmt = $pdo->prepare("UPDATE arena_rating SET weekly_score = GREATEST(0, weekly_score + ?) WHERE user_id = ?");
        $stmt->execute([$delta, $userId]);
    }
}

// Встать в очередь (с фиксацией времени)
function joinArenaQueue($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT 1 FROM arena_queue WHERE user_id = ?");
    $stmt->execute([$userId]);
    if ($stmt->fetch()) return false;

    if (isInArenaBattle($userId)) return false;

    $rating = getArenaRating($userId)['rating'];
    $stmt = $pdo->prepare("INSERT INTO arena_queue (user_id, rating, joined_at) VALUES (?, ?, NOW())");
    return $stmt->execute([$userId, $rating]);
}

function leaveArenaQueue($userId) {
    global $pdo;
    $stmt = $pdo->prepare("DELETE FROM arena_queue WHERE user_id = ?");
    return $stmt->execute([$userId]);
}

function getArenaQueueCount() {
    global $pdo;
    $result = $pdo->query("SELECT COUNT(*) FROM arena_queue");
    return $result->fetchColumn();
}

function getArenaQueueOldestTimestamp() {
    global $pdo;
    $stmt = $pdo->query("SELECT UNIX_TIMESTAMP(MIN(joined_at)) FROM arena_queue");
    return $stmt->fetchColumn();
}

function isArenaQueueTimeout() {
    $oldest = getArenaQueueOldestTimestamp();
    if (!$oldest) return false;
    return (time() - $oldest) >= 15;
}

function isInArenaBattle($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT 1 FROM arena_participants ap
        JOIN arena_battles ab ON ap.battle_id = ab.id
        WHERE ap.user_id = ? AND ab.status IN ('preparing', 'active')
    ");
    $stmt->execute([$userId]);
    return (bool)$stmt->fetch();
}

function getCurrentArenaBattle($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT ab.*, ap.team, ap.current_health, ap.current_stamina, ap.medkits_used, ap.used_abilities
        FROM arena_participants ap
        JOIN arena_battles ab ON ap.battle_id = ab.id
        WHERE ap.user_id = ? AND ab.status IN ('preparing', 'active')
        LIMIT 1
    ");
    $stmt->execute([$userId]);
    return $stmt->fetch();
}

function getBattleParticipants($battleId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT ap.*, u.username, u.level, u.attack, u.defense, u.health as max_health, u.stamina as max_stamina,
               u.accuracy, u.recovery, u.defense_percent, u.gender, u.medkits,
               ar.rating
        FROM arena_participants ap
        LEFT JOIN users u ON ap.user_id = u.id AND ap.is_bot = 0
        LEFT JOIN arena_rating ar ON ap.user_id = ar.user_id AND ap.is_bot = 0
        WHERE ap.battle_id = ?
        ORDER BY ap.team, ap.id
    ");
    $stmt->execute([$battleId]);
    $participants = $stmt->fetchAll();

    foreach ($participants as &$p) {
        // Безопасное декодирование bot_data
        $p['bot_data'] = !empty($p['bot_data']) ? json_decode($p['bot_data'], true) : [];

        // Безопасное декодирование used_abilities
        $p['used_abilities'] = !empty($p['used_abilities']) ? json_decode($p['used_abilities'], true) : [];

        // Безопасное декодирование active_effects
        $p['active_effects'] = !empty($p['active_effects']) ? json_decode($p['active_effects'], true) : [];
    }

    return $participants;
}

// ИСПРАВЛЕННАЯ ФУНКЦИЯ ФОРМИРОВАНИЯ КОМАНД (ТРАНЗАКЦИОННАЯ)
function tryFormTeams() {
    global $pdo;
    $pdo->beginTransaction();
    try {
        // Блокируем очередь для чтения
        $stmt = $pdo->query("SELECT user_id, rating FROM arena_queue ORDER BY rating DESC FOR UPDATE");
        $players = $stmt->fetchAll();
        if (count($players) < 1) {
            $pdo->rollBack();
            error_log("tryFormTeams: нет игроков в очереди");
            return false;
        }

        // Проверяем, существуют ли все игроки
        $validPlayers = [];
        $invalidIds = [];
        foreach ($players as $p) {
            $user = getUserById($p['user_id']);
            if ($user) {
                $validPlayers[] = $p;
            } else {
                $invalidIds[] = $p['user_id'];
            }
        }

        // Удаляем несуществующих из очереди
        if (!empty($invalidIds)) {
            $placeholders = implode(',', array_fill(0, count($invalidIds), '?'));
            $stmt = $pdo->prepare("DELETE FROM arena_queue WHERE user_id IN ($placeholders)");
            $stmt->execute($invalidIds);
            error_log("tryFormTeams: удалены несуществующие user_id: " . implode(',', $invalidIds));
        }

        if (count($validPlayers) < 1) {
            $pdo->rollBack();
            error_log("tryFormTeams: после фильтрации нет игроков");
            return false;
        }

        // Удаляем всех валидных из очереди
        $ids = array_column($validPlayers, 'user_id');
        $placeholders = implode(',', array_fill(0, count($ids), '?'));
        $stmt = $pdo->prepare("DELETE FROM arena_queue WHERE user_id IN ($placeholders)");
        $stmt->execute($ids);

        // Разделяем на команды (чёт/нечет)
        $team0 = [];
        $team1 = [];
        foreach ($validPlayers as $index => $p) {
            if ($index % 2 == 0) $team0[] = $p;
            else $team1[] = $p;
        }

        error_log("tryFormTeams: команда0=" . count($team0) . ", команда1=" . count($team1));

        $battleId = createArenaBattle($team0, $team1, $pdo);
        if (!$battleId) {
            throw new Exception("createArenaBattle вернул false");
        }

        $pdo->commit();
        error_log("tryFormTeams: бой $battleId успешно создан");
        return $battleId;
    } catch (Exception $e) {
        $pdo->rollBack();
        error_log("tryFormTeams ОШИБКА: " . $e->getMessage());
        return false;
    }
}

// ИСПРАВЛЕННАЯ createArenaBattle (без внутренней транзакции, с поддержкой user_id = NULL для ботов)
function createArenaBattle($team0, $team1, $pdo = null) {
    if ($pdo === null) $pdo = $GLOBALS['pdo']; // для обратной совместимости

    try {
        // Вставляем запись о бое
        $stmt = $pdo->prepare("INSERT INTO arena_battles (status, timeout_at) VALUES ('preparing', DATE_ADD(NOW(), INTERVAL 5 MINUTE))");
        $stmt->execute();
        $battleId = $pdo->lastInsertId();

        $insertParticipant = $pdo->prepare("
            INSERT INTO arena_participants 
            (battle_id, user_id, team, base_health, base_stamina, current_health, current_stamina, is_bot)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?)
        ");

        // Добавляем реальных игроков команды 0
        foreach ($team0 as $p) {
            $user = getUserById($p['user_id']);
            if (!$user) throw new Exception("User not found: " . $p['user_id']);
            $baseHealth = $user['health'] * 2;
            $baseStamina = $user['stamina'] * 2;
            $insertParticipant->execute([
                $battleId, $p['user_id'], 0,
                $baseHealth, $baseStamina, $baseHealth, $baseStamina, 0
            ]);
        }

        // Добавляем реальных игроков команды 1
        foreach ($team1 as $p) {
            $user = getUserById($p['user_id']);
            if (!$user) throw new Exception("User not found: " . $p['user_id']);
            $baseHealth = $user['health'] * 2;
            $baseStamina = $user['stamina'] * 2;
            $insertParticipant->execute([
                $battleId, $p['user_id'], 1,
                $baseHealth, $baseStamina, $baseHealth, $baseStamina, 0
            ]);
        }

        // Добиваем ботами до 4 в каждой команде
        $count0 = count($team0);
        $count1 = count($team1);
        $rating0 = isset($team0[0]['rating']) ? $team0[0]['rating'] : 1000;
        $rating1 = isset($team1[0]['rating']) ? $team1[0]['rating'] : 1000;

        // Вставляем ботов команды 0
        for ($i = $count0; $i < 4; $i++) {
            $bot = generateArenaBot($rating0, 0);
            $insertParticipant->execute([
                $battleId, null, 0,
                $bot['health']*2, $bot['stamina']*2, $bot['health']*2, $bot['stamina']*2, 1
            ]);
            $participantId = $pdo->lastInsertId();
            $stmtUpd = $pdo->prepare("UPDATE arena_participants SET bot_data = ? WHERE id = ?");
            $stmtUpd->execute([json_encode($bot), $participantId]);
        }

        // Вставляем ботов команды 1
        for ($i = $count1; $i < 4; $i++) {
            $bot = generateArenaBot($rating1, 1);
            $insertParticipant->execute([
                $battleId, null, 1,
                $bot['health']*2, $bot['stamina']*2, $bot['health']*2, $bot['stamina']*2, 1
            ]);
            $participantId = $pdo->lastInsertId();
            $stmtUpd = $pdo->prepare("UPDATE arena_participants SET bot_data = ? WHERE id = ?");
            $stmtUpd->execute([json_encode($bot), $participantId]);
        }

        return $battleId;
    } catch (Exception $e) {
        // Пробрасываем исключение дальше, чтобы транзакция откатилась
        throw $e;
    }
}

// Улучшенная генерация ботов (возвращает массив данных)
function generateArenaBot($baseRating, $team) {
    $level = rand(5, 30);
    $base = getBaseStatsForLevel($level);
    // Генерируем виртуальный ID (не используется как внешний ключ)
    $virtualId = -abs(crc32(uniqid(mt_rand(), true)));
    return [
        'virtual_id' => $virtualId,
        'username' => ($team == 0 ? 'Боец' : 'Наёмник') . rand(100,999),
        'level' => $level,
        'attack' => $base['attack'] + rand(-5,15),
        'defense' => $base['defense'] + rand(-3,8),
        'health' => $base['health'] + rand(-10,30),
        'stamina' => $base['stamina'] + rand(-5,10),
        'accuracy' => $base['accuracy'] + rand(-1,3),
        'recovery' => $base['recovery'] + rand(-1,2),
        'defense_percent' => round(($base['defense']+rand(-3,8))/10,1),
        'gender' => rand(0,1) ? 'Мужской' : 'Женский',
        'medkits' => 0,
        'rating' => max(500, $baseRating + rand(-200,200))
    ];
}

function startArenaBattle($battleId) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE arena_battles SET status = 'active', started_at = NOW() WHERE id = ? AND status = 'preparing'");
    return $stmt->execute([$battleId]);
}

// ========== НОВЫЕ ФУНКЦИИ ДЛЯ ВОССТАНОВЛЕНИЯ НА АРЕНЕ ==========

function regenerateInArena($participantId) {
    global $pdo;
    // Получаем данные участника
    $stmt = $pdo->prepare("SELECT * FROM arena_participants WHERE id = ?");
    $stmt->execute([$participantId]);
    $participant = $stmt->fetch();
    if (!$participant) return;

    // Определяем время последнего действия
    $lastAction = $participant['last_action'] ? strtotime($participant['last_action']) : time();
    $now = time();
    $diffSeconds = $now - $lastAction;
    if ($diffSeconds <= 0) return;

    // Получаем recovery
    if ($participant['is_bot']) {
        $botData = json_decode($participant['bot_data'], true) ?: [];
        $recovery = $botData['recovery'] ?? 1;
    } else {
        $user = getUserById($participant['user_id']);
        $recovery = $user['recovery'] ?? 1;
    }

    // Восстановление выносливости: recovery/60 в секунду
    $staminaRegen = round(($recovery / 60) * $diffSeconds);
    $newStamina = min($participant['base_stamina'], $participant['current_stamina'] + $staminaRegen);

    // Восстановление здоровья: recovery/180 в секунду
    $healthRegen = round(($recovery / 180) * $diffSeconds);
    $newHealth = min($participant['base_health'], $participant['current_health'] + $healthRegen);

    // Обновляем, если есть изменения
    if ($newStamina != $participant['current_stamina'] || $newHealth != $participant['current_health']) {
        $stmt = $pdo->prepare("UPDATE arena_participants SET current_stamina = ?, current_health = ?, last_action = NOW() WHERE id = ?");
        $stmt->execute([$newStamina, $newHealth, $participantId]);
    }
}

function regenerateAllParticipants($battleId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT id FROM arena_participants WHERE battle_id = ?");
    $stmt->execute([$battleId]);
    $ids = $stmt->fetchAll(PDO::FETCH_COLUMN);
    foreach ($ids as $pid) {
        regenerateInArena($pid);
    }
}

// ========== ОСНОВНЫЕ ФУНКЦИИ АРЕНЫ (исправленные) ==========

function processArenaAction($userId, $action, $targetId = null) {
    global $pdo;
    $battle = getCurrentArenaBattle($userId);
    if (!$battle) return ['error' => 'Вы не в бою'];

    // Восстанавливаем выносливость и здоровье перед действием
    $stmt = $pdo->prepare("SELECT id FROM arena_participants WHERE user_id = ? AND battle_id = ?");
    $stmt->execute([$userId, $battle['id']]);
    $participantId = $stmt->fetchColumn();
    if ($participantId) {
        regenerateInArena($participantId);
    }

    $participants = getBattleParticipants($battle['id']);
    $attacker = null;
    foreach ($participants as $p) {
        if ($p['user_id'] == $userId) {
            $attacker = $p;
            break;
        }
    }
    if (!$attacker) return ['error' => 'Участник не найден'];
    if ($attacker['current_health'] <= 0) return ['error' => 'Вы мертвы'];

    if ($battle['status'] == 'preparing') {
        startArenaBattle($battle['id']);
        $battle['status'] = 'active';
    }

    if (strtotime($battle['timeout_at']) < time()) {
        finishBattleByTimeout($battle['id']);
        return ['error' => 'Бой закончен по таймауту'];
    }

    switch ($action) {
        case 'attack_random':
            $enemies = [];
            foreach ($participants as $p) {
                if ($p['team'] != $attacker['team'] && $p['current_health'] > 0) {
                    $enemies[] = $p;
                }
            }
            if (empty($enemies)) {
                finishBattle($battle['id'], $attacker['team']);
                return ['error' => 'Бой завершён'];
            }
            $target = $enemies[array_rand($enemies)];
            break;
        case 'attack_target':
            if (!$targetId) return ['error' => 'Цель не указана'];
            $target = null;
            foreach ($participants as $p) {
                if ($p['id'] == $targetId && $p['team'] != $attacker['team'] && $p['current_health'] > 0) {
                    $target = $p;
                    break;
                }
            }
            if (!$target) return ['error' => 'Цель не найдена или мертва'];
            break;
        case 'use_medkit':
            return useMedkitInArena($attacker, $battle, $participants);
        case 'use_ability':
            if (!$targetId) return ['error' => 'Не указано умение'];
            return useAbilityInArena($attacker, $targetId, $battle, $participants);
        default:
            return ['error' => 'Неизвестное действие'];
    }

    return performArenaAttack($attacker, $target, $battle, $participants);
}

function performArenaAttack($attacker, $target, $battle, $participants) {
    global $pdo;

    $attackerEffects = is_array($attacker['active_effects'] ?? null) ? $attacker['active_effects'] : [];
    $targetEffects = is_array($target['active_effects'] ?? null) ? $target['active_effects'] : [];

    // ---- Характеристики атакующего ----
    if ($attacker['is_bot']) {
        $botData = is_array($attacker['bot_data'] ?? null) ? $attacker['bot_data'] : (json_decode($attacker['bot_data'] ?? '{}', true) ?: []);
        $attackVal = $botData['attack'] ?? 10;
        $accuracy = $botData['accuracy'] ?? 5;
        $attackerName = $botData['username'] ?? 'Бот';
    } else {
        $user = getUserById($attacker['user_id']);
        $attackVal = $user['attack'] ?? 10;
        $accuracy = $user['accuracy'] ?? 5;
        $attackerName = $user['username'] ?? 'Игрок';

        $staminaCost = round(3.5 * sqrt($attackVal));
        $pdo->prepare("UPDATE users SET stamina = GREATEST(0, stamina - ?) WHERE id = ?")->execute([$staminaCost, $user['id']]);

        $newStam = max(0, $attacker['current_stamina'] - $staminaCost);
        $pdo->prepare("UPDATE arena_participants SET current_stamina = ? WHERE id = ?")->execute([$newStam, $attacker['id']]);
    }

    $damage = $attackVal;
    $critChance = $accuracy;
    foreach ($attackerEffects as $eff) {
        if (is_array($eff)) {
            if (($eff['type'] ?? '') === 'attack_multiplier') {
                $damage = round($damage * ($eff['value'] ?? 0) / 100);
            }
            if (($eff['type'] ?? '') === 'crit_chance') {
                $critChance += $eff['value'] ?? 0;
            }
        }
    }

    $isCrit = (mt_rand(1, 100) <= $critChance);
    if ($isCrit) $damage = round($damage * 1.5);

    // ---- Характеристики цели ----
    if ($target['is_bot']) {
        $targetData = is_array($target['bot_data'] ?? null) ? $target['bot_data'] : (json_decode($target['bot_data'] ?? '{}', true) ?: []);
        $defPercent = $targetData['defense_percent'] ?? 0;
        $targetName = $targetData['username'] ?? 'Бот';
    } else {
        $defUser = getUserById($target['user_id']);
        $defPercent = $defUser['defense_percent'] ?? 0;
        $targetName = $defUser['username'] ?? 'Игрок';
    }

    $damageBlocked = round($damage * $defPercent / 100);
    foreach ($targetEffects as $eff) {
        if (is_array($eff)) {
            if (($eff['type'] ?? '') === 'defense_percent') {
                $damageBlocked = round($damageBlocked * (1 + ($eff['value'] ?? 0) / 100));
            }
            if (($eff['type'] ?? '') === 'evasion') {
                if (mt_rand(1, 100) <= ($eff['value'] ?? 0)) {
                    $damageBlocked = $damage;
                }
            }
        }
    }
    $damageTaken = max(0, $damage - $damageBlocked);

    $newHp = $target['current_health'] - $damageTaken;
    if ($newHp < 0) $newHp = 0;

    $participationGain = $damage;
    if ($isCrit) $participationGain = round($participationGain * 1.5);

    if (!$attacker['is_bot']) {
        addExp($attacker['user_id'], $damage);
    }

    // ---- Транзакция ----
    $pdo->beginTransaction();
    try {
        $pdo->prepare("UPDATE arena_participants SET current_health = ? WHERE id = ?")->execute([$newHp, $target['id']]);

        if ($newHp <= 0 && !$target['is_bot']) {
            $pdo->prepare("UPDATE arena_participants SET left_at = NOW() WHERE id = ?")->execute([$target['id']]);
        }

        $pdo->prepare("UPDATE arena_participants SET participation = participation + ? WHERE id = ?")->execute([$participationGain, $attacker['id']]);

        $teamScoreField = ($attacker['team'] == 0) ? 'team0_score' : 'team1_score';
        $pdo->prepare("UPDATE arena_battles SET $teamScoreField = $teamScoreField + ? WHERE id = ?")->execute([$participationGain, $battle['id']]);

        $killMsg = ($newHp <= 0) ? " и убивает его!" : "";
        $message = sprintf("%s ударил %s на %d%s %s",
            $attackerName, $targetName, $damageTaken, $killMsg, $isCrit ? '(крит)' : '');
        $pdo->prepare("INSERT INTO arena_battle_log (battle_id, user_id, message) VALUES (?, ?, ?)")
            ->execute([$battle['id'], $attacker['is_bot'] ? null : $attacker['user_id'], $message]);

        $pdo->prepare("UPDATE arena_participants SET last_action = NOW() WHERE id = ?")->execute([$attacker['id']]);
        if (!$target['is_bot']) {
            $pdo->prepare("UPDATE arena_participants SET last_action = NOW() WHERE id = ?")->execute([$target['id']]);
        }

        // Проверяем окончание боя
        $allParticipants = getBattleParticipants($battle['id']);
        $team0Alive = 0; $team1Alive = 0;
        foreach ($allParticipants as $p) {
            if ($p['current_health'] > 0) {
                if ($p['team'] == 0) $team0Alive++; else $team1Alive++;
            }
        }

        if ($team0Alive == 0) {
            finishBattle($battle['id'], 1);
            $pdo->commit();
            return [
                'success' => true,
                'battle_ended' => true,
                'message' => 'Победа команды оранжевых'
            ];
        }
        if ($team1Alive == 0) {
            finishBattle($battle['id'], 0);
            $pdo->commit();
            return [
                'success' => true,
                'battle_ended' => true,
                'message' => 'Победа команды синих'
            ];
        }

        $pdo->commit();
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['error' => 'Ошибка базы данных: ' . $e->getMessage()];
    }

    return [
        'success' => true,
        'damage_dealt' => $damage,
        'damage_taken' => $damageTaken,
        'damage_blocked' => $damageBlocked,
        'is_crit' => $isCrit,
        'target_new_hp' => $newHp,
        'target_killed' => ($newHp == 0),
        'attacker_participation' => $participationGain,
        'message' => $message
    ];
}

function useMedkitInArena($attacker, $battle, $participants) {
    global $pdo;
    if ($attacker['is_bot']) return ['error' => 'Боты не используют аптечки'];

    $user = getUserById($attacker['user_id']);
    if ($user['medkits'] <= 0) return ['error' => 'Нет аптечек'];

    $healAmount = round($attacker['base_health'] * 0.5);
    $newHp = min($attacker['base_health'], $attacker['current_health'] + $healAmount);

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE users SET medkits = medkits - 1 WHERE id = ?");
        $stmt->execute([$user['id']]);

        $stmt = $pdo->prepare("UPDATE arena_participants SET current_health = ?, medkits_used = medkits_used + 1, last_action = NOW() WHERE id = ?");
        $stmt->execute([$newHp, $attacker['id']]);

        $message = $user['username'] . ' использовал аптечку и восстановил ' . $healAmount . ' HP.';
        $stmt = $pdo->prepare("INSERT INTO arena_battle_log (battle_id, user_id, message) VALUES (?, ?, ?)");
        $stmt->execute([$battle['id'], $user['id'], $message]);

        $pdo->commit();
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['error' => 'Ошибка: ' . $e->getMessage()];
    }

    return ['success' => true, 'new_health' => $newHp, 'healed' => $healAmount, 'message' => $message];
}

function useAbilityInArena($attacker, $abilityId, $battle, $participants) {
    global $pdo;
    if ($attacker['is_bot']) return ['error' => 'Боты не используют умения'];

    // Проверка выбранного умения
    $stmt = $pdo->prepare("SELECT 1 FROM arena_selected_abilities WHERE user_id = ? AND ability_id = ?");
    $stmt->execute([$attacker['user_id'], $abilityId]);
    if (!$stmt->fetch()) return ['error' => 'Умение не выбрано для этого боя'];

    // Проверка, не использовано ли уже
    $used = $attacker['used_abilities'] ?? [];
    if (is_string($used)) $used = json_decode($used, true) ?: [];
    if (!is_array($used)) $used = [];
    if (in_array($abilityId, $used)) return ['error' => 'Умение уже использовано в этом бою'];

    // Данные умения
    $stmt = $pdo->prepare("
        SELECT ua.tier_level, a.effect_type, at.effect_value, at.duration_seconds
        FROM user_abilities ua
        JOIN abilities a ON ua.ability_id = a.id
        JOIN ability_tiers at ON ua.ability_id = at.ability_id AND ua.tier_level = at.tier_level
        WHERE ua.user_id = ? AND ua.ability_id = ?
    ");
    $stmt->execute([$attacker['user_id'], $abilityId]);
    $abilityData = $stmt->fetch();
    if (!$abilityData) return ['error' => 'Умение не найдено'];

    // Трата выносливости за умение
    if (!$attacker['is_bot']) {
        $user = getUserById($attacker['user_id']);
        if ($user['level'] > 25) {
            $staminaCost = $user['level'];
            $stmt = $pdo->prepare("UPDATE users SET stamina = GREATEST(0, stamina - ?) WHERE id = ?");
            $stmt->execute([$staminaCost, $user['id']]);
            $newStam = max(0, $attacker['current_stamina'] - $staminaCost);
            $stmt = $pdo->prepare("UPDATE arena_participants SET current_stamina = ? WHERE id = ?");
            $stmt->execute([$newStam, $attacker['id']]);
        }
    }

    // Помечаем как использованное
    $used[] = $abilityId;

    // Формируем эффект
    $effect = [
        'type' => $abilityData['effect_type'],
        'value' => $abilityData['effect_value'],
        'expires_at' => $abilityData['duration_seconds'] > 0 
            ? date('Y-m-d H:i:s', time() + $abilityData['duration_seconds'])
            : null,
        'ability_id' => $abilityId
    ];

    // Текущие активные эффекты (уже массив от getBattleParticipants)
    $currentEffects = $attacker['active_effects'] ?? [];
    if (!is_array($currentEffects)) {
        $currentEffects = json_decode($currentEffects, true) ?: [];
    }
    $currentEffects[] = $effect;

    // Обновляем запись участника
    $stmt = $pdo->prepare("UPDATE arena_participants SET used_abilities = ?, active_effects = ?, last_action = NOW() WHERE id = ?");
    $stmt->execute([json_encode($used), json_encode($currentEffects), $attacker['id']]);

    // Логируем
    $user = getUserById($attacker['user_id']);
    $ability = getAbility($abilityId);
    $abilityName = $ability ? $ability['name'] : 'Неизвестное умение';
    $message = $user['username'] . ' активировал умение "' . $abilityName . '"!';
    $stmt = $pdo->prepare("INSERT INTO arena_battle_log (battle_id, user_id, message) VALUES (?, ?, ?)");
    $stmt->execute([$battle['id'], $user['id'], $message]);

    return ['success' => true, 'message' => $message, 'effect' => $effect];
}

function finishBattle($battleId, $winnerTeam) {
    global $pdo;

    $battle = getArenaBattleById($battleId);
    if (!$battle || $battle['status'] == 'finished') return;

    $participants = getBattleParticipants($battleId);
    $ratingChanges = calculateArenaRatingChanges($participants, $winnerTeam);

    // Начинаем транзакцию только если нет активной
    $needCommit = !$pdo->inTransaction();
    if ($needCommit) $pdo->beginTransaction();

    try {
        $stmt = $pdo->prepare("UPDATE arena_battles SET status = 'finished', winner = ?, finished_at = NOW() WHERE id = ?");
        $stmt->execute([$winnerTeam, $battleId]);

        foreach ($participants as $p) {
            if (!$p['is_bot']) {
                $delta = isset($ratingChanges[$p['user_id']]) ? $ratingChanges[$p['user_id']] : 0;
                if ($delta != 0) {
                    updateArenaRating($p['user_id'], $delta);
                }
                $weeklyDelta = ($p['team'] == $winnerTeam) ? 1 : -1;
                updateWeeklyScore($p['user_id'], $weeklyDelta);

                $today = date('Y-m-d');
                $stmt2 = $pdo->prepare("INSERT INTO arena_daily (user_id, battle_date, battles_count, last_battle_time) 
                                        VALUES (?, ?, 1, NOW()) 
                                        ON DUPLICATE KEY UPDATE battles_count = battles_count + 1, last_battle_time = NOW()");
                $stmt2->execute([$p['user_id'], $today]);

                grantArenaPointsIfEligible($p['user_id']);
            }
        }

        $message = "Бой завершён. Победила команда " . ($winnerTeam == 0 ? 'синих' : 'оранжевых');
        $stmt = $pdo->prepare("INSERT INTO arena_battle_log (battle_id, message) VALUES (?, ?)");
        $stmt->execute([$battleId, $message]);

        if ($needCommit) $pdo->commit();
    } catch (Exception $e) {
        if ($needCommit) $pdo->rollBack();
        error_log("finishBattle error: " . $e->getMessage());
    }
}

function finishBattleByTimeout($battleId) {
    global $pdo;
    $battle = getArenaBattleById($battleId);
    if (!$battle || $battle['status'] != 'active') return;

    $winner = ($battle['team0_score'] > $battle['team1_score']) ? 0 : (($battle['team1_score'] > $battle['team0_score']) ? 1 : null);
    if ($winner === null) {
        $stmt = $pdo->prepare("UPDATE arena_battles SET status = 'timeout', finished_at = NOW() WHERE id = ?");
        $stmt->execute([$battleId]);
        return;
    }
    finishBattle($battleId, $winner);
}

function getArenaBattleById($battleId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM arena_battles WHERE id = ?");
    $stmt->execute([$battleId]);
    return $stmt->fetch();
}

function calculateArenaRatingChanges($participants, $winnerTeam) {
    $changes = [];
    $sumRatingWinners = 0;
    $sumRatingLosers = 0;
    $winnerIds = [];
    $loserIds = [];

    foreach ($participants as $p) {
        if ($p['is_bot']) continue;
        $rating = isset($p['rating']) ? $p['rating'] : 1000;
        if ($p['team'] == $winnerTeam) {
            $sumRatingWinners += $rating;
            $winnerIds[] = $p['user_id'];
        } else {
            $sumRatingLosers += $rating;
            $loserIds[] = $p['user_id'];
        }
    }

    if (count($winnerIds) == 0 || count($loserIds) == 0) return $changes;

    $K = 32;
    $expectedWinner = 1 / (1 + pow(10, ($sumRatingLosers - $sumRatingWinners) / 400));
    $delta = round($K * (1 - $expectedWinner));

    if ($sumRatingWinners - $sumRatingLosers > 400) $delta = 0;

    foreach ($winnerIds as $uid) {
        $changes[$uid] = $delta;
    }
    foreach ($loserIds as $uid) {
        $changes[$uid] = -$delta;
    }

    foreach ($participants as $p) {
        if (!$p['is_bot'] && $p['left_at'] !== null) {
            if ($p['team'] == $winnerTeam) {
                $changes[$p['user_id']] = round($changes[$p['user_id']] / 2);
            } else {
                $changes[$p['user_id']] -= 20;
            }
        }
    }

    return $changes;
}

function getTodayArenaBattlesCount($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT battles_count FROM arena_daily WHERE user_id = ? AND battle_date = ?");
    $stmt->execute([$userId, date('Y-m-d')]);
    $row = $stmt->fetch();
    return $row ? $row['battles_count'] : 0;
}

function grantArenaPointsIfEligible($userId) {
    global $pdo;
    $today = date('Y-m-d');
    $stmt = $pdo->prepare("SELECT battles_count, points_received FROM arena_daily WHERE user_id = ? AND battle_date = ?");
    $stmt->execute([$userId, $today]);
    $row = $stmt->fetch();
    if (!$row || $row['battles_count'] < 5 || $row['points_received']) return false;

    $ratingData = getArenaRating($userId);
    $rating = $ratingData['rating'];
    if ($rating <= 1300) $points = 300;
    elseif ($rating <= 1600) $points = 500;
    elseif ($rating <= 1900) $points = 1000;
    elseif ($rating <= 2200) $points = 1300;
    elseif ($rating <= 2500) $points = 1500;
    else $points = 1600;

    $stmt = $pdo->prepare("SELECT arena_points FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    $currentPoints = $stmt->fetchColumn();
    if ($currentPoints >= 30000) return false;
    $points = min($points, 30000 - $currentPoints);

    if ($ratingData['multiplier_until'] && strtotime($ratingData['multiplier_until']) > time()) {
        $points *= 2;
    }

    $stmt = $pdo->prepare("UPDATE users SET arena_points = arena_points + ? WHERE id = ?");
    $stmt->execute([$points, $userId]);

    $stmt = $pdo->prepare("UPDATE arena_daily SET points_received = 1 WHERE user_id = ? AND battle_date = ?");
    $stmt->execute([$userId, $today]);

    return $points;
}

function weeklyArenaReset() {
    global $pdo;
    $stmt = $pdo->query("
        SELECT user_id, weekly_score 
        FROM arena_rating 
        ORDER BY weekly_score DESC 
        LIMIT 5
    ");
    $top5 = $stmt->fetchAll();

    foreach ($top5 as $row) {
        $uid = $row['user_id'];
        $stmt = $pdo->prepare("UPDATE users SET medkits = medkits + 5 WHERE id = ?");
        $stmt->execute([$uid]);

        $multUntil = date('Y-m-d H:i:s', strtotime('+5 days'));
        $stmt = $pdo->prepare("UPDATE arena_rating SET multiplier_until = ? WHERE user_id = ?");
        $stmt->execute([$multUntil, $uid]);
    }

    $topIds = implode(',', array_column($top5, 'user_id'));
    if ($topIds) {
        $pdo->exec("UPDATE arena_rating SET weekly_score = 0 WHERE user_id IN ($topIds)");
    }
    $pdo->exec("UPDATE arena_rating SET weekly_score = FLOOR(weekly_score / 2) WHERE user_id NOT IN ($topIds) OR $topIds = ''");

    $pdo->exec("UPDATE arena_rating SET weekly_reset_at = NOW()");
}
function safeJsonDecode($json, $default = []) {
    return !empty($json) ? json_decode($json, true) : $default;
}
// ==================== ДЕЙСТВИЯ БОТОВ НА АРЕНЕ ====================

/**
 * Совершить ход одного бота
 */
function botPerformAttack($bot, $target, $battleId) {
    global $pdo;

    $botData = json_decode($bot['bot_data'], true) ?: [];
    $attackVal = $botData['attack'] ?? 10;
    $accuracy = $botData['accuracy'] ?? 5;
    $critChance = $accuracy;
    $damage = $attackVal;
    $isCrit = (mt_rand(1, 100) <= $critChance);
    if ($isCrit) $damage = round($damage * 1.5);

    // Расход выносливости бота
    $staminaCost = round(3.5 * sqrt($attackVal));
    if ($bot['current_stamina'] < $staminaCost) return;
    $newStam = $bot['current_stamina'] - $staminaCost;
    $stmt = $pdo->prepare("UPDATE arena_participants SET current_stamina = ? WHERE id = ?");
    $stmt->execute([$newStam, $bot['id']]);

    if ($target['is_bot']) {
        $targetData = json_decode($target['bot_data'], true) ?: [];
        $defPercent = $targetData['defense_percent'] ?? 0;
        $targetName = $targetData['username'] ?? 'Бот';
    } else {
        $defUser = getUserById($target['user_id']);
        $defPercent = $defUser['defense_percent'] ?? 0;
        $targetName = $defUser['username'] ?? 'Игрок';
    }

    $damageBlocked = round($damage * $defPercent / 100);
    $damageTaken = max(0, $damage - $damageBlocked);
    $newHp = $target['current_health'] - $damageTaken;
    if ($newHp < 0) $newHp = 0;

    $stmt = $pdo->prepare("UPDATE arena_participants SET current_health = ? WHERE id = ?");
    $stmt->execute([$newHp, $target['id']]);

    if ($newHp <= 0 && !$target['is_bot']) {
        $stmt = $pdo->prepare("UPDATE arena_participants SET left_at = NOW() WHERE id = ?");
        $stmt->execute([$target['id']]);
    }

    $botName = $botData['username'] ?? 'Бот';
    $killMsg = ($newHp <= 0) ? " и убивает его!" : "";
    $message = sprintf("%s атакует %s и наносит %d ед. урона%s%s",
        $botName, $targetName, $damageTaken, $killMsg, $isCrit ? ' (крит)' : '');
    $stmt = $pdo->prepare("INSERT INTO arena_battle_log (battle_id, message) VALUES (?, ?)");
    $stmt->execute([$battleId, $message]);
}
/**
 * Ходы всех живых ботов в бою
 */
function processBotTurns($battleId) {
    global $pdo;

    // Получаем всех живых ботов
    $stmt = $pdo->prepare("
        SELECT * FROM arena_participants 
        WHERE battle_id = ? AND is_bot = 1 AND current_health > 0
    ");
    $stmt->execute([$battleId]);
    $bots = $stmt->fetchAll();

    foreach ($bots as $bot) {
        // Ищем случайную живую цель из противоположной команды
        $enemyTeam = 1 - $bot['team'];
        $stmt = $pdo->prepare("
            SELECT * FROM arena_participants 
            WHERE battle_id = ? AND team = ? AND current_health > 0
            ORDER BY RAND() LIMIT 1
        ");
        $stmt->execute([$battleId, $enemyTeam]);
        $target = $stmt->fetch();

        if ($target) {
            botPerformAttack($bot, $target, $battleId);
        }
    }
}
// ========== НОВЫЕ ФОРМУЛЫ ==========
function calculateDefensePercent($defense) {
    if ($defense <= 0) return 0;
    $percent = (0.95 / (1 + 4 * exp(-0.002 * $defense)) - 0.2) * 100;
    $percent = max(0, $percent); // не допускаем отрицательных значений
    return min(80, round($percent, 1));
}
function calculateAccuracy($stamina) {
    if ($stamina <= 0) return 0;
    $accuracy = (0.385 / (1 + 10 * exp(-0.0011 * $stamina)) - 0.035) * 100;
    return max(0, round($accuracy, 1));
}
function regenerateAllStats($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT energy, max_energy, stamina, max_stamina, recovery, last_activity FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    $user = $stmt->fetch();
    if (!$user) return;

    $lastActivity = strtotime($user['last_activity']);
    $now = time();
    $diffSeconds = $now - $lastActivity;
    if ($diffSeconds <= 0) return;

    $updated = false;

    // Энергия (1 в минуту) – оставляем как есть
    $energyRegen = floor($diffSeconds / 60);
    if ($energyRegen > 0) {
        $newEnergy = min($user['energy'] + $energyRegen, $user['max_energy']);
        $stmt = $pdo->prepare("UPDATE users SET energy = ? WHERE id = ?");
        $stmt->execute([$newEnergy, $userId]);
        $updated = true;
    }

    // Выносливость – восстанавливается на 1 каждые 15 секунд
    $staminaRegen = floor($diffSeconds / 15);
    if ($staminaRegen > 0) {
        $newStamina = min($user['stamina'] + $staminaRegen, $user['max_stamina']);
        $stmt = $pdo->prepare("UPDATE users SET stamina = ? WHERE id = ?");
        $stmt->execute([$newStamina, $userId]);
        $updated = true;
    }

    // Обновляем last_activity если была регенерация
    if ($diffSeconds >= 60 || $updated) {
        $stmt = $pdo->prepare("UPDATE users SET last_activity = NOW() WHERE id = ?");
        $stmt->execute([$userId]);
    }
}
// ==================== ФОРУМ ====================

/**
 * Возвращает список всех разделов форума
 */
function getForumSections() {
    global $pdo;
    $stmt = $pdo->query("SELECT * FROM forum_sections ORDER BY position ASC, id ASC");
    return $stmt->fetchAll();
}

/**
 * Получает раздел по ID
 */
function getSectionById($id) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM forum_sections WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

/**
 * Возвращает темы раздела с пагинацией
 */
function getTopicsBySection($sectionId, $page = 1, $perPage = 15) {
    global $pdo;
    $offset = ($page - 1) * $perPage;
    $stmt = $pdo->prepare("
        SELECT t.*, u.username, u.side, u.role, u.last_activity,
               (SELECT COUNT(*) FROM forum_posts WHERE topic_id = t.id) as posts_count
        FROM forum_topics t
        JOIN users u ON t.user_id = u.id
        WHERE t.section_id = ?
        ORDER BY t.is_pinned DESC, t.updated_at DESC
        LIMIT ? OFFSET ?
    ");
    $stmt->bindValue(1, $sectionId, PDO::PARAM_INT);
    $stmt->bindValue(2, $perPage, PDO::PARAM_INT);
    $stmt->bindValue(3, $offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}


/**
 * Общее количество тем в разделе
 */
function getTotalTopicsInSection($sectionId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM forum_topics WHERE section_id = ?");
    $stmt->execute([$sectionId]);
    return $stmt->fetchColumn();
}

/**
 * Получает тему по ID
 */
function getTopicById($id) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT t.*, u.username, u.side, u.role, u.last_activity
        FROM forum_topics t
        JOIN users u ON t.user_id = u.id
        WHERE t.id = ?
    ");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

/**
 * Увеличивает счётчик просмотров темы
 */
function incrementTopicViews($topicId) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE forum_topics SET views = views + 1 WHERE id = ?");
    $stmt->execute([$topicId]);
}

/**
 * Возвращает посты темы с пагинацией
 */
function getPostsByTopic($topicId, $page = 1, $perPage = 10) {
    global $pdo;
    $offset = ($page - 1) * $perPage;
    $stmt = $pdo->prepare("
        SELECT p.*, u.username, u.side, u.role, u.last_activity
        FROM forum_posts p
        JOIN users u ON p.user_id = u.id
        WHERE p.topic_id = :topic_id
        ORDER BY p.created_at ASC
        LIMIT :limit OFFSET :offset
    ");
    $stmt->bindValue(':topic_id', $topicId, PDO::PARAM_INT);
    $stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

/**
 * Общее количество постов в теме
 */
function getTotalPostsInTopic($topicId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM forum_posts WHERE topic_id = ?");
    $stmt->execute([$topicId]);
    return $stmt->fetchColumn();
}

/**
 * Создаёт новую тему
 */
function createTopic($userId, $sectionId, $title, $content) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO forum_topics (user_id, section_id, title, content) VALUES (?, ?, ?, ?)");
    return $stmt->execute([$userId, $sectionId, $title, $content]);
}

/**
 * Создаёт новый пост (комментарий)
 */
function createPost($userId, $topicId, $content) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO forum_posts (user_id, topic_id, content) VALUES (?, ?, ?)");
    return $stmt->execute([$userId, $topicId, $content]);
}

/**
 * Проверяет, закрыта ли тема
 */
function isTopicClosed($topicId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT is_closed FROM forum_topics WHERE id = ?");
    $stmt->execute([$topicId]);
    return (bool)$stmt->fetchColumn();
}

/**
 * Закрыть/открыть тему (только для модераторов и админов)
 */
function setTopicClosed($topicId, $closed) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE forum_topics SET is_closed = ? WHERE id = ?");
    return $stmt->execute([$closed ? 1 : 0, $topicId]);
}

/**
 * Закрепить/открепить тему
 */
function setTopicPinned($topicId, $pinned) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE forum_topics SET is_pinned = ? WHERE id = ?");
    return $stmt->execute([$pinned ? 1 : 0, $topicId]);
}

/**
 * Возвращает список модераторов раздела
 */
function getSectionModerators($sectionId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT u.id, u.username, u.side, u.role, u.last_activity
        FROM forum_moderators fm
        JOIN users u ON fm.user_id = u.id
        WHERE fm.section_id = ?
    ");
    $stmt->execute([$sectionId]);
    return $stmt->fetchAll();
}

/**
 * Проверяет, является ли пользователь модератором раздела (или админом)
 */
function isModerator($userId, $sectionId) {
    global $pdo;
    // Админ может всё
    $user = getUserById($userId);
    if ($user && $user['role'] === 'admin') return true;

    $stmt = $pdo->prepare("SELECT 1 FROM forum_moderators WHERE section_id = ? AND user_id = ?");
    $stmt->execute([$sectionId, $userId]);
    return (bool)$stmt->fetchColumn();
}

/**
 * Проверяет, может ли пользователь создавать тему в разделе
 * (админы могут везде, для остальных учитываем особые правила)
 */
function canCreateTopic($userId, $sectionId) {
    $user = getUserById($userId);
    if (!$user) return false;

    // Админ может всё
    if ($user['role'] === 'admin') return true;

    // Для раздела новостей нужен админ
    $section = getSectionById($sectionId);
    if ($section && $section['title'] === 'Новости') {
        return false;
    }

    return true;
}

/**
 * Возвращает иконку пользователя в зависимости от стороны и онлайна
 */
function getUserForumIcon($user) {
    $side = $user['side'] ?? '';
    $isOnline = (time() - strtotime($user['last_activity'])) < 300;
    $map = [
        'Коза Ностра' => ['on' => 'blue_warrior.png', 'off' => 'blue_warrior_offline.png'],
        'Якудза'      => ['on' => 'red_warrior.png',   'off' => 'red_warrior_offline.png'],
    ];
    $icons = $map[$side] ?? ['on' => 'gray_warrior.png', 'off' => 'gray_warrior_offline.png'];
    return 'images/icons/' . ($isOnline ? $icons['on'] : $icons['off']);
}
// ---------- МАСТЕРСТВО ----------
/**
 * Повышает уровень мастерства игрока на 1, если хватает золота.
 * Стоимость = 1 + 5 * (текущий уровень). Уровень мастерства = mastery (целое число).
 */
function upgradeMastery($userId) {
    global $pdo;
    $user = getUserById($userId);
    if (!$user) return ['success' => false, 'message' => 'Пользователь не найден'];

    $currentLevel = (int)$user['mastery'];
    $cost = 1 + 5 * $currentLevel; // 1, 6, 11, 16, ...

    if ($user['gold'] < $cost) {
        return ['success' => false, 'message' => 'Недостаточно золота'];
    }

    $pdo->beginTransaction();
    try {
        // Списываем золото
        $stmt = $pdo->prepare("UPDATE users SET gold = gold - ? WHERE id = ?");
        $stmt->execute([$cost, $userId]);

        // Увеличиваем мастерство
        $newLevel = $currentLevel + 1;
        $stmt = $pdo->prepare("UPDATE users SET mastery = ? WHERE id = ?");
        $stmt->execute([$newLevel, $userId]);

        // Пересчитываем статы с учётом нового мастерства
        recalcUserStats($userId);

        $pdo->commit();
        return ['success' => true, 'new_level' => $newLevel, 'cost' => $cost];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()];
    }
}

/**
 * Возвращает относительное время (например, "5 мин. назад")
 */
function timeAgo($datetime) {
    if (!$datetime) return 'неизвестно';
    $timestamp = strtotime($datetime);
    $diff = time() - $timestamp;
    if ($diff < 60) return 'только что';
    if ($diff < 3600) return floor($diff / 60) . ' мин. назад';
    if ($diff < 86400) return floor($diff / 3600) . ' ч. назад';
    if ($diff < 2592000) return floor($diff / 86400) . ' дн. назад';
    return date('d.m.Y', $timestamp);
}


// ---------- ЧАТ С КАНАЛАМИ И ПАГИНАЦИЕЙ ----------

/**
 * Получить сообщения чата с пагинацией и учётом канала
 */
function getChatMessagesPaginated($channel = 'general', $page = 1, $perPage = 10) {
    global $pdo;
    $offset = ($page - 1) * $perPage;
    $stmt = $pdo->prepare("
        SELECT cm.*, u.username, u.role, u.side, u.last_activity,
               ru.username as reply_username
        FROM chat_messages cm
        JOIN users u ON cm.user_id = u.id
        LEFT JOIN chat_messages rm ON cm.reply_to = rm.id
        LEFT JOIN users ru ON rm.user_id = ru.id
        WHERE cm.channel = :channel
        ORDER BY cm.created_at DESC
        LIMIT :limit OFFSET :offset
    ");
    $stmt->bindValue(':channel', $channel, PDO::PARAM_STR);
    $stmt->bindValue(':limit', $perPage, PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset, PDO::PARAM_INT);
    $stmt->execute();
    $messages = $stmt->fetchAll();
    // Декодируем смайлики для каждого сообщения
    foreach ($messages as &$msg) {
        $msg['message'] = parseSmiles($msg['message']);
    }
    return $messages;
}

/**
 * Получить общее количество сообщений в канале (для пагинации)
 */
function getTotalMessagesInChannel($channel = 'general') {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM chat_messages WHERE channel = ?");
    $stmt->execute([$channel]);
    return $stmt->fetchColumn();
}

/**
 * Добавить сообщение в чат с указанием канала и ответа
 */
function addChatMessageExtended($userId, $message, $channel = 'general', $replyTo = null, $itemId = null) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO chat_messages (user_id, message, channel, reply_to, item_id) VALUES (?, ?, ?, ?, ?)");
    return $stmt->execute([$userId, $message, $channel, $replyTo, $itemId]);
}

/**
 * Простая замена текстовых смайликов на emoji/изображения
 */
function parseSmiles($text) {
    $smiles = [
        ':)' => '😊',
        ':(' => '😞',
        ':D' => '😃',
        ';)' => '😉',
        ':P' => '😛',
        ':p' => '😛',
        ':O' => '😮',
        'B)' => '😎',
        ':*' => '😘',
        ':*' => '😗',
        '<3' => '❤️',
        ':-)' => '🙂',
        ':-(' => '🙁',
    ];
    return str_replace(array_keys($smiles), array_values($smiles), $text);
}

/**
 * Получить информацию о предмете, прикреплённом к сообщению (для торгового канала)
 */
function getChatMessageItem($itemId) {
    global $pdo;
    if (!$itemId) return null;
    $stmt = $pdo->prepare("
        SELECT ui.*, i.name as item_name, q.name as quality_name, q.color as quality_color,
               u.username as seller_name, mo.price, mo.currency, mo.id as offer_id, mo.status
        FROM market_offers mo
        JOIN user_items ui ON mo.user_item_id = ui.id
        JOIN items i ON ui.item_id = i.id
        JOIN item_qualities q ON ui.quality_id = q.id
        JOIN users u ON mo.user_id = u.id
        WHERE mo.id = ? AND mo.status = 'active'
    ");
    $stmt->execute([$itemId]);
    return $stmt->fetch();
}

/**
 * Создать торговое предложение (выставить предмет на продажу)
 */
function createMarketOffer($userId, $userItemId, $price, $currency = 'gold') {
    global $pdo;
    // Проверяем, что предмет принадлежит пользователю и не надет
    $item = getUserItemById($userItemId);
    if (!$item || $item['user_id'] != $userId || $item['is_equipped']) {
        return ['success' => false, 'message' => 'Предмет не может быть продан'];
    }
    $pdo->beginTransaction();
    try {
        // Помечаем предмет как заблокированный для продажи (нельзя одновременно носить и продавать)
        $stmt = $pdo->prepare("UPDATE user_items SET is_bound = 1 WHERE id = ?");
        $stmt->execute([$userItemId]);

        $stmt = $pdo->prepare("INSERT INTO market_offers (user_id, user_item_id, price, currency) VALUES (?, ?, ?, ?)");
        $stmt->execute([$userId, $userItemId, $price, $currency]);
        $offerId = $pdo->lastInsertId();

        // Создаём сообщение в торговом канале с привязкой к этому предложению
        $messageText = "Продаётся: {$item['item_name']} (качество: {$item['quality_name']}, бонус +{$item['bonus_percent']}%) за {$price} " . ($currency == 'gold' ? 'золота' : 'баксов');
        addChatMessageExtended($userId, $messageText, 'trade', null, $offerId);

        $pdo->commit();
        return ['success' => true, 'offer_id' => $offerId];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Совершить покупку предмета из торгового чата
 */
function buyMarketOffer($offerId, $buyerId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM market_offers WHERE id = ? AND status = 'active'");
    $stmt->execute([$offerId]);
    $offer = $stmt->fetch();
    if (!$offer) return ['success' => false, 'message' => 'Предложение не найдено или уже неактуально'];

    $buyer = getUserById($buyerId);
    $seller = getUserById($offer['user_id']);
    if ($buyer['id'] == $seller['id']) return ['success' => false, 'message' => 'Нельзя купить свой предмет'];

    $price = $offer['price'];
    $currency = $offer['currency'];
    $balance = ($currency == 'gold') ? $buyer['gold'] : $buyer['bucks'];
    if ($balance < $price) return ['success' => false, 'message' => 'Недостаточно средств'];

    $pdo->beginTransaction();
    try {
        // Списываем деньги у покупателя
        $stmt = $pdo->prepare("UPDATE users SET " . ($currency == 'gold' ? 'gold' : 'bucks') . " = " . ($currency == 'gold' ? 'gold' : 'bucks') . " - ? WHERE id = ?");
        $stmt->execute([$price, $buyerId]);

        // Начисляем деньги продавцу
        $stmt = $pdo->prepare("UPDATE users SET " . ($currency == 'gold' ? 'gold' : 'bucks') . " = " . ($currency == 'gold' ? 'gold' : 'bucks') . " + ? WHERE id = ?");
        $stmt->execute([$price, $seller['id']]);

        // Передаём предмет покупателю
        $stmt = $pdo->prepare("UPDATE user_items SET user_id = ? WHERE id = ?");
        $stmt->execute([$buyerId, $offer['user_item_id']]);

        // Помечаем предложение как проданное
        $stmt = $pdo->prepare("UPDATE market_offers SET status = 'sold' WHERE id = ?");
        $stmt->execute([$offerId]);

        // Добавляем системное сообщение в чат о покупке
        $messageText = $buyer['username'] . " купил предмет у " . $seller['username'];
        addChatMessageExtended(0, $messageText, 'trade', null, null); // user_id = 0 для системных

        $pdo->commit();
        return ['success' => true, 'message' => 'Покупка совершена успешно'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Проверить, может ли пользователь редактировать/удалять сообщение (админ или своё)
 */
function canEditMessage($userId, $messageUserId) {
    $user = getUserById($userId);
    return ($userId == $messageUserId || $user['role'] == 'admin' || $user['role'] == 'moderator');
}

function attackTower($userId, $districtId) {
    global $pdo;
    $user = getUserById($userId);
    $stmt = $pdo->prepare("SELECT * FROM district_players WHERE user_id = ?");
    $stmt->execute([$userId]);
    $dp = $stmt->fetch();
    if (!$dp) return ['success' => false, 'message' => 'Вы не в районе'];

    // Проверка выносливости
    $staminaCost = round(3.5 * sqrt($user['attack']));
    if ($dp['current_stamina'] < $staminaCost) {
        return ['success' => false, 'message' => 'Недостаточно выносливости'];
    }

    $damage = $user['attack'];
    $isCrit = false;
    if (mt_rand(1, 100) <= $user['accuracy']) {
        $damage = round($damage * 1.5);
        $isCrit = true;
    }

    // Применяем эффекты атакующего
    $effects = getActiveEffects($userId);
    $damage = applyOffensiveEffects($damage, $effects);

    $pdo->beginTransaction();
    try {
        // Списываем выносливость
        $newStam = $dp['current_stamina'] - $staminaCost;
        $stmt = $pdo->prepare("UPDATE district_players SET current_stamina = ?, last_action = NOW() WHERE user_id = ?");
        $stmt->execute([$newStam, $userId]);

        // Обновляем прогресс района
        ensureDistrictState($districtId);
        $stmt = $pdo->prepare("UPDATE district_state SET progress = progress + ? WHERE district_id = ?");
        $stmt->execute([$damage, $districtId]);

        // Получаем обновлённый прогресс
        $stmt = $pdo->prepare("SELECT progress FROM district_state WHERE district_id = ?");
        $stmt->execute([$districtId]);
        $progress = $stmt->fetchColumn();

        $district = getDistrictById($districtId);
        $captured = false;
        if ($progress >= $district['capture_threshold']) {
            $newOwner = $user['side'];
            $stmt = $pdo->prepare("UPDATE district_state SET controlling_side = ?, progress = 0 WHERE district_id = ?");
            $stmt->execute([$newOwner, $districtId]);
            $stmt = $pdo->prepare("INSERT INTO district_captures (district_id, captured_by_side, captured_by_user_id) VALUES (?, ?, ?)");
            $stmt->execute([$districtId, $newOwner, $userId]);
            $captured = true;
        }

        // Опыт и участие
        addExp($userId, $damage);
        addUserParticipation($userId, $damage);

        // --- ДОБАВЛЯЕМ ЛОГ ---
        $logMessage = sprintf("%s атаковал башню и нанёс %d урона",
            $user['username'],
            $damage
        );
        addDistrictBattleLog($districtId, $userId, null, $logMessage);
        // --- КОНЕЦ ЛОГА ---

        $pdo->commit();

        return [
            'success' => true,
            'message' => "Атака башни нанесла $damage урона",
            'damage' => $damage,
            'is_crit' => $isCrit,
            'new_progress' => $progress,
            'captured' => $captured
        ];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

// ---------- ПРЕМИУМ ----------

/**
 * Возвращает дату окончания премиума или null.
 */
function getPremiumUntil($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT premium_until FROM users WHERE id = ?");
    $stmt->execute([$userId]);
    return $stmt->fetchColumn();
}

/**
 * Проверяет, активен ли премиум у пользователя.
 */
function isPremiumActive($userId) {
    $until = getPremiumUntil($userId);
    return $until && strtotime($until) > time();
}

/**
 * Сумма золота, купленного за последние 3 дня.
 */
function getPurchasedGoldLast3Days($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT COALESCE(SUM(amount), 0) 
        FROM gold_purchases 
        WHERE user_id = ? AND purchased_at >= DATE_SUB(NOW(), INTERVAL 3 DAY)
    ");
    $stmt->execute([$userId]);
    return (int)$stmt->fetchColumn();
}

/**
 * Скидка на премиум в процентах (0–50).
 */
function getPremiumDiscount($userId) {
    $gold = getPurchasedGoldLast3Days($userId);
    return min(50, $gold);
}

/**
 * Активировать / продлить премиум на указанное количество дней.
 * Списывает золото с учётом скидки.
 * Возвращает ['success' => true/false, 'message' => ...]
 */
function activatePremium($userId, $days) {
    global $pdo;

    $prices = [1 => 15, 3 => 35, 7 => 60, 30 => 110]; // базовая цена за дни
    if (!isset($prices[$days])) {
        return ['success' => false, 'message' => 'Недопустимый срок премиума.'];
    }

    $basePrice = $prices[$days];
    $discount = getPremiumDiscount($userId);
    $finalPrice = (int)round($basePrice * (100 - $discount) / 100);
    if ($finalPrice < 1) $finalPrice = 1; // минимум 1 золото

    $user = getUserById($userId);
    if ($user['gold'] < $finalPrice) {
        return ['success' => false, 'message' => 'Недостаточно золота.'];
    }

    $pdo->beginTransaction();
    try {
        // Списываем золото
        $stmt = $pdo->prepare("UPDATE users SET gold = gold - ? WHERE id = ?");
        $stmt->execute([$finalPrice, $userId]);

        // Устанавливаем/продлеваем премиум
        $now = time();
        $currentUntil = getPremiumUntil($userId);
        $start = $currentUntil && strtotime($currentUntil) > $now ? strtotime($currentUntil) : $now;
        $newUntil = date('Y-m-d H:i:s', $start + $days * 86400);

        $stmt = $pdo->prepare("UPDATE users SET premium_until = ? WHERE id = ?");
        $stmt->execute([$newUntil, $userId]);

        // Пересчитываем статы, чтобы применить +1% мастерства
        recalcUserStats($userId);

        $pdo->commit();
        return ['success' => true, 'message' => "Премиум активирован до $newUntil"];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Логирует покупку золота (вызывать при каждом пополнении).
 */
function logGoldPurchase($userId, $amount) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO gold_purchases (user_id, amount) VALUES (?, ?)");
    $stmt->execute([$userId, $amount]);
}
// ---------- ПРЕМИУМ (ДОПОЛНЕНИЯ) ----------

/**
 * Возвращает количество полных дней до окончания премиума (или 0, если не активен).
 */
function getPremiumRemainingDays($userId) {
    $until = getPremiumUntil($userId);
    if (!$until) return 0;
    $diff = strtotime($until) - time();
    if ($diff <= 0) return 0;
    return floor($diff / 86400);
}

/**
 * Склонение существительных после числительных.
 */
function plural($n, $form1, $form2, $form5) {
    $n = abs($n) % 100;
    $n1 = $n % 10;
    if ($n > 10 && $n < 20) return $form5;
    if ($n1 > 1 && $n1 < 5) return $form2;
    if ($n1 == 1) return $form1;
    return $form5;
}
// ==================== ПОЧТА (ЛИЧНЫЕ СООБЩЕНИЯ) ====================

/**
 * Отправить письмо.
 * Проверяет, что отправитель и получатель на одной стороне.
 * from_user_id = 0 – системное сообщение (без проверки стороны).
 * Возвращает true при успехе, иначе false.
 */
function sendMail($fromUserId, $toUserId, $subject, $body, $attachmentType = null, $attachmentValue = null) {
    global $pdo;

    // Проверка существования получателя
    $toUser = getUserById($toUserId);
    if (!$toUser) return false;

    // Если отправитель не система (id != 0), проверяем сторону
    if ($fromUserId != 0) {
        $fromUser = getUserById($fromUserId);
        if (!$fromUser) return false;
        if ($fromUser['side'] !== $toUser['side']) {
            return false; // нельзя писать на другую сторону
        }
    }

    $stmt = $pdo->prepare("
        INSERT INTO mail_messages 
        (from_user_id, to_user_id, subject, body, sent_at, attachment_type, attachment_value)
        VALUES (?, ?, ?, ?, NOW(), ?, ?)
    ");
    return $stmt->execute([$fromUserId, $toUserId, $subject, $body, $attachmentType, $attachmentValue]);
}

/**
 * Получить входящие сообщения (с пагинацией).
 */
function getInbox($userId, $page = 1, $perPage = 10) {
    global $pdo;
    $offset = ($page - 1) * $perPage;
    $stmt = $pdo->prepare("
        SELECT m.*, u.username as from_username, u.side as from_side
        FROM mail_messages m
        LEFT JOIN users u ON m.from_user_id = u.id
        WHERE m.to_user_id = ? AND m.is_deleted_by_recipient = 0
        ORDER BY m.sent_at DESC
        LIMIT ? OFFSET ?
    ");
    $stmt->bindValue(1, $userId, PDO::PARAM_INT);
    $stmt->bindValue(2, $perPage, PDO::PARAM_INT);
    $stmt->bindValue(3, $offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

/**
 * Получить исходящие сообщения.
 */
function getOutbox($userId, $page = 1, $perPage = 10) {
    global $pdo;
    $offset = ($page - 1) * $perPage;
    $stmt = $pdo->prepare("
        SELECT m.*, u.username as to_username, u.side as to_side
        FROM mail_messages m
        LEFT JOIN users u ON m.to_user_id = u.id
        WHERE m.from_user_id = ? AND m.is_deleted_by_sender = 0
        ORDER BY m.sent_at DESC
        LIMIT ? OFFSET ?
    ");
    $stmt->bindValue(1, $userId, PDO::PARAM_INT);
    $stmt->bindValue(2, $perPage, PDO::PARAM_INT);
    $stmt->bindValue(3, $offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

/**
 * Получить количество непрочитанных сообщений.
 */
function getUnreadMailCount($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM mail_messages WHERE to_user_id = ? AND is_read = 0 AND is_deleted_by_recipient = 0");
    $stmt->execute([$userId]);
    return $stmt->fetchColumn();
}

/**
 * Получить одно письмо по ID (проверяет, что письмо принадлежит пользователю).
 * Помечает как прочитанное, если получатель – текущий пользователь.
 */
function getMail($mailId, $userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT m.*, 
               fu.username as from_username, fu.side as from_side,
               tu.username as to_username, tu.side as to_side
        FROM mail_messages m
        LEFT JOIN users fu ON m.from_user_id = fu.id
        LEFT JOIN users tu ON m.to_user_id = tu.id
        WHERE m.id = ? AND (m.to_user_id = ? OR m.from_user_id = ?)
    ");
    $stmt->execute([$mailId, $userId, $userId]);
    $mail = $stmt->fetch();
    if ($mail && $mail['to_user_id'] == $userId && !$mail['is_read']) {
        // Помечаем как прочитанное
        $stmt2 = $pdo->prepare("UPDATE mail_messages SET is_read = 1 WHERE id = ?");
        $stmt2->execute([$mailId]);
        $mail['is_read'] = 1;
    }
    return $mail;
}

/**
 * Удалить письмо (мягкое удаление для одной из сторон).
 */
function deleteMail($mailId, $userId, $asSender = true) {
    global $pdo;
    $field = $asSender ? 'is_deleted_by_sender' : 'is_deleted_by_recipient';
    $stmt = $pdo->prepare("
        UPDATE mail_messages 
        SET $field = 1 
        WHERE id = ? AND (".($asSender ? "from_user_id" : "to_user_id")." = ?)
    ");
    return $stmt->execute([$mailId, $userId]);
}

/**
 * Забрать вложение из письма.
 * Возвращает true, если вложение успешно получено.
 */
function claimAttachment($mailId, $userId) {
    global $pdo;

    $mail = getMail($mailId, $userId);
    if (!$mail || $mail['to_user_id'] != $userId) return false;
    if ($mail['attachment_type'] === null || $mail['is_claimed']) return false;

    $user = getUserById($userId);
    if (!$user) return false;

    $pdo->beginTransaction();
    try {
        switch ($mail['attachment_type']) {
            case 'gold':
                $stmt = $pdo->prepare("UPDATE users SET gold = gold + ? WHERE id = ?");
                $stmt->execute([$mail['attachment_value'], $userId]);
                break;
            case 'bucks':
                $stmt = $pdo->prepare("UPDATE users SET bucks = bucks + ? WHERE id = ?");
                $stmt->execute([$mail['attachment_value'], $userId]);
                break;
            case 'medkits':
                $stmt = $pdo->prepare("UPDATE users SET medkits = medkits + ? WHERE id = ?");
                $stmt->execute([$mail['attachment_value'], $userId]);
                break;
            case 'item':
                // Предполагаем, что attachment_value = item_id
                // Создаём запись в user_items для пользователя
                $stmt = $pdo->prepare("INSERT INTO user_items (user_id, item_id, quality_id, bonus_percent, is_equipped, is_bound, created_at) VALUES (?, ?, 1, 0, 0, 0, NOW())");
                $stmt->execute([$userId, $mail['attachment_value']]);
                break;
            default:
                $pdo->rollBack();
                return false;
        }

        $stmt = $pdo->prepare("UPDATE mail_messages SET is_claimed = 1 WHERE id = ?");
        $stmt->execute([$mailId]);

        $pdo->commit();
        return true;
    } catch (Exception $e) {
        $pdo->rollBack();
        return false;
    }
}
// ==================== ПОЧТА: ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ ====================

/**
 * Отметить все входящие как прочитанные.
 */
function markAllIncomingAsRead($userId) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE mail_messages SET is_read = 1 WHERE to_user_id = ? AND is_read = 0 AND is_deleted_by_recipient = 0");
    return $stmt->execute([$userId]);
}

/**
 * Удалить все прочитанные входящие сообщения.
 */
function deleteAllReadIncoming($userId) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE mail_messages SET is_deleted_by_recipient = 1 WHERE to_user_id = ? AND is_read = 1 AND is_deleted_by_recipient = 0");
    return $stmt->execute([$userId]);
}

/**
 * Получить все сообщения (входящие и исходящие) для пользователя.
 */
function getAllMail($userId, $page = 1, $perPage = 10) {
    global $pdo;
    $offset = ($page - 1) * $perPage;
    $stmt = $pdo->prepare("
        (SELECT m.*, u.username as other_username, u.side as other_side, 'incoming' as direction
         FROM mail_messages m
         LEFT JOIN users u ON m.from_user_id = u.id
         WHERE m.to_user_id = ? AND m.is_deleted_by_recipient = 0)
        UNION ALL
        (SELECT m.*, u.username as other_username, u.side as other_side, 'outgoing' as direction
         FROM mail_messages m
         LEFT JOIN users u ON m.to_user_id = u.id
         WHERE m.from_user_id = ? AND m.is_deleted_by_sender = 0)
        ORDER BY sent_at DESC
        LIMIT ? OFFSET ?
    ");
    $stmt->bindValue(1, $userId, PDO::PARAM_INT);
    $stmt->bindValue(2, $userId, PDO::PARAM_INT);
    $stmt->bindValue(3, $perPage, PDO::PARAM_INT);
    $stmt->bindValue(4, $offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

/**
 * Получить только непрочитанные входящие.
 */
function getUnreadMail($userId, $page = 1, $perPage = 10) {
    global $pdo;
    $offset = ($page - 1) * $perPage;
    $stmt = $pdo->prepare("
        SELECT m.*, u.username as from_username, u.side as from_side
        FROM mail_messages m
        LEFT JOIN users u ON m.from_user_id = u.id
        WHERE m.to_user_id = ? AND m.is_read = 0 AND m.is_deleted_by_recipient = 0
        ORDER BY m.sent_at DESC
        LIMIT ? OFFSET ?
    ");
    $stmt->bindValue(1, $userId, PDO::PARAM_INT);
    $stmt->bindValue(2, $perPage, PDO::PARAM_INT);
    $stmt->bindValue(3, $offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

/**
 * Количество всех сообщений (для пагинации в режиме "Все").
 */
function countAllMail($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT COUNT(*) FROM (
            SELECT id FROM mail_messages WHERE to_user_id = ? AND is_deleted_by_recipient = 0
            UNION ALL
            SELECT id FROM mail_messages WHERE from_user_id = ? AND is_deleted_by_sender = 0
        ) as t
    ");
    $stmt->execute([$userId, $userId]);
    return $stmt->fetchColumn();
}
// ==================== ПОЧТА: ДОПОЛНИТЕЛЬНЫЕ ФУНКЦИИ ====================

/**
 * Переключить пометку письма (звездочка)
 */
function toggleFlagged($mailId, $userId) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE mail_messages SET is_flagged = NOT is_flagged WHERE id = ? AND to_user_id = ?");
    return $stmt->execute([$mailId, $userId]);
}

/**
 * Получить всю переписку между двумя пользователями
 */
function getConversation($user1Id, $user2Id, $limit = 100) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT m.*, 
               u_from.username as from_username, u_from.side as from_side, u_from.level as from_level,
               u_to.username as to_username, u_to.side as to_side
        FROM mail_messages m
        LEFT JOIN users u_from ON m.from_user_id = u_from.id
        LEFT JOIN users u_to ON m.to_user_id = u_to.id
        WHERE (m.from_user_id = ? AND m.to_user_id = ?) OR (m.from_user_id = ? AND m.to_user_id = ?)
        ORDER BY m.sent_at ASC
        LIMIT ?
    ");
    $stmt->bindValue(1, $user1Id, PDO::PARAM_INT);
    $stmt->bindValue(2, $user2Id, PDO::PARAM_INT);
    $stmt->bindValue(3, $user2Id, PDO::PARAM_INT);
    $stmt->bindValue(4, $user1Id, PDO::PARAM_INT);
    $stmt->bindValue(5, $limit, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}
// ==================== ПЛАТЕЖИ ====================

/**
 * Создаёт запись о платеже
 */
function createPayment($userId, $packageId, $amountGold, $price, $invoiceId = null) {
    global $pdo;
    $stmt = $pdo->prepare("
        INSERT INTO payments (user_id, package_id, amount_gold, price, invoice_id, status)
        VALUES (?, ?, ?, ?, ?, 'pending')
    ");
    return $stmt->execute([$userId, $packageId, $amountGold, $price, $invoiceId]);
}

/**
 * Обновляет статус платежа по invoice_id
 */
function updatePaymentStatus($invoiceId, $status, $transactionId = null) {
    global $pdo;
    $sql = "UPDATE payments SET status = ? WHERE invoice_id = ?";
    $params = [$status, $invoiceId];
    if ($transactionId !== null) {
        $sql = "UPDATE payments SET status = ?, transaction_id = ? WHERE invoice_id = ?";
        $params = [$status, $transactionId, $invoiceId];
    }
    $stmt = $pdo->prepare($sql);
    return $stmt->execute($params);
}

/**
 * Получает платеж по invoice_id
 */
function getPaymentByInvoiceId($invoiceId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM payments WHERE invoice_id = ?");
    $stmt->execute([$invoiceId]);
    return $stmt->fetch();
}

/**
 * Получает платежи с фильтром и пагинацией
 */
function getPayments($status = null, $page = 1, $perPage = 20) {
    global $pdo;
    $offset = ($page - 1) * $perPage;

    $sql = "SELECT p.*, u.username FROM payments p JOIN users u ON p.user_id = u.id";
    if ($status) {
        $sql .= " WHERE p.status = :status";
    }
    $sql .= " ORDER BY p.created_at DESC LIMIT :limit OFFSET :offset";

    $stmt = $pdo->prepare($sql);
    $stmt->bindValue(':limit', (int)$perPage, PDO::PARAM_INT);
    $stmt->bindValue(':offset', (int)$offset, PDO::PARAM_INT);
    if ($status) {
        $stmt->bindValue(':status', $status, PDO::PARAM_STR);
    }
    $stmt->execute();
    return $stmt->fetchAll();
}
/**
 * Количество платежей по фильтру
 */
function countPayments($status = null) {
    global $pdo;
    $sql = "SELECT COUNT(*) FROM payments";
    $params = [];
    if ($status) {
        $sql .= " WHERE status = ?";
        $params[] = $status;
    }
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchColumn();
}
/**
 * Отправляет письмо с вложением золота после успешной покупки
 */
function sendGoldPurchaseMail($userId, $goldAmount) {
    $subject = "Пополнение золота";
    $body = "Вы успешно пополнили золото. Чтобы получить его, заберите посылку во вложении.";
    return sendMail(2, $userId, $subject, $body, 'gold', $goldAmount);
}
// ========== НОВЫЙ БЛОК: ИНФОРМАЦИЯ О ПОЛЬЗОВАТЕЛЕ И МОДЕРАЦИЯ ==========

/**
 * Детальная информация по IP и User-Agent (гео + определение ОС/браузера)
 */
function getDetailedInfo($ip, $userAgent) {
    if (empty($ip) || empty($userAgent)) {
        return false;
    }

    // Получаем гео-данные через ipwho.is
    $url = 'https://ipwho.is/' . $ip . '?fields=success,ip,country,region,city,flag.img,connection.org,timezone,latitude,longitude&lang=ru';
    $geoJson = @file_get_contents($url);
    $geo = $geoJson ? json_decode($geoJson, true) : null;

    if (!$geo || !isset($geo['success']) || $geo['success'] !== true) {
        // если сервис недоступен, возвращаем минимум
        $geo = [
            'ip' => $ip,
            'country' => 'Не определено',
            'region' => 'Не определено',
            'city' => 'Не определено',
            'flag' => ['img' => ''],
            'connection' => ['org' => 'Не определено'],
            'timezone' => ['id' => 'Не определено'],
            'latitude' => 0,
            'longitude' => 0
        ];
    }

    // Определение ОС
    $osPlatform = 'Не определена';
    $osArray = [
        '/windows nt 10/i'      => 'Windows 10',
        '/windows nt 6.3/i'     => 'Windows 8.1',
        '/windows nt 6.2/i'     => 'Windows 8',
        '/windows nt 6.1/i'     => 'Windows 7',
        '/windows nt 6.0/i'     => 'Windows Vista',
        '/windows nt 5.2/i'     => 'Windows Server 2003/XP x64',
        '/windows nt 5.1/i'     => 'Windows XP',
        '/windows xp/i'         => 'Windows XP',
        '/windows nt 5.0/i'     => 'Windows 2000',
        '/windows me/i'         => 'Windows ME',
        '/win98/i'              => 'Windows 98',
        '/win95/i'              => 'Windows 95',
        '/win16/i'              => 'Windows 3.11',
        '/macintosh|mac os x/i' => 'Mac OS X',
        '/mac_powerpc/i'        => 'Mac OS 9',
        '/linux/i'              => 'Linux',
        '/ubuntu/i'             => 'Ubuntu',
        '/iphone/i'             => 'iPhone',
        '/ipod/i'               => 'iPod',
        '/ipad/i'               => 'iPad',
        '/android/i'            => 'Android',
        '/blackberry/i'         => 'BlackBerry',
        '/webos/i'              => 'Mobile'
    ];
    foreach ($osArray as $pattern => $name) {
        if (preg_match($pattern, $userAgent)) {
            $osPlatform = $name;
            break;
        }
    }

    // Определение браузера
    $browser = 'Не определен';
    $browserArray = [
        '/msie/i'      => 'Internet Explorer',
        '/firefox/i'   => 'Firefox',
        '/safari/i'    => 'Safari',
        '/chrome/i'    => 'Chrome',
        '/edge/i'      => 'Edge',
        '/opera/i'     => 'Opera',
        '/netscape/i'  => 'Netscape',
        '/maxthon/i'   => 'Maxthon',
        '/konqueror/i' => 'Konqueror',
        '/mobile/i'    => 'Handheld Browser'
    ];
    foreach ($browserArray as $pattern => $name) {
        if (preg_match($pattern, $userAgent)) {
            $browser = $name;
            break;
        }
    }

    // Определение устройства
    $device = 'ПК';
    if (preg_match('/(tablet|ipad|playbook)|(android(?!.*mobile))/i', $userAgent)) {
        $device = 'Планшет';
    } elseif (preg_match('/(mobile|phone)/i', $userAgent)) {
        $device = 'Смартфон';
    } elseif (preg_match('/(bot|googlebot|crawler)/i', $userAgent)) {
        $device = 'Бот';
    }

    return [
        'ip'        => $geo['ip'] ?? $ip,
        'country'   => $geo['country'] ?? 'Не определено',
        'flag'      => $geo['flag']['img'] ?? '',
        'region'    => $geo['region'] ?? 'Не определено',
        'city'      => $geo['city'] ?? 'Не определено',
        'operator'  => $geo['connection']['org'] ?? 'Не определено',
        'timezone'  => $geo['timezone']['id'] ?? 'Не определено',
        'latitude'  => $geo['latitude'] ?? 0,
        'longitude' => $geo['longitude'] ?? 0,
        'agent'     => $userAgent,
        'os'        => $osPlatform,
        'browser'   => $browser,
        'device'    => $device,
    ];
}

/**
 * Поиск аккаунтов с одинаковым IP (исключая текущего пользователя)
 */
function getUsersByIP($ip, $excludeUserId = null) {
    global $pdo;
    $sql = "SELECT id, username, last_login, role FROM users WHERE ip = ?";
    $params = [$ip];
    if ($excludeUserId) {
        $sql .= " AND id != ?";
        $params[] = $excludeUserId;
    }
    $sql .= " ORDER BY last_login DESC";
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchAll();
}

/**
 * Админская выдача премиума (бесплатно)
 */
function givePremiumByAdmin($adminId, $targetUserId, $days) {
    global $pdo;

    $admin = getUserById($adminId);
    if (!$admin || ($admin['role'] !== 'admin' && $admin['role'] !== 'moderator')) {
        return ['success' => false, 'message' => 'Недостаточно прав'];
    }

    if (!in_array($days, [1,3,7,30])) {
        return ['success' => false, 'message' => 'Недопустимый срок'];
    }

    $currentUntil = getPremiumUntil($targetUserId);
    $now = time();
    $start = ($currentUntil && strtotime($currentUntil) > $now) ? strtotime($currentUntil) : $now;
    $newUntil = date('Y-m-d H:i:s', $start + $days * 86400);

    $stmt = $pdo->prepare("UPDATE users SET premium_until = ? WHERE id = ?");
    $stmt->execute([$newUntil, $targetUserId]);

    // Пересчёт статов (премиум даёт +1% мастерства)
    recalcUserStats($targetUserId);

    return ['success' => true, 'message' => "Премиум выдан до $newUntil"];
}

/**
 * Бан / разбан пользователя (для админов и модераторов)
 */
function setUserBan($adminId, $targetUserId, $action, $reason = '', $durationHours = 0) {
    global $pdo;

    $admin = getUserById($adminId);
    if (!$admin || ($admin['role'] !== 'admin' && $admin['role'] !== 'moderator')) {
        return ['success' => false, 'message' => 'Недостаточно прав'];
    }

    $target = getUserById($targetUserId);
    if (!$target) {
        return ['success' => false, 'message' => 'Пользователь не найден'];
    }

    // Нельзя банить админа модератором
    if ($admin['role'] === 'moderator' && $target['role'] === 'admin') {
        return ['success' => false, 'message' => 'Нельзя забанить администратора'];
    }

    $pdo->beginTransaction();
    try {
        if ($action === 'ban') {
            $bannedUntil = $durationHours > 0 ? date('Y-m-d H:i:s', time() + $durationHours * 3600) : null;
            $stmt = $pdo->prepare("UPDATE users SET is_banned = 1, ban_reason = ?, banned_at = NOW(), banned_until = ? WHERE id = ?");
            $stmt->execute([$reason, $bannedUntil, $targetUserId]);
            addBanHistory($targetUserId, $adminId, 'ban', $reason, $bannedUntil, null);
        } elseif ($action === 'unban') {
            $stmt = $pdo->prepare("UPDATE users SET is_banned = 0, ban_reason = NULL, banned_at = NULL, banned_until = NULL WHERE id = ?");
            $stmt->execute([$targetUserId]);
            addBanHistory($targetUserId, $adminId, 'unban', $reason, null, null);
        } elseif ($action === 'mute') {
            $mutedUntil = $durationHours > 0 ? date('Y-m-d H:i:s', time() + $durationHours * 3600) : null;
            $stmt = $pdo->prepare("UPDATE users SET is_muted = 1, muted_until = ? WHERE id = ?");
            $stmt->execute([$mutedUntil, $targetUserId]);
            addBanHistory($targetUserId, $adminId, 'mute', $reason, null, $mutedUntil);
        } elseif ($action === 'unmute') {
            $stmt = $pdo->prepare("UPDATE users SET is_muted = 0, muted_until = NULL WHERE id = ?");
            $stmt->execute([$targetUserId]);
            addBanHistory($targetUserId, $adminId, 'unmute', $reason, null, null);
        }
        $pdo->commit();
        return ['success' => true, 'message' => 'Операция выполнена'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}
function getRoleDisplay($role) {
    $map = [
        'player'    => 'игрок',
        'moderator' => 'Модератор',
        'support'   => 'Служба поддержки',
        'admin'     => 'Администратор'
    ];
    return $map[$role] ?? $role;
}
// ==================== ТАЛАНТЫ ====================

/**
 * Получить список всех талантов
 */
function getAllTalents() {
    global $pdo;
    $stmt = $pdo->query("SELECT * FROM talents ORDER BY sort_order ASC");
    return $stmt->fetchAll();
}

/**
 * Получить талант по ID
 */
function getTalentById($id) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM talents WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

/**
 * Получить уровень таланта у пользователя (0, если не изучал)
 */
function getUserTalentLevel($userId, $talentId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT level FROM user_talents WHERE user_id = ? AND talent_id = ?");
    $stmt->execute([$userId, $talentId]);
    $row = $stmt->fetch();
    return $row ? (int)$row['level'] : 0;
}

/**
 * Установить уровень таланта (вставить или обновить)
 */
function setUserTalentLevel($userId, $talentId, $level) {
    global $pdo;
    $stmt = $pdo->prepare("REPLACE INTO user_talents (user_id, talent_id, level) VALUES (?, ?, ?)");
    return $stmt->execute([$userId, $talentId, $level]);
}

/**
 * Проверить, может ли пользователь повысить талант
 */
function canUpgradeTalent($userId, $talentId) {
    global $pdo;
    $user = getUserById($userId);
    if (!$user) return false;
    
    $talent = getTalentById($talentId);
    if (!$talent) return false;
    
    $currentLevel = getUserTalentLevel($userId, $talentId);
    if ($currentLevel >= $talent['max_level']) return false;
    
    $availablePoints = $user['talent_points_total'] - $user['talent_points_spent'];
    return $availablePoints > 0;
}

/**
 * Повысить уровень таланта (списать очко, увеличить уровень)
 */
function upgradeTalent($userId, $talentId) {
    global $pdo;
    
    if (!canUpgradeTalent($userId, $talentId)) {
        return ['success' => false, 'message' => 'Недостаточно очков или достигнут максимум.'];
    }
    
    $pdo->beginTransaction();
    try {
        $currentLevel = getUserTalentLevel($userId, $talentId);
        $newLevel = $currentLevel + 1;
        
        // Обновить уровень таланта
        setUserTalentLevel($userId, $talentId, $newLevel);
        
        // Увеличить потраченные очки
        $stmt = $pdo->prepare("UPDATE users SET talent_points_spent = talent_points_spent + 1 WHERE id = ?");
        $stmt->execute([$userId]);
        
        $pdo->commit();
        return ['success' => true, 'message' => 'Талант повышен.', 'new_level' => $newLevel];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Получить стоимость следующего очка таланта за баксы
 */
function getBucksPointCost($userId) {
    $user = getUserById($userId);
    if (!$user) return 999999;
    $bought = (int)$user['bucks_points_bought'];
    // Прогрессия: 2000, 5000, 8000, 11000, 14000 ...
    return 2000 + $bought * 3000;
}

/**
 * Получить стоимость следующего очка таланта за золото
 */
function getGoldPointCost($userId) {
    $user = getUserById($userId);
    if (!$user) return 999999;
    $bought = (int)$user['gold_points_bought'];
    // Прогрессия: 1, 5, 10, 15, 20 ... (можно настроить)
    return 1 + $bought * 4; // 1,5,9,13,17...
}

/**
 * Купить очко таланта за баксы
 */
function buyTalentPointWithBucks($userId) {
    global $pdo;
    $user = getUserById($userId);
    if (!$user) return ['success' => false, 'message' => 'Пользователь не найден.'];
    
    $cost = getBucksPointCost($userId);
    if ($user['bucks'] < $cost) {
        return ['success' => false, 'message' => 'Недостаточно баксов.'];
    }
    
    $pdo->beginTransaction();
    try {
        // Списать баксы
        $stmt = $pdo->prepare("UPDATE users SET bucks = bucks - ? WHERE id = ?");
        $stmt->execute([$cost, $userId]);
        
        // Увеличить счётчик купленных за баксы и общее количество очков
        $stmt = $pdo->prepare("UPDATE users SET bucks_points_bought = bucks_points_bought + 1, talent_points_total = talent_points_total + 1 WHERE id = ?");
        $stmt->execute([$userId]);
        
        $pdo->commit();
        return ['success' => true, 'message' => 'Очко таланта куплено за баксы.'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Купить очко таланта за золото
 */
function buyTalentPointWithGold($userId) {
    global $pdo;
    $user = getUserById($userId);
    if (!$user) return ['success' => false, 'message' => 'Пользователь не найден.'];
    
    $cost = getGoldPointCost($userId);
    if ($user['gold'] < $cost) {
        return ['success' => false, 'message' => 'Недостаточно золота.'];
    }
    
    $pdo->beginTransaction();
    try {
        // Списать золото
        $stmt = $pdo->prepare("UPDATE users SET gold = gold - ? WHERE id = ?");
        $stmt->execute([$cost, $userId]);
        
        // Увеличить счётчик купленных за золото и общее количество очков
        $stmt = $pdo->prepare("UPDATE users SET gold_points_bought = gold_points_bought + 1, talent_points_total = talent_points_total + 1 WHERE id = ?");
        $stmt->execute([$userId]);
        
        $pdo->commit();
        return ['success' => true, 'message' => 'Очко таланта куплено за золото.'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Сбросить все таланты пользователя (вернуть очки)
 */
function resetAllTalents($userId) {
    global $pdo;
    $pdo->beginTransaction();
    try {
        // Получить текущее количество потраченных очков
        $user = getUserById($userId);
        $spent = (int)$user['talent_points_spent'];
        
        // Удалить все записи о талантах пользователя
        $stmt = $pdo->prepare("DELETE FROM user_talents WHERE user_id = ?");
        $stmt->execute([$userId]);
        
        // Вернуть потраченные очки в доступные (уменьшить потраченные, увеличить общее количество не нужно, т.к. они уже были в total)
        $stmt = $pdo->prepare("UPDATE users SET talent_points_spent = 0 WHERE id = ?");
        $stmt->execute([$userId]);
        
        $pdo->commit();
        return ['success' => true, 'message' => 'Все таланты сброшены.'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Получить описание эффекта таланта для указанного уровня
 */
function getTalentEffectDescription($talent, $level) {
    if ($level == 0) return '';
    $value = $talent['base_value'] + ($level - 1) * $talent['increment'];
    // Для таланта 4 (Бронеэкипировка) нужно два значения, пока используем одно
    if ($talent['id'] == 4) {
        return sprintf($talent['description'], $value, $value);
    }
    return sprintf($talent['description'], $value);
}

/**
 * Получить общее количество доступных очков талантов (непотраченных)
 */
function getAvailableTalentPoints($userId) {
    $user = getUserById($userId);
    return $user['talent_points_total'] - $user['talent_points_spent'];
}

/**
 * Начислить очко таланта за уровень (вызывать при повышении уровня >=11)
 */
function grantTalentPointForLevel($userId) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE users SET talent_points_total = talent_points_total + 1 WHERE id = ?");
    $stmt->execute([$userId]);
}
// ---------- МАГАЗИН АВАТАРОВ ----------
function getShopAvatars($page = 1, $perPage = 10) {
    global $pdo;
    $offset = ($page - 1) * $perPage;
    $stmt = $pdo->prepare("SELECT * FROM shop_avatars ORDER BY sort_order ASC, id ASC LIMIT ? OFFSET ?");
    $stmt->bindValue(1, $perPage, PDO::PARAM_INT);
    $stmt->bindValue(2, $offset, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

function countShopAvatars() {
    global $pdo;
    return $pdo->query("SELECT COUNT(*) FROM shop_avatars")->fetchColumn();
}

function getShopAvatarById($id) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM shop_avatars WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

function isAvatarPurchased($userId, $avatarId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT 1 FROM user_avatars WHERE user_id = ? AND avatar_id = ?");
    $stmt->execute([$userId, $avatarId]);
    return (bool)$stmt->fetchColumn();
}

function buyAvatar($userId, $avatarId) {
    global $pdo;
    $avatar = getShopAvatarById($avatarId);
    if (!$avatar) {
        return ['success' => false, 'message' => 'Аватар не найден.'];
    }

    if (isAvatarPurchased($userId, $avatarId)) {
        return ['success' => false, 'message' => 'Аватар уже куплен.'];
    }

    $user = getUserById($userId);
    if ($user['gold'] < $avatar['price_gold'] || $user['bucks'] < $avatar['price_silver']) {
        return ['success' => false, 'message' => 'Недостаточно средств.'];
    }

    $pdo->beginTransaction();
    try {
        if ($avatar['price_gold'] > 0) {
            $stmt = $pdo->prepare("UPDATE users SET gold = gold - ? WHERE id = ?");
            $stmt->execute([$avatar['price_gold'], $userId]);
        }
        if ($avatar['price_silver'] > 0) {
            $stmt = $pdo->prepare("UPDATE users SET bucks = bucks - ? WHERE id = ?");
            $stmt->execute([$avatar['price_silver'], $userId]);
        }

        $stmt = $pdo->prepare("INSERT INTO user_avatars (user_id, avatar_id, purchased_at) VALUES (?, ?, NOW())");
        $stmt->execute([$userId, $avatarId]);

        $stmt = $pdo->prepare("UPDATE shop_avatars SET popularity = popularity + 1 WHERE id = ?");
        $stmt->execute([$avatarId]);

        $pdo->commit();
        return ['success' => true, 'message' => 'Аватар успешно куплен!'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка при покупке: ' . $e->getMessage()];
    }
}

function getUserAvatars($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT sa.*, ua.purchased_at, ua.is_active 
        FROM user_avatars ua
        JOIN shop_avatars sa ON ua.avatar_id = sa.id
        WHERE ua.user_id = ?
        ORDER BY ua.purchased_at DESC
    ");
    $stmt->execute([$userId]);
    return $stmt->fetchAll();
}

function setActiveAvatar($userId, $avatarId) {
    global $pdo;
    if (!isAvatarPurchased($userId, $avatarId)) {
        return ['success' => false, 'message' => 'Аватар не куплен.'];
    }
    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE user_avatars SET is_active = 0 WHERE user_id = ?");
        $stmt->execute([$userId]);

        $stmt = $pdo->prepare("UPDATE user_avatars SET is_active = 1 WHERE user_id = ? AND avatar_id = ?");
        $stmt->execute([$userId, $avatarId]);

        $avatar = getShopAvatarById($avatarId);
        $stmt = $pdo->prepare("UPDATE users SET avatar_type = 'shop', custom_avatar = ? WHERE id = ?");
        $stmt->execute([$avatar['image'], $userId]);

        $pdo->commit();
        return ['success' => true, 'message' => 'Аватар активирован.'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}
// ==================== ДОСТИЖЕНИЯ ====================

/**
 * Получить все категории достижений
 */
function getAchievementCategories() {
    global $pdo;
    $stmt = $pdo->query("SELECT * FROM achievement_categories ORDER BY sort_order ASC, id ASC");
    return $stmt->fetchAll();
}

/**
 * Получить категорию по ID
 */
function getAchievementCategoryById($id) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM achievement_categories WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

/**
 * Получить все достижения (с категориями)
 */
function getAllAchievements() {
    global $pdo;
    $stmt = $pdo->query("
        SELECT a.*, c.name as category_name, c.icon as category_icon
        FROM achievements a
        JOIN achievement_categories c ON a.category_id = c.id
        ORDER BY c.sort_order, a.sort_order, a.id
    ");
    return $stmt->fetchAll();
}

/**
 * Получить достижение по ID
 */
function getAchievementById($id) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM achievements WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

/**
 * Получить достижения по категории
 */
function getAchievementsByCategory($categoryId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM achievements WHERE category_id = ? ORDER BY sort_order, id");
    $stmt->execute([$categoryId]);
    return $stmt->fetchAll();
}

/**
 * Получить все достижения, полученные пользователем
 */
function getUserAchievements($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT ua.*, a.*, c.name as category_name, c.icon as category_icon
        FROM user_achievements ua
        JOIN achievements a ON ua.achievement_id = a.id
        JOIN achievement_categories c ON a.category_id = c.id
        WHERE ua.user_id = ?
        ORDER BY ua.earned_at DESC
    ");
    $stmt->execute([$userId]);
    return $stmt->fetchAll();
}

/**
 * Проверить, получено ли достижение пользователем
 */
function hasAchievement($userId, $achievementId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT 1 FROM user_achievements WHERE user_id = ? AND achievement_id = ?");
    $stmt->execute([$userId, $achievementId]);
    return (bool)$stmt->fetchColumn();
}

/**
 * Получить сумму очков достижений пользователя
 */
function getUserAchievementPoints($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COALESCE(SUM(a.points), 0) FROM user_achievements ua JOIN achievements a ON ua.achievement_id = a.id WHERE ua.user_id = ?");
    $stmt->execute([$userId]);
    return (int)$stmt->fetchColumn();
}

/**
 * Получить сумму бонусов от достижений пользователя
 */
function getUserAchievementBonuses($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT 
            COALESCE(SUM(a.bonus_attack), 0) as attack,
            COALESCE(SUM(a.bonus_health), 0) as health,
            COALESCE(SUM(a.bonus_defense), 0) as defense,
            COALESCE(SUM(a.bonus_stamina), 0) as stamina,
            COALESCE(SUM(a.bonus_recovery), 0) as recovery
        FROM user_achievements ua
        JOIN achievements a ON ua.achievement_id = a.id
        WHERE ua.user_id = ?
    ");
    $stmt->execute([$userId]);
    return $stmt->fetch();
}

/**
 * Выдать достижение пользователю (если ещё не получено)
 * Обновляет очки и бонусы в таблице users
 */
function earnAchievement($userId, $achievementId) {
    global $pdo;

    if (hasAchievement($userId, $achievementId)) {
        return ['success' => false, 'message' => 'Достижение уже получено'];
    }

    $achievement = getAchievementById($achievementId);
    if (!$achievement) {
        return ['success' => false, 'message' => 'Достижение не найдено'];
    }

    $pdo->beginTransaction();
    try {
        // Запись о получении
        $stmt = $pdo->prepare("INSERT INTO user_achievements (user_id, achievement_id, earned_at) VALUES (?, ?, NOW())");
        $stmt->execute([$userId, $achievementId]);

        // Обновляем суммарные очки и бонусы пользователя
        $points = $achievement['points'];
        $bonusAttack = $achievement['bonus_attack'];
        $bonusHealth = $achievement['bonus_health'];
        $bonusDefense = $achievement['bonus_defense'];
        $bonusStamina = $achievement['bonus_stamina'];
        $bonusRecovery = $achievement['bonus_recovery'];

        $stmt = $pdo->prepare("
            UPDATE users SET 
                achievement_points = achievement_points + ?,
                bonus_attack = bonus_attack + ?,
                bonus_health = bonus_health + ?,
                bonus_defense = bonus_defense + ?,
                bonus_stamina = bonus_stamina + ?,
                bonus_recovery = bonus_recovery + ?
            WHERE id = ?
        ");
        $stmt->execute([$points, $bonusAttack, $bonusHealth, $bonusDefense, $bonusStamina, $bonusRecovery, $userId]);

        // Пересчитываем итоговые статы с учётом новых бонусов
        recalcUserStats($userId);

        $pdo->commit();
        return ['success' => true, 'message' => 'Достижение получено!'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Установить достижение как показываемое в профиле (титул)
 * Только одно достижение может быть активным.
 */
function setDisplayedAchievement($userId, $achievementId) {
    global $pdo;

    if ($achievementId !== null && !hasAchievement($userId, $achievementId)) {
        return ['success' => false, 'message' => 'Достижение не получено'];
    }

    $pdo->beginTransaction();
    try {
        // Сбрасываем флаг у всех достижений пользователя
        $stmt = $pdo->prepare("UPDATE user_achievements SET is_displayed = 0 WHERE user_id = ?");
        $stmt->execute([$userId]);

        if ($achievementId !== null) {
            $stmt = $pdo->prepare("UPDATE user_achievements SET is_displayed = 1 WHERE user_id = ? AND achievement_id = ?");
            $stmt->execute([$userId, $achievementId]);
        }

        $pdo->commit();
        return ['success' => true, 'message' => 'Титул обновлён'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Получить текущее показываемое достижение пользователя
 */
function getDisplayedAchievement($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT a.* 
        FROM user_achievements ua
        JOIN achievements a ON ua.achievement_id = a.id
        WHERE ua.user_id = ? AND ua.is_displayed = 1
        LIMIT 1
    ");
    $stmt->execute([$userId]);
    return $stmt->fetch();
}

/**
 * Получить количество полученных достижений в категории для пользователя
 */
function getUserAchievementsCountInCategory($userId, $categoryId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT COUNT(*) 
        FROM user_achievements ua
        JOIN achievements a ON ua.achievement_id = a.id
        WHERE ua.user_id = ? AND a.category_id = ?
    ");
    $stmt->execute([$userId, $categoryId]);
    return $stmt->fetchColumn();
}

/**
 * Получить общее количество достижений в категории
 */
function getTotalAchievementsCountInCategory($categoryId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM achievements WHERE category_id = ?");
    $stmt->execute([$categoryId]);
    return $stmt->fetchColumn();
}

/**
 * Получить недавние достижения пользователя (последние N)
 */
function getRecentUserAchievements($userId, $limit = 5) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT ua.*, a.name, a.description, a.icon, a.points
        FROM user_achievements ua
        JOIN achievements a ON ua.achievement_id = a.id
        WHERE ua.user_id = ?
        ORDER BY ua.earned_at DESC
        LIMIT ?
    ");
    $stmt->bindValue(1, $userId, PDO::PARAM_INT);
    $stmt->bindValue(2, $limit, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

// ==================== АДМИНИСТРИРОВАНИЕ ДОСТИЖЕНИЙ ====================

function adminCreateCategory($name, $icon, $sortOrder) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO achievement_categories (name, icon, sort_order) VALUES (?, ?, ?)");
    return $stmt->execute([$name, $icon, $sortOrder]);
}

function adminUpdateCategory($id, $name, $icon, $sortOrder) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE achievement_categories SET name = ?, icon = ?, sort_order = ? WHERE id = ?");
    return $stmt->execute([$name, $icon, $sortOrder, $id]);
}

function adminDeleteCategory($id) {
    global $pdo;
    $stmt = $pdo->prepare("DELETE FROM achievement_categories WHERE id = ?");
    return $stmt->execute([$id]);
}

function adminCreateAchievement($data) {
    global $pdo;
    $sql = "INSERT INTO achievements 
            (category_id, name, description, icon, points, bonus_attack, bonus_health, bonus_defense, bonus_stamina, bonus_recovery, is_title, requirement_type, requirement_value, optional_data, sort_order)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
    $stmt = $pdo->prepare($sql);
    return $stmt->execute([
        $data['category_id'],
        $data['name'],
        $data['description'],
        $data['icon'],
        $data['points'],
        $data['bonus_attack'],
        $data['bonus_health'],
        $data['bonus_defense'],
        $data['bonus_stamina'],
        $data['bonus_recovery'],
        $data['is_title'] ?? 0,
        $data['requirement_type'] ?? null,
        $data['requirement_value'] ?? null,
        $data['optional_data'] ?? null,
        $data['sort_order'] ?? 0
    ]);
}

function adminUpdateAchievement($id, $data) {
    global $pdo;
    $sql = "UPDATE achievements SET 
                category_id = ?, name = ?, description = ?, icon = ?, points = ?, 
                bonus_attack = ?, bonus_health = ?, bonus_defense = ?, bonus_stamina = ?, bonus_recovery = ?,
                is_title = ?, requirement_type = ?, requirement_value = ?, optional_data = ?, sort_order = ?
            WHERE id = ?";
    $stmt = $pdo->prepare($sql);
    return $stmt->execute([
        $data['category_id'],
        $data['name'],
        $data['description'],
        $data['icon'],
        $data['points'],
        $data['bonus_attack'],
        $data['bonus_health'],
        $data['bonus_defense'],
        $data['bonus_stamina'],
        $data['bonus_recovery'],
        $data['is_title'] ?? 0,
        $data['requirement_type'] ?? null,
        $data['requirement_value'] ?? null,
        $data['optional_data'] ?? null,
        $data['sort_order'] ?? 0,
        $id
    ]);
}

function adminDeleteAchievement($id) {
    global $pdo;
    $stmt = $pdo->prepare("DELETE FROM achievements WHERE id = ?");
    return $stmt->execute([$id]);
}
/**
 * Возвращает количество полученных достижений пользователя
 */
function getUserAchievementsCount($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM user_achievements WHERE user_id = ?");
    $stmt->execute([$userId]);
    return $stmt->fetchColumn();
}
function formatBigNumber($num) {
    if ($num >= 1e9) return round($num / 1e9, 2) . 'G';
    if ($num >= 1e6) return round($num / 1e6, 2) . 'M';
    if ($num >= 1e3) return round($num / 1e3, 2) . 'K';
    return $num;
}
// ---------- ФУНКЦИИ КЛАНА ----------

// Проверка, состоит ли пользователь в клане, возвращает clan_id или false
function isUserInClan($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT clan_id FROM clan_members WHERE user_id = ?");
    $stmt->execute([$userId]);
    return $stmt->fetchColumn();
}

// Получить ранг пользователя в клане (или false)
function getUserRankInClan($userId) {
    global $pdo;
$stmt = $pdo->prepare("SELECT `rank` FROM clan_members WHERE user_id = ?");
    $stmt->execute([$userId]);
    return $stmt->fetchColumn();
}

// Получить клан по ID
function getClanById($clanId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM clans WHERE id = ?");
    $stmt->execute([$clanId]);
    return $stmt->fetch();
}

// Получить список участников клана
function getClanMembers($clanId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT u.*, cm.rank, cm.joined_at 
        FROM clan_members cm 
        JOIN users u ON cm.user_id = u.id 
        WHERE cm.clan_id = ? 
        ORDER BY FIELD(cm.rank, 'leader', 'deputy', 'advisor', 'recruit'), u.username
    ");
    $stmt->execute([$clanId]);
    return $stmt->fetchAll();
}

// Права на приглашение
function canUserInvite($userId, $clanId) {
    $rank = getUserRankInClan($userId);
    return in_array($rank, ['leader', 'deputy']);
}

// Права на исключение
function canUserKick($userId, $clanId) {
    return canUserInvite($userId, $clanId);
}

// Права на управление заявками
function canUserManageApplications($userId, $clanId) {
    return canUserInvite($userId, $clanId);
}

// Права на смену рангов (только лидер)
function canUserChangeRank($userId, $clanId) {
    return getUserRankInClan($userId) == 'leader';
}

// Права на редактирование клана
function canUserEditClan($userId, $clanId) {
    return getUserRankInClan($userId) == 'leader';
}

// Проверка возможности создания клана
function canCreateClan($userId) {
    $user = getUserById($userId);
    return $user && $user['level'] >= 10;
}

// Создание клана
function createClan($leaderId, $name, $description) {
    global $pdo;
    if (!canCreateClan($leaderId)) {
        return ['success' => false, 'message' => 'Необходим 10 уровень'];
    }
    $stmt = $pdo->prepare("SELECT 1 FROM clans WHERE name = ?");
    $stmt->execute([$name]);
    if ($stmt->fetch()) {
        return ['success' => false, 'message' => 'Клан с таким названием уже существует'];
    }
    if (isUserInClan($leaderId)) {
        return ['success' => false, 'message' => 'Вы уже состоите в клане'];
    }
    $user = getUserById($leaderId);
    if ($user['gold'] < 100) {
        return ['success' => false, 'message' => 'Недостаточно золота (нужно 100)'];
    }

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE users SET gold = gold - 100 WHERE id = ?");
        $stmt->execute([$leaderId]);

        $stmt = $pdo->prepare("INSERT INTO clans (name, description, leader_id, created_at) VALUES (?, ?, ?, NOW())");
        $stmt->execute([$name, $description, $leaderId]);
        $clanId = $pdo->lastInsertId();

        $stmt = $pdo->prepare("INSERT INTO clan_members (clan_id, user_id, rank, joined_at) VALUES (?, ?, 'leader', NOW())");
        $stmt->execute([$clanId, $leaderId]);

        logClanAction($clanId, $leaderId, 'create', 'Клан создан');

        $pdo->commit();
        return ['success' => true, 'clan_id' => $clanId];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

// Расформирование клана
function deleteClan($clanId, $userId) {
    global $pdo;
    $clan = getClanById($clanId);
    if (!$clan) return ['success' => false, 'message' => 'Клан не найден'];
    if ($clan['leader_id'] != $userId) {
        return ['success' => false, 'message' => 'Только лидер может расформировать клан'];
    }
    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("DELETE FROM clans WHERE id = ?");
        $stmt->execute([$clanId]);
        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

// Покинуть клан
function leaveClan($userId) {
    global $pdo;
    $clanId = isUserInClan($userId);
    if (!$clanId) return ['success' => false, 'message' => 'Вы не в клане'];

    $rank = getUserRankInClan($userId);
    if ($rank == 'leader') {
        return ['success' => false, 'message' => 'Лидер не может покинуть клан. Передайте лидерство или расформируйте клан.'];
    }

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("DELETE FROM clan_members WHERE user_id = ?");
        $stmt->execute([$userId]);

        logClanAction($clanId, $userId, 'leave', 'Покинул клан');

        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

// Исключить игрока
function kickFromClan($userId, $kickedById) {
    global $pdo;
    $clanId = isUserInClan($userId);
    if (!$clanId) return ['success' => false, 'message' => 'Игрок не в клане'];

    if (!canUserKick($kickedById, $clanId)) {
        return ['success' => false, 'message' => 'Недостаточно прав для исключения'];
    }

    $rank = getUserRankInClan($userId);
    if ($rank == 'leader') {
        return ['success' => false, 'message' => 'Нельзя исключить лидера'];
    }

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("DELETE FROM clan_members WHERE user_id = ?");
        $stmt->execute([$userId]);

        logClanAction($clanId, $kickedById, 'kick', "Исключён пользователь ID $userId");

        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

// Сменить ранг
function changeRank($userId, $targetUserId, $newRank) {
    global $pdo;
    $clanId = isUserInClan($userId);
    if (!$clanId) return ['success' => false, 'message' => 'Вы не в клане'];
    if (!canUserChangeRank($userId, $clanId)) {
        return ['success' => false, 'message' => 'Только лидер может менять ранги'];
    }
    $targetClanId = isUserInClan($targetUserId);
    if ($targetClanId != $clanId) {
        return ['success' => false, 'message' => 'Игрок не в вашем клане'];
    }
    // Разрешённые ранги (исключая лидера)
    $allowedRanks = ['deputy', 'advisor', 'veteran', 'recruit'];
    if (!in_array($newRank, $allowedRanks)) {
        return ['success' => false, 'message' => 'Недопустимый ранг'];
    }
    $targetRank = getUserRankInClan($targetUserId);
    if ($targetRank == 'leader') {
        return ['success' => false, 'message' => 'Нельзя изменить ранг лидера'];
    }

    $stmt = $pdo->prepare("UPDATE clan_members SET rank = ? WHERE user_id = ?");
    $stmt->execute([$newRank, $targetUserId]);

    logClanAction($clanId, $userId, 'rank_change', "Пользователю $targetUserId установлен ранг $newRank");

    return ['success' => true];
}

// Передача лидерства
function transferLeadership($clanId, $newLeaderId, $oldLeaderId) {
    global $pdo;
    $clan = getClanById($clanId);
    if (!$clan) return ['success' => false, 'message' => 'Клан не найден'];
    if ($clan['leader_id'] != $oldLeaderId) {
        return ['success' => false, 'message' => 'Вы не лидер'];
    }
    $newLeaderClanId = isUserInClan($newLeaderId);
    if ($newLeaderClanId != $clanId) {
        return ['success' => false, 'message' => 'Новый лидер не состоит в клане'];
    }
    // Проверка верности (30 дней)
    $stmt = $pdo->prepare("SELECT joined_at FROM clan_members WHERE user_id = ? AND clan_id = ?");
    $stmt->execute([$newLeaderId, $clanId]);
    $joined = $stmt->fetchColumn();
    if (!$joined) return ['success' => false, 'message' => 'Ошибка данных'];
    $daysInClan = floor((time() - strtotime($joined)) / 86400);
    if ($daysInClan < 30) {
        return ['success' => false, 'message' => 'Новый лидер должен пробыть в клане не менее 30 дней'];
    }

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE clans SET leader_id = ? WHERE id = ?");
        $stmt->execute([$newLeaderId, $clanId]);

        $stmt = $pdo->prepare("UPDATE clan_members SET rank = 'advisor' WHERE user_id = ?");
        $stmt->execute([$oldLeaderId]);

        $stmt = $pdo->prepare("UPDATE clan_members SET rank = 'leader' WHERE user_id = ?");
        $stmt->execute([$newLeaderId]);

        logClanAction($clanId, $oldLeaderId, 'transfer', "Лидерство передано пользователю $newLeaderId");

        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

// Пополнение казны золотом
function addClanGold($clanId, $amount, $userId) {
    global $pdo;
    if ($amount <= 0) return ['success' => false, 'message' => 'Некорректная сумма'];

    $userClanId = isUserInClan($userId);
    if ($userClanId != $clanId) return ['success' => false, 'message' => 'Вы не в этом клане'];

    $user = getUserById($userId);
    if ($user['gold'] < $amount) return ['success' => false, 'message' => 'Недостаточно золота'];

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE users SET gold = gold - ? WHERE id = ?");
        $stmt->execute([$amount, $userId]);

        $stmt = $pdo->prepare("UPDATE clans SET gold = gold + ? WHERE id = ?");
        $stmt->execute([$amount, $clanId]);

        logClanAction($clanId, $userId, 'gold_deposit', "Пополнил казну на $amount золота");

        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

// Пополнение казны баксами
function addClanBucks($clanId, $amount, $userId) {
    global $pdo;
    if ($amount <= 0) return ['success' => false, 'message' => 'Некорректная сумма'];

    $userClanId = isUserInClan($userId);
    if ($userClanId != $clanId) return ['success' => false, 'message' => 'Вы не в этом клане'];

    $user = getUserById($userId);
    if ($user['bucks'] < $amount) return ['success' => false, 'message' => 'Недостаточно баксов'];

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE users SET bucks = bucks - ? WHERE id = ?");
        $stmt->execute([$amount, $userId]);

        $stmt = $pdo->prepare("UPDATE clans SET bucks = bucks + ? WHERE id = ?");
        $stmt->execute([$amount, $clanId]);

        logClanAction($clanId, $userId, 'bucks_deposit', "Пополнил казну на $amount баксов");

        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

// Трата золота из казны
function spendClanGold($clanId, $amount, $userId, $reason = '') {
    global $pdo;
    if (!canUserManageApplications($userId, $clanId)) {
        return ['success' => false, 'message' => 'Недостаточно прав для траты казны'];
    }
    $clan = getClanById($clanId);
    if ($clan['gold'] < $amount) {
        return ['success' => false, 'message' => 'Недостаточно золота в казне'];
    }
    $stmt = $pdo->prepare("UPDATE clans SET gold = gold - ? WHERE id = ?");
    $stmt->execute([$amount, $clanId]);

    logClanAction($clanId, $userId, 'gold_spend', "Потрачено $amount золота. Причина: $reason");

    return ['success' => true];
}

// Трата баксов из казны
function spendClanBucks($clanId, $amount, $userId, $reason = '') {
    global $pdo;
    if (!canUserManageApplications($userId, $clanId)) {
        return ['success' => false, 'message' => 'Недостаточно прав для траты казны'];
    }
    $clan = getClanById($clanId);
    if ($clan['bucks'] < $amount) {
        return ['success' => false, 'message' => 'Недостаточно баксов в казне'];
    }
    $stmt = $pdo->prepare("UPDATE clans SET bucks = bucks - ? WHERE id = ?");
    $stmt->execute([$amount, $clanId]);

    logClanAction($clanId, $userId, 'bucks_spend', "Потрачено $amount баксов. Причина: $reason");

    return ['success' => true];
}

// Бонус верности клану (множитель опыта)
function getClanLoyaltyBonus($userId) {
    $clanId = isUserInClan($userId);
    if (!$clanId) return 1.0;

    $stmt = $GLOBALS['pdo']->prepare("SELECT joined_at FROM clan_members WHERE user_id = ?");
    $stmt->execute([$userId]);
    $joined = $stmt->fetchColumn();
    if (!$joined) return 1.0;

    $days = floor((time() - strtotime($joined)) / 86400);
    if ($days >= 90) return 1.10;
    if ($days >= 59) return 1.07;
    if ($days >= 30) return 1.05;
    return 1.0;
}

// Начисление кланового опыта на основе полученного участия
function addClanExp($userId, $participationGained) {
    global $pdo;
    $clanId = isUserInClan($userId);
    if (!$clanId) return;

    $stmt = $pdo->prepare("SELECT COUNT(*) FROM clan_members WHERE clan_id = ?");
    $stmt->execute([$clanId]);
    $memberCount = $stmt->fetchColumn();
    if ($memberCount == 0) return;

    $bonus = getClanLoyaltyBonus($userId);
    $contribution = $participationGained / sqrt($memberCount) * $bonus;
    $contribution = round($contribution);
    if ($contribution <= 0) return;

    $stmt = $pdo->prepare("UPDATE clans SET exp = exp + ? WHERE id = ?");
    $stmt->execute([$contribution, $clanId]);
}

// Обновление участия игрока с одновременным начислением опыта клану
function addUserParticipation($userId, $participationGained) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE users SET participation = participation + ? WHERE id = ?");
    $stmt->execute([$participationGained, $userId]);

    addClanExp($userId, $participationGained);
}

// ==================== ПРИГЛАШЕНИЯ В КЛАН ====================

function getPendingInvitesCount($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM clan_invites WHERE user_id = ? AND status = 'pending'");
    $stmt->execute([$userId]);
    return $stmt->fetchColumn();
}

function getPendingInvites($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT i.*, c.name as clan_name, u.username as inviter_name
        FROM clan_invites i
        JOIN clans c ON i.clan_id = c.id
        JOIN users u ON i.invited_by = u.id
        WHERE i.user_id = ? AND i.status = 'pending'
        ORDER BY i.created_at DESC
    ");
    $stmt->execute([$userId]);
    return $stmt->fetchAll();
}

// ==================== ЗАЯВКИ В КЛАН ====================

function createApplication($clanId, $userId, $message = '') {
    global $pdo;
    if (isUserInClan($userId)) {
        return ['success' => false, 'message' => 'Вы уже в клане'];
    }
    $stmt = $pdo->prepare("SELECT 1 FROM clan_applications WHERE clan_id = ? AND user_id = ? AND status = 'pending'");
    $stmt->execute([$clanId, $userId]);
    if ($stmt->fetch()) {
        return ['success' => false, 'message' => 'Заявка уже подана'];
    }
    $stmt = $pdo->prepare("INSERT INTO clan_applications (clan_id, user_id, message, status) VALUES (?, ?, ?, 'pending')");
    $stmt->execute([$clanId, $userId, $message]);

    logClanAction($clanId, $userId, 'apply', 'Подана заявка');

    return ['success' => true];
}

function getClanApplications($clanId, $status = 'pending') {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT a.*, u.username, u.level, u.side
        FROM clan_applications a
        JOIN users u ON a.user_id = u.id
        WHERE a.clan_id = ? AND a.status = ?
        ORDER BY a.created_at DESC
    ");
    $stmt->execute([$clanId, $status]);
    return $stmt->fetchAll();
}

function acceptApplication($applicationId, $acceptedBy) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT ca.*, c.leader_id FROM clan_applications ca JOIN clans c ON ca.clan_id = c.id WHERE ca.id = ?");
    $stmt->execute([$applicationId]);
    $app = $stmt->fetch();
    if (!$app) return ['success' => false, 'message' => 'Заявка не найдена'];
    if ($app['status'] != 'pending') return ['success' => false, 'message' => 'Заявка уже обработана'];

    $clanId = $app['clan_id'];
    $userId = $app['user_id'];

    if (!canUserManageApplications($acceptedBy, $clanId)) {
        return ['success' => false, 'message' => 'Недостаточно прав'];
    }

    $stmt = $pdo->prepare("SELECT COUNT(*) FROM clan_members WHERE clan_id = ?");
    $stmt->execute([$clanId]);
    if ($stmt->fetchColumn() >= 10) {
        return ['success' => false, 'message' => 'В клане уже 10 участников'];
    }

    if (isUserInClan($userId)) {
        return ['success' => false, 'message' => 'Пользователь уже в клане'];
    }

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE clan_applications SET status = 'accepted' WHERE id = ?");
        $stmt->execute([$applicationId]);

        $stmt = $pdo->prepare("INSERT INTO clan_members (clan_id, user_id, rank, joined_at) VALUES (?, ?, 'recruit', NOW())");
        $stmt->execute([$clanId, $userId]);

        logClanAction($clanId, $acceptedBy, 'accept_application', "Принята заявка от пользователя $userId");

        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

function declineApplication($applicationId, $declinedBy) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT ca.* FROM clan_applications ca WHERE ca.id = ?");
    $stmt->execute([$applicationId]);
    $app = $stmt->fetch();
    if (!$app) return ['success' => false, 'message' => 'Заявка не найдена'];
    if ($app['status'] != 'pending') return ['success' => false, 'message' => 'Заявка уже обработана'];

    $clanId = $app['clan_id'];
    if (!canUserManageApplications($declinedBy, $clanId)) {
        return ['success' => false, 'message' => 'Недостаточно прав'];
    }

    $stmt = $pdo->prepare("UPDATE clan_applications SET status = 'declined' WHERE id = ?");
    $stmt->execute([$applicationId]);

    logClanAction($clanId, $declinedBy, 'decline_application', "Отклонена заявка от пользователя {$app['user_id']}");

    return ['success' => true];
}

// ==================== ПРИГЛАШЕНИЯ ====================

function createInvite($clanId, $inviterId, $targetUserId) {
    global $pdo;
    if (!canUserInvite($inviterId, $clanId)) {
        return ['success' => false, 'message' => 'Недостаточно прав для приглашения'];
    }
    if (isUserInClan($targetUserId)) {
        return ['success' => false, 'message' => 'Игрок уже в клане'];
    }
    $stmt = $pdo->prepare("SELECT 1 FROM clan_invites WHERE clan_id = ? AND user_id = ? AND status = 'pending'");
    $stmt->execute([$clanId, $targetUserId]);
    if ($stmt->fetch()) {
        return ['success' => false, 'message' => 'Приглашение уже отправлено'];
    }
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM clan_members WHERE clan_id = ?");
    $stmt->execute([$clanId]);
    if ($stmt->fetchColumn() >= 10) {
        return ['success' => false, 'message' => 'В клане уже 10 участников'];
    }

    $stmt = $pdo->prepare("INSERT INTO clan_invites (clan_id, user_id, invited_by, status) VALUES (?, ?, ?, 'pending')");
    $stmt->execute([$clanId, $targetUserId, $inviterId]);

    logClanAction($clanId, $inviterId, 'invite', "Приглашён пользователь $targetUserId");

    return ['success' => true];
}

function acceptInvite($inviteId, $userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT ci.*, c.leader_id FROM clan_invites ci JOIN clans c ON ci.clan_id = c.id WHERE ci.id = ?");
    $stmt->execute([$inviteId]);
    $invite = $stmt->fetch();
    if (!$invite) return ['success' => false, 'message' => 'Приглашение не найдено'];
    if ($invite['status'] != 'pending') return ['success' => false, 'message' => 'Приглашение уже неактуально'];
    if ($invite['user_id'] != $userId) return ['success' => false, 'message' => 'Это приглашение не для вас'];

    $clanId = $invite['clan_id'];

    if (isUserInClan($userId)) return ['success' => false, 'message' => 'Вы уже в клане'];

    $stmt = $pdo->prepare("SELECT COUNT(*) FROM clan_members WHERE clan_id = ?");
    $stmt->execute([$clanId]);
    if ($stmt->fetchColumn() >= 10) {
        return ['success' => false, 'message' => 'В клане уже 10 участников'];
    }

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE clan_invites SET status = 'accepted' WHERE id = ?");
        $stmt->execute([$inviteId]);

        $stmt = $pdo->prepare("INSERT INTO clan_members (clan_id, user_id, rank, joined_at) VALUES (?, ?, 'recruit', NOW())");
        $stmt->execute([$clanId, $userId]);

        logClanAction($clanId, $userId, 'accept_invite', 'Принял приглашение');

        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

function declineInvite($inviteId, $userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM clan_invites WHERE id = ? AND user_id = ?");
    $stmt->execute([$inviteId, $userId]);
    $invite = $stmt->fetch();
    if (!$invite) return ['success' => false, 'message' => 'Приглашение не найдено'];

    $stmt = $pdo->prepare("UPDATE clan_invites SET status = 'declined' WHERE id = ?");
    $stmt->execute([$inviteId]);

    return ['success' => true];
}

// ==================== ЖУРНАЛ КЛАНА ====================

function logClanAction($clanId, $userId, $action, $details = '') {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO clan_logs (clan_id, user_id, action, details, created_at) VALUES (?, ?, ?, ?, NOW())");
    $stmt->execute([$clanId, $userId, $action, $details]);
}

function getClanLog($clanId, $limit = 50) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT l.*, u.username 
        FROM clan_logs l 
        LEFT JOIN users u ON l.user_id = u.id 
        WHERE l.clan_id = ? 
        ORDER BY l.created_at DESC 
        LIMIT ?
    ");
    $stmt->bindValue(1, $clanId, PDO::PARAM_INT);
    $stmt->bindValue(2, (int)$limit, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}

// ==================== УРОВЕНЬ КЛАНА ====================

function getClanLevelInfo($exp) {
    $levels = [
        1 => 0,
        2 => 1000000,
        3 => 2500000,
        4 => 4750000,
        5 => 8120000,
        6 => 13000000,
        7 => 20000000,
        8 => 32000000,
        9 => 49000000,
        10 => 74000000,
        11 => 113000000,
        12 => 170000000,
        13 => 257000000,
        14 => 387000000,
        15 => 581000000,
        16 => 873000000,
        17 => 1311000000,
        18 => 1968000000,
        19 => 2953000000,
        20 => 4431000000,
        21 => 6648000000,
        22 => 9973000000,
        23 => 14961000000,
        24 => 22443000000,
        25 => 33666000000,
        26 => 50500000000,
        27 => 75750000000,
        28 => 113628000000,
    ];
    $level = 1;
    foreach ($levels as $lvl => $req) {
        if ($exp >= $req) $level = $lvl;
        else break;
    }
    $nextReq = $levels[$level+1] ?? null;
    $currentReq = $levels[$level];
    return [
        'level' => $level,
        'exp_current' => $exp,
        'exp_needed' => $nextReq,
        'exp_required_current' => $currentReq,
        'progress' => $nextReq ? round(($exp - $currentReq) / ($nextReq - $currentReq) * 100, 1) : 100
    ];
}

// Обновить объявление клана
function updateClanAnnouncement($clanId, $userId, $announcement) {
    global $pdo;
    if (!canUserEditClan($userId, $clanId)) {
        return ['success' => false, 'message' => 'Недостаточно прав'];
    }
    
    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("UPDATE clans SET announcement = ? WHERE id = ?");
        $stmt->execute([$announcement, $clanId]);
        
        // Удаляем все записи просмотров для этого клана, чтобы объявление показалось заново
        $stmt = $pdo->prepare("DELETE FROM clan_announcement_views WHERE clan_id = ?");
        $stmt->execute([$clanId]);
        
        logClanAction($clanId, $userId, 'announcement', 'Обновлено объявление');
        
        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}
function getRankName($rank) {
    $ranks = [
        'leader'   => 'Босс',
        'deputy'   => 'Заместитель',
        'advisor'  => 'Советник',
        'veteran'  => 'Ветеран',
        'recruit'  => 'Соучастник'
    ];
    return $ranks[$rank] ?? $rank;
}
// ==================== ОБЪЯВЛЕНИЯ КЛАНА ====================

function getClanAnnouncementHash($clanId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT announcement FROM clans WHERE id = ?");
    $stmt->execute([$clanId]);
    $announcement = $stmt->fetchColumn();
    return $announcement ? md5($announcement) : '';
}

function hasUserSeenAnnouncement($userId, $clanId) {
    global $pdo;
    $hash = getClanAnnouncementHash($clanId);
    if (!$hash) return true; // нет объявления
    
    $stmt = $pdo->prepare("SELECT announcement_hash FROM clan_announcement_views WHERE user_id = ? AND clan_id = ?");
    $stmt->execute([$userId, $clanId]);
    $storedHash = $stmt->fetchColumn();
    
    return $storedHash === $hash;
}

function markAnnouncementAsSeen($userId, $clanId) {
    global $pdo;
    $hash = getClanAnnouncementHash($clanId);
    if (!$hash) return;
    
    $stmt = $pdo->prepare("REPLACE INTO clan_announcement_views (user_id, clan_id, announcement_hash) VALUES (?, ?, ?)");
    $stmt->execute([$userId, $clanId, $hash]);
}

// ==================== ФУНКЦИИ БЕЗОПАСНОСТИ ====================

/**
 * Логирование действий пользователя
 */
function logUserAction($userId, $action, $details = '') {
    global $pdo;
    $ip = $_SERVER['REMOTE_ADDR'] ?? '';
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
    $stmt = $pdo->prepare("INSERT INTO user_action_logs (user_id, action, details, ip, user_agent) VALUES (?, ?, ?, ?, ?)");
    $stmt->execute([$userId, $action, $details, $ip, $ua]);
}

/**
 * Логирование событий безопасности
 */
function logSecurityEvent($type, $url, $requestData = '') {
    global $pdo;
    $ip = $_SERVER['REMOTE_ADDR'] ?? '';
    $ua = $_SERVER['HTTP_USER_AGENT'] ?? '';
    $stmt = $pdo->prepare("INSERT INTO security_logs (type, url, ip, user_agent, request_data) VALUES (?, ?, ?, ?, ?)");
    $stmt->execute([$type, $url, $ip, $ua, $requestData]);
}

/**
 * Проверка на SQL-инъекции в массиве данных
 */
function detectSqlInjection($input) {
    if (is_array($input)) {
        foreach ($input as $value) {
            if (detectSqlInjection($value)) return true;
        }
    } else {
        $input = strtolower($input);
        // Проверяем на типичные ключевые слова SQL
        if (preg_match('/(union|select|insert|update|delete|drop|--|#|\/\*|\*\/|;)/', $input)) {
            return true;
        }
    }
    return false;
}

function check404() {
    $requestUri = $_SERVER['REQUEST_URI'];
    // Исключаем статические файлы
    if (!file_exists($_SERVER['DOCUMENT_ROOT'] . $requestUri) && !preg_match('/\.(css|js|png|jpg|jpeg|gif|ico|webp|svg|woff2?|ttf|eot)$/', $requestUri)) {
        $data = [
            'method' => $_SERVER['REQUEST_METHOD'],
            'query' => $_GET,
            'referer' => $_SERVER['HTTP_REFERER'] ?? '',
            'request_uri' => $requestUri,
        ];
        logSecurityEvent('404', $requestUri, json_encode($data, JSON_UNESCAPED_UNICODE));
    }
}

function securityCheck() {
    if (detectSqlInjection($_GET) || detectSqlInjection($_POST)) {
        $data = [
            'get' => $_GET,
            'post' => $_POST,
            'method' => $_SERVER['REQUEST_METHOD'],
            'referer' => $_SERVER['HTTP_REFERER'] ?? '',
        ];
        logSecurityEvent('sql_injection', $_SERVER['REQUEST_URI'], json_encode($data, JSON_UNESCAPED_UNICODE));
        // Можно также завершить выполнение, но пока просто логируем
    }
}
// ==================== ЛОГИ РАЙОНОВ ====================

function addDistrictBattleLog($districtId, $attackerId, $defenderId, $message) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO district_battle_log (district_id, attacker_id, defender_id, message) VALUES (?, ?, ?, ?)");
    $stmt->execute([$districtId, $attackerId, $defenderId, $message]);
}

function getDistrictBattleLogs($districtId, $limit = 20) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT l.*, 
               a.username as attacker_name, a.side as attacker_side,
               d.username as defender_name, d.side as defender_side
        FROM district_battle_log l
        LEFT JOIN users a ON l.attacker_id = a.id
        LEFT JOIN users d ON l.defender_id = d.id
        WHERE l.district_id = ?
        ORDER BY l.created_at DESC
        LIMIT ?
    ");
    $stmt->bindValue(1, $districtId, PDO::PARAM_INT);
    $stmt->bindValue(2, (int)$limit, PDO::PARAM_INT);
    $stmt->execute();
    return $stmt->fetchAll();
}


// ==================== БОССЫ ====================

/**
 * Получить всех боссов (доступных для уровня, если указан)
 */
function getBosses($level = null) {
    global $pdo;
    $sql = "SELECT * FROM bosses ORDER BY level_required ASC, id ASC";
    if ($level !== null) {
        $sql = "SELECT * FROM bosses WHERE level_required <= ? ORDER BY level_required ASC, id ASC";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$level]);
        return $stmt->fetchAll();
    }
    $stmt = $pdo->query($sql);
    return $stmt->fetchAll();
}

/**
 * Получить босса по ID
 */
function getBossById($id) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM bosses WHERE id = ?");
    $stmt->execute([$id]);
    return $stmt->fetch();
}

/**
 * Создать нового босса (админка)
 */
function createBoss($data, $file) {
    global $pdo;
    // Загрузка изображения
    $imagePath = null;
    if ($file && $file['error'] == UPLOAD_ERR_OK) {
        $targetDir = 'uploads/bosses/';
        if (!is_dir($targetDir)) mkdir($targetDir, 0755, true);

        $check = getimagesize($file['tmp_name']);
        if ($check === false) return ['success' => false, 'message' => 'Файл не является изображением.'];

        $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        $allowed = ['jpg', 'jpeg', 'png', 'gif'];
        if (!in_array($ext, $allowed)) return ['success' => false, 'message' => 'Допустимые форматы: JPG, PNG, GIF.'];

        $newFileName = 'boss_' . time() . '_' . uniqid() . '.' . $ext;
        $targetFile = $targetDir . $newFileName;

        if (move_uploaded_file($file['tmp_name'], $targetFile)) {
            $imagePath = $targetFile;
        } else {
            return ['success' => false, 'message' => 'Ошибка сохранения файла.'];
        }
    }

    $stmt = $pdo->prepare("INSERT INTO bosses (name, description, image, level_required, health, attack, defense, accuracy, reward_exp, reward_bucks, cooldown_minutes) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
    $result = $stmt->execute([
        $data['name'],
        $data['description'],
        $imagePath,
        $data['level_required'],
        $data['health'],
        $data['attack'],
        $data['defense'],
        $data['accuracy'] ?? 5,
        $data['reward_exp'] ?? 0,
        $data['reward_bucks'] ?? 0,
        $data['cooldown_minutes'] ?? 60
    ]);

    return $result ? ['success' => true, 'id' => $pdo->lastInsertId()] : ['success' => false, 'message' => 'Ошибка базы данных'];
}

/**
 * Обновить босса (админка)
 */
function updateBoss($id, $data, $file) {
    global $pdo;
    $boss = getBossById($id);
    if (!$boss) return ['success' => false, 'message' => 'Босс не найден'];

    // Если загружено новое изображение
    $imagePath = $boss['image'];
    if ($file && $file['error'] == UPLOAD_ERR_OK) {
        $targetDir = 'uploads/bosses/';
        if (!is_dir($targetDir)) mkdir($targetDir, 0755, true);

        $check = getimagesize($file['tmp_name']);
        if ($check === false) return ['success' => false, 'message' => 'Файл не является изображением.'];

        $ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
        $allowed = ['jpg', 'jpeg', 'png', 'gif'];
        if (!in_array($ext, $allowed)) return ['success' => false, 'message' => 'Допустимые форматы: JPG, PNG, GIF.'];

        $newFileName = 'boss_' . time() . '_' . uniqid() . '.' . $ext;
        $targetFile = $targetDir . $newFileName;

        if (move_uploaded_file($file['tmp_name'], $targetFile)) {
            // Удаляем старое изображение, если есть
            if ($boss['image'] && file_exists($boss['image'])) {
                unlink($boss['image']);
            }
            $imagePath = $targetFile;
        } else {
            return ['success' => false, 'message' => 'Ошибка сохранения файла.'];
        }
    }

    $stmt = $pdo->prepare("UPDATE bosses SET name = ?, description = ?, image = ?, level_required = ?, health = ?, attack = ?, defense = ?, accuracy = ?, reward_exp = ?, reward_bucks = ?, cooldown_minutes = ? WHERE id = ?");
    $result = $stmt->execute([
        $data['name'],
        $data['description'],
        $imagePath,
        $data['level_required'],
        $data['health'],
        $data['attack'],
        $data['defense'],
        $data['accuracy'] ?? 5,
        $data['reward_exp'] ?? 0,
        $data['reward_bucks'] ?? 0,
        $data['cooldown_minutes'] ?? 60,
        $id
    ]);

    return $result ? ['success' => true] : ['success' => false, 'message' => 'Ошибка базы данных'];
}

/**
 * Удалить босса
 */
function deleteBoss($id) {
    global $pdo;
    $boss = getBossById($id);
    if (!$boss) return false;

    // Удаляем связанные записи
    $pdo->prepare("DELETE FROM boss_fights WHERE boss_id = ?")->execute([$id]);

    // Удаляем изображение
    if ($boss['image'] && file_exists($boss['image'])) {
        unlink($boss['image']);
    }

    $stmt = $pdo->prepare("DELETE FROM bosses WHERE id = ?");
    return $stmt->execute([$id]);
}

/**
 * Получить активный бой пользователя с боссом
 */
function getActiveBossFight($userId, $bossId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM boss_fights WHERE user_id = ? AND boss_id = ? AND status = 'active'");
    $stmt->execute([$userId, $bossId]);
    return $stmt->fetch();
}

/**
 * Проверить, есть ли кулдаун у пользователя на босса
 * Возвращает оставшиеся секунды или 0
 */
function getBossCooldownSeconds($userId, $bossId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT cooldown_until FROM boss_fights WHERE user_id = ? AND boss_id = ? AND status != 'active' ORDER BY id DESC LIMIT 1");
    $stmt->execute([$userId, $bossId]);
    $row = $stmt->fetch();
    if ($row && $row['cooldown_until']) {
        $cooldown = strtotime($row['cooldown_until']) - time();
        return $cooldown > 0 ? $cooldown : 0;
    }
    return 0;
}

/**
 * Начать новый бой с боссом
 */
function startBossFight($userId, $bossId) {
    global $pdo;
    $user = getUserById($userId);
    $boss = getBossById($bossId);
    if (!$user || !$boss) return false;

    // Проверяем уровень
    if ($user['level'] < $boss['level_required']) return false;

    // Проверяем кулдаун
    if (getBossCooldownSeconds($userId, $bossId) > 0) return false;

    // Завершаем старые активные бои с этим боссом (если вдруг)
    $pdo->prepare("UPDATE boss_fights SET status = 'cooldown' WHERE user_id = ? AND boss_id = ? AND status = 'active'")->execute([$userId, $bossId]);

    $stmt = $pdo->prepare("INSERT INTO boss_fights (user_id, boss_id, player_current_hp, boss_current_hp, status) VALUES (?, ?, ?, ?, 'active')");
    return $stmt->execute([$userId, $bossId, $user['health'], $boss['health']]);
}

/**
 * Выполнить одну атаку в бою с боссом
 * Возвращает массив с результатами или false
 */
function processBossAttack($userId, $bossId) {
    global $pdo;

    $fight = getActiveBossFight($userId, $bossId);
    if (!$fight) return false;

    $user = getUserById($userId);
    $boss = getBossById($bossId);
    if (!$user || !$boss) return false;

    // Получаем актуальные HP из fight (они могут отличаться от текущих статов игрока, если игрок изменил экипировку)
    $playerHp = $fight['player_current_hp'];
    $bossHp = $fight['boss_current_hp'];

    if ($playerHp <= 0 || $bossHp <= 0) {
        // Бой уже должен быть завершён, но на всякий случай
        return false;
    }

    // --- Атака игрока ---
    $damage = $user['attack'];
    $critChance = $user['accuracy'];
    $isCrit = (mt_rand(1, 100) <= $critChance);
    if ($isCrit) $damage = round($damage * 1.5);

    // Защита босса
    $defensePercent = calculateDefensePercent($boss['defense']);
    $damageBlocked = round($damage * $defensePercent / 100);
    $damageTakenByBoss = max(0, $damage - $damageBlocked);
    $bossHp -= $damageTakenByBoss;
    if ($bossHp < 0) $bossHp = 0;

    // --- Атака босса (если он ещё жив и игрок жив) ---
    $bossDamage = 0;
    $bossCrit = false;
    $damageBlockedByPlayer = 0;
    $playerDamageTaken = 0;

    if ($bossHp > 0 && $playerHp > 0) {
        $bossDamage = $boss['attack'];
        $bossCritChance = $boss['accuracy'];
        $bossCrit = (mt_rand(1, 100) <= $bossCritChance);
        if ($bossCrit) $bossDamage = round($bossDamage * 1.5);

        $playerDefensePercent = $user['defense_percent'];
        $damageBlockedByPlayer = round($bossDamage * $playerDefensePercent / 100);
        $playerDamageTaken = max(0, $bossDamage - $damageBlockedByPlayer);
        $playerHp -= $playerDamageTaken;
        if ($playerHp < 0) $playerHp = 0;
    }

    // Обновляем запись боя
    $pdo->prepare("UPDATE boss_fights SET player_current_hp = ?, boss_current_hp = ?, last_attack_at = NOW() WHERE id = ?")
        ->execute([$playerHp, $bossHp, $fight['id']]);

    // Проверяем завершение боя
    $status = 'active';
    $cooldownMinutes = $boss['cooldown_minutes'];
    $rewardExp = 0;
    $rewardBucks = 0;

    if ($playerHp <= 0) {
        $status = 'lost';
        // Проигрыш: награды нет, кулдаун ставим
        $cooldownUntil = date('Y-m-d H:i:s', time() + $cooldownMinutes * 60);
        $pdo->prepare("UPDATE boss_fights SET status = ?, cooldown_until = ? WHERE id = ?")
            ->execute([$status, $cooldownUntil, $fight['id']]);
    } elseif ($bossHp <= 0) {
        $status = 'won';
        $rewardExp = $boss['reward_exp'];
        $rewardBucks = $boss['reward_bucks'];

        // Начисляем награду
        $pdo->prepare("UPDATE users SET exp = exp + ?, bucks = bucks + ? WHERE id = ?")
            ->execute([$rewardExp, $rewardBucks, $userId]);

        // Добавляем клановый опыт
        addUserParticipation($userId, $rewardExp); // или отдельно addClanExp

        // Ставим кулдаун
        $cooldownUntil = date('Y-m-d H:i:s', time() + $cooldownMinutes * 60);
        $pdo->prepare("UPDATE boss_fights SET status = ?, cooldown_until = ? WHERE id = ?")
            ->execute([$status, $cooldownUntil, $fight['id']]);

        // Пересчитываем уровень и статы (если exp изменился)
        recalcUserStats($userId);
    }

    return [
        'fight_id' => $fight['id'],
        'status' => $status,
        'player_hp' => $playerHp,
        'boss_hp' => $bossHp,
        'player_damage' => $damageTakenByBoss,
        'player_crit' => $isCrit,
        'boss_damage' => $playerDamageTaken,
        'boss_crit' => $bossCrit,
        'reward_exp' => $rewardExp,
        'reward_bucks' => $rewardBucks
    ];
}
// ==================== СЛУЖБА ПОДДЕРЖКИ (TICKETS) ====================

/**
 * Создать новый тикет
 * @param int $userId ID автора
 * @param string $subject Тема (одно из: Ошибки, Предложения, Платежи, Общие вопросы)
 * @param string $message Текст сообщения
 * @return array ['success' => bool, 'ticket_id' => int, 'message' => string]
 */
function createTicket($userId, $subject, $message) {
    global $pdo;

    $allowedSubjects = ['Ошибки', 'Предложения', 'Платежи', 'Общие вопросы'];
    if (!in_array($subject, $allowedSubjects)) {
        return ['success' => false, 'message' => 'Недопустимая тема.'];
    }

    if (empty(trim($message))) {
        return ['success' => false, 'message' => 'Сообщение не может быть пустым.'];
    }

    $pdo->beginTransaction();
    try {
        // Вставляем тикет
        $stmt = $pdo->prepare("
            INSERT INTO support_tickets (user_id, subject, status, created_at, updated_at)
            VALUES (?, ?, 'open', NOW(), NOW())
        ");
        $stmt->execute([$userId, $subject]);
        $ticketId = $pdo->lastInsertId();

        // Вставляем первое сообщение
        $stmt = $pdo->prepare("
            INSERT INTO support_messages (ticket_id, user_id, message, created_at, is_staff)
            VALUES (?, ?, ?, NOW(), 0)
        ");
        $stmt->execute([$ticketId, $userId, $message]);

        $pdo->commit();
        return ['success' => true, 'ticket_id' => $ticketId];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()];
    }
}

/**
 * Добавить сообщение в тикет
 * @param int $ticketId ID тикета
 * @param int $userId ID автора сообщения
 * @param string $message Текст
 * @param bool $isStaff Является ли автор сотрудником поддержки
 * @return array ['success' => bool, 'message' => string]
 */
function addTicketMessage($ticketId, $userId, $message, $isStaff = false) {
    global $pdo;

    if (empty(trim($message))) {
        return ['success' => false, 'message' => 'Сообщение не может быть пустым.'];
    }

    $ticket = getTicketById($ticketId);
    if (!$ticket) {
        return ['success' => false, 'message' => 'Тикет не найден.'];
    }

    // Проверяем статус
    if (!isset($ticket['ticket']['status']) || $ticket['ticket']['status'] !== 'open') {
        return ['success' => false, 'message' => 'Тикет закрыт. Нельзя добавить сообщение.'];
    }

    // Если не сотрудник, проверяем авторство
    if (!$isStaff && (!isset($ticket['ticket']['user_id']) || $ticket['ticket']['user_id'] != $userId)) {
        return ['success' => false, 'message' => 'Вы не можете отвечать в этом тикете.'];
    }

    $pdo->beginTransaction();
    try {
        $stmt = $pdo->prepare("
            INSERT INTO support_messages (ticket_id, user_id, message, created_at, is_staff)
            VALUES (?, ?, ?, NOW(), ?)
        ");
        $stmt->execute([$ticketId, $userId, $message, $isStaff ? 1 : 0]);

        $stmt = $pdo->prepare("UPDATE support_tickets SET updated_at = NOW() WHERE id = ?");
        $stmt->execute([$ticketId]);

        $pdo->commit();
        return ['success' => true];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()];
    }
}

/**
 * Получить тикет по ID (с сообщениями)
 * @param int $ticketId
 * @param int|null $userId Если указан, проверяется принадлежность пользователю (кроме админа)
 * @return array|null Массив с ключами ticket (массив) и messages (массив сообщений)
 */
function getTicketById($ticketId, $userId = null) {
    global $pdo;

    // Получаем тикет
    $stmt = $pdo->prepare("SELECT * FROM support_tickets WHERE id = ?");
    $stmt->execute([$ticketId]);
    $ticket = $stmt->fetch();
    if (!$ticket) return null;

    // Проверка прав: если userId указан и это не админ, то пользователь должен быть автором
    if ($userId !== null) {
        $user = getUserById($userId);
        if (!$user || ($user['role'] !== 'admin' && $user['role'] !== 'moderator' && $ticket['user_id'] != $userId)) {
            return null;
        }
    }

    // Получаем сообщения
    $stmt = $pdo->prepare("
        SELECT m.*, u.username 
        FROM support_messages m
        LEFT JOIN users u ON m.user_id = u.id
        WHERE m.ticket_id = ?
        ORDER BY m.created_at ASC
    ");
    $stmt->execute([$ticketId]);
    $messages = $stmt->fetchAll();

    return ['ticket' => $ticket, 'messages' => $messages];
}

/**
 * Получить тикеты пользователя
 * @param int $userId
 * @param string|null $status Фильтр по статусу ('open', 'closed' или null для всех)
 * @return array
 */
function getTicketsByUser($userId, $status = null) {
    global $pdo;
    $sql = "SELECT * FROM support_tickets WHERE user_id = ?";
    $params = [$userId];
    if ($status) {
        $sql .= " AND status = ?";
        $params[] = $status;
    }
    $sql .= " ORDER BY updated_at DESC";
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchAll();
}

/**
 * Закрыть тикет
 * @param int $ticketId
 * @param int $userId ID пользователя, выполняющего закрытие (проверка прав внутри)
 * @return array ['success' => bool, 'message' => string]
 */
function closeTicket($ticketId, $userId) {
    global $pdo;

    $ticket = getTicketById($ticketId);
    if (!$ticket) {
        return ['success' => false, 'message' => 'Тикет не найден.'];
    }

    // Проверка прав
    $user = getUserById($userId);
    $isStaff = ($user && ($user['role'] === 'admin' || $user['role'] === 'moderator'));
    if (!$isStaff && (!isset($ticket['ticket']['user_id']) || $ticket['ticket']['user_id'] != $userId)) {
        return ['success' => false, 'message' => 'Недостаточно прав для закрытия тикета.'];
    }

    // Проверяем, не закрыт ли уже
    if (isset($ticket['ticket']['status']) && $ticket['ticket']['status'] === 'closed') {
        return ['success' => false, 'message' => 'Тикет уже закрыт.'];
    }

    $stmt = $pdo->prepare("UPDATE support_tickets SET status = 'closed', updated_at = NOW() WHERE id = ?");
    $success = $stmt->execute([$ticketId]);

    return $success ? ['success' => true] : ['success' => false, 'message' => 'Ошибка при закрытии.'];
}

/**
 * Количество непрочитанных ответов поддержки для пользователя
 * Считаем количество тикетов в статусе open, где есть сообщения от поддержки (is_staff=1)
 * и где последнее прочтение пользователем (user_last_read_at) меньше времени последнего сообщения от поддержки.
 * Для упрощения добавим в таблицу support_tickets поле user_last_read_at (timestamp).
 * @param int $userId
 * @return int
 */
function getUserUnreadSupportReplies($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT COUNT(DISTINCT t.id)
        FROM support_tickets t
        WHERE t.user_id = ? AND t.status = 'open'
          AND EXISTS (
              SELECT 1 FROM support_messages m
              WHERE m.ticket_id = t.id
                AND m.is_staff = 1
                AND (t.user_last_read_at IS NULL OR m.created_at > t.user_last_read_at)
          )
    ");
    $stmt->execute([$userId]);
    return (int)$stmt->fetchColumn();
}

/**
 * Обновить время последнего прочтения тикета пользователем (при просмотре)
 * @param int $ticketId
 * @param int $userId
 */
function markTicketAsReadByUser($ticketId, $userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        UPDATE support_tickets SET user_last_read_at = NOW()
        WHERE id = ? AND user_id = ?
    ");
    $stmt->execute([$ticketId, $userId]);
}

/**
 * Получить количество непрочитанных тикетов для персонала (новые сообщения от пользователей)
 * Считаем количество тикетов со статусом open, где есть сообщения от пользователя (is_staff=0)
 * и где последнее прочтение персоналом (staff_last_read_at) меньше времени последнего сообщения от пользователя.
 * Для этого нужно добавить поле staff_last_read_at в support_tickets.
 * @return int
 */
function getUnreadTicketsForStaff() {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT COUNT(DISTINCT t.id)
        FROM support_tickets t
        WHERE t.status = 'open'
          AND EXISTS (
              SELECT 1 FROM support_messages m
              WHERE m.ticket_id = t.id
                AND m.is_staff = 0
                AND (t.staff_last_read_at IS NULL OR m.created_at > t.staff_last_read_at)
          )
    ");
    $stmt->execute();
    return (int)$stmt->fetchColumn();
}

/**
 * Обновить время последнего прочтения тикета персоналом (при просмотре в админке)
 * @param int $ticketId
 */
function markTicketAsReadByStaff($ticketId) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE support_tickets SET staff_last_read_at = NOW() WHERE id = ?");
    $stmt->execute([$ticketId]);
}

/**
 * Получить все тикеты (для админки) с фильтром и пагинацией
 * @param string|null $status
 * @param int $page
 * @param int $perPage
 * @return array
 */
function getAllTickets($status = null, $page = 1, $perPage = 20) {
    global $pdo;
    $offset = ($page - 1) * $perPage;
    $sql = "SELECT t.*, u.username FROM support_tickets t JOIN users u ON t.user_id = u.id";
    if ($status) {
        $sql .= " WHERE t.status = ?";
    }
    $sql .= " ORDER BY t.updated_at DESC LIMIT ? OFFSET ?";
    
    $stmt = $pdo->prepare($sql);
    $paramIndex = 1;
    if ($status) {
        $stmt->bindValue($paramIndex++, $status, PDO::PARAM_STR);
    }
    $stmt->bindValue($paramIndex++, (int)$perPage, PDO::PARAM_INT);
    $stmt->bindValue($paramIndex++, (int)$offset, PDO::PARAM_INT);
    
    $stmt->execute();
    return $stmt->fetchAll();
}

/**
 * Получить общее количество тикетов (для пагинации в админке)
 */
function countAllTickets($status = null) {
    global $pdo;
    $sql = "SELECT COUNT(*) FROM support_tickets";
    $params = [];
    if ($status) {
        $sql .= " WHERE status = ?";
        $params[] = $status;
    }
    $stmt = $pdo->prepare($sql);
    $stmt->execute($params);
    return $stmt->fetchColumn();
}

// ==================== ПОДПОЛЬНЫЕ БОИ (5 ИГРОКОВ, КАЖДЫЙ САМ ЗА СЕБЯ) ====================


// ---------- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ----------

/**
 * Получить данные подпольного боя по ID
 */
function getUndergroundFight($fightId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM underground_fights WHERE id = ?");
    $stmt->execute([$fightId]);
    return $stmt->fetch();
}

/**
 * Получить участников подпольного боя (живых и мёртвых)
 */
function getUndergroundParticipants($fightId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT up.*, u.username, u.side, u.role, u.level, u.attack, u.defense, u.accuracy, u.recovery, u.defense_percent,
               u.avatar_type, u.custom_avatar, u.gender
        FROM underground_participants up
        LEFT JOIN users u ON up.user_id = u.id AND up.is_bot = 0
        WHERE up.fight_id = ?
        ORDER BY up.joined_at ASC
    ");
    $stmt->execute([$fightId]);
    $participants = $stmt->fetchAll();

    foreach ($participants as &$p) {
        if ($p['is_bot']) {
            $p['bot_data'] = json_decode($p['bot_data'], true) ?: [];
        }
        // Безопасно подставляем статы для ботов
    }
    return $participants;
}

/**
 * Проверить, участвует ли пользователь в активном подпольном бою
 */
function isInUndergroundFight($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT f.id FROM underground_fights f
        JOIN underground_participants p ON f.id = p.fight_id
        WHERE p.user_id = ? AND f.status IN ('waiting', 'active') AND p.eliminated = 0
        LIMIT 1
    ");
    $stmt->execute([$userId]);
    return $stmt->fetchColumn();
}

/**
 * Получить текущий бой пользователя (если есть)
 */
function getCurrentUndergroundFight($userId) {
    global $pdo;
    $stmt = $pdo->prepare("
        SELECT f.* FROM underground_fights f
        JOIN underground_participants p ON f.id = p.fight_id
        WHERE p.user_id = ? AND f.status IN ('waiting', 'active') AND p.eliminated = 0
        LIMIT 1
    ");
    $stmt->execute([$userId]);
    return $stmt->fetch();
}

/**
 * Добавить запись в лог подпольного боя
 */
function addUndergroundLog($fightId, $userId, $actionType, $message) {
    global $pdo;
    $stmt = $pdo->prepare("INSERT INTO underground_actions_log (fight_id, user_id, action_type, message, created_at) VALUES (?, ?, ?, ?, NOW())");
    $stmt->execute([$fightId, $userId, $actionType, $message]);
}

/**
 * Регенерация здоровья и выносливости участника (каждые 10 секунд)
 */
function regenerateInUnderground($participantId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT up.*, u.recovery FROM underground_participants up LEFT JOIN users u ON up.user_id = u.id WHERE up.id = ?");
    $stmt->execute([$participantId]);
    $part = $stmt->fetch();
    if (!$part) return;

    // Если бот, recovery берём из bot_data
    if ($part['is_bot']) {
        $botData = json_decode($part['bot_data'], true) ?: [];
        $recovery = $botData['recovery'] ?? 1;
    } else {
        $recovery = $part['recovery'] ?? 1;
    }

    $lastAction = strtotime($part['last_action'] ?? $part['joined_at']);
    $now = time();
    $diff = $now - $lastAction;
    if ($diff <= 0) return;

    // Восстанавливаем здоровье (recovery/60 в секунду)
    $hpRegen = round(($recovery / 60) * $diff);
    $newHp = min($part['max_hp'], $part['current_hp'] + $hpRegen);
    // Восстанавливаем выносливость (recovery/30 в секунду — быстрее, чем здоровье)
    $stamRegen = round(($recovery / 30) * $diff);
    $newStam = min($part['max_stamina'], $part['current_stamina'] + $stamRegen);

    if ($newHp != $part['current_hp'] || $newStam != $part['current_stamina']) {
        $stmt = $pdo->prepare("UPDATE underground_participants SET current_hp = ?, current_stamina = ? WHERE id = ?");
        $stmt->execute([$newHp, $newStam, $participantId]);
    }
}

/**
 * Обновить last_action для участника (вызывать после каждого действия)
 */
function updateUndergroundLastAction($participantId) {
    global $pdo;
    $stmt = $pdo->prepare("UPDATE underground_participants SET last_action = NOW() WHERE id = ?");
    $stmt->execute([$participantId]);
}

// ---------- ПОИСК / СОЗДАНИЕ БОЯ ----------

/**
 * Найти подходящий открытый бой или создать новый
 */
function findOrCreateUndergroundFight($userId) {
    global $pdo;

    // Проверяем, не в бою ли уже
    if (isInUndergroundFight($userId)) {
        return ['success' => false, 'message' => 'Вы уже участвуете в подпольном бою.'];
    }

    $user = getUserById($userId);
    if (!$user) return ['success' => false, 'message' => 'Пользователь не найден.'];

    $pdo->beginTransaction();

    try {
        // Ищем открытый бой (waiting) с подходящим уровнем и местом
        $stmt = $pdo->prepare("
            SELECT * FROM underground_fights
            WHERE status = 'waiting' AND current_players < capacity
                AND min_level <= ? AND max_level >= ?
            ORDER BY created_at ASC
            LIMIT 1
            FOR UPDATE
        ");
        $stmt->execute([$user['level'], $user['level']]);
        $fight = $stmt->fetch();

        if ($fight) {
            // Присоединяемся к существующему
            $fightId = $fight['id'];
            $capacity = $fight['capacity'];
            $current = $fight['current_players'] + 1;
            $stmt = $pdo->prepare("UPDATE underground_fights SET current_players = ? WHERE id = ?");
            $stmt->execute([$current, $fightId]);

            // Вставляем участника
            $stmt = $pdo->prepare("
                INSERT INTO underground_participants
                (fight_id, user_id, max_hp, max_stamina, current_hp, current_stamina, joined_at, last_action)
                VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())
            ");
            $stmt->execute([
                $fightId,
                $userId,
                $user['health'],
                $user['stamina'],
                $user['health'],
                $user['stamina']
            ]);

            // Если набралось 5 игроков, запускаем бой
            if ($current >= $capacity) {
                $stmt = $pdo->prepare("UPDATE underground_fights SET status = 'active', started_at = NOW() WHERE id = ?");
                $stmt->execute([$fightId]);
                addUndergroundLog($fightId, null, 'system', 'Бой начат! Участники: 5 человек.');
            } else {
                addUndergroundLog($fightId, $userId, 'join', $user['username'] . ' присоединился к бою (' . $current . '/' . $capacity . ')');
            }

            $pdo->commit();
            return ['success' => true, 'fight_id' => $fightId, 'status' => $fight['status']];
        } else {
            // Создаём новый бой
            $minLevel = max(1, $user['level'] - 10);
            $maxLevel = $user['level'] + 10;
            $stmt = $pdo->prepare("
                INSERT INTO underground_fights (min_level, max_level, created_at, current_players)
                VALUES (?, ?, NOW(), 1)
            ");
            $stmt->execute([$minLevel, $maxLevel]);
            $fightId = $pdo->lastInsertId();

            // Добавляем создателя как первого участника
            $stmt = $pdo->prepare("
                INSERT INTO underground_participants
                (fight_id, user_id, max_hp, max_stamina, current_hp, current_stamina, joined_at, last_action)
                VALUES (?, ?, ?, ?, ?, ?, NOW(), NOW())
            ");
            $stmt->execute([
                $fightId,
                $userId,
                $user['health'],
                $user['stamina'],
                $user['health'],
                $user['stamina']
            ]);

            addUndergroundLog($fightId, $userId, 'create', $user['username'] . ' создал бой и ждёт противников (1/5)');

            $pdo->commit();
            return ['success' => true, 'fight_id' => $fightId, 'status' => 'waiting'];
        }
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

/**
 * Покинуть подпольный бой (только если он ещё не начался)
 */
function leaveUndergroundFight($userId) {
    global $pdo;

    $fight = getCurrentUndergroundFight($userId);
    if (!$fight) return ['success' => false, 'message' => 'Вы не в подпольном бою.'];

    if ($fight['status'] != 'waiting') {
        return ['success' => false, 'message' => 'Нельзя покинуть уже начавшийся бой.'];
    }

    $pdo->beginTransaction();
    try {
        // Удаляем участника
        $stmt = $pdo->prepare("DELETE FROM underground_participants WHERE fight_id = ? AND user_id = ?");
        $stmt->execute([$fight['id'], $userId]);

        // Уменьшаем счётчик игроков
        $newCount = $fight['current_players'] - 1;
        $stmt = $pdo->prepare("UPDATE underground_fights SET current_players = ? WHERE id = ?");
        $stmt->execute([$newCount, $fight['id']]);

        // Если никого не осталось, удаляем бой
        if ($newCount <= 0) {
            $stmt = $pdo->prepare("DELETE FROM underground_fights WHERE id = ?");
            $stmt->execute([$fight['id']]);
        } else {
            addUndergroundLog($fight['id'], $userId, 'leave', getUserById($userId)['username'] . ' покинул бой.');
        }

        $pdo->commit();
        return ['success' => true, 'message' => 'Вы покинули подпольный бой.'];
    } catch (Exception $e) {
        $pdo->rollBack();
        return ['success' => false, 'message' => 'Ошибка: ' . $e->getMessage()];
    }
}

// ---------- БОЕВЫЕ ДЕЙСТВИЯ ----------

/**
 * Атаковать случайного противника в бою
 */
function attackRandomInUnderground($userId) {
    $fight = getCurrentUndergroundFight($userId);
    if (!$fight || $fight['status'] != 'active') {
        return ['success' => false, 'message' => 'Вы не в активном подпольном бою.'];
    }

    $participants = getUndergroundParticipants($fight['id']);
    $attacker = null;
    $targets = [];
    foreach ($participants as $p) {
        if ($p['user_id'] == $userId && !$p['eliminated']) {
            $attacker = $p;
        } elseif (!$p['eliminated']) {
            $targets[] = $p;
        }
    }

    if (!$attacker) return ['success' => false, 'message' => 'Вы не участвуете в бою.'];
    if (empty($targets)) return ['success' => false, 'message' => 'Нет живых противников.'];

    // Выбираем случайную цель
    $target = $targets[array_rand($targets)];
    return processUndergroundAttack($attacker, $target, $fight);
}

/**
 * Атаковать конкретного противника
 */
function attackTargetInUnderground($userId, $targetId) {
    $fight = getCurrentUndergroundFight($userId);
    if (!$fight || $fight['status'] != 'active') {
        return ['success' => false, 'message' => 'Вы не в активном подпольном бою.'];
    }

    $participants = getUndergroundParticipants($fight['id']);
    $attacker = null;
    $target = null;
    foreach ($participants as $p) {
        if ($p['user_id'] == $userId && !$p['eliminated']) {
            $attacker = $p;
        }
        if ($p['user_id'] == $targetId && !$p['eliminated']) {
            $target = $p;
        }
    }

    if (!$attacker) return ['success' => false, 'message' => 'Вы не участвуете в бою.'];
    if (!$target) return ['success' => false, 'message' => 'Цель не найдена или мертва.'];
    if ($attacker['user_id'] == $target['user_id']) return ['success' => false, 'message' => 'Нельзя атаковать себя.'];

    return processUndergroundAttack($attacker, $target, $fight);
}

/**
 * Основная логика атаки
 */
function processUndergroundAttack($attacker, $target, $fight) {
    global $pdo;

    // Регенерируем перед атакой
    regenerateInUnderground($attacker['id']);
    regenerateInUnderground($target['id']);

    // Получаем свежие данные
    $stmt = $pdo->prepare("SELECT * FROM underground_participants WHERE id = ?");
    $stmt->execute([$attacker['id']]);
    $attacker = $stmt->fetch();
    $stmt->execute([$target['id']]);
    $target = $stmt->fetch();

    // Стоимость выносливости
    $staminaCost = round(3.5 * sqrt($attacker['max_hp'] / 10)); // примерная формула
    if ($attacker['current_stamina'] < $staminaCost) {
        return ['success' => false, 'message' => 'Недостаточно выносливости.'];
    }

    // Определяем характеристики атакующего
    if ($attacker['is_bot']) {
        $botData = json_decode($attacker['bot_data'], true);
        $attackVal = $botData['attack'] ?? 10;
        $accuracy = $botData['accuracy'] ?? 5;
        $critChance = $accuracy;
        $attackerName = $botData['username'] ?? 'Бот';
    } else {
        $user = getUserById($attacker['user_id']);
        $attackVal = $user['attack'];
        $accuracy = $user['accuracy'];
        $critChance = $accuracy;
        $attackerName = $user['username'];
    }

    // Характеристики цели
    if ($target['is_bot']) {
        $targetData = json_decode($target['bot_data'], true);
        $defPercent = $targetData['defense_percent'] ?? 0;
        $targetName = $targetData['username'] ?? 'Бот';
    } else {
        $targetUser = getUserById($target['user_id']);
        $defPercent = $targetUser['defense_percent'];
        $targetName = $targetUser['username'];
    }

    // Расчёт урона
    $damage = $attackVal;
    $isCrit = (mt_rand(1, 100) <= $critChance);
    if ($isCrit) $damage = round($damage * 1.5);

    $damageBlocked = round($damage * $defPercent / 100);
    $damageTaken = max(0, $damage - $damageBlocked);

    $newHp = $target['current_hp'] - $damageTaken;
    if ($newHp < 0) $newHp = 0;

    // Обновляем состояние цели
    $pdo->prepare("UPDATE underground_participants SET current_hp = ? WHERE id = ?")->execute([$newHp, $target['id']]);

    // Обновляем выносливость атакующего
    $newStam = $attacker['current_stamina'] - $staminaCost;
    if ($newStam < 0) $newStam = 0;
    $pdo->prepare("UPDATE underground_participants SET current_stamina = ? WHERE id = ?")->execute([$newStam, $attacker['id']]);

    // Участие (опыт)
    $participationGain = $damage;
    if ($isCrit) $participationGain = round($participationGain * 1.5);
    $pdo->prepare("UPDATE underground_participants SET participation = participation + ? WHERE id = ?")->execute([$participationGain, $attacker['id']]);

    // Логируем
    $killMsg = '';
    $targetEliminated = false;
    if ($newHp <= 0) {
        $targetEliminated = true;
        $killMsg = ' и убивает его!';
        // Помечаем цель как eliminated
        $pdo->prepare("UPDATE underground_participants SET eliminated = 1, left_at = NOW() WHERE id = ?")->execute([$target['id']]);
        // Увеличиваем счётчик убийств атакующего
        $pdo->prepare("UPDATE underground_participants SET kills = kills + 1 WHERE id = ?")->execute([$attacker['id']]);
    }

    $logMessage = sprintf("%s атакует %s и наносит %d урона%s%s",
        $attackerName, $targetName, $damageTaken,
        $isCrit ? ' (крит)' : '',
        $killMsg
    );
    addUndergroundLog($fight['id'], $attacker['is_bot'] ? null : $attacker['user_id'], 'attack', $logMessage);

    // Проверяем окончание боя
    $participants = getUndergroundParticipants($fight['id']);
    $alive = array_filter($participants, function($p) { return !$p['eliminated']; });
    $aliveCount = count($alive);

    if ($aliveCount <= 1) {
        // Бой окончен
        $winner = $aliveCount == 1 ? reset($alive) : null;
        finishUndergroundFight($fight['id'], $winner ? $winner['user_id'] : null);
    }

    return [
        'success' => true,
        'damage_dealt' => $damage,
        'damage_taken' => $damageTaken,
        'damage_blocked' => $damageBlocked,
        'is_crit' => $isCrit,
        'target_new_hp' => $newHp,
        'target_killed' => $targetEliminated,
        'attacker_new_stamina' => $newStam,
        'message' => $logMessage
    ];
}

// ---------- ЗАВЕРШЕНИЕ БОЯ ----------

/**
 * Завершить подпольный бой и начислить награды
 */
function finishUndergroundFight($fightId, $winnerId) {
    global $pdo;

    $fight = getUndergroundFight($fightId);
    if (!$fight || $fight['status'] == 'finished') return;

    $participants = getUndergroundParticipants($fightId);

    // Рассчитываем награды: базовый пул зависит от уровней участников
    $totalLevel = 0;
    foreach ($participants as $p) {
        if (!$p['is_bot']) {
            $user = getUserById($p['user_id']);
            $totalLevel += $user['level'];
        } else {
            $totalLevel += $p['bot_data']['level'] ?? 1;
        }
    }
    $avgLevel = $totalLevel / max(1, count($participants));
    $baseExp = round($avgLevel * 50);
    $baseBucks = round($avgLevel * 10);

    $pdo->beginTransaction();
    try {
        // Обновляем статус боя
        $pdo->prepare("UPDATE underground_fights SET status = 'finished', finished_at = NOW(), winner_id = ? WHERE id = ?")
            ->execute([$winnerId, $fightId]);

        // Начисляем награды участникам
        foreach ($participants as $p) {
            if ($p['is_bot']) continue;

            $share = $p['participation'] / max(1, array_sum(array_column($participants, 'participation')));
            $expReward = round($baseExp * $share) + ($p['user_id'] == $winnerId ? $baseExp * 2 : 0);
            $bucksReward = round($baseBucks * $share) + ($p['user_id'] == $winnerId ? $baseBucks : 0);

            if ($expReward > 0) {
                addExp($p['user_id'], $expReward);
            }
            if ($bucksReward > 0) {
                $pdo->prepare("UPDATE users SET bucks = bucks + ? WHERE id = ?")->execute([$bucksReward, $p['user_id']]);
            }

            // Добавляем клановый опыт
            addUserParticipation($p['user_id'], $expReward);
        }

        // Лог
        if ($winnerId) {
            $winner = getUserById($winnerId);
            $winnerName = $winner ? $winner['username'] : 'Неизвестный';
            addUndergroundLog($fightId, $winnerId, 'win', "Победитель подпольного боя: $winnerName!");
        } else {
            addUndergroundLog($fightId, null, 'system', "Бой завершён без победителя (все погибли).");
        }

        $pdo->commit();
    } catch (Exception $e) {
        $pdo->rollBack();
        error_log("finishUndergroundFight error: " . $e->getMessage());
    }
}

/**
 * Получить текущее состояние подпольного боя для отображения
 */
function getUndergroundBattleState($fightId) {
    $fight = getUndergroundFight($fightId);
    if (!$fight) return null;

    $participants = getUndergroundParticipants($fightId);
    // Для каждого участника подставляем имя и аватар
    foreach ($participants as &$p) {
        if ($p['is_bot']) {
            $p['username'] = $p['bot_data']['username'] ?? 'Бот';
            $p['gender'] = $p['bot_data']['gender'] ?? 'Мужской';
            $p['avatar_type'] = 'gender';
            $p['custom_avatar'] = null;
        } else {
            $user = getUserById($p['user_id']);
            $p['username'] = $user['username'];
            $p['gender'] = $user['gender'];
            $p['avatar_type'] = $user['avatar_type'];
            $p['custom_avatar'] = $user['custom_avatar'];
        }
    }

    return [
        'fight' => $fight,
        'participants' => $participants
    ];
}
// ==================== БЕЗОПАСНОСТЬ ====================

/**
 * Генерирует и сохраняет CSRF-токен в сессии.
 * @return string
 */
function generateCsrfToken() {
    if (empty($_SESSION['csrf_token'])) {
        $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['csrf_token'];
}

/**
 * Проверяет переданный CSRF-токен.
 * @param string $token
 * @return bool
 */
function checkCsrfToken($token) {
    return isset($_SESSION['csrf_token']) && hash_equals($_SESSION['csrf_token'], $token);
}

/**
 * Удаляет CSRF-токен после проверки (опционально, для одноразовых).
 */
function consumeCsrfToken() {
    unset($_SESSION['csrf_token']);
}

/**
 * Проверяет превышение лимита запросов для действия.
 * @param string $action    Ключ действия (например, 'login', 'register')
 * @param int    $max       Максимальное количество попыток за период
 * @param int    $period    Период в секундах
 * @param string $identifier Идентификатор (по умолчанию IP + User-Agent)
 * @return bool true - если лимит превышен (блокировать), false - можно выполнять
 */
function isRateLimitExceeded($action, $max = 5, $period = 60, $identifier = null) {
    if ($identifier === null) {
        $identifier = $_SERVER['REMOTE_ADDR'] . '|' . ($_SERVER['HTTP_USER_AGENT'] ?? '');
    }
    $key = 'rate_limit_' . $action . '_' . md5($identifier);
    
    $data = $_SESSION[$key] ?? ['count' => 0, 'reset_time' => time() + $period];
    
    // Сброс счётчика, если время вышло
    if (time() > $data['reset_time']) {
        $data = ['count' => 1, 'reset_time' => time() + $period];
        $_SESSION[$key] = $data;
        return false;
    }
    
    // Увеличение счётчика
    $data['count']++;
    $_SESSION[$key] = $data;
    
    return $data['count'] > $max;
}

/**
 * Очищает счётчик лимита (после успешного действия, например, входа).
 */
function resetRateLimit($action, $identifier = null) {
    if ($identifier === null) {
        $identifier = $_SERVER['REMOTE_ADDR'] . '|' . ($_SERVER['HTTP_USER_AGENT'] ?? '');
    }
    $key = 'rate_limit_' . $action . '_' . md5($identifier);
    unset($_SESSION[$key]);
}

/**
 * Отправляет заголовки безопасности.
 * Вызывать в самом начале каждого скрипта перед любым выводом.
 */
function sendSecurityHeaders() {
    header("X-Frame-Options: DENY");
    header("X-Content-Type-Options: nosniff");
    header("Referrer-Policy: strict-origin-when-cross-origin");
    header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline' https://code.jquery.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self';");
    header("X-Permitted-Cross-Domain-Policies: none");
}
/**
 * Получить или создать запись о положении игрока в открытом мире
 */
/**
 * Получить или создать запись о положении игрока в открытом мире
 */
/**
 * Получить или создать запись о положении игрока в открытом мире
 */
function getPlayerWorldPosition($userId) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM player_world WHERE user_id = ?");
    $stmt->execute([$userId]);
    $pos = $stmt->fetch();
    if (!$pos) {
        // Создаём новую позицию со случайным seed
        $seed = rand(1, 1000000);
        $stmt = $pdo->prepare("INSERT INTO player_world (user_id, seed) VALUES (?, ?)");
        $stmt->execute([$userId, $seed]);
        return ['user_id' => $userId, 'x' => 0, 'y' => 0, 'seed' => $seed, 'last_move' => null];
    }
    return $pos;
}

/**
 * Переместить игрока (если прошло достаточно времени – например, 1 секунда)
 */
function movePlayer($userId, $dx, $dy) {
    global $pdo;
    $pos = getPlayerWorldPosition($userId);
    $newX = $pos['x'] + $dx;
    $newY = $pos['y'] + $dy;

    // Ограничим карту, например, от -10 до 10
    if ($newX < -10 || $newX > 10 || $newY < -10 || $newY > 10) {
        return ['success' => false, 'message' => 'Дальше идти нельзя.'];
    }

    // Проверим, не двигался ли игрок слишком часто (защита от спама)
    if ($pos['last_move'] && (time() - strtotime($pos['last_move'])) < 1) {
        return ['success' => false, 'message' => 'Подождите секунду перед следующим шагом.'];
    }

    $pdo->prepare("UPDATE player_world SET x = ?, y = ?, last_move = NOW() WHERE user_id = ?")
        ->execute([$newX, $newY, $userId]);

    return ['success' => true, 'x' => $newX, 'y' => $newY];
}

/**
 * Сгенерировать монстра в текущей клетке, если его нет
 */
function ensureMonsterAtPosition($seed, $x, $y, $playerLevel) {
    global $pdo;
    $stmt = $pdo->prepare("SELECT * FROM world_monsters WHERE seed = ? AND x = ? AND y = ?");
    $stmt->execute([$seed, $x, $y]);
    $monster = $stmt->fetch();
    if ($monster) {
        if ($monster['health'] <= 0) return null; // мёртвый монстр (нужно удалить или воскресить)
        return $monster;
    }

    // Генерируем нового монстра
    $monsterLevel = max(1, $playerLevel + rand(-2, 2));
    $baseStats = getBaseStatsForLevel($monsterLevel);
    $health = $baseStats['health'] * 2;
    $attack = $baseStats['attack'];
    $defense = $baseStats['defense'];
    $accuracy = $baseStats['accuracy'];
    $rewardExp = $monsterLevel * 10;
    $rewardBucks = $monsterLevel * 2;

    $types = ['Громила', 'Бандит', 'Наёмник', 'Псих', 'Шестёрка'];
    $type = $types[array_rand($types)];

    $stmt = $pdo->prepare("
        INSERT INTO world_monsters (seed, x, y, monster_type, level, health, max_health, attack, defense, accuracy, reward_exp, reward_bucks)
        VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
    ");
    $stmt->execute([$seed, $x, $y, $type, $monsterLevel, $health, $health, $attack, $defense, $accuracy, $rewardExp, $rewardBucks]);
    $id = $pdo->lastInsertId();

    return [
        'id' => $id,
        'monster_type' => $type,
        'level' => $monsterLevel,
        'health' => $health,
        'max_health' => $health,
        'attack' => $attack,
        'defense' => $defense,
        'accuracy' => $accuracy,
        'reward_exp' => $rewardExp,
        'reward_bucks' => $rewardBucks
    ];
}

/**
 * Атаковать монстра в текущей клетке
 */
function fightWorldMonster($userId) {
    global $pdo;
    $pos = getPlayerWorldPosition($userId);
    $user = getUserById($userId);

    $monster = ensureMonsterAtPosition($pos['seed'], $pos['x'], $pos['y'], $user['level']);
    if (!$monster) {
        return ['success' => false, 'message' => 'Здесь нет монстра.'];
    }
    if ($monster['health'] <= 0) {
        return ['success' => false, 'message' => 'Монстр уже побеждён.'];
    }

    // Простейший бой: один удар игрока, один удар монстра (как в brawl)
    // Можно использовать существующую логику fight(), но адаптировать под монстра
    $playerAttack = $user['attack'];
    $monsterDefense = $monster['defense'];
    $damageToMonster = max(1, $playerAttack - $monsterDefense / 2);
    $isCrit = (mt_rand(1, 100) <= $user['accuracy']);
    if ($isCrit) $damageToMonster = round($damageToMonster * 1.5);

    $newMonsterHp = $monster['health'] - $damageToMonster;
    if ($newMonsterHp < 0) $newMonsterHp = 0;

    // Монстр отвечает, если жив
    $damageToPlayer = 0;
    $playerNewHp = $user['health'];
    if ($newMonsterHp > 0) {
        $monsterAttack = $monster['attack'];
        $playerDefensePercent = $user['defense_percent'];
        $damageToPlayer = max(1, $monsterAttack - $monsterAttack * $playerDefensePercent / 100);
        $playerNewHp = $user['health'] - $damageToPlayer;
        if ($playerNewHp < 0) $playerNewHp = 0;
    }

    // Обновляем здоровье монстра в БД
    $pdo->prepare("UPDATE world_monsters SET health = ? WHERE id = ?")->execute([$newMonsterHp, $monster['id']]);

    $victory = ($newMonsterHp <= 0);
    $defeat = ($playerNewHp <= 0);

    if ($victory) {
        // Награда
        $expGain = $monster['reward_exp'];
        $bucksGain = $monster['reward_bucks'];
        $pdo->prepare("UPDATE users SET exp = exp + ?, bucks = bucks + ? WHERE id = ?")->execute([$expGain, $bucksGain, $userId]);
        addUserParticipation($userId, $expGain);
        // Удаляем монстра или помечаем как мёртвого (можно оставить health=0)
    } else {
        $expGain = 0;
        $bucksGain = 0;
    }

    if ($defeat) {
        // Смерть игрока – возможно, телепорт в безопасную зону (0,0)
        $pdo->prepare("UPDATE player_world SET x = 0, y = 0 WHERE user_id = ?")->execute([$userId]);
        // Восстановить здоровье? Пока оставим как есть, может быть лечиться в городе.
    }

    return [
        'success' => true,
        'monster' => $monster,
        'damage_to_monster' => $damageToMonster,
        'new_monster_hp' => $newMonsterHp,
        'damage_to_player' => $damageToPlayer,
        'new_player_hp' => $playerNewHp,
        'is_crit' => $isCrit,
        'victory' => $victory,
        'defeat' => $defeat,
        'exp_gain' => $expGain,
        'bucks_gain' => $bucksGain
    ];
}

?>