/**
 * @file test_logger.cpp
 * @brief Unit tests for ASF Logger
 * @author Mahmoud Elmohtady
 * @company Nabd solutions - ASF
 * @copyright Copyright (c) 2025
 */

#include "unity.h"
#include "logger.hpp"
#include <cstring>
#include <cstdio>

// Test constants
static const char* TEST_TAG = "TEST_LOGGER";
static const uint32_t TEST_ID = 12345;

/**
 * @brief Test logger initialization
 */
void test_logger_initialization()
{
    // Test default configuration
    asf::logger::LoggerConfig defaultConfig = asf::logger::getDefaultConfig();
    
    TEST_ASSERT_EQUAL(asf::logger::LogLevel::INFO, defaultConfig.minLevel);
    TEST_ASSERT_TRUE(defaultConfig.enableTimestamp);
    TEST_ASSERT_TRUE(defaultConfig.enableColor);
    TEST_ASSERT_TRUE(defaultConfig.enableId);
    TEST_ASSERT_EQUAL(256, defaultConfig.maxMessageLength);
    
    // Test custom configuration
    asf::logger::LoggerConfig customConfig = {};
    customConfig.minLevel = asf::logger::LogLevel::DEBUG;
    customConfig.enableTimestamp = false;
    customConfig.enableColor = false;
    customConfig.enableId = false;
    customConfig.maxMessageLength = 128;
    
    asf::logger::initialize(customConfig);
    
    TEST_ASSERT_EQUAL(asf::logger::LogLevel::DEBUG, asf::logger::getLogLevel());
}

/**
 * @brief Test log level functionality
 */
void test_log_levels()
{
    // Test setting different log levels
    asf::logger::setLogLevel(asf::logger::LogLevel::VERBOSE);
    TEST_ASSERT_EQUAL(asf::logger::LogLevel::VERBOSE, asf::logger::getLogLevel());
    
    asf::logger::setLogLevel(asf::logger::LogLevel::DEBUG);
    TEST_ASSERT_EQUAL(asf::logger::LogLevel::DEBUG, asf::logger::getLogLevel());
    
    asf::logger::setLogLevel(asf::logger::LogLevel::INFO);
    TEST_ASSERT_EQUAL(asf::logger::LogLevel::INFO, asf::logger::getLogLevel());
    
    asf::logger::setLogLevel(asf::logger::LogLevel::WARNING);
    TEST_ASSERT_EQUAL(asf::logger::LogLevel::WARNING, asf::logger::getLogLevel());
    
    asf::logger::setLogLevel(asf::logger::LogLevel::ERROR);
    TEST_ASSERT_EQUAL(asf::logger::LogLevel::ERROR, asf::logger::getLogLevel());
    
    asf::logger::setLogLevel(asf::logger::LogLevel::NONE);
    TEST_ASSERT_EQUAL(asf::logger::LogLevel::NONE, asf::logger::getLogLevel());
}

/**
 * @brief Test log level to string conversion
 */
void test_log_level_to_string()
{
    TEST_ASSERT_EQUAL_STRING("VERBOSE", asf::logger::logLevelToString(asf::logger::LogLevel::VERBOSE));
    TEST_ASSERT_EQUAL_STRING("DEBUG", asf::logger::logLevelToString(asf::logger::LogLevel::DEBUG));
    TEST_ASSERT_EQUAL_STRING("INFO", asf::logger::logLevelToString(asf::logger::LogLevel::INFO));
    TEST_ASSERT_EQUAL_STRING("WARNING", asf::logger::logLevelToString(asf::logger::LogLevel::WARNING));
    TEST_ASSERT_EQUAL_STRING("ERROR", asf::logger::logLevelToString(asf::logger::LogLevel::ERROR));
    TEST_ASSERT_EQUAL_STRING("NONE", asf::logger::logLevelToString(asf::logger::LogLevel::NONE));
}

/**
 * @brief Test log level to color conversion
 */
