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

	This file is part of EQ2Emulator.
*/

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

// Standard library includes
#include <algorithm>
#include <array>
#include <chrono>
#include <cstring>
#include <ctime>
#include <filesystem>
#include <format>
#include <fstream>
#include <iostream>
#include <map>
#include <memory>
#include <random>
#include <signal.h>
#include <sstream>
#include <string>
#include <string_view>
#include <thread>
#include <termios.h>
#include <fcntl.h>

// Project common includes
#include "../common/queue.h"
#include "../common/timer.h"
#include "../common/seperator.h"
#include "../common/packet_functions.h"
#include "../common/EQStreamFactory.h"
#include "../common/MiscFunctions.h"
#include "../common/version.h"
#include "../common/PacketStruct.h"
#include "../common/DataBuffer.h"
#include "../common/ConfigReader.h"
#include "../common/Log.h"
#include "../common/JsonParser.h"
#include "../common/Common_Defines.h"
#include "../common/CRC16.h"
#include "../common/timer.h"
#include "../common/unix.h"

// Login server includes
#include "net.h"
#include "client.h"
#include "LoginDatabase.h"
#include "LWorld.h"

// Global instances
EQStreamFactory eqsf(LoginStream);                    // Factory for creating EQ network streams
std::map<std::int16_t, OpcodeManager*> EQOpcodeManager; // Maps version to opcode manager
NetConnection net;                                     // Main network connection instance
ClientList client_list;                                // List of connected clients
LWorldList world_list;                                 // List of connected world servers
LoginDatabase database;                                // Database connection manager
ConfigReader configReader;                             // Configuration file reader
std::map<std::int16_t, std::int16_t> EQOpcodeVersions; // Maps client versions to opcode versions
Timer statTimer(60000);                                // Timer for periodic statistics updates (1 minute)

// Global control flag for main loop
volatile bool RunLoops = true;

// Forward declarations
bool ReadLoginConfig();

/**
 * @brief Check if a key has been pressed (Linux implementation)
 * @return true if a key is available, false otherwise
 */
int kbhit()
{
    struct termios oldt, newt;
    int ch;
    int oldf;
    
    // Get current terminal settings
    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    
    // Disable canonical mode and echo
    newt.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    
    // Set non-blocking mode
    oldf = fcntl(STDIN_FILENO, F_GETFL, 0);
    fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK);
    
    ch = getchar();
    
    // Restore terminal settings
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    fcntl(STDIN_FILENO, F_SETFL, oldf);
    
    if(ch != EOF)
    {
        ungetc(ch, stdin);
        return 1;
    }
    
    return 0;
}

/**
 * @brief Main entry point for the login server
 * @param argc Number of command line arguments
 * @param argv Array of command line arguments
 * @return Exit code (0 for success, 1 for failure)
 */
