#pragma semicolon 1

#include <sourcemod>
#include <sdktools>
#include <cstrike>
#include <admin>

#define PLUGIN_VERSION "1.5"

// ==================== КОНСТАНТЫ ====================
#define SPECBAN_STEAMID_LENGTH 32
#define SPECBAN_NAME_LENGTH 64
#define SPECBAN_REASON_LENGTH 256
#define SPECBAN_IP_LENGTH 32
#define SPECBAN_ADMINNAME_LENGTH 64
#define SPECBAN_QUERY_LENGTH 1024
#define SPECBAN_HISTORY_DISPLAY 10
#define SPECBAN_MAX_LINKED 32

public Plugin:myinfo = 
{
    name = "SpecBan",
    author = "Aidar Zamaldinov",
    description = "Плагин для бана/разбана игроков в зрителей с сохранением в SQLite по SteamID",
    version = PLUGIN_VERSION,
    url = ""
};

// ==================== ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ ====================
new Handle:g_hDatabase = INVALID_HANDLE;
new Handle:g_hSpecBanTimer[MAXPLAYERS+1] = {INVALID_HANDLE, ...};
new bool:g_bPermanentSpecBan[MAXPLAYERS+1];
new g_iBanEndTime[MAXPLAYERS+1];      
new String:g_sBanReason[MAXPLAYERS+1][SPECBAN_REASON_LENGTH];
new String:g_sBanAdminName[MAXPLAYERS+1][SPECBAN_ADMINNAME_LENGTH]; 
new g_iPendingTarget[MAXPLAYERS+1];   
new g_iPendingDuration[MAXPLAYERS+1]; 
new g_iPendingState[MAXPLAYERS+1];    

// ==================== ИНИЦИАЛИЗАЦИЯ ПЛАГИНА ====================
public OnPluginStart()
{
    RegAdminCmd("sm_specban", Command_SpecBanMenu, ADMFLAG_BAN, "Меню для бана/разбана в зрители");
    RegAdminCmd("sm_checkplayer", Command_CheckPlayer, ADMFLAG_GENERIC, "Проверить историю игрока по SteamID или нику");
    RegAdminCmd("sm_specban_reload", Command_ReloadConfig, ADMFLAG_ROOT, "Перезагрузить конфигурацию SpecBan");
    
    HookEvent("player_team", Event_PlayerTeam);
    HookEvent("player_connect", Event_PlayerConnect);
    
    InitializeDatabase();
    CreateTimer(60.0, Timer_UpdateHints, _, TIMER_REPEAT);
    
    // Очистка всех pending состояний при старте плагина
    for (new i = 1; i <= MaxClients; i++)
    {
        ResetPlayerBanData(i);
    }
}

ResetPlayerBanData(client)
{
    if (g_hSpecBanTimer[client] != INVALID_HANDLE)
    {
        KillTimer(g_hSpecBanTimer[client]);
        g_hSpecBanTimer[client] = INVALID_HANDLE;
    }
    g_bPermanentSpecBan[client] = false;
    g_iBanEndTime[client] = 0;
    g_sBanReason[client][0] = '\0';
    g_sBanAdminName[client][0] = '\0';
    g_iPendingTarget[client] = 0;
    g_iPendingDuration[client] = 0;
    g_iPendingState[client] = 0;
}

InitializeDatabase()
{
    new String:error[255];
    g_hDatabase = SQLite_UseDatabase("specban", error, sizeof(error));
    if (g_hDatabase == INVALID_HANDLE)
    {
        SetFailState("Не удалось подключиться к базе данных: %s", error);
    }
    
    // ОСНОВНАЯ ТАБЛИЦА АКТИВНЫХ БАНОВ
    SQL_FastQuery(g_hDatabase, "CREATE TABLE IF NOT EXISTS specbans (steamid TEXT PRIMARY KEY, name TEXT, ip TEXT, endtime INTEGER, reason TEXT, admin_name TEXT, ban_time INTEGER)");
    
    // НОВАЯ ТАБЛИЦА ДЛЯ ИСТОРИИ БАНОВ (НИКОГДА НЕ УДАЛЯЕТСЯ)
    SQL_FastQuery(g_hDatabase, "CREATE TABLE IF NOT EXISTS ban_history (id INTEGER PRIMARY KEY AUTOINCREMENT, steamid TEXT, name TEXT, ip TEXT, ban_time INTEGER, unban_time INTEGER, reason TEXT, admin_name TEXT)");
    
    // Таблица для истории SteamID (связи между аккаунтами)
    SQL_FastQuery(g_hDatabase, "CREATE TABLE IF NOT EXISTS steam_history (id INTEGER PRIMARY KEY AUTOINCREMENT, main_steamid TEXT, linked_steamid TEXT, first_seen INTEGER, last_seen INTEGER, UNIQUE(main_steamid, linked_steamid))");
    
    // Таблица для истории ников
    SQL_FastQuery(g_hDatabase, "CREATE TABLE IF NOT EXISTS name_history (id INTEGER PRIMARY KEY AUTOINCREMENT, steamid TEXT, name TEXT, first_seen INTEGER, last_seen INTEGER)");
    
    // Индексы для быстрого поиска
    SQL_FastQuery(g_hDatabase, "CREATE INDEX IF NOT EXISTS idx_steam_history_main ON steam_history (main_steamid)");
    SQL_FastQuery(g_hDatabase, "CREATE INDEX IF NOT EXISTS idx_steam_history_linked ON steam_history (linked_steamid)");
    SQL_FastQuery(g_hDatabase, "CREATE INDEX IF NOT EXISTS idx_name_history_steam ON name_history (steamid)");
    SQL_FastQuery(g_hDatabase, "CREATE INDEX IF NOT EXISTS idx_ban_history_steam ON ban_history (steamid)");
    SQL_FastQuery(g_hDatabase, "CREATE INDEX IF NOT EXISTS idx_ban_history_name ON ban_history (name)");
    SQL_FastQuery(g_hDatabase, "CREATE INDEX IF NOT EXISTS idx_ban_history_endtime ON specbans (endtime)");
}

public OnPluginEnd()
{
    if (g_hDatabase != INVALID_HANDLE)
    {
        CloseHandle(g_hDatabase);
    }
    
    for (new i = 1; i <= MaxClients; i++)
    {
        if (g_hSpecBanTimer[i] != INVALID_HANDLE)
        {
            KillTimer(g_hSpecBanTimer[i]);
            g_hSpecBanTimer[i] = INVALID_HANDLE;
        }
    }
}

// ==================== КОМАНДА ПЕРЕЗАГРУЗКИ ====================
public Action:Command_ReloadConfig(client, args)
{
    ReplyToCommand(client, "[SpecBan] Перезагрузка плагина...");
    InitializeDatabase();
    ReplyToCommand(client, "[SpecBan] База данных переинициализирована.");
    return Plugin_Handled;
}

// ==================== ОБРАБОТКА АВТОРИЗАЦИИ ====================
public OnClientAuthorized(client, const String:auth[])
{
    if (IsFakeClient(client))
        return;
    
    decl String:steamid[SPECBAN_STEAMID_LENGTH];
    if (!GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid)))
    {
        LogError("Не удалось получить SteamID для клиента %N", client);
        return;
    }
    
    // Экранируем SteamID для SQL
    decl String:escapedSteamID[SPECBAN_STEAMID_LENGTH * 2 + 1];
    SQL_EscapeString(g_hDatabase, steamid, escapedSteamID, sizeof(escapedSteamID));
    
    // Проверяем бан
    CheckPlayerBan(client, escapedSteamID);
    
    // Проверяем историю SteamID и сохраняем данные
    CheckPlayerHistory(client, escapedSteamID);
}

