/*  
	EQ2Emulator:  Everquest II Server Emulator
	Copyright (C) 2007  EQ2EMulator Development Team (http://www.eq2emulator.net)

	This file is part of EQ2Emulator.
*/

// Debug includes
#include "../common/debug.h"

// Linux network headers
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

// Standard library includes
#include <algorithm>
#include <cassert>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <memory>
#include <string>
#include <string_view>
#include <vector>

// Project includes
#include "net.h"
#include "client.h"
#include "../common/EQStream.h"
#include "../common/packet_dump.h"
#include "../common/packet_functions.h"
#include "../common/emu_opcodes.h"
#include "../common/MiscFunctions.h"
#include "LWorld.h"
#include "LoginDatabase.h"
#include "../common/ConfigReader.h"
#include "../common/Log.h"

// Global instances
extern NetConnection net;
extern LWorldList world_list;
extern ClientList client_list;
extern LoginDatabase database;
extern std::map<std::int16_t, OpcodeManager*> EQOpcodeManager;
extern ConfigReader configReader;

using namespace std;

/**
 * @brief Constructs a new Client instance
 * @param ieqnc The EQStream connection for this client
 */
Client::Client(EQStream* ieqnc)
    : eqnc_{ieqnc}
    , ip_{ieqnc->GetrIP()}
    , port_{ntohs(ieqnc->GetrPort())}
    , account_id_{0}
    , lsadmin_{0}
    , worldadmin_{0}
    , lsstatus_{0}
    , version_{0}
    , kicked_{false}
    , verified_{false}
    , login_mode_{LoginMode::None}
    , num_updates_{0}
    , request_num_{0}
    , createRequest_{nullptr}
    , playWaitTimer_{nullptr}
    , start_{false}
    , update_position_{0}
    , update_packets_{nullptr}
    , needs_world_list_{true}
    , sent_character_list_{false}
{
    // Clear critical buffers
    std::memset(bannedreason_, 0, sizeof(bannedreason_));
    std::memset(key_, 0, sizeof(key_));
    std::memset(ClientSession, 0, sizeof(ClientSession));
    
    // Initialize timers
    updatetimer = std::make_unique<Timer>(500);
    updatelisttimer = std::make_unique<Timer>(10000);
}

/**
 * @brief Destructor - cleans up all client resources
 */
Client::~Client()
{
    // Close the connection
    if (eqnc_)
    {
        eqnc_->Close();
    }
    
    // Clean up character creation request
    safe_delete(createRequest_);
    
    // Clean up update packets
    if (update_packets_)
    {
        for (auto* packet : *update_packets_)
        {
            safe_delete(packet);
        }
        safe_delete(update_packets_);
    }
}

/**
 * @brief Main processing function for the client
 * @return true if client is still active, false if should be disconnected
 */