int main(int argc, char** argv)
{
    // Set up signal handler for graceful shutdown
    if (signal(SIGINT, CatchSignal) == SIG_ERR)
    {
        std::cerr << "Could not set signal handler" << std::endl;
    }

    // Initialize logging system
    LogStart();
    LogParseConfigs();
    
    // Display welcome header
    net.WelcomeHeader();

    // Initialize random number generator with current time
    std::random_device rd;
    std::mt19937 gen(rd());

    // Read and validate login server configuration
    if (!net.ReadLoginConfig())
    {
        return 1;
    }

    // Initialize web server if configured
    net.InitWebServer(
        net.GetWebLoginAddress(),
        net.GetWebLoginPort(),
        net.GetWebCertFile(),
        net.GetWebKeyFile(),
        net.GetWebKeyPassword(),
        net.GetWebHardcodeUser(),
        net.GetWebHardcodePassword()
    );
    
    // Load structure definition files for packet parsing
    const std::array<const char*, 2> structList = 
    {
        "CommonStructs.xml",
        "LoginStructs.xml"
    };

    for (const auto& structFile : structList)
    {
        LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structFile);
        
        if (configReader.processXML_Elements(structFile))
        {
            LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structFile);
        }
        else
        {
            LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structFile);
            return 1;
        }
    }

    // Initialize world server list
    LogWrite(INIT__INFO, 0, "Init", "Initialize World List..");
    world_list.Init();
    
    // Log listening configuration
    if (eqsf.listen_ip_address)
    {
        LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", 
                 eqsf.listen_ip_address, net.GetPort());
    }
    else
    {
        LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort());
    }
    
    // Open network port for incoming connections
    if (!eqsf.Open(net.GetPort()))
    {
        LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort());
        return 1;
    }
    
    // Set server running state
    net.login_running = true;
    net.login_uptime = getCurrentTimestamp();
    
    // Update console window title (no-op on Linux)
    net.UpdateWindowTitle();
    
    // Initialize timeout timer for connection management
    EQStream* eqs = nullptr;
    auto timeoutTimer = std::make_unique<Timer>(5000);
    timeoutTimer->Start();
    
    // Main server loop
    while (RunLoops)
    {
        // Update current time for all timers
        Timer::SetCurrentTime();
        
        // Process new incoming connections
        while ((eqs = eqsf.Pop()))
        {
            struct in_addr in;
            in.s_addr = eqs->GetRemoteIP();

            LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", 
                     inet_ntoa(in), ntohs(eqs->GetRemotePort()));
            
            // Create new client instance and add to client list
            auto client = std::make_unique<Client>(eqs);
            eqs->SetClientVersion(0);
            client_list.Add(client.release());
            net.numclients++;
            net.UpdateWindowTitle();
        }
        
        // Check for timed out connections
        if (timeoutTimer->Check())
        {
            eqsf.CheckTimeout();
        }
        
        // Update statistics periodically
        if (statTimer.Check())
        {
            world_list.UpdateWorldStats();
            database.RemoveOldWorldServerStats();
            database.FixBugReport();
        }
        
        // Process active clients and world servers
        client_list.Process();
        world_list.Process();
        
        // Handle console input (Linux version)
        if (kbhit())
        {
            int hitkey = getchar();
            
            switch (hitkey)
            {
                case 'l':
                case 'L':
                {
                    // List all connected world servers
                    world_list.ListWorldsToConsole();
                    break;
                }
                
                case 'v':
                case 'V':
                {
                    // Display version information
                    std::cout << "========Version Info=========\n";
                    std::cout << std::format("{} {}\n", EQ2EMU_MODULE, CURRENT_VERSION);
                    std::cout << std::format("Last Compiled on {} {}\n", COMPILE_DATE, COMPILE_TIME);
                    std::cout << "=============================\n\n";
                    break;
                }
                
                case 'H':
                case 'h':
                {
                    // Display help menu
                    std::cout << "===========Help=============\n";
                    std::cout << "Available Commands:\n";
                    std::cout << "l = Listing of World Servers\n";
                    std::cout << "v = Login Version\n";
                    std::cout << "q = Quit server\n";
                    std::cout << "============================\n\n";
                    break;
                }
                
                case 'q':
                case 'Q':
                {
                    // Quit server gracefully
                    std::cout << "Shutting down server...\n";
                    RunLoops = false;
                    break;
                }
                
                default:
                {
                    std::cout << "Invalid Command. Press 'h' for help.\n";
                    break;
                }
            }
        }
        
        // Small sleep to prevent CPU spinning
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    
    // Cleanup and shutdown
    eqsf.Close();
    world_list.Shutdown();
    
    return 0;
}

/**
 * @brief Signal handler for graceful shutdown
 * @param sig_num The signal number received
 */
void CatchSignal(int sig_num)
{
    std::cout << "Got signal " << sig_num << std::endl;
    RunLoops = false;
}

/**
 * @brief Constructor for NetConnection class
 * Initializes all member variables to their default values
 */
NetConnection::NetConnection() noexcept
    : port_{5999}
    , listening_socket_{0}
    , master_address_{}
    , uplink_port_{0}
    , uplink_account_{}
    , uplink_password_{}
    , login_mode_{ServerMode::Standalone}
    , uplink_wrong_version_{false}
    , numclients{0}
    , numservers{0}
    , allow_account_creation_{true}
    , expansion_flag_{0x7CFF}     // Full support = 0x7CFF
    , cities_flag_{0xFF}           // All cities enabled
    , default_subscription_level_{0xFFFFFFFF}
    , enabled_races_{0xFFFF}       // All races enabled
    , web_login_port_{0}
    , login_webserver_{nullptr}
    , login_running{false}
    , login_uptime{getCurrentTimestamp()}
{
    // Initialize address buffer
    std::memset(address, 0, sizeof(address));
}