CheckPlayerBan(client, const String:escapedSteamID[])
{
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT endtime, reason, admin_name FROM specbans WHERE steamid = '%s'", 
        escapedSteamID);
    
    new Handle:hQuery = SQL_Query(g_hDatabase, query);
    if (hQuery == INVALID_HANDLE)
    {
        LogError("Ошибка SQL запроса в CheckPlayerBan: %s", query);
        return;
    }
    
    if (SQL_FetchRow(hQuery))
    {
        new endtime = SQL_FetchInt(hQuery, 0);
        decl String:reason[SPECBAN_REASON_LENGTH];
        SQL_FetchString(hQuery, 1, reason, sizeof(reason));
        
        decl String:adminName[SPECBAN_ADMINNAME_LENGTH];
        SQL_FetchString(hQuery, 2, adminName, sizeof(adminName));
        
        if (endtime == 0 || endtime > GetTime())
        {
            ApplyBanToClient(client, endtime, reason, adminName);
        }
        else if (endtime > 0 && endtime <= GetTime())
        {
            // Бан истёк - удаляем из активных, но история остаётся
            decl String:deleteQuery[SPECBAN_QUERY_LENGTH];
            Format(deleteQuery, sizeof(deleteQuery), 
                "DELETE FROM specbans WHERE steamid = '%s'", 
                escapedSteamID);
            SQL_FastQuery(g_hDatabase, deleteQuery);
        }
    }
    
    CloseHandle(hQuery);
}

ApplyBanToClient(client, endtime, const String:reason[], const String:adminName[])
{
    g_bPermanentSpecBan[client] = (endtime == 0);
    g_iBanEndTime[client] = endtime;
    strcopy(g_sBanReason[client], sizeof(g_sBanReason[]), reason);
    strcopy(g_sBanAdminName[client], sizeof(g_sBanAdminName[]), adminName);
    
    if (!g_bPermanentSpecBan[client] && g_iBanEndTime[client] > 0)
    {
        new remaining = g_iBanEndTime[client] - GetTime();
        if (remaining > 0)
        {
            g_hSpecBanTimer[client] = CreateTimer(float(remaining), Timer_Unban, GetClientUserId(client));
        }
    }
    
    if (IsClientInGame(client))
    {
        PrintHintText(client, "Обнаружен бан в зрители по SteamID. Вы забанены в зрители! Не пытайтесь выходить.");
    }
    else
    {
        CreateTimer(1.0, Timer_DelayedHint, GetClientUserId(client));
    }
}

// ==================== ИСТОРИЯ ИГРОКА ====================
CheckPlayerHistory(client, const String:escapedSteamID[])
{
    SaveNameHistory(client, escapedSteamID);
    UpdateSteamHistoryByIP(client, escapedSteamID);
    CheckSteamIDConnections(client, escapedSteamID);
}

SaveNameHistory(client, const String:escapedSteamID[])
{
    decl String:name[SPECBAN_NAME_LENGTH];
    GetClientName(client, name, sizeof(name));
    
    decl String:escapedName[SPECBAN_NAME_LENGTH * 2 + 1];
    SQL_EscapeString(g_hDatabase, name, escapedName, sizeof(escapedName));
    
    new currentTime = GetTime();
    
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT id, last_seen FROM name_history WHERE steamid = '%s' AND name = '%s'",
        escapedSteamID, escapedName);
    
    new Handle:hQuery = SQL_Query(g_hDatabase, query);
    if (hQuery == INVALID_HANDLE)
    {
        LogError("Ошибка SQL запроса в SaveNameHistory: %s", query);
        return;
    }
    
    if (SQL_FetchRow(hQuery))
    {
        new id = SQL_FetchInt(hQuery, 0);
        CloseHandle(hQuery);
        
        Format(query, sizeof(query), 
            "UPDATE name_history SET last_seen = %d WHERE id = %d",
            currentTime, id);
        SQL_FastQuery(g_hDatabase, query);
    }
    else
    {
        CloseHandle(hQuery);
        
        Format(query, sizeof(query), 
            "INSERT INTO name_history (steamid, name, first_seen, last_seen) VALUES ('%s', '%s', %d, %d)",
            escapedSteamID, escapedName, currentTime, currentTime);
        SQL_FastQuery(g_hDatabase, query);
    }
}

UpdateSteamHistoryByIP(client, const String:currentSteamID[])
{
    decl String:ip[SPECBAN_IP_LENGTH];
    GetClientIP(client, ip, sizeof(ip));
    
    decl String:escapedIP[SPECBAN_IP_LENGTH * 2 + 1];
    SQL_EscapeString(g_hDatabase, ip, escapedIP, sizeof(escapedIP));
    
    new currentTime = GetTime();
    
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT steamid FROM specbans WHERE ip = '%s' AND steamid != '%s'",
        escapedIP, currentSteamID);
    
    new Handle:hQuery = SQL_Query(g_hDatabase, query);
    if (hQuery != INVALID_HANDLE)
    {
        while (SQL_FetchRow(hQuery))
        {
            decl String:linkedSteamID[SPECBAN_STEAMID_LENGTH];
            SQL_FetchString(hQuery, 0, linkedSteamID, sizeof(linkedSteamID));
            
            decl String:escapedLinkedID[SPECBAN_STEAMID_LENGTH * 2 + 1];
            SQL_EscapeString(g_hDatabase, linkedSteamID, escapedLinkedID, sizeof(escapedLinkedID));
            
            CreateSteamLink(currentSteamID, escapedLinkedID, currentTime);
            CreateSteamLink(escapedLinkedID, currentSteamID, currentTime);
        }
        CloseHandle(hQuery);
    }
    
    Format(query, sizeof(query), 
        "SELECT steamid FROM ban_history WHERE ip = '%s' AND steamid != '%s' GROUP BY steamid",
        escapedIP, currentSteamID);
    
    hQuery = SQL_Query(g_hDatabase, query);
    if (hQuery != INVALID_HANDLE)
    {
        while (SQL_FetchRow(hQuery))
        {
            decl String:linkedSteamID[SPECBAN_STEAMID_LENGTH];
            SQL_FetchString(hQuery, 0, linkedSteamID, sizeof(linkedSteamID));
            
            decl String:escapedLinkedID[SPECBAN_STEAMID_LENGTH * 2 + 1];
            SQL_EscapeString(g_hDatabase, linkedSteamID, escapedLinkedID, sizeof(escapedLinkedID));
            
            CreateSteamLink(currentSteamID, escapedLinkedID, currentTime);
            CreateSteamLink(escapedLinkedID, currentSteamID, currentTime);
        }
        CloseHandle(hQuery);
    }
}

CreateSteamLink(const String:mainSteamID[], const String:linkedSteamID[], time)
{
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "INSERT OR IGNORE INTO steam_history (main_steamid, linked_steamid, first_seen, last_seen) VALUES ('%s', '%s', %d, %d)",
        mainSteamID, linkedSteamID, time, time);
    SQL_FastQuery(g_hDatabase, query);
    
    Format(query, sizeof(query), 
        "UPDATE steam_history SET last_seen = %d WHERE main_steamid = '%s' AND linked_steamid = '%s'",
        time, mainSteamID, linkedSteamID);
    SQL_FastQuery(g_hDatabase, query);
}