bool Client::Process()
{
    // Handle initial connection state
    if (!start_ && !eqnc_->CheckActive())
    {
        if (!playWaitTimer_)
        {
            playWaitTimer_ = std::make_unique<Timer>(5000);
        }
        else if (playWaitTimer_->Check())
        {
            playWaitTimer_.reset();
            return false;
        }
        return true;
    }
    else if (!start_)
    {
        playWaitTimer_.reset();
        start_ = true;
    }
    
    // Check disconnect timer
    if (disconnectTimer && disconnectTimer->Check())
    {
        disconnectTimer.reset();
        getConnection()->SendDisconnect();
    }
    
    // Process packets if not kicked
    if (!kicked_)
    {
        // Handle play wait timeout
        if (playWaitTimer_ != nullptr && playWaitTimer_->Check())
        {
            SendPlayFailed(PLAY_ERROR_SERVER_TIMEOUT);
            playWaitTimer_.reset();
        }
        
        // Process world list updates
        if (!needs_world_list_ && updatetimer && updatetimer->Check())
        {
            if (updatelisttimer && updatelisttimer->Check())
            {
                // Disconnect after 30 minutes of updates
                if (num_updates_ >= 180)
                {
                    getConnection()->SendDisconnect();
                }
                else
                {
                    // Clean up old update packets
                    if (update_packets_)
                    {
                        for (auto* packet : *update_packets_)
                        {
                            safe_delete(packet);
                        }
                    }
                    safe_delete(update_packets_);
                    
                    // Get new update packets
                    update_packets_ = world_list.GetServerListUpdate(version_);
                }
                num_updates_++;
            }
            else
            {
                // Send update packets in sequence
                if (!update_packets_)
                {
                    update_packets_ = world_list.GetServerListUpdate(version_);
                }
                else
                {
                    if (update_position_ < update_packets_->size())
                    {
                        QueuePacket(update_packets_->at(update_position_)->serialize());
                        update_position_++;
                    }
                    else
                    {
                        update_position_ = 0;
                    }
                }
            }
        }

        // Process incoming packets
        EQApplicationPacket* app = nullptr;
        while ((app = eqnc_->PopPacket()))
        {
            switch (app->GetOpcode())
            {
                case OP_LoginRequestMsg:
                {
                    ProcessLoginRequest(app);
                    break;
                }
                
                case OP_KeymapLoadMsg:
                {
                    // Keymap message - currently unused
                    break;
                }
                
                case OP_AllWSDescRequestMsg:
                {
                    ProcessWorldListRequest();
                    break;
                }
                
                case OP_LsClientCrashlogReplyMsg:
                {
                    SaveErrorsToDB(app, "Crash Log", GetVersion());
                    break;
                }
                
                case OP_LsClientVerifylogReplyMsg:
                {
                    SaveErrorsToDB(app, "Verify Log", GetVersion());
                    break;
                }
                
                case OP_LsClientAlertlogReplyMsg:
                {
                    SaveErrorsToDB(app, "Alert Log", GetVersion());
                    break;
                }
                
                case OP_LsClientBaselogReplyMsg:
                {
                    SaveErrorsToDB(app, "Base Log", GetVersion());
                    break;
                }
                
                case OP_AllCharactersDescRequestMsg:
                {
                    // Character description request - handled elsewhere
                    break;
                }
                
                case OP_CreateCharacterRequestMsg:
                {
                    ProcessCharacterCreation(app);
                    break;
                }
                
                case OP_PlayCharacterRequestMsg:
                {
                    ProcessPlayCharacter(app);
                    break;
                }
                
                case OP_DeleteCharacterRequestMsg:
                {
                    ProcessDeleteCharacter(app);
                    break;
                }
                
                default:
                {
                    const char* name = app->GetOpcodeName();
                    if (name)
                    {
                        LogWrite(OPCODE__DEBUG, 1, "Opcode", "%s Received %04X (%i)", 
                                name, app->GetRawOpcode(), app->GetRawOpcode());
                    }
                    else
                    {
                        LogWrite(OPCODE__DEBUG, 1, "Opcode", "Received %04X (%i)", 
                                app->GetRawOpcode(), app->GetRawOpcode());
                    }
                    break;
                }
            }
            delete app;
        }
    }

    // Check if connection is still active
    if (!eqnc_->CheckActive())
    {
        return false;
    }

    return true;
}

/**
 * @brief Processes a login request packet
 * @param app The login request packet
 */