void test_log_level_to_color()
{
    // Test that color codes are returned (non-empty strings)
    const char* verboseColor = asf::logger::logLevelToColor(asf::logger::LogLevel::VERBOSE);
    const char* debugColor = asf::logger::logLevelToColor(asf::logger::LogLevel::DEBUG);
    const char* infoColor = asf::logger::logLevelToColor(asf::logger::LogLevel::INFO);
    const char* warningColor = asf::logger::logLevelToColor(asf::logger::LogLevel::WARNING);
    const char* errorColor = asf::logger::logLevelToColor(asf::logger::LogLevel::ERROR);
    
    TEST_ASSERT_NOT_NULL(verboseColor);
    TEST_ASSERT_NOT_NULL(debugColor);
    TEST_ASSERT_NOT_NULL(infoColor);
    TEST_ASSERT_NOT_NULL(warningColor);
    TEST_ASSERT_NOT_NULL(errorColor);
    
    // Test that different levels have different colors
    TEST_ASSERT_NOT_EQUAL_STRING(verboseColor, debugColor);
    TEST_ASSERT_NOT_EQUAL_STRING(debugColor, infoColor);
    TEST_ASSERT_NOT_EQUAL_STRING(infoColor, warningColor);
    TEST_ASSERT_NOT_EQUAL_STRING(warningColor, errorColor);
}

/**
 * @brief Test ISO timestamp generation
 */
void test_iso_timestamp()
{
    char timestampBuffer[32];
    const char* timestamp = asf::logger::getIsoTimestamp(timestampBuffer, sizeof(timestampBuffer));
    
    TEST_ASSERT_NOT_NULL(timestamp);
    TEST_ASSERT_GREATER_THAN(0, strlen(timestamp));
    
    // Check basic format (should contain 'T' and 'Z')
    TEST_ASSERT_NOT_NULL(strchr(timestamp, 'T'));
    TEST_ASSERT_NOT_NULL(strchr(timestamp, 'Z'));
    
    // Test with insufficient buffer
    char smallBuffer[10];
    const char* smallTimestamp = asf::logger::getIsoTimestamp(smallBuffer, sizeof(smallBuffer));
    TEST_ASSERT_EQUAL_STRING("", smallTimestamp);
    
    // Test with null buffer
    const char* nullTimestamp = asf::logger::getIsoTimestamp(nullptr, 32);
    TEST_ASSERT_EQUAL_STRING("", nullTimestamp);
}

/**
 * @brief Test configuration toggles
 */
void test_configuration_toggles()
{
    // Test timestamp toggle
    asf::logger::enableTimestamp(true);
    // No direct way to test this without capturing output, but ensure no crash
    
    asf::logger::enableTimestamp(false);
    // No direct way to test this without capturing output, but ensure no crash
    
    // Test color toggle
    asf::logger::enableColor(true);
    // No direct way to test this without capturing output, but ensure no crash
    
    asf::logger::enableColor(false);
    // No direct way to test this without capturing output, but ensure no crash
    
    // Test ID toggle
    asf::logger::enableId(true);
    // No direct way to test this without capturing output, but ensure no crash
    
    asf::logger::enableId(false);
    // No direct way to test this without capturing output, but ensure no crash
}

/**
 * @brief Test basic logging functions
 */
void test_basic_logging()
{
    // Initialize logger with verbose level to test all functions
    asf::logger::LoggerConfig config = asf::logger::getDefaultConfig();
    config.minLevel = asf::logger::LogLevel::VERBOSE;
    asf::logger::initialize(config);
    
    // Test all logging functions (should not crash)
    asf::logger::logVerbose(TEST_TAG, TEST_ID, "Verbose message: %d", 1);
    asf::logger::logDebug(TEST_TAG, TEST_ID, "Debug message: %d", 2);
    asf::logger::logInfo(TEST_TAG, TEST_ID, "Info message: %d", 3);
    asf::logger::logWarning(TEST_TAG, TEST_ID, "Warning message: %d", 4);
    asf::logger::logError(TEST_TAG, TEST_ID, "Error message: %d", 5);
    
    // Test main log function
    asf::logger::log(TEST_TAG, TEST_ID, asf::logger::LogLevel::INFO, "Main log function: %s", "test");
}

/**
 * @brief Test logging macros
 */