CheckSteamIDConnections(client, const String:currentSteamID[])
{
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT main_steamid, linked_steamid, first_seen, last_seen FROM steam_history WHERE main_steamid = '%s' OR linked_steamid = '%s'",
        currentSteamID, currentSteamID);
    
    SQL_TQuery(g_hDatabase, SQL_CheckConnectionsCallback, query, GetClientUserId(client));
}

public SQL_CheckConnectionsCallback(Handle:db, Handle:results, const String:error[], any:userid)
{
    if (error[0])
    {
        LogError("SQL Error in CheckConnectionsCallback: %s", error);
        return;
    }
    
    new client = GetClientOfUserId(userid);
    if (!client || !IsClientInGame(client))
        return;
    
    if (results == INVALID_HANDLE || SQL_GetRowCount(results) == 0)
        return;
    
    decl String:playerName[SPECBAN_NAME_LENGTH];
    GetClientName(client, playerName, sizeof(playerName));
    
    decl String:currentSteamID[SPECBAN_STEAMID_LENGTH];
    GetClientAuthId(client, AuthId_Steam2, currentSteamID, sizeof(currentSteamID));
    
    // ИСПРАВЛЕНО ДЛЯ SM 1.1
    new String:linkedSteamIDs[SPECBAN_MAX_LINKED][SPECBAN_STEAMID_LENGTH];
    new String:linkedNames[SPECBAN_MAX_LINKED][SPECBAN_NAME_LENGTH];
    new linkedCount = 0;
    
    decl String:mainSteamID[SPECBAN_STEAMID_LENGTH];
    decl String:linkedSteamID[SPECBAN_STEAMID_LENGTH];
    
    while (SQL_FetchRow(results) && linkedCount < SPECBAN_MAX_LINKED)
    {
        SQL_FetchString(results, 0, mainSteamID, sizeof(mainSteamID));
        SQL_FetchString(results, 1, linkedSteamID, sizeof(linkedSteamID));
        
        if (strcmp(mainSteamID, currentSteamID) != 0 && !IsSteamIDInList(linkedSteamIDs, linkedCount, mainSteamID))
        {
            strcopy(linkedSteamIDs[linkedCount], SPECBAN_STEAMID_LENGTH, mainSteamID);
            GetNameForSteamID(mainSteamID, linkedNames[linkedCount], SPECBAN_NAME_LENGTH);
            linkedCount++;
        }
        
        if (strcmp(linkedSteamID, currentSteamID) != 0 && !IsSteamIDInList(linkedSteamIDs, linkedCount, linkedSteamID))
        {
            strcopy(linkedSteamIDs[linkedCount], SPECBAN_STEAMID_LENGTH, linkedSteamID);
            GetNameForSteamID(linkedSteamID, linkedNames[linkedCount], SPECBAN_NAME_LENGTH);
            linkedCount++;
        }
    }
    
    if (linkedCount > 0)
    {
        PrintToAdmins("\x04[SpecBan] \x03[ВНИМАНИЕ] У игрока \x04%s \x03обнаружены связанные аккаунты:", playerName);
        
        for (new i = 0; i < linkedCount; i++)
        {
            PrintToAdmins("\x04[SpecBan] \x01- SteamID: \x02%s \x01| Ник: \x04%s", 
                linkedSteamIDs[i], linkedNames[i]);
        }
        
        PrintToAdmins("\x04[SpecBan] \x03Всего найдено связанных аккаунтов: \x04%d", linkedCount);
    }
}

// ИСПРАВЛЕНО ДЛЯ SM 1.1
bool:IsSteamIDInList(String:list[][SPECBAN_STEAMID_LENGTH], count, const String:steamid[])
{
    for (new i = 0; i < count; i++)
    {
        if (strcmp(list[i], steamid) == 0)
            return true;
    }
    return false;
}

GetNameForSteamID(const String:steamid[], String:name[], maxlen)
{
    decl String:escapedSteamID[SPECBAN_STEAMID_LENGTH * 2 + 1];
    SQL_EscapeString(g_hDatabase, steamid, escapedSteamID, sizeof(escapedSteamID));
    
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT name FROM name_history WHERE steamid = '%s' ORDER BY last_seen DESC LIMIT 1",
        escapedSteamID);
    
    new Handle:hQuery = SQL_Query(g_hDatabase, query);
    if (hQuery != INVALID_HANDLE && SQL_FetchRow(hQuery))
    {
        SQL_FetchString(hQuery, 0, name, maxlen);
        CloseHandle(hQuery);
    }
    else
    {
        strcopy(name, maxlen, "неизвестно");
        if (hQuery != INVALID_HANDLE) CloseHandle(hQuery);
    }
}

// ==================== КОМАНДА ПРОВЕРКИ ИГРОКА ====================
public Action:Command_CheckPlayer(client, args)
{
    if (!client)
    {
        PrintToServer("[SpecBan] Команда доступна только игрокам.");
        return Plugin_Handled;
    }
    
    if (args == 0)
    {
        PrintToChat(client, "\x04[SpecBan] \x03Использование: sm_checkplayer <SteamID или ник>");
        return Plugin_Handled;
    }
    
    decl String:search[SPECBAN_NAME_LENGTH * 2];
    GetCmdArgString(search, sizeof(search));
    TrimString(search);
    
    if (StrContains(search, "STEAM_") == 0 || StrContains(search, "[U:") == 0)
    {
        CheckPlayerBySteamID(client, search);
    }
    else
    {
        CheckPlayerByName(client, search);
    }
    
    return Plugin_Handled;
}

CheckPlayerBySteamID(client, const String:steamid[])
{
    decl String:escapedSteamID[SPECBAN_STEAMID_LENGTH * 2 + 1];
    SQL_EscapeString(g_hDatabase, steamid, escapedSteamID, sizeof(escapedSteamID));
    
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT name, ip, endtime, reason, admin_name, ban_time FROM specbans WHERE steamid = '%s'",
        escapedSteamID);
    
    SQL_TQuery(g_hDatabase, SQL_CheckPlayerBySteamIDCallback, query, GetClientUserId(client));
}