void Client::ProcessLoginRequest(EQApplicationPacket* app)
{
    DumpPacket(app);
    
    // Try loading with version 1 structure
    auto packet = std::unique_ptr<PacketStruct>(configReader.getStruct("LS_LoginRequest", 1));
    
    if (packet && packet->LoadPacketData(app->pBuffer, app->size))
    {
        version_ = packet->getType_int16_ByName("version");
        LogWrite(LOGIN__DEBUG, 0, "Login", "Classic Client Version Provided: %i", version_);

        // Check if we need to try the newer structure
        if (version_ == 0 || EQOpcodeManager.count(GetOpcodeVersion(version_)) == 0)
        {
            packet.reset(configReader.getStruct("LS_LoginRequest", 1208));
            if (packet && packet->LoadPacketData(app->pBuffer, app->size))
            {
                version_ = packet->getType_int16_ByName("version");
            }
            else
            {
                return;
            }
        }

        LogWrite(LOGIN__DEBUG, 0, "Login", "New Client Version Provided: %i", version_);

        // Verify client version is supported
        if (EQOpcodeManager.count(GetOpcodeVersion(version_)) == 0)
        {
            LogWrite(LOGIN__ERROR, 0, "Login", "Incompatible client version provided: %i", version_);
            SendLoginDenied();
            return;
        }
        
        // Process login credentials
        if (EQOpcodeManager.count(GetOpcodeVersion(version_)) > 0 && getConnection())
        {
            getConnection()->SetClientVersion(GetVersion());
            
            EQ2_16BitString username = packet->getType_EQ2_16BitString_ByName("username");
            EQ2_16BitString password = packet->getType_EQ2_16BitString_ByName("password");
            
            // Load account from database
            LoginAccount* acct = database.LoadAccount(username.data.c_str(), 
                                                     password.data.c_str(), 
                                                     net.IsAllowingAccountCreation());
            
            // Check for duplicate login
            if (acct)
            {
                Client* otherclient = client_list.FindByLSID(acct->getLoginAccountID());
                if (otherclient)
                {
                    // Kick the previous client (might be a ghost)
                    otherclient->getConnection()->SendDisconnect();
                }
            }
            
            // Process successful login
            if (acct)
            {
                SetAccountName(username.data.c_str());
                database.UpdateAccountIPAddress(acct->getLoginAccountID(), getConnection()->GetrIP());
                database.UpdateAccountClientDataVersion(acct->getLoginAccountID(), version_);
                LogWrite(LOGIN__INFO, 0, "Login", "%s successfully logged in.", 
                        username.data.c_str());
                
                needs_world_list_ = true;
                SetLoginAccount(acct);
                SendLoginAccepted();
            }
            else
            {
                // Login failed
                if (username.size > 0)
                {
                    LogWrite(LOGIN__ERROR, 0, "Login", "%s login failed!", 
                            username.data.c_str());
                }
                else
                {
                    LogWrite(LOGIN__ERROR, 0, "Login", "[UNKNOWN USER] login failed!");
                }
                SendLoginDenied();
            }
        }
        else
        {
            std::cout << "Error bad version: " << version_ << std::endl;
            SendLoginDeniedBadVersion();
        }
    }
    else
    {
        std::cout << "Error loading LS_LoginRequest packet" << std::endl;
    }
}

/**
 * @brief Processes a world list request
 */
void Client::ProcessWorldListRequest()
{
    SendWorldList();
    needs_world_list_ = false;
    
    if (!sent_character_list_)
    {
        database.LoadCharacters(GetLoginAccount(), GetVersion());
        sent_character_list_ = true;
    }
    
    SendCharList();
}

/**
 * @brief Processes a character creation request
 * @param app The character creation packet
 */