void test_logging_macros()
{
    // Initialize logger with verbose level
    asf::logger::LoggerConfig config = asf::logger::getDefaultConfig();
    config.minLevel = asf::logger::LogLevel::VERBOSE;
    asf::logger::initialize(config);
    
    // Test all macros (should not crash)
    ASF_LOG_VERBOSE(TEST_TAG, TEST_ID, "Verbose macro: %d", 1);
    ASF_LOG_DEBUG(TEST_TAG, TEST_ID, "Debug macro: %d", 2);
    ASF_LOG_INFO(TEST_TAG, TEST_ID, "Info macro: %d", 3);
    ASF_LOG_WARNING(TEST_TAG, TEST_ID, "Warning macro: %d", 4);
    ASF_LOG_ERROR(TEST_TAG, TEST_ID, "Error macro: %d", 5);
    
    // Test short form macros
    ASF_LOGV(TEST_TAG, TEST_ID, "Verbose short macro: %d", 1);
    ASF_LOGD(TEST_TAG, TEST_ID, "Debug short macro: %d", 2);
    ASF_LOGI(TEST_TAG, TEST_ID, "Info short macro: %d", 3);
    ASF_LOGW(TEST_TAG, TEST_ID, "Warning short macro: %d", 4);
    ASF_LOGE(TEST_TAG, TEST_ID, "Error short macro: %d", 5);
}

/**
 * @brief Test log level filtering
 */
void test_log_level_filtering()
{
    // Set log level to WARNING
    asf::logger::setLogLevel(asf::logger::LogLevel::WARNING);
    
    // These should be filtered out (no crash expected)
    asf::logger::logVerbose(TEST_TAG, TEST_ID, "This should be filtered");
    asf::logger::logDebug(TEST_TAG, TEST_ID, "This should be filtered");
    asf::logger::logInfo(TEST_TAG, TEST_ID, "This should be filtered");
    
    // These should pass through (no crash expected)
    asf::logger::logWarning(TEST_TAG, TEST_ID, "This should pass");
    asf::logger::logError(TEST_TAG, TEST_ID, "This should pass");
}

/**
 * @brief Test null parameter handling
 */
void test_null_parameters()
{
    // Test with null tag (should not crash)
    asf::logger::logInfo(nullptr, TEST_ID, "Test message");
    
    // Test with null format (should not crash)
    asf::logger::logInfo(TEST_TAG, TEST_ID, nullptr);
    
    // Test main log function with null parameters
    asf::logger::log(nullptr, TEST_ID, asf::logger::LogLevel::INFO, "Test message");
    asf::logger::log(TEST_TAG, TEST_ID, asf::logger::LogLevel::INFO, nullptr);
}

/**
 * @brief Test long messages
 */
void test_long_messages()
{
    // Create a long message
    char longMessage[512];
    memset(longMessage, 'A', sizeof(longMessage) - 1);
    longMessage[sizeof(longMessage) - 1] = '\0';
    
    // Should handle long messages gracefully (truncate if necessary)
    asf::logger::logInfo(TEST_TAG, TEST_ID, "Long message: %s", longMessage);
}

/**
 * @brief Test performance (basic timing)
 */
void test_performance()
{
    // Set to ERROR level to minimize output
    asf::logger::setLogLevel(asf::logger::LogLevel::ERROR);
    
    // Perform many log calls that should be filtered out
    for (int i = 0; i < 1000; i++) {
        asf::logger::logInfo(TEST_TAG, i, "Performance test message %d", i);
    }
    
    // This test mainly ensures no crashes during high-frequency logging
    TEST_ASSERT_TRUE(true); // If we reach here, performance test passed
}

/**
 * @brief Run all logger tests
 */
extern "C" void app_main()
{
    UNITY_BEGIN();
    
    RUN_TEST(test_logger_initialization);
    RUN_TEST(test_log_levels);
    RUN_TEST(test_log_level_to_string);
    RUN_TEST(test_log_level_to_color);
    RUN_TEST(test_iso_timestamp);
    RUN_TEST(test_configuration_toggles);
    RUN_TEST(test_basic_logging);
    RUN_TEST(test_logging_macros);
    RUN_TEST(test_log_level_filtering);
    RUN_TEST(test_null_parameters);
    RUN_TEST(test_long_messages);
    RUN_TEST(test_performance);
    
    UNITY_END();
}