public SQL_CheckPlayerBySteamIDCallback(Handle:db, Handle:results, const String:error[], any:userid)
{
    if (error[0])
    {
        LogError("SQL Error in CheckPlayerBySteamIDCallback: %s", error);
        return;
    }
    
    new client = GetClientOfUserId(userid);
    if (!client || !IsClientInGame(client))
        return;
    
    decl String:steamid[SPECBAN_STEAMID_LENGTH];
    GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid));
    
    PrintToChat(client, "\x04[SpecBan] \x03=== ИНФОРМАЦИЯ ОБ ИГРОКЕ ===");
    
    new bool:hasActiveBan = false;
    
    if (results != INVALID_HANDLE && SQL_FetchRow(results))
    {
        hasActiveBan = true;
        
        decl String:name[SPECBAN_NAME_LENGTH];
        decl String:ip[SPECBAN_IP_LENGTH];
        decl String:reason[SPECBAN_REASON_LENGTH];
        decl String:adminName[SPECBAN_ADMINNAME_LENGTH];
        
        SQL_FetchString(results, 0, name, sizeof(name));
        SQL_FetchString(results, 1, ip, sizeof(ip));
        new endtime = SQL_FetchInt(results, 2);
        SQL_FetchString(results, 3, reason, sizeof(reason));
        SQL_FetchString(results, 4, adminName, sizeof(adminName));
        new banTime = SQL_FetchInt(results, 5);
        
        decl String:banDate[64];
        decl String:endDate[64];
        FormatTime(banDate, sizeof(banDate), "%d.%m.%Y %H:%M", banTime);
        
        if (endtime == 0)
        {
            strcopy(endDate, sizeof(endDate), "Навсегда");
        }
        else
        {
            FormatTime(endDate, sizeof(endDate), "%d.%m.%Y %H:%M", endtime);
        }
        
        PrintToChat(client, "\x04[SpecBan] \x01=== АКТИВНЫЙ БАН ===");
        PrintToChat(client, "\x04[SpecBan] \x01Ник: \x04%s", name);
        PrintToChat(client, "\x04[SpecBan] \x01IP: \x04%s", ip);
        PrintToChat(client, "\x04[SpecBan] \x01Дата бана: \x04%s", banDate);
        PrintToChat(client, "\x04[SpecBan] \x01Срок окончания: \x04%s", endDate);
        PrintToChat(client, "\x04[SpecBan] \x01Причина: \x04%s", reason);
        PrintToChat(client, "\x04[SpecBan] \x01Забанил: \x04%s", adminName);
    }
    
    if (!hasActiveBan)
    {
        PrintToChat(client, "\x04[SpecBan] \x01Активных банов нет.");
    }
    
    ShowBanHistory(client, steamid);
    ShowNameHistory(client, steamid);
    ShowLinkedAccounts(client, steamid);
}

ShowBanHistory(client, const String:steamid[])
{
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT name, ban_time, unban_time, reason, admin_name FROM ban_history WHERE steamid = '%s' ORDER BY ban_time DESC LIMIT %d",
        steamid, SPECBAN_HISTORY_DISPLAY);
    
    new Handle:hQuery = SQL_Query(g_hDatabase, query);
    if (hQuery == INVALID_HANDLE)
        return;
    
    if (SQL_GetRowCount(hQuery) > 0)
    {
        PrintToChat(client, "\x04[SpecBan] \x03=== ИСТОРИЯ БАНОВ (последние %d) ===", SPECBAN_HISTORY_DISPLAY);
        
        decl String:name[SPECBAN_NAME_LENGTH];
        decl String:reason[SPECBAN_REASON_LENGTH];
        decl String:adminName[SPECBAN_ADMINNAME_LENGTH];
        new banTime, unbanTime;
        new banNumber = 1;
        
        while (SQL_FetchRow(hQuery) && banNumber <= SPECBAN_HISTORY_DISPLAY)
        {
            SQL_FetchString(hQuery, 0, name, sizeof(name));
            banTime = SQL_FetchInt(hQuery, 1);
            unbanTime = SQL_FetchInt(hQuery, 2);
            SQL_FetchString(hQuery, 3, reason, sizeof(reason));
            SQL_FetchString(hQuery, 4, adminName, sizeof(adminName));
            
            decl String:banDate[64];
            decl String:unbanDate[64];
            FormatTime(banDate, sizeof(banDate), "%d.%m.%Y %H:%M", banTime);
            
            if (unbanTime == 0)
            {
                strcopy(unbanDate, sizeof(unbanDate), "Навсегда");
            }
            else
            {
                FormatTime(unbanDate, sizeof(unbanDate), "%d.%m.%Y %H:%M", unbanTime);
            }
            
            decl String:status[32];
            if (unbanTime > 0 && unbanTime <= GetTime())
            {
                strcopy(status, sizeof(status), "Истёк");
            }
            else if (unbanTime == 0)
            {
                strcopy(status, sizeof(status), "Навсегда");
            }
            else
            {
                strcopy(status, sizeof(status), "Активен");
            }
            
            PrintToChat(client, "\x04[SpecBan] \x01%d. Бан от \x04%s", banNumber, banDate);
            PrintToChat(client, "\x04[SpecBan] \x01   Ник: \x04%s \x01| Срок: \x04%s \x01| Статус: \x04%s", name, unbanDate, status);
            PrintToChat(client, "\x04[SpecBan] \x01   Причина: \x04%s \x01| Забанил: \x04%s", reason, adminName);
            
            banNumber++;
        }
    }
    else
    {
        PrintToChat(client, "\x04[SpecBan] \x01История банов отсутствует.");
    }
    
    CloseHandle(hQuery);
}

CheckPlayerByName(client, const String:name[])
{
    decl String:escapedName[SPECBAN_NAME_LENGTH * 2 + 1];
    SQL_EscapeString(g_hDatabase, name, escapedName, sizeof(escapedName));
    
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT steamid, name, last_seen FROM name_history WHERE name LIKE '%%%s%%' GROUP BY steamid ORDER BY last_seen DESC LIMIT 10",
        escapedName);
    
    SQL_TQuery(g_hDatabase, SQL_CheckPlayerByNameCallback, query, GetClientUserId(client));
}

public SQL_CheckPlayerByNameCallback(Handle:db, Handle:results, const String:error[], any:userid)
{
    if (error[0])
    {
        LogError("SQL Error in CheckPlayerByNameCallback: %s", error);
        return;
    }
    
    new client = GetClientOfUserId(userid);
    if (!client || !IsClientInGame(client))
        return;
    
    if (results != INVALID_HANDLE && SQL_GetRowCount(results) > 0)
    {
        PrintToChat(client, "\x04[SpecBan] \x03Найдены игроки с похожим ником:");
        
        decl String:steamid[SPECBAN_STEAMID_LENGTH];
        decl String:playerName[SPECBAN_NAME_LENGTH];
        new lastSeen;
        
        while (SQL_FetchRow(results))
        {
            SQL_FetchString(results, 0, steamid, sizeof(steamid));
            SQL_FetchString(results, 1, playerName, sizeof(playerName));
            lastSeen = SQL_FetchInt(results, 2);
            
            decl String:lastDate[64];
            FormatTime(lastDate, sizeof(lastDate), "%d.%m.%Y", lastSeen);
            
            PrintToChat(client, "\x04[SpecBan] \x01- \x04%s \x01| SteamID: \x02%s \x01| Последний раз: %s", 
                playerName, steamid, lastDate);
        }
    }
    else
    {
        PrintToChat(client, "\x04[SpecBan] \x01Игроки с таким ником не найдены.");
    }
}

ShowNameHistory(client, const String:steamid[])
{
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT name, first_seen, last_seen FROM name_history WHERE steamid = '%s' ORDER BY last_seen DESC",
        steamid);
    
    new Handle:hQuery = SQL_Query(g_hDatabase, query);
    if (hQuery == INVALID_HANDLE)
        return;
    
    if (SQL_GetRowCount(hQuery) > 0)
    {
        PrintToChat(client, "\x04[SpecBan] \x03История ников для этого SteamID:");
        
        decl String:name[SPECBAN_NAME_LENGTH];
        new firstSeen, lastSeen;
        
        while (SQL_FetchRow(hQuery))
        {
            SQL_FetchString(hQuery, 0, name, sizeof(name));
            firstSeen = SQL_FetchInt(hQuery, 1);
            lastSeen = SQL_FetchInt(hQuery, 2);
            
            decl String:firstDate[64];
            decl String:lastDate[64];
            FormatTime(firstDate, sizeof(firstDate), "%d.%m.%Y", firstSeen);
            FormatTime(lastDate, sizeof(lastDate), "%d.%m.%Y", lastSeen);
            
            PrintToChat(client, "\x04[SpecBan] \x01- \x04%s \x01| Период: %s - %s", name, firstDate, lastDate);
        }
    }
    
    CloseHandle(hQuery);
}