void Client::ProcessCharacterCreation(EQApplicationPacket* app)
{
    auto packet = std::unique_ptr<PacketStruct>(
        configReader.getStruct("CreateCharacter", GetVersion()));
    
    DumpPacket(app);
    
    // Start play wait timer
    playWaitTimer_ = std::make_unique<Timer>(15000);
    playWaitTimer_->Start();
    
    LogWrite(WORLD__INFO, 1, "World", "Character creation request from account %s", 
            GetAccountName());
    
    if (packet->LoadPacketData(app->pBuffer, app->size, GetVersion() <= 561 ? false : true))
    {
        DumpPacket(app->pBuffer, app->size);
        packet->setDataByName("account_id", GetAccountID());
        
        // Find the target world server
        LWorld* world_server = world_list.FindByID(packet->getType_int32_ByName("server_id"));
        if (!world_server)
        {
            DumpPacket(app->pBuffer, app->size);
            std::cout << GetAccountName() << " attempted creation of character with an invalid server id of: " 
                     << packet->getType_int32_ByName("server_id") << "\n";
            return;
        }
        
        // Store creation request and forward to world server
        createRequest_ = packet.release();
        
        auto outpack = std::make_unique<ServerPacket>(ServerOP_CharacterCreate, 
                                                      app->size + sizeof(std::int16_t));
        std::int16_t out_version = GetVersion();
        std::memcpy(outpack->pBuffer, &out_version, sizeof(std::int16_t));
        std::memcpy(outpack->pBuffer + sizeof(std::int16_t), app->pBuffer, app->size);
        
        // Adjust pointer based on version
        unsigned char* tmp = outpack->pBuffer;
        if (out_version <= 283)
        {
            tmp += 2;
        }
        else if (out_version == 373)
        {
            tmp += 6;
        }
        else
        {
            tmp += 7;
        }
        
        // Add account ID
        std::int32_t account_id = GetAccountID();
        std::memcpy(tmp, &account_id, sizeof(std::int32_t));
        
        world_server->SendPacket(outpack.get());
    }
    else
    {
        LogWrite(WORLD__ERROR, 1, "World", "Error in character creation request from account %s!", 
                GetAccountName());
    }
}

/**
 * @brief Processes a play character request
 * @param app The play character packet
 */
void Client::ProcessPlayCharacter(EQApplicationPacket* app)
{
    std::int32_t char_id = 0;
    std::int32_t server_id = 0;
    
    auto request = std::unique_ptr<PacketStruct>(
        configReader.getStruct("LS_PlayRequest", GetVersion()));
    
    if (request && request->LoadPacketData(app->pBuffer, app->size))
    {
        char_id = request->getType_int32_ByName("char_id");
        
        // Handle version-specific server ID retrieval
        if (GetVersion() <= 283)
        {
            server_id = database.GetServer(GetAccountID(), char_id, 
                                          request->getType_EQ2_16BitString_ByName("name").data);
        }
        else
        {
            server_id = request->getType_int32_ByName("server_id");
        }
        
        // Find world server and character
        LWorld* world = world_list.FindByID(server_id);
        std::string name = database.GetCharacterName(char_id, server_id, GetAccountID());
        
        if (world && !name.empty())
        {
            pending_play_char_id_ = char_id;
            
            // Create user to world request
            auto outpack = std::make_unique<ServerPacket>(ServerOP_UsertoWorldReq, 
                                                          sizeof(UsertoWorldRequest_Struct));
            auto* req = reinterpret_cast<UsertoWorldRequest_Struct*>(outpack->pBuffer);
            req->char_id = char_id;
            req->lsaccountid = GetAccountID();
            req->worldid = server_id;

            // Set IP address
            struct in_addr in;
            in.s_addr = GetIP();
            std::strcpy(req->ip_address, inet_ntoa(in));
            
            world->SendPacket(outpack.get());

            // Start play wait timer
            playWaitTimer_.reset();
            playWaitTimer_ = std::make_unique<Timer>(5000);
            playWaitTimer_->Start();
        }
        else
        {
            std::cout << GetAccountName() << " sent invalid Play Request" << std::endl;
            SendPlayFailed(PLAY_ERROR_PROBLEM);
            DumpPacket(app);
        }
    }
}

/**
 * @brief Processes a delete character request
 * @param app The delete character packet
 */
