//
// LDASTools AL - Debug Level Management Utility Implementation
//
// Copyright (C) 2025 California Institute of Technology
//

// Suppress false positive from GCC 14.3+ about uninitialized std::function members
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#endif

#include "DebugLevelManager.hh"
#include "Logger.hh"
#include "SimpleLogger.hh"

#include <iostream>
#include <fstream>
#include <sstream>
#include <algorithm>
#include <stdexcept>
#include <cstdlib>
#include <regex>
#include <mutex>

#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>

namespace LDASTools {
namespace AL {

//=======================================================================
// Static member initialization
//=======================================================================

// Initialize static constexpr members (C++14 compatibility)
constexpr int DebugLevelManager::MIN_DEBUG_LEVEL;
constexpr int DebugLevelManager::MAX_DEBUG_LEVEL;
constexpr int DebugLevelManager::DEFAULT_DEBUG_LEVEL;

//=======================================================================
// Library Customization Support (Thread-Safe Static Storage)
//=======================================================================

// Static storage for library-specific customization callbacks
static DebugLevelManager::help_callback_type s_help_callback = nullptr;
static std::string s_library_name = "ldas_utility";
static std::mutex s_callback_mutex;

//=======================================================================
// Library Customization Interface
//=======================================================================

void DebugLevelManager::set_help_callback(help_callback_type callback) {
    std::lock_guard<std::mutex> lock(s_callback_mutex);
    s_help_callback = callback;
}

void DebugLevelManager::set_library_name(const std::string& name) {
    std::lock_guard<std::mutex> lock(s_callback_mutex);
    s_library_name = name;
}

//=======================================================================
// Core Interface
//=======================================================================

bool DebugLevelManager::initialize(int argc, char* argv[], 
                                  const std::string& program_name,
                                  const std::string& env_prefix) {
    auto& mgr = instance();
    mgr.m_env_prefix = env_prefix;
    
    try {
        // Set up program options
        boost::program_options::options_description desc("Debug Options");
        boost::program_options::variables_map vm;
        
        add_debug_options(desc, vm);
        
        // Parse command line
        boost::program_options::store(
            boost::program_options::command_line_parser(argc, argv)
                .options(desc)
                .allow_unregistered()
                .run(), 
            vm);
        boost::program_options::notify(vm);
        
        // Check for help request
        if (vm.count("debug-help")) {
            // Use library name from callback if available, otherwise program name
            std::string display_name;
            {
                std::lock_guard<std::mutex> lock(s_callback_mutex);
                display_name = (s_library_name != "ldas_utility") ? s_library_name : program_name;
            }
            std::cout << "\n" << display_name << " - Debug Configuration Help\n";
            std::cout << desc << std::endl;
            std::cout << get_help_text() << std::endl;
            return false;
        }
        
        // Load from environment first (lowest priority)
        load_from_environment();
        
        // Apply command-line options (highest priority)
        configure_from_options(vm);
        
        mgr.m_initialized = true;
        return true;
        
    } catch (const std::exception& e) {
        std::cerr << "Error initializing debug level manager: " << e.what() << std::endl;
        return false;
    }
}

void DebugLevelManager::add_debug_options(boost::program_options::options_description& desc,
                                         boost::program_options::variables_map& vm) {
    desc.add_options()
        ("debug-level,d", boost::program_options::value<int>(),
         "Set global debug level (0-100)")
        ("debug-channels,c", boost::program_options::value<std::string>(),
         "Set channel-specific debug levels (format: channel1:level1,channel2:level2)")
        ("debug-config", boost::program_options::value<std::string>(),
         "Load debug configuration from file")
        ("debug-help", "Show debug configuration help")
        ("debug-show", "Show current debug configuration");
}

void DebugLevelManager::configure_from_options(const boost::program_options::variables_map& vm) {
    // Set global debug level
    if (vm.count("debug-level")) {
        set_global_level(vm["debug-level"].as<int>());
    }
    
    // Parse channel-specific levels
    if (vm.count("debug-channels")) {
        parse_channel_spec(vm["debug-channels"].as<std::string>());
    }
    
    // Load from configuration file
    if (vm.count("debug-config")) {
        load_from_file(vm["debug-config"].as<std::string>());
    }
    
    // Show current configuration
    if (vm.count("debug-show")) {
        std::cout << "\nCurrent Debug Configuration:\n";
        print_configuration(std::cout);
        std::cout << std::endl;
    }
}

void DebugLevelManager::set_global_level(int level) {
    validate_level(level);
    auto& mgr = instance();
    mgr.m_global_level = level;
}

void DebugLevelManager::set_channel_level(const std::string& channel_pattern, int level) {
    validate_level(level);
    auto& mgr = instance();
    
    // Check if pattern already exists - update it
    for (auto& config : mgr.m_channel_configs) {
        if (config.pattern == channel_pattern) {
            config.level = level;
            return;
        }
    }
    
    // Add new pattern
    ChannelConfig config;
    config.pattern = channel_pattern;
    config.level = level;
    config.is_wildcard = (channel_pattern.find('*') != std::string::npos ||
                          channel_pattern.find('?') != std::string::npos);
    
    mgr.m_channel_configs.push_back(config);
}

void DebugLevelManager::parse_channel_spec(const std::string& channel_spec) {
    std::vector<std::string> entries;
    boost::split(entries, channel_spec, boost::is_any_of(","));
    
    for (const std::string& entry : entries) {
        if (entry.empty()) continue;
        
        try {
            auto [channel, level] = parse_channel_entry(entry);
            set_channel_level(channel, level);
        } catch (const std::exception& e) {
            std::cerr << "Warning: Failed to parse channel entry '" << entry 
                      << "': " << e.what() << std::endl;
        }
    }
}

int DebugLevelManager::get_channel_level(const std::string& channel) {
    const auto& mgr = instance();
    
    // Look for exact match first
    for (const auto& config : mgr.m_channel_configs) {
        if (!config.is_wildcard && config.pattern == channel) {
            return config.level;
        }
    }
    
    // Look for pattern match
    for (const auto& config : mgr.m_channel_configs) {
        if (config.is_wildcard && match_pattern(config.pattern, channel)) {
            return config.level;
        }
    }
    
    // Return global level if no match found
    return mgr.m_global_level;
}

bool DebugLevelManager::should_log(const std::string& channel, int level) {
    return level <= get_channel_level(channel);
}

int DebugLevelManager::get_global_level() {
    return instance().m_global_level;
}

void DebugLevelManager::load_from_environment() {
    const auto& mgr = instance();
    
    // Load global debug level
    std::string level_var = mgr.m_env_prefix + "_LEVEL";
    const char* env_level = std::getenv(level_var.c_str());
    if (env_level) {
        try {
            int level = boost::lexical_cast<int>(env_level);
            set_global_level(level);
        } catch (const std::exception& e) {
            std::cerr << "Warning: Invalid " << level_var << " value '" << env_level 
                      << "': " << e.what() << std::endl;
        }
    }
    
    // Load channel-specific levels
    std::string channels_var = mgr.m_env_prefix + "_CHANNELS";
    const char* env_channels = std::getenv(channels_var.c_str());
    if (env_channels) {
        try {
            parse_channel_spec(env_channels);
        } catch (const std::exception& e) {
            std::cerr << "Warning: Failed to parse " << channels_var << " '" << env_channels
                      << "': " << e.what() << std::endl;
        }
    }
}

void DebugLevelManager::load_from_file(const std::string& config_file) {
    std::ifstream file(config_file);
    if (!file.is_open()) {
        throw std::runtime_error("Cannot open debug configuration file: " + config_file);
    }
    
    std::string line;
    int line_number = 0;
    
    while (std::getline(file, line)) {
        ++line_number;
        
        // Skip empty lines and comments
        boost::trim(line);
        if (line.empty() || line[0] == '#') {
            continue;
        }
        
        try {
            // Check for global level setting
            if (boost::starts_with(line, "global=") || boost::starts_with(line, "level=")) {
                size_t eq_pos = line.find('=');
                int level = boost::lexical_cast<int>(line.substr(eq_pos + 1));
                set_global_level(level);
            } else {
                // Parse as channel:level entry
                auto [channel, level] = parse_channel_entry(line);
                set_channel_level(channel, level);
            }
        } catch (const std::exception& e) {
            std::cerr << "Warning: Error parsing line " << line_number 
                      << " in " << config_file << ": " << e.what() << std::endl;
        }
    }
}

void DebugLevelManager::clear_channel_configs() {
    auto& mgr = instance();
    mgr.m_channel_configs.clear();
}

std::vector<DebugLevelManager::ChannelConfig> DebugLevelManager::get_channel_configs() {
    return instance().m_channel_configs;
}

void DebugLevelManager::print_configuration(std::ostream& os) {
    const auto& mgr = instance();
    
    os << "Global debug level: " << mgr.m_global_level << "\n";
    os << "Environment prefix: " << mgr.m_env_prefix << "\n";
    
    if (!mgr.m_channel_configs.empty()) {
        os << "Channel-specific levels:\n";
        for (const auto& config : mgr.m_channel_configs) {
            os << "  " << config.pattern << " = " << config.level;
            if (config.is_wildcard) {
                os << " (pattern)";
            }
            os << "\n";
        }
    } else {
        os << "No channel-specific configurations.\n";
    }
}

void DebugLevelManager::validate_level(int level) {
    if (level < MIN_DEBUG_LEVEL || level > MAX_DEBUG_LEVEL) {
        throw std::invalid_argument("Debug level " + std::to_string(level) + 
                                   " is out of range [" + std::to_string(MIN_DEBUG_LEVEL) +
                                   ", " + std::to_string(MAX_DEBUG_LEVEL) + "]");
    }
}

void DebugLevelManager::set_env_prefix(const std::string& prefix) {
    auto& mgr = instance();
    mgr.m_env_prefix = prefix;
}

void DebugLevelManager::set_example_channels(const std::vector<std::string>& examples) {
    auto& mgr = instance();
    mgr.m_example_channels = examples;
}

std::string DebugLevelManager::get_env_prefix() {
    return instance().m_env_prefix;
}

std::string DebugLevelManager::get_help_text() {
    const auto& mgr = instance();
    std::ostringstream help;
    
    help << "\nDebug Level Management:\n"
         << "  Debug levels can be any integer from " << MIN_DEBUG_LEVEL 
         << " to " << MAX_DEBUG_LEVEL << ".\n"
         << "  Higher numbers mean more verbose output.\n\n"
         << "  Typical levels:\n"
         << "    0  - No debug output (default)\n"
         << "    10 - Entry/exit of major functions\n"
         << "    20 - Important values and decisions\n"
         << "    30 - Detailed operational information\n"
         << "    40 - Very verbose output\n"
         << "    50 - Extremely detailed debugging\n\n"
         << "  Channel Patterns:\n";
    
    for (const std::string& example : mgr.m_example_channels) {
        help << "    " << example << " - Example channel pattern\n";
    }
    
    help << "\n  Environment Variables:\n"
         << "    " << mgr.m_env_prefix << "_LEVEL    - Global debug level\n"
         << "    " << mgr.m_env_prefix << "_CHANNELS - Channel:level pairs (comma-separated)\n\n"
         << "  Examples:\n"
         << "    --debug-level 20\n"
         << "    --debug-channels \"" << (mgr.m_example_channels.empty() ? "project.component.*" : mgr.m_example_channels[0]) << ":30\"\n"
         << "    " << mgr.m_env_prefix << "_LEVEL=25 program\n";
    
    // Append library-specific help content if callback is registered
    {
        std::lock_guard<std::mutex> lock(s_callback_mutex);
        if (s_help_callback) {
            try {
                std::string library_help = s_help_callback();
                if (!library_help.empty()) {
                    help << library_help;
                }
            } catch (const std::exception& e) {
                // Gracefully handle callback exceptions
                help << "\n[Error retrieving library-specific help: " << e.what() << "]\n";
            }
        }
    }
    
    return help.str();
}

//=======================================================================
// Private Implementation
//=======================================================================

DebugLevelManager& DebugLevelManager::instance() {
    static DebugLevelManager mgr;
    return mgr;
}

bool DebugLevelManager::match_pattern(const std::string& pattern, const std::string& channel) {
    // Simple wildcard matching - convert to regex
    std::string regex_pattern = pattern;
    
    // Escape regex special characters except * and ?
    // Fixed: Escape backslashes FIRST, then other characters to avoid double-escaping
    
    // First, escape any existing backslashes in user input
    size_t pos = 0;
    while ((pos = regex_pattern.find("\\", pos)) != std::string::npos) {
        regex_pattern.replace(pos, 1, "\\\\");
        pos += 2; // Skip the newly inserted backslashes
    }
    
    // Then escape other special regex characters
    std::string special_chars = ".^$+{}[]|()";
    for (char c : special_chars) {
        std::string char_str(1, c);
        std::string escaped = "\\" + char_str;
        pos = 0;
        while ((pos = regex_pattern.find(char_str, pos)) != std::string::npos) {
            regex_pattern.replace(pos, 1, escaped);
            pos += escaped.length();
        }
    }
    
    // Convert wildcards to regex
    boost::replace_all(regex_pattern, "*", ".*");
    boost::replace_all(regex_pattern, "?", ".");
    
    // Add anchors for exact matching
    regex_pattern = "^" + regex_pattern + "$";
    
    try {
        std::regex regex(regex_pattern);
        return std::regex_match(channel, regex);
    } catch (const std::regex_error& e) {
        std::cerr << "Warning: Invalid regex pattern '" << regex_pattern 
                  << "' derived from '" << pattern << "': " << e.what() << std::endl;
        return false;
    }
}

std::pair<std::string, int> DebugLevelManager::parse_channel_entry(const std::string& entry) {
    size_t colon_pos = entry.find(':');
    if (colon_pos == std::string::npos) {
        throw std::invalid_argument("Channel entry must be in format 'channel:level'");
    }
    
    std::string channel = entry.substr(0, colon_pos);
    std::string level_str = entry.substr(colon_pos + 1);
    
    boost::trim(channel);
    boost::trim(level_str);
    
    if (channel.empty()) {
        throw std::invalid_argument("Channel name cannot be empty");
    }
    
    try {
        int level = boost::lexical_cast<int>(level_str);
        validate_level(level);
        return {channel, level};
    } catch (const boost::bad_lexical_cast& e) {
        throw std::invalid_argument("Invalid debug level: " + level_str);
    }
}

} // namespace AL
} // namespace LDASTools

// Restore GCC diagnostic state
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic pop
#endif