// !!! ЭТО СТРОКА 424 - ИСПРАВЛЕННАЯ ВЕРСИЯ ДЛЯ SM 1.1 !!!
ShowLinkedAccounts(client, const String:steamid[])
{
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT main_steamid, linked_steamid, last_seen FROM steam_history WHERE main_steamid = '%s' OR linked_steamid = '%s'",
        steamid, steamid);
    
    new Handle:hQuery = SQL_Query(g_hDatabase, query);
    if (hQuery == INVALID_HANDLE)
        return;
    
    if (SQL_GetRowCount(hQuery) > 0)
    {
        PrintToChat(client, "\x04[SpecBan] \x03Связанные аккаунты:");
        
        // ИСПРАВЛЕНО ДЛЯ SM 1.1
        new String:linkedSteamIDs[SPECBAN_MAX_LINKED][SPECBAN_STEAMID_LENGTH];
        new String:linkedNames[SPECBAN_MAX_LINKED][SPECBAN_NAME_LENGTH];
        new linkedCount = 0;
        
        decl String:mainSteamID[SPECBAN_STEAMID_LENGTH];
        decl String:linkedSteamID[SPECBAN_STEAMID_LENGTH];
        
        while (SQL_FetchRow(hQuery) && linkedCount < SPECBAN_MAX_LINKED)
        {
            SQL_FetchString(hQuery, 0, mainSteamID, sizeof(mainSteamID));
            SQL_FetchString(hQuery, 1, linkedSteamID, sizeof(linkedSteamID));
            
            if (strcmp(mainSteamID, steamid) != 0 && !IsSteamIDInList(linkedSteamIDs, linkedCount, mainSteamID))
            {
                strcopy(linkedSteamIDs[linkedCount], SPECBAN_STEAMID_LENGTH, mainSteamID);
                GetNameForSteamID(mainSteamID, linkedNames[linkedCount], SPECBAN_NAME_LENGTH);
                linkedCount++;
            }
            
            if (strcmp(linkedSteamID, steamid) != 0 && !IsSteamIDInList(linkedSteamIDs, linkedCount, linkedSteamID))
            {
                strcopy(linkedSteamIDs[linkedCount], SPECBAN_STEAMID_LENGTH, linkedSteamID);
                GetNameForSteamID(linkedSteamID, linkedNames[linkedCount], SPECBAN_NAME_LENGTH);
                linkedCount++;
            }
        }
        
        for (new i = 0; i < linkedCount; i++)
        {
            PrintToChat(client, "\x04[SpecBan] \x01- \x02%s \x01| Ник: \x04%s", 
                linkedSteamIDs[i], linkedNames[i]);
        }
    }
    
    CloseHandle(hQuery);
}

// ==================== ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ====================
PrintToAdmins(const String:format[], any:...)
{
    decl String:message[256];
    VFormat(message, sizeof(message), format, 2);
    
    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && CheckCommandAccess(i, "sm_admin", ADMFLAG_GENERIC))
        {
            PrintToChat(i, "%s", message);
        }
    }
}

public Action:Timer_DelayedHint(Handle:timer, any:userid)
{
    new client = GetClientOfUserId(userid);
    if (client && IsClientInGame(client) && (g_bPermanentSpecBan[client] || g_iBanEndTime[client] > GetTime()))
    {
        PrintHintText(client, "Обнаружен бан в зрители по SteamID. Вы забанены! Не пытайтесь выходить.");
        ChangeClientTeam(client, CS_TEAM_SPECTATOR);
    }
    return Plugin_Stop;
}

public OnClientPutInServer(client)
{
    if (IsClientInGame(client) && (g_bPermanentSpecBan[client] || g_iBanEndTime[client] > GetTime()))
    {
        ChangeClientTeam(client, CS_TEAM_SPECTATOR);
        UpdateSpecHint(client);
    }
}

public OnClientDisconnect(client)
{
    ResetPlayerBanData(client);
}

public Event_PlayerConnect(Handle:event, const String:name[], bool:dontBroadcast)
{
    // Обрабатывается в OnClientAuthorized
}

public Action:Event_PlayerTeam(Handle:event, const String:name[], bool:dontBroadcast)
{
    new userid = GetEventInt(event, "userid");
    new client = GetClientOfUserId(userid);
    
    if (client && IsClientInGame(client))
    {
        if (g_bPermanentSpecBan[client] || g_iBanEndTime[client] > GetTime())
        {
            new oldteam = GetEventInt(event, "oldteam");
            new team = GetEventInt(event, "team");
            
            if (oldteam == CS_TEAM_SPECTATOR && team != CS_TEAM_SPECTATOR)
            {
                CreateTimer(0.1, Timer_ForceSpec, userid);
            }
        }
    }
    
    return Plugin_Continue;
}

public Action:Timer_ForceSpec(Handle:timer, any:userid)
{
    new client = GetClientOfUserId(userid);
    if (client && IsClientInGame(client))
    {
        if (g_bPermanentSpecBan[client] || g_iBanEndTime[client] > GetTime())
        {
            ChangeClientTeam(client, CS_TEAM_SPECTATOR);
            UpdateSpecHint(client);
            
            if (g_bPermanentSpecBan[client])
            {
                PrintToChat(client, "\x04[SpecBan] \x03Администратор %s забанил вас навсегда. Причина: %s",
                    g_sBanAdminName[client], g_sBanReason[client]);
            }
            else
            {
                new remaining = g_iBanEndTime[client] - GetTime();
                new minutes = remaining / 60;
                
                PrintToChat(client, "\x04[SpecBan] \x03Администратор %s забанил вас на %d минут. Причина: %s",
                    g_sBanAdminName[client], minutes, g_sBanReason[client]);
            }
        }
    }
    return Plugin_Stop;
}

UpdateSpecHint(client)
{
    if (!IsClientInGame(client))
        return;
    
    if (g_bPermanentSpecBan[client])
    {
        PrintHintText(client, "Вы забанены навсегда в зрителях! Причина: %s", g_sBanReason[client]);
    }
    else if (g_iBanEndTime[client] > GetTime())
    {
        new remaining = g_iBanEndTime[client] - GetTime();
        new minutes = remaining / 60;
        PrintHintText(client, "Вы забанены в зрителях! Осталось: %d мин. Причина: %s", minutes, g_sBanReason[client]);
    }
}

public Action:Timer_UpdateHints(Handle:timer)
{
    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && (g_bPermanentSpecBan[i] || g_iBanEndTime[i] > GetTime()))
        {
            UpdateSpecHint(i);
        }
    }
    return Plugin_Continue;
}