void Client::ProcessDeleteCharacter(EQApplicationPacket* app)
{
    auto request = std::unique_ptr<PacketStruct>(
        configReader.getStruct("LS_DeleteCharacterRequest", GetVersion()));
    auto response = std::unique_ptr<PacketStruct>(
        configReader.getStruct("LS_DeleteCharacterResponse", GetVersion()));
    
    if (request && response && request->LoadPacketData(app->pBuffer, app->size))
    {
        EQ2_16BitString name = request->getType_EQ2_16BitString_ByName("name");
        std::int32_t acct_id = GetAccountID();
        std::int32_t char_id = request->getType_int32_ByName("char_id");
        std::int32_t server_id = request->getType_int32_ByName("server_id");
        
        // Verify deletion
        if (database.VerifyDelete(acct_id, char_id, name.data.c_str()))
        {
            response->setDataByName("response", 1);
            GetLoginAccount()->removeCharacter(name.data.c_str(), GetVersion());
            
            // Notify world server
            LWorld* world_server = world_list.FindByID(server_id);
            if (world_server != nullptr)
            {
                world_server->SendDeleteCharacter(char_id, acct_id);
            }
        }
        else
        {
            response->setDataByName("response", 0);
        }
        
        // Build response
        response->setDataByName("server_id", server_id);
        response->setDataByName("char_id", char_id);
        response->setDataByName("account_id", account_id_);
        response->setMediumStringByName("name", name.data.c_str());
        response->setDataByName("max_characters", 10);

        EQ2Packet* outapp = response->serialize();
        QueuePacket(outapp);
        
        // Refresh character list
        SendCharList();
    }
}

/**
 * @brief Saves client error logs to the database
 * @param app Packet containing error data
 * @param type Type of error log
 * @param version Client version
 */
void Client::SaveErrorsToDB(EQApplicationPacket* app, const char* type, std::int32_t version)
{
    std::int32_t size = 0;
    z_stream zstream{};
    
    // Extract size and data based on version
    if (version >= 546)
    {
        std::memcpy(&size, app->pBuffer + sizeof(std::int32_t), sizeof(std::int32_t));
        zstream.next_in = app->pBuffer + 8;
        zstream.avail_in = app->size - 8;
    }
    else
    {
        // Box set version
        size = 0xFFFF;
        zstream.next_in = app->pBuffer + 2;
        zstream.avail_in = app->size - 2;
    }
    
    size++;
    auto message = std::make_unique<char[]>(size);
    std::memset(message.get(), 0, size);
    
    // Setup decompression
    zstream.next_out = reinterpret_cast<Bytef*>(message.get());
    zstream.avail_out = size;
    zstream.zalloc = Z_NULL;
    zstream.zfree = Z_NULL;
    zstream.opaque = Z_NULL;

    // Decompress message
    int zerror = inflateInit(&zstream);
    if (zerror != Z_OK)
    {
        return;
    }
    
    zerror = inflate(&zstream, 0);
    inflateEnd(&zstream);
    
    // Save to database if message is valid
    if (message && std::strlen(message.get()) > 0)
    {
        database.SaveClientLog(type, message.get(), GetLoginAccount()->getLoginName(), GetVersion());
    }
}

/**
 * @brief Handles character creation approval from world server
 * @param server_id World server ID
 * @param char_id New character ID
 */
void Client::CharacterApproved(std::int32_t server_id, std::int32_t char_id)
{
    if (createRequest_ && server_id == createRequest_->getType_int32_ByName("server_id"))
    {
        LWorld* world_server = world_list.FindByID(server_id);
        if (!world_server)
        {
            return;
        }

        // Send success response
        auto packet = std::unique_ptr<PacketStruct>(
            configReader.getStruct("LS_CreateCharacterReply", GetVersion()));
        
        if (packet)
        {
            packet->setDataByName("account_id", GetAccountID());
            packet->setDataByName("unknown", 0xFFFFFFFF);
            packet->setDataByName("response", CREATESUCCESS_REPLY);
            packet->setMediumStringByName("name", 
                createRequest_->getType_EQ2_16BitString_ByName("name").data.c_str());
            
            EQ2Packet* outapp = packet->serialize();
            QueuePacket(outapp);
            
            // Save character to database
            database.SaveCharacter(createRequest_, GetLoginAccount(), char_id, GetVersion());

            // Refresh character list
            database.LoadCharacters(GetLoginAccount(), GetVersion());
            SendCharList();

            // Auto-enter world for older clients
            if (GetVersion() <= 561)
            {
                pending_play_char_id_ = char_id;
                
                auto outpack = std::make_unique<ServerPacket>(ServerOP_UsertoWorldReq, 
                                                              sizeof(UsertoWorldRequest_Struct));
                auto* req = reinterpret_cast<UsertoWorldRequest_Struct*>(outpack->pBuffer);
                req->char_id = char_id;
                req->lsaccountid = GetAccountID();
                req->worldid = server_id;

                struct in_addr in;
                in.s_addr = GetIP();
                std::strcpy(req->ip_address, inet_ntoa(in));
                
                world_server->SendPacket(outpack.get());
            }
        }
    }
    else
    {
        std::cout << GetAccountName() << " received invalid CharacterApproval from server: " 
                 << server_id << std::endl;
    }
    
    safe_delete(createRequest_);
}