/**
 * @brief Destructor for NetConnection class
 * Ensures proper cleanup of web server resources
 */
NetConnection::~NetConnection()
{
    // Unique_ptr automatically handles cleanup of login_webserver
}

/**
 * @brief Reads and parses the login server configuration file
 * @return true if configuration loaded successfully, false otherwise
 */
bool NetConnection::ReadLoginConfig()
{
    // Load JSON configuration file
    JsonParser parser(MAIN_CONFIG_FILE);
    if (!parser.IsLoaded())
    {
        LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE);
        return false;
    }
    
    // Parse server port configuration
    std::string serverport = parser.getValue("loginconfig.serverport");
    std::string serverip = parser.getValue("loginconfig.serverip");
    
    if (!parser.convertStringToUnsignedShort(serverport, port_))
    {
        LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport..");
        return false;
    }
    
    // Set listening IP address if specified
    if (!serverip.empty())
    {
        eqsf.listen_ip_address = new char[serverip.size() + 1];
        std::strcpy(eqsf.listen_ip_address, serverip.c_str());
    }
    else
    {
        safe_delete(eqsf.listen_ip_address);
        eqsf.listen_ip_address = nullptr;
    }
    
    // Parse account creation setting
    std::string acctcreate_str = parser.getValue("loginconfig.accountcreation");
    std::uint16_t allow_acct = 0;
    parser.convertStringToUnsignedShort(acctcreate_str, allow_acct);
    allow_account_creation_ = (allow_acct > 0);
    
    // Parse expansion and feature flags
    std::string expflag_str = parser.getValue("loginconfig.expansionflag");
    parser.convertStringToUnsignedInt(expflag_str, expansion_flag_);
    
    std::string citiesflag_str = parser.getValue("loginconfig.citiesflag");
    parser.convertStringToUnsignedChar(citiesflag_str, cities_flag_);
    
    std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel");
    parser.convertStringToUnsignedInt(defaultsublevel_str, default_subscription_level_);
    
    std::string enableraces_str = parser.getValue("loginconfig.enabledraces");
    parser.convertStringToUnsignedInt(enableraces_str, enabled_races_);
    
    // Parse web server configuration
    web_login_address_ = parser.getValue("loginconfig.webloginaddress");
    web_cert_file_ = parser.getValue("loginconfig.webcertfile");
    web_key_file_ = parser.getValue("loginconfig.webkeyfile");
    web_key_password_ = parser.getValue("loginconfig.webkeypassword");
    web_hardcode_user_ = parser.getValue("loginconfig.webhardcodeuser");
    web_hardcode_password_ = parser.getValue("loginconfig.webhardcodepassword");
    
    std::string webloginport_str = parser.getValue("loginconfig.webloginport");
    parser.convertStringToUnsignedShort(webloginport_str, web_login_port_);

    LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE);

    // Initialize database connection
    LogWrite(INIT__INFO, 0, "Init", "Database init begin..");
    if (!database.Init())
    {
        LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!");
        LogStop();
        return false;
    }

    // Load opcode definitions from database
    LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0..");
    EQOpcodeVersions = database.GetVersions();
    
    for (const auto& [version, _] : EQOpcodeVersions)
    {
        EQOpcodeManager[version] = new RegularOpcodeManager();
        auto opcodes = database.GetOpcodes(version);
        
        if (!EQOpcodeManager[version]->LoadOpcodes(&opcodes))
        {
            LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!");
            return false;
        }
    }

    return true;
}

/**
 * @brief Updates the console window title with server statistics
 * @param iNewTitle Optional custom title string
 * Note: This is a no-op on Linux, kept for API compatibility
 */
void NetConnection::UpdateWindowTitle(const char* iNewTitle)
{
    // No-op on Linux - terminal titles are handled differently
    // Could potentially use ANSI escape codes if needed:
    // std::cout << "\033]0;" << title << "\007";
}

/**
 * @brief Displays the welcome header with ASCII art and information
 */