// ==================== ОСНОВНАЯ ЛОГИКА БАНОВ ====================
SpecBanPlayer(admin, target, time, const String:reason[])
{
    if (time < 0)
    {
        LogError("Попытка бана с отрицательным временем: %d", time);
        time = 0;
    }
    
    decl String:adminName[SPECBAN_ADMINNAME_LENGTH];
    if (admin != 0 && IsValidClient(admin))
        GetClientName(admin, adminName, sizeof(adminName));
    else
        strcopy(adminName, sizeof(adminName), "Сервер");

    strcopy(g_sBanAdminName[target], sizeof(g_sBanAdminName[]), adminName);

    new banTime = GetTime();
    
    decl String:timeStr[64];
    FormatTimeString(time, timeStr, sizeof(timeStr));
    
    if (time == 0)
    {
        g_bPermanentSpecBan[target] = true;
        g_iBanEndTime[target] = 0;
    }
    else
    {
        g_bPermanentSpecBan[target] = false;
        g_iBanEndTime[target] = banTime + (time * 60);
        if (time * 60 > 0)
        {
            g_hSpecBanTimer[target] = CreateTimer(float(time * 60), Timer_Unban, GetClientUserId(target));
        }
    }

    strcopy(g_sBanReason[target], sizeof(g_sBanReason[]), reason);

    decl String:steamid[SPECBAN_STEAMID_LENGTH];
    decl String:name[SPECBAN_NAME_LENGTH];
    decl String:ip[SPECBAN_IP_LENGTH];
    
    if (!GetClientAuthId(target, AuthId_Steam2, steamid, sizeof(steamid)))
    {
        LogError("Не удалось получить SteamID для бана");
        return;
    }
    
    GetClientName(target, name, sizeof(name));
    GetClientIP(target, ip, sizeof(ip));

    decl String:escapedSteamID[SPECBAN_STEAMID_LENGTH * 2 + 1];
    decl String:escapedName[SPECBAN_NAME_LENGTH * 2 + 1];
    decl String:escapedIP[SPECBAN_IP_LENGTH * 2 + 1];
    decl String:escapedReason[SPECBAN_REASON_LENGTH * 2 + 1];
    decl String:escapedAdminName[SPECBAN_ADMINNAME_LENGTH * 2 + 1];
    
    SQL_EscapeString(g_hDatabase, steamid, escapedSteamID, sizeof(escapedSteamID));
    SQL_EscapeString(g_hDatabase, name, escapedName, sizeof(escapedName));
    SQL_EscapeString(g_hDatabase, ip, escapedIP, sizeof(escapedIP));
    SQL_EscapeString(g_hDatabase, reason, escapedReason, sizeof(escapedReason));
    SQL_EscapeString(g_hDatabase, adminName, escapedAdminName, sizeof(escapedAdminName));

    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "INSERT OR REPLACE INTO specbans (steamid, name, ip, endtime, reason, admin_name, ban_time) VALUES ('%s', '%s', '%s', %d, '%s', '%s', %d)",
        escapedSteamID, escapedName, escapedIP, g_iBanEndTime[target], escapedReason, escapedAdminName, banTime);
    SQL_FastQuery(g_hDatabase, query);

    decl String:historyQuery[SPECBAN_QUERY_LENGTH];
    Format(historyQuery, sizeof(historyQuery), 
        "INSERT INTO ban_history (steamid, name, ip, ban_time, unban_time, reason, admin_name) VALUES ('%s', '%s', '%s', %d, %d, '%s', '%s')",
        escapedSteamID, escapedName, escapedIP, banTime, g_iBanEndTime[target], escapedReason, escapedAdminName);
    SQL_FastQuery(g_hDatabase, historyQuery);

    ChangeClientTeam(target, CS_TEAM_SPECTATOR);

    PrintToChatAll("\x04[SpecBan] \x03Администратор \x04%s \x03забанил \x04%s \x03в спектаторы %s. Причина: \x04%s",
        adminName, name, timeStr, reason);

    if (IsValidClient(admin))
    {
        decl String:banDate[64];
        decl String:endDate[64];
        FormatTime(banDate, sizeof(banDate), "%d.%m.%Y %H:%M", banTime);
        
        if (time == 0)
        {
            strcopy(endDate, sizeof(endDate), "Навсегда");
        }
        else
        {
            FormatTime(endDate, sizeof(endDate), "%d.%m.%Y %H:%M", g_iBanEndTime[target]);
        }
        
        PrintToChat(admin, "\x04[SpecBan] \x01=== ИНФОРМАЦИЯ О БАНЕ ===");
        PrintToChat(admin, "\x04[SpecBan] \x01Ник игрока: \x04%s", name);
        PrintToChat(admin, "\x04[SpecBan] \x01SteamID: \x04%s", steamid);
        PrintToChat(admin, "\x04[SpecBan] \x01IP: \x04%s", ip);
        PrintToChat(admin, "\x04[SpecBan] \x01Дата бана: \x04%s", banDate);
        PrintToChat(admin, "\x04[SpecBan] \x01Срок окончания: \x04%s", endDate);
        PrintToChat(admin, "\x04[SpecBan] \x01Причина: \x04%s", reason);
        PrintToChat(admin, "\x04[SpecBan] \x01Забанил: \x04%s", adminName);
    }

    LogAction(admin, target, "\"%L\" забанил \"%L\" на %d минут (причина: %s)", admin, target, time, reason);
    UpdateSpecHint(target);
}

FormatTimeString(minutes, String:buffer[], maxlen)
{
    if (minutes == 0)
    {
        strcopy(buffer, maxlen, "навсегда");
        return;
    }
    
    new lastDigit = minutes % 10;
    new lastTwoDigits = minutes % 100;
    
    if (lastTwoDigits >= 11 && lastTwoDigits <= 14)
    {
        Format(buffer, maxlen, "на %d минут", minutes);
    }
    else if (lastDigit == 1)
    {
        Format(buffer, maxlen, "на %d минуту", minutes);
    }
    else if (lastDigit >= 2 && lastDigit <= 4)
    {
        Format(buffer, maxlen, "на %d минуты", minutes);
    }
    else
    {
        Format(buffer, maxlen, "на %d минут", minutes);
    }
}

SpecUnbanPlayer(admin, const String:sAuth[])
{
    decl String:adminName[SPECBAN_ADMINNAME_LENGTH];
    if (admin != 0 && IsValidClient(admin))
        GetClientName(admin, adminName, sizeof(adminName));
    else
        strcopy(adminName, sizeof(adminName), "Сервер");
    
    decl String:targetName[SPECBAN_NAME_LENGTH];
    strcopy(targetName, sizeof(targetName), "Неизвестный игрок");
    
    decl String:escapedSteamID[SPECBAN_STEAMID_LENGTH * 2 + 1];
    SQL_EscapeString(g_hDatabase, sAuth, escapedSteamID, sizeof(escapedSteamID));
    
    // Проверяем, есть ли игрок онлайн
    new iTarget = GetTargetOfAuth(sAuth);
    if (iTarget)
    {
        GetClientName(iTarget, targetName, sizeof(targetName));
        
        if (g_hSpecBanTimer[iTarget] != INVALID_HANDLE)
        {
            KillTimer(g_hSpecBanTimer[iTarget]);
            g_hSpecBanTimer[iTarget] = INVALID_HANDLE;
        }
        g_bPermanentSpecBan[iTarget] = false;
        g_iBanEndTime[iTarget] = 0;
        g_sBanReason[iTarget][0] = '\0';
        g_sBanAdminName[iTarget][0] = '\0';

        if (IsClientInGame(iTarget))
        {
            ChangeClientTeam(iTarget, CS_TEAM_T);
            PrintHintText(iTarget, "Вы были разбанены администратором %s", adminName);
        }
        LogAction(admin, iTarget, "\"%L\" разбанил \"%L\" из зрителей", admin, iTarget);
    }
    else
    {
        decl String:query[SPECBAN_QUERY_LENGTH];
        Format(query, sizeof(query), "SELECT name FROM specbans WHERE steamid = '%s'", escapedSteamID);
        
        new Handle:hQuery = SQL_Query(g_hDatabase, query);
        if (hQuery != INVALID_HANDLE && SQL_FetchRow(hQuery))
        {
            SQL_FetchString(hQuery, 0, targetName, sizeof(targetName));
            CloseHandle(hQuery);
        }
        
        LogAction(admin, -1, "\"%L\" разбанил \"%s\" из зрителей", admin, sAuth);
    }

    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), "DELETE FROM specbans WHERE steamid = '%s'", escapedSteamID);
    SQL_FastQuery(g_hDatabase, query);
    
    PrintToChatAll("\x04[SpecBan] \x03Администратор %s разбанил из зрителей %s", adminName, targetName);
}