/**
 * @brief Handles character creation rejection
 * @param reason_number Rejection reason code
 */
void Client::CharacterRejected(std::int8_t reason_number)
{
    auto packet = std::unique_ptr<PacketStruct>(
        configReader.getStruct("LS_CreateCharacterReply", GetVersion()));
    
    if (createRequest_ && packet)
    {
        packet->setDataByName("account_id", GetAccountID());
        packet->setDataByName("response", reason_number);
        packet->setMediumStringByName("name", "");
        
        EQ2Packet* outapp = packet->serialize();
        QueuePacket(outapp);
    }
    
    safe_delete(createRequest_);
}

/**
 * @brief Sends the character list to the client
 */
void Client::SendCharList()
{
    LogWrite(LOGIN__INFO, 0, "Login", "[%s] sending character list.", GetAccountName());
    
    LS_CharSelectList list;
    list.loadData(GetAccountID(), GetLoginAccount()->charlist, GetVersion());
    
    EQ2Packet* outapp = list.serialize(GetVersion());
    DumpPacket(outapp->pBuffer, outapp->size);
    QueuePacket(outapp);
}

/**
 * @brief Sends login denied due to bad version
 */
void Client::SendLoginDeniedBadVersion()
{
    auto app = std::make_unique<EQ2Packet>(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
    auto* ls_response = reinterpret_cast<LS_LoginResponse*>(app->pBuffer);
    
    ls_response->reply_code = 6;  // Version mismatch
    ls_response->unknown03 = 0xFFFFFFFF;
    ls_response->unknown04 = 0xFFFFFFFF;
    
    QueuePacket(app.release());
    StartDisconnectTimer();
}

/**
 * @brief Sends login denied message
 */
void Client::SendLoginDenied()
{
    auto app = std::make_unique<EQ2Packet>(OP_LoginReplyMsg, 0, sizeof(LS_LoginResponse));
    auto* ls_response = reinterpret_cast<LS_LoginResponse*>(app->pBuffer);
    
    // Reply codes for AoM:
    // 1 = Invalid username or password
    // 2 = Account already playing
    // 6 = Version mismatch
    // 7 = No scheduled playtimes
    // 8 = Missing features
    // 11 = Build mismatch
    // 12 = Must update password
    ls_response->reply_code = 1;
    ls_response->unknown03 = 0xFFFFFFFF;
    ls_response->unknown04 = 0xFFFFFFFF;
    
    QueuePacket(app.release());
    StartDisconnectTimer();
}

/**
 * @brief Sends login accepted message
 * @param account_id Account ID
 * @param login_response Login response code
 */
void Client::SendLoginAccepted(std::int32_t account_id, std::int8_t login_response)
{
    auto packet = std::unique_ptr<PacketStruct>(
        configReader.getStruct("LS_LoginReplyMsg", GetVersion()));
    
    if (packet)
    {
        packet->setDataByName("account_id", account_id);
        packet->setDataByName("login_response", login_response);
        packet->setDataByName("do_not_force_soga", 1);
        
        // Set subscription and expansion flags
        packet->setDataByName("sub_level", net.GetDefaultSubscriptionLevel());
        packet->setDataByName("race_flag", 0x1FFFFF);
        packet->setDataByName("class_flag", 0x7FFFFFE);
        packet->setMediumStringByName("username", GetAccountName());
        packet->setMediumStringByName("password", GetAccountName());
        
        // Expansion flags
        packet->setDataByName("unknown5", net.GetExpansionFlag());
        packet->setDataByName("unknown6", 0xFF);
        packet->setDataByName("unknown6", 0xFF, 1);
        packet->setDataByName("unknown6", 0xFF, 2);
        
        // Class access
        packet->setDataByName("unknown10", 0xFF);
        
        // Race and city flags
        packet->setDataByName("unknown7", net.GetEnabledRaces());
        packet->setDataByName("unknown7a", 0xEE);
        packet->setDataByName("unknown8", net.GetCitiesFlag(), 1);

        EQ2Packet* outapp = packet->serialize();
        QueuePacket(outapp);
    }
}

/**
 * @brief Sends the world server list
 */
void Client::SendWorldList()
{
    EQ2Packet* pack = world_list.MakeServerListPacket(lsadmin_, version_);
    EQ2Packet* dupe = pack->Copy();
    
    DumpPacket(dupe->pBuffer, dupe->size);
    QueuePacket(dupe);
    
    // Trigger special client code path
    SendLoginAccepted(0, 10);
}

/**
 * @brief Queues a packet for sending
 * @param app Packet to queue
 */
void Client::QueuePacket(EQ2Packet* app)
{
    eqnc_->EQ2QueuePacket(app);
}

/**
 * @brief Handles world server response for play request
 * @param worldid World server ID
 * @param response Response code
 * @param ip_address World server IP
 * @param port World server port
 * @param access_key Access key
 */
void Client::WorldResponse(std::int32_t worldid, std::int8_t response, 
                          char* ip_address, std::int32_t port, std::int32_t access_key)
{
    LWorld* world = world_list.FindByID(worldid);
    if (world == nullptr)
    {
        FatalError(0);
        return;
    }
    
    if (response != 1)
    {
        // Handle character not loaded error
        if (response == PLAY_ERROR_CHAR_NOT_LOADED)
        {
            std::string pending_play_char_name = database.GetCharacterName(
                pending_play_char_id_, worldid, GetAccountID());
            
            if (database.VerifyDelete(GetAccountID(), pending_play_char_id_, 
                                     pending_play_char_name.c_str()))
            {
                GetLoginAccount()->removeCharacter(pending_play_char_name.c_str(), GetVersion());
            }
        }
        FatalError(response);
        return;
    }

    // Send successful play response
    auto response_packet = std::unique_ptr<PacketStruct>(
        configReader.getStruct("LS_PlayResponse", GetVersion()));
    
    if (response_packet)
    {
        playWaitTimer_.reset();
        
        response_packet->setDataByName("response", 1);
        response_packet->setSmallStringByName("server", ip_address);
        response_packet->setDataByName("port", port);
        response_packet->setDataByName("account_id", GetAccountID());
        response_packet->setDataByName("access_code", access_key);
        
        EQ2Packet* outapp = response_packet->serialize();
        QueuePacket(outapp);
    }
}

/**
 * @brief Sends a fatal error to the client
 * @param response Error response code
 */
void Client::FatalError(std::int8_t response)
{
    playWaitTimer_.reset();
    SendPlayFailed(response);
}

/**
 * @brief Sends play failed message
 * @param response Failure reason code
 */
void Client::SendPlayFailed(std::int8_t response)
{
    auto response_packet = std::unique_ptr<PacketStruct>(
        configReader.getStruct("LS_PlayResponse", GetVersion()));
    
    if (response_packet)
    {
        response_packet->setDataByName("response", response);
        response_packet->setSmallStringByName("server", "");
        response_packet->setDataByName("port", 0);
        response_packet->setDataByName("account_id", GetAccountID());
        response_packet->setDataByName("access_code", 0);
        
        EQ2Packet* outapp = response_packet->serialize();
        QueuePacket(outapp);
    }
}

/**
 * @brief Starts the disconnect timer
 */
void Client::StartDisconnectTimer()
{
    if (!disconnectTimer)
    {
        disconnectTimer = std::make_unique<Timer>(1000);
        disconnectTimer->Start();
    }
}

// ClientList implementation

/**
 * @brief Adds a client to the list
 * @param client Client to add
 */
void ClientList::Add(Client* client)
{
    std::lock_guard<std::mutex> lock(mutex_);
    client_list_[client] = true;
}

/**
 * @brief Finds a client by IP and port
 * @param ip Client IP
 * @param port Client port
 * @return Client pointer or nullptr
 */
Client* ClientList::Get(std::int32_t ip, std::int16_t port)
{
    std::lock_guard<std::mutex> lock(mutex_);
    
    for (auto& [client, active] : client_list_)
    {
        if (client->GetIP() == ip && client->GetPort() == port)
        {
            return client;
        }
    }
    
    return nullptr;
}

/**
 * @brief Finds clients waiting for character creation
 */
void ClientList::FindByCreateRequest()
{
    Client* found_client = nullptr;
    
    {
        std::lock_guard<std::mutex> lock(mutex_);
        
        for (auto& [client, active] : client_list_)
        {
            if (client->AwaitingCharCreationRequest())
            {
                if (!found_client)
                {
                    found_client = client;
                }
                else
                {
                    // More than one waiting, don't send rejection
                    found_client = nullptr;
                    break;
                }
            }
        }
    }
    
    if (found_client)
    {
        found_client->CharacterRejected(UNKNOWNERROR_REPLY);
    }
}

/**
 * @brief Finds a client by login server account ID
 * @param lsaccountid Account ID
 * @return Client pointer or nullptr
 */
Client* ClientList::FindByLSID(std::int32_t lsaccountid)
{
    std::lock_guard<std::mutex> lock(mutex_);
    
    for (auto& [client, active] : client_list_)
    {
        if (client->GetAccountID() == lsaccountid)
        {
            return client;
        }
    }
    
    return nullptr;
}

/**
 * @brief Sends a packet to all connected clients
 * @param app Packet to send
 */
void ClientList::SendPacketToAllClients(EQ2Packet* app)
{
    {
        std::lock_guard<std::mutex> lock(mutex_);
        
        if (!client_list_.empty())
        {
            for (auto& [client, active] : client_list_)
            {
                client->QueuePacket(app->Copy());
            }
        }
    }
    
    safe_delete(app);
}

/**
 * @brief Processes all clients in the list
 */
void ClientList::Process()
{
    std::vector<Client*> erase_list;
    
    // Process clients and identify disconnected ones
    {
        std::lock_guard<std::mutex> lock(mutex_);
        
        for (auto& [client, active] : client_list_)
        {
            if (!client->Process())
            {
                erase_list.push_back(client);
            }
        }
    }
    
    // Remove disconnected clients
    if (!erase_list.empty())
    {
        std::lock_guard<std::mutex> lock(mutex_);
        
        for (Client* client : erase_list)
        {
            struct in_addr in;
            in.s_addr = client->getConnection()->GetRemoteIP();
            
            net.numclients--;
            
            LogWrite(LOGIN__INFO, 0, "Login", 
                    "Removing client from ip: %s on port %i, Account Name: %s", 
                    inet_ntoa(in), 
                    ntohs(client->getConnection()->GetRemotePort()), 
                    client->GetAccountName());
            
            client->getConnection()->Close();
            net.UpdateWindowTitle();
            client_list_.erase(client);
            delete client;
        }
    }
}