/* -*- mode: c++ -*-
 * Copyright 2025 Dracal Technologies Inc. All rights reserved.
 */

#pragma once

#include <dracal/common/fmt.hpp>
#include <dracal/common/fs.hpp>
#include <dracal/common/platform.hpp>
#include <dracal/common/string.hpp>
#include <dracal/common/version.hpp>

#include <spdlog/spdlog.h>

#include <cstdint>
#include <memory>
#include <string>

#if defined(DRACAL_HAS_SOURCE_LOCATION)
    #include <source_location>
#endif

namespace dracal::common {

/**
 * @brief Enumeration representing severity levels for logging.
 */
enum class log_severity { trace, debug, info, warn, error };

/**
 * @brief Logs a trace message.
 *
 * Logs the specified trace message with optional format arguments. Trace logs provide the most
 * detailed debugging information and are typically used for detailed troubleshooting and
 * development purposes.
 *
 * @param ... The message string or format string with optional format arguments to be logged.
 *
 * @example
 * dracal_trace("Entering function");
 * dracal_trace("Processing item {} of {}", current_index, total_items);
 * dracal_trace("Buffer state: size={}, capacity={}, ptr={}", buf.size(), buf.capacity(), fmt::ptr(buf.data()));
 */
#if defined(DRACAL_HAS_SOURCE_LOCATION)
    #define dracal_trace(...)                                                                                          \
        dracal::common::log(dracal::common::log_severity::trace, std::source_location::current().file_name(),          \
                            std::source_location::current().line(), "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#else
    #define dracal_trace(...)                                                                                          \
        dracal::common::log(dracal::common::log_severity::trace, __FILE__, __LINE__, "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#endif

/**
 * @brief Logs a debug message.
 *
 * Logs the specified debug message with optional format arguments. Debug logs are used for
 * diagnostic information that is useful during development and troubleshooting.
 *
 * @param ... The message string or format string with optional format arguments to be logged.
 *
 * @example
 * dracal_debug("Configuration loaded successfully");
 * dracal_debug("Device {} initialized with {} endpoints", device_name, endpoint_count);
 * dracal_debug("Received packet: type={:#x}, length={} bytes", packet_type, packet_length);
 */
#if defined(DRACAL_HAS_SOURCE_LOCATION)
    #define dracal_debug(...)                                                                                          \
        dracal::common::log(dracal::common::log_severity::debug, std::source_location::current().file_name(),          \
                            std::source_location::current().line(), "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#else
    #define dracal_debug(...)                                                                                          \
        dracal::common::log(dracal::common::log_severity::debug, __FILE__, __LINE__, "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#endif

/**
 * @brief Logs an informational message.
 *
 * Logs the specified informational message with optional format arguments. Info logs are used
 * for general informational messages that highlight the progress or state of the application.
 *
 * @param ... The message string or format string with optional format arguments to be logged.
 *
 * @example
 * dracal_info("Application started");
 * dracal_info("Connected to device: {}", device_serial);
 * dracal_info("Processing completed: {} items in {:.2f}s", item_count, elapsed_time);
 */
#if defined(DRACAL_HAS_SOURCE_LOCATION)
    #define dracal_info(...)                                                                                           \
        dracal::common::log(dracal::common::log_severity::info, std::source_location::current().file_name(),           \
                            std::source_location::current().line(), "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#else
    #define dracal_info(...)                                                                                           \
        dracal::common::log(dracal::common::log_severity::info, __FILE__, __LINE__, "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#endif

/**
 * @brief Logs a warning message.
 *
 * Logs the specified warning message with optional format arguments. Warning logs indicate
 * potentially harmful situations or unexpected conditions that don't prevent the application
 * from continuing.
 *
 * @param ... The message string or format string with optional format arguments to be logged.
 *
 * @example
 * dracal_warn("Configuration file not found, using defaults");
 * dracal_warn("Retry attempt {} of {}", retry_count, max_retries);
 * dracal_warn("Device {} temperature high: {:.1f}°C (threshold: {:.1f}°C)", device_id, temp, threshold);
 */
#if defined(DRACAL_HAS_SOURCE_LOCATION)
    #define dracal_warn(...)                                                                                           \
        dracal::common::log(dracal::common::log_severity::warn, std::source_location::current().file_name(),           \
                            std::source_location::current().line(), "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#else
    #define dracal_warn(...)                                                                                           \
        dracal::common::log(dracal::common::log_severity::warn, __FILE__, __LINE__, "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#endif

/**
 * @brief Logs an error message.
 *
 * Logs the specified error message with optional format arguments. Error logs indicate serious
 * problems that prevent normal operation or cause a function to fail.
 *
 * @param ... The message string or format string with optional format arguments to be logged.
 *
 * @example
 * dracal_error("Failed to open device");
 * dracal_error("Connection timeout after {}ms", timeout_ms);
 * dracal_error("Device {:#x} error: {} (code: {})", device_id, error_msg, error_code);
 */
#if defined(DRACAL_HAS_SOURCE_LOCATION)
    #define dracal_error(...)                                                                                          \
        dracal::common::log(dracal::common::log_severity::error, std::source_location::current().file_name(),          \
                            std::source_location::current().line(), "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#else
    #define dracal_error(...)                                                                                          \
        dracal::common::log(dracal::common::log_severity::error, __FILE__, __LINE__, "[LOC] {}#L{} [MSG] " __VA_ARGS__)
#endif

/**
 * @brief Internal logger implementation with private access control.
 */
class global_logger final {
  private:
    /**
     * @brief Gets the global logger.
     *
     * @return The global logger.
     */
    // cppcheck-suppress unusedPrivateFunction
    // cppcheck-suppress unmatchedSuppression
    static std::shared_ptr<spdlog::logger> &get();

    template <typename... Args>
    friend void log(const log_severity level, const std::string &file, const size_t line, const std::string &fmt_str,
                    Args &&...args);

    friend bool initialize_logging(const std::string &, const std::string &, bool, size_t, bool);
    friend void enable_trace_logs(bool);
    friend void enable_debug_logs(bool);
    friend void quiet_logs();
};

/**
 * @brief Logs a message with the specified severity level (with format arguments).
 *
 * Logs the message with the specified severity level, file, line number, and message text.
 * The message is formatted using fmt/spdlog formatting syntax with compile-time format string validation.
 *
 * @tparam Args The types of the format arguments.
 * @param level The severity level of the message.
 * @param file The file where the message originated.
 * @param line The line number in the file where the message originated.
 * @param fmt The format string (must be a compile-time constant string compatible with fmt syntax).
 * @param args The format arguments to be inserted into the format string.
 *
 * @note This function uses fmt::format_string for compile-time format string validation.
 * @note If the logger is not initialized, this function returns silently without logging.
 *
 * @example
 * dracal_info("Processing file: {}", filename);
 * dracal_error("Failed to open device {:#x} with error code {}", device_id, error);
 */
template <typename... Args>
void log(const log_severity level, const std::string &file, const size_t line, const std::string &fmt_str,
         Args &&...args) {
    auto &logger = global_logger::get();
    if (!logger) {
        return;
    }

    const std::string filename_1 = str::trim_all_before_last(fs::path(file).generic_string(), "/dracal/src/");
    const std::string filename_2 = str::trim_all_before_last(filename_1, "/sw-dracal/src/");
    const std::string filename_3 = str::trim_all_before_last(filename_2, "/_deps/dracal-view-src/");

    switch (level) {
    case common::log_severity::trace:
        logger->trace(fmt::runtime(fmt_str), filename_2, line, std::forward<Args>(args)...);
        break;
    case common::log_severity::debug:
        logger->debug(fmt::runtime(fmt_str), filename_2, line, std::forward<Args>(args)...);
        break;
    case common::log_severity::info:
        logger->info(fmt::runtime(fmt_str), filename_2, line, std::forward<Args>(args)...);
        break;
    case common::log_severity::warn:
        logger->warn(fmt::runtime(fmt_str), filename_2, line, std::forward<Args>(args)...);
        break;
    case common::log_severity::error:
        logger->error(fmt::runtime(fmt_str), filename_2, line, std::forward<Args>(args)...);
        break;
    default:
        logger->warn("unknown severity level({}) using WARNING as fallback", static_cast<int>(level));
        logger->warn(fmt::runtime(fmt_str), filename_2, line, std::forward<Args>(args)...);
        break;
    }
}

/**
 * @brief Initializes logging.
 *
 * Initializes logging with the specified log filename, format, and maximum file size.
 *
 * @param log_filename The name of the log file.
 * @param format The log format (spdlog pattern).
 * @param enable_std_output Whether to enable standard output logging.
 * @param max_file_size The maximum size of the log file in bytes.
 * @param service Whether the logging is for a service in %PROGRAMDATA% instead of %APPDATA% for windows.
 * @return True if logging was successfully initialized, false otherwise.
 */
bool initialize_logging(const std::string &log_filename, const std::string &format, const bool enable_std_output,
                        const size_t max_file_size, const bool service);

/**
 * @brief Enables trace level logging for all loggers.
 *
 * This function enables TRACE level logging which provides detailed debugging information
 * including function names and locations. Trace logs are typically used for detailed
 * debugging and troubleshooting purposes.
 *
 * @param enable Whether to enable or disable trace level logging.
 */
void enable_trace_logs(const bool enable);

/**
 * @brief Enables debug level logging for all loggers.
 *
 * This function enables DEBUG level logging which provides detailed debugging information
 * including function names and locations. Debug logs are typically used for detailed
 * debugging and troubleshooting purposes.
 *
 * @param enable Whether to enable or disable debug level logging.
 */
void enable_debug_logs(const bool enable);

/**
 * @brief Disables all logging output by setting the log level to off.
 *
 * This function sets the global logger's level to spdlog::level::off,
 * effectively silencing all log messages. A final info message is logged
 * before logging is disabled.
 */
void quiet_logs();

} // namespace dracal::common