GetTargetOfAuth(const String:sAuth[])
{
    decl String:sBuffer[SPECBAN_STEAMID_LENGTH];
    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && !IsFakeClient(i) && GetClientAuthId(i, AuthId_Steam2, sBuffer, sizeof(sBuffer)) && strcmp(sAuth, sBuffer) == 0)
            return i;
    }
    return 0;
}

public Action:Timer_Unban(Handle:timer, any:userid)
{
    new client = GetClientOfUserId(userid);
    if (!client)
        return Plugin_Stop;
    
    decl String:steamid[SPECBAN_STEAMID_LENGTH];
    if (GetClientAuthId(client, AuthId_Steam2, steamid, sizeof(steamid)))
    {
        decl String:escapedSteamID[SPECBAN_STEAMID_LENGTH * 2 + 1];
        SQL_EscapeString(g_hDatabase, steamid, escapedSteamID, sizeof(escapedSteamID));
        
        decl String:query[SPECBAN_QUERY_LENGTH];
        Format(query, sizeof(query), "DELETE FROM specbans WHERE steamid = '%s'", escapedSteamID);
        SQL_FastQuery(g_hDatabase, query);
    }
    
    g_bPermanentSpecBan[client] = false;
    g_iBanEndTime[client] = 0;
    g_sBanReason[client][0] = '\0';
    g_sBanAdminName[client][0] = '\0';
    g_hSpecBanTimer[client] = INVALID_HANDLE;
    
    if (IsClientInGame(client))
    {
        PrintToChat(client, "\x04[SpecBan] \x03Вы разбанены! Срок вашего бана истёк.");
        ChangeClientTeam(client, CS_TEAM_T);
        PrintHintText(client, "");
        
        CreateTimer(0.5, Timer_ForceTeamApply, GetClientUserId(client));
    }
    return Plugin_Stop;
}

public Action:Timer_ForceTeamApply(Handle:timer, any:userid)
{
    new client = GetClientOfUserId(userid);
    if (client && IsClientInGame(client))
    {
        new currentTeam = GetClientTeam(client);
        if (currentTeam == CS_TEAM_SPECTATOR)
        {
            ChangeClientTeam(client, CS_TEAM_T);
            PrintToChat(client, "\x04[SpecBan] \x03Теперь вы можете выбрать команду.");
        }
    }
    return Plugin_Stop;
}

bool:IsValidClient(client)
{
    return (client > 0 && client <= MaxClients && IsClientInGame(client) && !IsFakeClient(client));
}

// ==================== МЕНЮ ====================
public Action:Command_SpecBanMenu(client, args)
{
    if (!client)
    {
        PrintToServer("[SpecBan] Команда доступна только игрокам.");
        return Plugin_Handled;
    }
    
    new Handle:menu = CreateMenu(Menu_SpecBanMain);
    SetMenuTitle(menu, "SpecBan Меню");
    AddMenuItem(menu, "ban", "Забанить игрока");
    AddMenuItem(menu, "unban", "Разбанить игрока");
    SetMenuExitButton(menu, true);
    DisplayMenu(menu, client, MENU_TIME_FOREVER);
    return Plugin_Handled;
}

public Menu_SpecBanMain(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_Select)
    {
        decl String:info[32];
        GetMenuItem(menu, param2, info, sizeof(info));
        
        if (strcmp(info, "ban") == 0)
        {
            ShowBanPlayerMenu(param1);
        }
        else if (strcmp(info, "unban") == 0)
        {
            ShowUnbanPlayerMenu(param1);
        }
    }
    else if (action == MenuAction_End)
    {
        CloseHandle(menu);
    }
}

ShowBanPlayerMenu(client)
{
    new Handle:playerMenu = CreateMenu(Menu_SelectPlayerBan);
    SetMenuTitle(playerMenu, "Выберите игрока для бана");
    
    new count = 0;
    for (new i = 1; i <= MaxClients; i++)
    {
        if (IsClientInGame(i) && !IsFakeClient(i) && i != client)
        {
            decl String:playerInfo[SPECBAN_NAME_LENGTH + 16];
            decl String:userId[16];
            Format(playerInfo, sizeof(playerInfo), "%N", i);
            IntToString(GetClientUserId(i), userId, sizeof(userId));
            AddMenuItem(playerMenu, userId, playerInfo);
            count++;
        }
    }
    
    if (count == 0)
    {
        PrintToChat(client, "\x04[SpecBan] \x03Нет активных игроков для бана.");
        CloseHandle(playerMenu);
        Command_SpecBanMenu(client, 0);
        return;
    }
    
    SetMenuExitBackButton(playerMenu, true);
    DisplayMenu(playerMenu, client, MENU_TIME_FOREVER);
}

public Menu_SelectPlayerBan(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_Select)
    {
        decl String:userId[16];
        GetMenuItem(menu, param2, userId, sizeof(userId));
        new targetClient = GetClientOfUserId(StringToInt(userId));
        
        if (!targetClient || !IsClientInGame(targetClient))
        {
            PrintToChat(param1, "\x04[SpecBan] \x03Игрок не найден.");
            ShowBanPlayerMenu(param1);
            return;
        }
        
        ShowBanDurationMenu(param1, targetClient);
    }
    else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack)
    {
        Command_SpecBanMenu(param1, 0);
    }
    else if (action == MenuAction_End)
    {
        CloseHandle(menu);
    }
}

ShowBanDurationMenu(client, target)
{
    new Handle:durationMenu = CreateMenu(Menu_SelectDuration);
    decl String:title[256];
    Format(title, sizeof(title), "Выберите длительность бана для %N", target);
    SetMenuTitle(durationMenu, title);
    AddMenuItem(durationMenu, "1", "1 минута");
    AddMenuItem(durationMenu, "5", "5 минут");
    AddMenuItem(durationMenu, "15", "15 минут");
    AddMenuItem(durationMenu, "30", "30 минут");
    AddMenuItem(durationMenu, "60", "1 час");
    AddMenuItem(durationMenu, "0", "Навсегда");
    AddMenuItem(durationMenu, "custom", "Кастомное время");
    SetMenuExitBackButton(durationMenu, true);
    DisplayMenu(durationMenu, client, MENU_TIME_FOREVER);
    
    g_iPendingTarget[client] = target;
}