void NetConnection::WelcomeHeader()
{
    // Use ANSI color codes for Linux terminal
    const char* WHITE_BOLD = "\033[1;37m";
    const char* YELLOW_BOLD = "\033[1;33m";
    const char* GREEN_BOLD = "\033[1;32m";
    const char* MAGENTA_BOLD = "\033[1;35m";
    const char* RESET = "\033[0m";

    // Display module and version information
    std::cout << WHITE_BOLD;
    std::cout << std::format("Module: {}, Version: {}", EQ2EMU_MODULE, CURRENT_VERSION);
    
    // Display copyright and license information
    std::cout << YELLOW_BOLD;
    std::cout << "\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n";
    std::cout << "EQ2Emulator is free software: you can redistribute it and/or modify\n";
    std::cout << "it under the terms of the GNU General Public License as published by\n";
    std::cout << "the Free Software Foundation, either version 3 of the License, or\n";
    std::cout << "(at your option) any later version.\n\n";
    std::cout << "EQ2Emulator is distributed in the hope that it will be useful,\n";
    std::cout << "but WITHOUT ANY WARRANTY; without even the implied warranty of\n";
    std::cout << "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n";
    std::cout << "GNU General Public License for more details.\n\n";
    
    // Display ASCII art logo
    std::cout << GREEN_BOLD;
    std::cout << " /$$$$$$$$  /$$$$$$   /$$$$$$  /$$$$$$$$                        \n";
    std::cout << "| $$_____/ /$$__  $$ /$$__  $$| $$_____/                        \n";
    std::cout << "| $$      | $$  \\ $$|__/  \\ $$| $$       /$$$$$$/$$$$  /$$   /$$\n";
    std::cout << "| $$$$$   | $$  | $$  /$$$$$$/| $$$$$   | $$_  $$_  $$| $$  | $$\n";
    std::cout << "| $$__/   | $$  | $$ /$$____/ | $$__/   | $$ \\ $$ \\ $$| $$  | $$\n";
    std::cout << "| $$      | $$/$$ $$| $$      | $$      | $$ | $$ | $$| $$  | $$\n";
    std::cout << "| $$$$$$$$|  $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$|  $$$$$$/\n";
    std::cout << "|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n";
    std::cout << "                \\__/                                            \n\n";
    
    // Display community links
    std::cout << MAGENTA_BOLD;
    std::cout << " Website     : https://eq2emu.com \n";
    std::cout << " Wiki        : https://wiki.eq2emu.com \n";
    std::cout << " Git         : https://git.eq2emu.com \n";
    std::cout << " Discord     : https://discord.gg/5Cavm9NYQf \n\n";
    
    std::cout << WHITE_BOLD;
    std::cout << "For more detailed logging, modify 'Level' param the log_config.xml file.\n\n";
    
    // Reset terminal colors
    std::cout << RESET;
    std::cout.flush();
}

/**
 * @brief Initializes the web server for HTTP/HTTPS API access
 * @param web_ipaddr IP address to bind to
 * @param web_port Port number for web server
 * @param cert_file SSL certificate file path
 * @param key_file SSL key file path
 * @param key_password Password for SSL key
 * @param hardcode_user Basic auth username
 * @param hardcode_password Basic auth password
 */
void NetConnection::InitWebServer(std::string_view web_ipaddr,
                                 std::uint16_t web_port,
                                 std::string_view cert_file,
                                 std::string_view key_file,
                                 std::string_view key_password,
                                 std::string_view hardcode_user,
                                 std::string_view hardcode_password)
{
    // Only initialize if address and port are configured
    if (!web_ipaddr.empty() && web_port > 0)
    {
        try
        {
            // Create web server instance with SSL configuration
            login_webserver_ = std::make_unique<WebServer>(
                std::string(web_ipaddr),
                web_port,
                std::string(cert_file),
                std::string(key_file),
                std::string(key_password),
                std::string(hardcode_user),
                std::string(hardcode_password)
            );
            
            // Register API routes
            login_webserver_->register_route("/status", NetConnection::Web_loginhandle_status);
            login_webserver_->register_route("/worlds", NetConnection::Web_loginhandle_worlds);
            
            // Start web server
            login_webserver_->run();
            
            LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..",
                     web_ipaddr.data(), web_port);
        }
        catch (const std::exception& e)
        {
            LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s",
                     web_ipaddr.data(), web_port, e.what());
        }
    }
}