public Menu_SelectDuration(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_Select)
    {
        decl String:info[32];
        GetMenuItem(menu, param2, info, sizeof(info));
        
        new targetClient = g_iPendingTarget[param1];
        if (!targetClient || !IsClientInGame(targetClient))
        {
            PrintToChat(param1, "\x04[SpecBan] \x03Целевой игрок не найден.");
            ShowBanPlayerMenu(param1);
            return;
        }
        
        if (strcmp(info, "custom") == 0)
        {
            PrintToChat(param1, "\x04[SpecBan] \x03Введите время в минутах в чат (только число):");
            g_iPendingState[param1] = 1;
        }
        else
        {
            new duration = StringToInt(info);
            g_iPendingDuration[param1] = duration;
            PrintToChat(param1, "\x04[SpecBan] \x03Введите причину бана в чат:");
            g_iPendingState[param1] = 2;
        }
    }
    else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack)
    {
        ShowBanPlayerMenu(param1);
    }
    else if (action == MenuAction_End)
    {
        CloseHandle(menu);
    }
}

public Action:OnClientSayCommand(client, const String:command[], const String:sArgs[])
{
    if (g_iPendingState[client] > 0)
    {
        if (strlen(sArgs) < 1)
        {
            PrintToChat(client, "\x04[SpecBan] \x03Ввод не может быть пустым.");
            return Plugin_Handled;
        }
        
        new target = g_iPendingTarget[client];
        if (!target || !IsClientInGame(target))
        {
            PrintToChat(client, "\x04[SpecBan] \x03Целевой игрок больше не в игре.");
            g_iPendingState[client] = 0;
            return Plugin_Handled;
        }
        
        if (g_iPendingState[client] == 1)
        {
            new time = StringToInt(sArgs);
            if (time < 0)
            {
                PrintToChat(client, "\x04[SpecBan] \x03Время не может быть отрицательным. Использую 'Навсегда'.");
                time = 0;
            }
            g_iPendingDuration[client] = time;
            PrintToChat(client, "\x04[SpecBan] \x03Введите причину бана в чат:");
            g_iPendingState[client] = 2;
        }
        else if (g_iPendingState[client] == 2)
        {
            SpecBanPlayer(client, target, g_iPendingDuration[client], sArgs);
            g_iPendingState[client] = 0;
            g_iPendingTarget[client] = 0;
            g_iPendingDuration[client] = 0;
        }
        
        return Plugin_Handled;
    }
    return Plugin_Continue;
}

ShowUnbanPlayerMenu(client)
{
    decl String:query[SPECBAN_QUERY_LENGTH];
    Format(query, sizeof(query), 
        "SELECT steamid, name, ban_time, endtime, reason, admin_name FROM specbans ORDER BY name");
    
    SQL_TQuery(g_hDatabase, SQL_GetBansCallback, query, GetClientUserId(client));
}

public SQL_GetBansCallback(Handle:db, Handle:results, const String:error[], any:userid)
{
    if (error[0])
    {
        LogError("[SQL_GetBansCallback] %s", error);
        return;
    }

    new client = GetClientOfUserId(userid);
    if (!client || !IsClientInGame(client))
        return;

    if (results == INVALID_HANDLE || SQL_GetRowCount(results) == 0)
    {
        PrintToChat(client, "\x04[SpecBan] \x03Нет забаненных игроков.");
        Command_SpecBanMenu(client, 0);
        return;
    }

    new Handle:playerMenu = CreateMenu(Menu_SelectPlayerUnban);
    SetMenuTitle(playerMenu, "Выберите игрока для разбана");

    decl String:steamid[SPECBAN_STEAMID_LENGTH];
    decl String:name[SPECBAN_NAME_LENGTH];
    decl String:reason[SPECBAN_REASON_LENGTH];
    decl String:adminName[SPECBAN_ADMINNAME_LENGTH];
    new banTime, endTime;
    decl String:display[SPECBAN_NAME_LENGTH + 64];
    decl String:info[SPECBAN_QUERY_LENGTH];
    
    while(SQL_FetchRow(results))
    {
        SQL_FetchString(results, 0, steamid, sizeof(steamid));
        SQL_FetchString(results, 1, name, sizeof(name));
        banTime = SQL_FetchInt(results, 2);
        endTime = SQL_FetchInt(results, 3);
        SQL_FetchString(results, 4, reason, sizeof(reason));
        SQL_FetchString(results, 5, adminName, sizeof(adminName));
        
        decl String:banDate[64];
        decl String:endDate[64];
        FormatTime(banDate, sizeof(banDate), "%d.%m.%Y", banTime);
        
        if (endTime == 0)
        {
            strcopy(endDate, sizeof(endDate), "навсегда");
        }
        else
        {
            FormatTime(endDate, sizeof(endDate), "%d.%m.%Y", endTime);
        }
        
        Format(display, sizeof(display), "%s (бан: %s, до: %s)", name, banDate, endDate);
        
        Format(info, sizeof(info), "%s|%s|%s|%s|%s", 
            steamid, banDate, endDate, reason, adminName);
        
        AddMenuItem(playerMenu, info, display);
    }

    SetMenuExitBackButton(playerMenu, true);
    DisplayMenu(playerMenu, client, MENU_TIME_FOREVER);
}

public Menu_SelectPlayerUnban(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_Select)
    {
        decl String:info[SPECBAN_QUERY_LENGTH];
        GetMenuItem(menu, param2, info, sizeof(info));
        
        decl String:parts[5][256];
        ExplodeString(info, "|", parts, sizeof(parts), sizeof(parts[]));
        
        decl String:steamid[SPECBAN_STEAMID_LENGTH];
        strcopy(steamid, sizeof(steamid), parts[0]);
        
        ShowUnbanConfirmMenu(param1, steamid, parts[1], parts[2], parts[3], parts[4]);
    }
    else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack)
    {
        Command_SpecBanMenu(param1, 0);
    }
    else if (action == MenuAction_End)
    {
        CloseHandle(menu);
    }
}

ShowUnbanConfirmMenu(client, const String:steamid[], const String:banDate[], const String:endDate[], const String:reason[], const String:adminName[])
{
    new Handle:detailMenu = CreateMenu(Menu_UnbanConfirm);
    
    decl String:title[512];
    Format(title, sizeof(title), 
        "Информация о бане\n \nSteamID: %s\nДата бана: %s\nСрок окончания: %s\nПричина: %s\nЗабанил: %s\n \nВыберите действие:", 
        steamid, banDate, endDate, reason, adminName);
    
    SetMenuTitle(detailMenu, title);
    AddMenuItem(detailMenu, steamid, "✅ Разбанить игрока");
    AddMenuItem(detailMenu, "back", "◀ Назад к списку");
    SetMenuExitBackButton(detailMenu, true);
    DisplayMenu(detailMenu, client, MENU_TIME_FOREVER);
}

public Menu_UnbanConfirm(Handle:menu, MenuAction:action, param1, param2)
{
    if (action == MenuAction_Select)
    {
        decl String:info[SPECBAN_STEAMID_LENGTH];
        GetMenuItem(menu, param2, info, sizeof(info));
        
        if (strcmp(info, "back") == 0)
        {
            ShowUnbanPlayerMenu(param1);
        }
        else
        {
            SpecUnbanPlayer(param1, info);
            ShowUnbanPlayerMenu(param1);
        }
    }
    else if (action == MenuAction_Cancel && param2 == MenuCancel_ExitBack)
    {
        ShowUnbanPlayerMenu(param1);
    }
    else if (action == MenuAction_End)
    {
        CloseHandle(menu);
    }
}