/**
 * @file wifi.cpp
 * @brief WiFi wrapper component implementation
 * @author Mahmoud Elmohtady
 * @company Nabd solutions - ASF
 * @copyright Copyright (c) 2025
 */

#include "wifi.hpp"
#include "logger.hpp"
#include "nvs_flash.h"
#include <cstring>

static const char* TAG = "WIFI_WRAPPER";

Wifi::Wifi()
    : m_isInitialized_(false)
    , m_connectionStatus_(WifiConnectionStatus::DISCONNECTED)
    , m_netifSta_(nullptr)
    , m_netifAp_(nullptr)
    , m_eventCallback_(nullptr)
    , m_eventCallbackArg_(nullptr)
{
    memset(&m_config_, 0, sizeof(m_config_));
    ASF_LOGI(TAG, 2700, asf::logger::Criticality::LOW, "WiFi wrapper initialized");
}

Wifi::~Wifi()
{
    deinitialize();
    ASF_LOGI(TAG, 2701, asf::logger::Criticality::LOW, "WiFi wrapper destroyed");
}

bool Wifi::initialize(const WifiConfig& config)
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (m_isInitialized_) {
        ASF_LOGW(TAG, 2702, asf::logger::Criticality::MEDIUM, "WiFi already initialized");
        return true;
    }

    // Initialize NVS
    esp_err_t ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // Initialize TCP/IP stack
    ESP_ERROR_CHECK(esp_netif_init());

    // Create default event loop
    ESP_ERROR_CHECK(esp_event_loop_create_default());

    // Create network interfaces
    if (config.mode == WifiMode::STA || config.mode == WifiMode::APSTA) {
        m_netifSta_ = esp_netif_create_default_wifi_sta();
    }
    
    if (config.mode == WifiMode::AP || config.mode == WifiMode::APSTA) {
        m_netifAp_ = esp_netif_create_default_wifi_ap();
    }

    // Initialize WiFi
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));

    // Register event handlers
    ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, 
                                               &wifiEventHandler, this));
    ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, 
                                               &ipEventHandler, this));

    // Set WiFi mode
    ESP_ERROR_CHECK(esp_wifi_set_mode(convertMode(config.mode)));

    // Configure WiFi
    if (config.mode == WifiMode::STA || config.mode == WifiMode::APSTA) {
        wifi_config_t staConfig = {};
        strncpy((char*)staConfig.sta.ssid, config.staConfig.ssid, sizeof(staConfig.sta.ssid) - 1);
        strncpy((char*)staConfig.sta.password, config.staConfig.password, sizeof(staConfig.sta.password) - 1);
        staConfig.sta.threshold.authmode = convertAuthMode(config.staConfig.authMode);
        staConfig.sta.pmf_cfg.required = config.staConfig.pmfRequired;
        
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &staConfig));
    }

    if (config.mode == WifiMode::AP || config.mode == WifiMode::APSTA) {
        wifi_config_t apConfig = {};
        strncpy((char*)apConfig.ap.ssid, config.apConfig.ssid, sizeof(apConfig.ap.ssid) - 1);
        strncpy((char*)apConfig.ap.password, config.apConfig.password, sizeof(apConfig.ap.password) - 1);
        apConfig.ap.ssid_len = strlen(config.apConfig.ssid);
        apConfig.ap.channel = config.apConfig.channel;
        apConfig.ap.max_connection = config.apConfig.maxConnections;
        apConfig.ap.authmode = convertAuthMode(config.apConfig.authMode);
        apConfig.ap.ssid_hidden = config.apConfig.ssidHidden ? 1 : 0;
        
        ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &apConfig));
    }

    m_config_ = config;
    m_isInitialized_ = true;
    ASF_LOGI(TAG, 2703, asf::logger::Criticality::LOW, "WiFi initialized successfully");
    return true;
#else
    ASF_LOGW(TAG, 2704, asf::logger::Criticality::MEDIUM, "WiFi disabled in sdkconfig");
    return false;
#endif
}

bool Wifi::deinitialize()
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGW(TAG, 2705, asf::logger::Criticality::MEDIUM, "WiFi not initialized");
        return true;
    }

    stop();

    // Unregister event handlers
    esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifiEventHandler);
    esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &ipEventHandler);

    // Deinitialize WiFi
    esp_wifi_deinit();

    // Destroy network interfaces
    if (m_netifSta_) {
        esp_netif_destroy_default_wifi(m_netifSta_);
        m_netifSta_ = nullptr;
    }
    
    if (m_netifAp_) {
        esp_netif_destroy_default_wifi(m_netifAp_);
        m_netifAp_ = nullptr;
    }

    m_isInitialized_ = false;
    m_connectionStatus_ = WifiConnectionStatus::DISCONNECTED;
    ASF_LOGI(TAG, 2706, asf::logger::Criticality::LOW, "WiFi deinitialized");
    return true;
#else
    return true;
#endif
}

bool Wifi::start()
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGE(TAG, 2707, asf::logger::Criticality::HIGH, "WiFi not initialized");
        return false;
    }

    esp_err_t ret = esp_wifi_start();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2708, asf::logger::Criticality::HIGH, "Failed to start WiFi: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2709, asf::logger::Criticality::LOW, "WiFi started");
    return true;
#else
    return false;
#endif
}

bool Wifi::stop()
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGW(TAG, 2710, asf::logger::Criticality::MEDIUM, "WiFi not initialized");
        return true;
    }

    esp_err_t ret = esp_wifi_stop();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2711, asf::logger::Criticality::HIGH, "Failed to stop WiFi: %s", esp_err_to_name(ret));
        return false;
    }

    m_connectionStatus_ = WifiConnectionStatus::DISCONNECTED;
    ASF_LOGI(TAG, 2712, asf::logger::Criticality::LOW, "WiFi stopped");
    return true;
#else
    return true;
#endif
}

bool Wifi::connect()
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGE(TAG, 2713, asf::logger::Criticality::HIGH, "WiFi not initialized");
        return false;
    }

    if (m_config_.mode != WifiMode::STA && m_config_.mode != WifiMode::APSTA) {
        ASF_LOGE(TAG, 2714, asf::logger::Criticality::HIGH, "WiFi not in STA mode");
        return false;
    }

    m_connectionStatus_ = WifiConnectionStatus::CONNECTING;
    
    esp_err_t ret = esp_wifi_connect();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2715, asf::logger::Criticality::HIGH, "Failed to connect to WiFi: %s", esp_err_to_name(ret));
        m_connectionStatus_ = WifiConnectionStatus::FAILED;
        return false;
    }

    ASF_LOGI(TAG, 2716, asf::logger::Criticality::LOW, "WiFi connection initiated");
    return true;
#else
    return false;
#endif
}

bool Wifi::disconnect()
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGW(TAG, 2717, asf::logger::Criticality::MEDIUM, "WiFi not initialized");
        return true;
    }

    esp_err_t ret = esp_wifi_disconnect();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2718, asf::logger::Criticality::HIGH, "Failed to disconnect WiFi: %s", esp_err_to_name(ret));
        return false;
    }

    m_connectionStatus_ = WifiConnectionStatus::DISCONNECTED;
    ASF_LOGI(TAG, 2719, asf::logger::Criticality::LOW, "WiFi disconnected");
    return true;
#else
    return true;
#endif
}

int32_t Wifi::scan(WifiScanResult* results, size_t maxResults, uint32_t scanTimeMs)
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (!m_isInitialized_ || results == nullptr || maxResults == 0) {
        ASF_LOGE(TAG, 2720, asf::logger::Criticality::HIGH, "Invalid scan parameters");
        return -1;
    }

    wifi_scan_config_t scanConfig = {};
    scanConfig.ssid = nullptr;
    scanConfig.bssid = nullptr;
    scanConfig.channel = 0;
    scanConfig.show_hidden = true;
    scanConfig.scan_type = WIFI_SCAN_TYPE_ACTIVE;
    scanConfig.scan_time.active.min = scanTimeMs / 2;
    scanConfig.scan_time.active.max = scanTimeMs;

    esp_err_t ret = esp_wifi_scan_start(&scanConfig, true);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2721, asf::logger::Criticality::HIGH, "Failed to start WiFi scan: %s", esp_err_to_name(ret));
        return -1;
    }

    uint16_t apCount = 0;
    esp_wifi_scan_get_ap_num(&apCount);
    
    if (apCount == 0) {
        ASF_LOGI(TAG, 2722, asf::logger::Criticality::LOW, "No access points found");
        return 0;
    }

    uint16_t actualCount = (apCount > maxResults) ? maxResults : apCount;
    wifi_ap_record_t* apRecords = new wifi_ap_record_t[actualCount];
    
    ret = esp_wifi_scan_get_ap_records(&actualCount, apRecords);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2723, asf::logger::Criticality::HIGH, "Failed to get scan results: %s", esp_err_to_name(ret));
        delete[] apRecords;
        return -1;
    }

    // Convert results
    for (uint16_t i = 0; i < actualCount; i++) {
        strncpy(results[i].ssid, (char*)apRecords[i].ssid, sizeof(results[i].ssid) - 1);
        results[i].ssid[sizeof(results[i].ssid) - 1] = '\0';
        memcpy(results[i].bssid, apRecords[i].bssid, 6);
        results[i].channel = apRecords[i].primary;
        results[i].rssi = apRecords[i].rssi;
        results[i].authMode = static_cast<WifiAuthMode>(apRecords[i].authmode);
    }

    delete[] apRecords;
    ASF_LOGI(TAG, 2724, asf::logger::Criticality::LOW, "WiFi scan completed, found %d networks", actualCount);
    return actualCount;
#else
    return -1;
#endif
}

WifiConnectionStatus Wifi::getConnectionStatus() const
{
    return m_connectionStatus_;
}

bool Wifi::isConnected() const
{
    return m_connectionStatus_ == WifiConnectionStatus::CONNECTED;
}

int32_t Wifi::getRssi()
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (!isConnected()) {
        ASF_LOGW(TAG, 2725, asf::logger::Criticality::MEDIUM, "WiFi not connected");
        return 0;
    }

    wifi_ap_record_t apInfo;
    esp_err_t ret = esp_wifi_sta_get_ap_info(&apInfo);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2726, asf::logger::Criticality::HIGH, "Failed to get AP info: %s", esp_err_to_name(ret));
        return 0;
    }

    return apInfo.rssi;
#else
    return 0;
#endif
}

bool Wifi::getIpAddress(char* ip, size_t maxLen)
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (!isConnected() || ip == nullptr || maxLen == 0) {
        return false;
    }

    esp_netif_ip_info_t ipInfo;
    esp_err_t ret = esp_netif_get_ip_info(m_netifSta_, &ipInfo);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2727, asf::logger::Criticality::HIGH, "Failed to get IP info: %s", esp_err_to_name(ret));
        return false;
    }

    snprintf(ip, maxLen, IPSTR, IP2STR(&ipInfo.ip));
    return true;
#else
    return false;
#endif
}

bool Wifi::getMacAddress(uint8_t* mac, WifiMode mode)
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (mac == nullptr) {
        return false;
    }

    wifi_interface_t interface = (mode == WifiMode::AP) ? WIFI_IF_AP : WIFI_IF_STA;
    esp_err_t ret = esp_wifi_get_mac(interface, mac);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2728, asf::logger::Criticality::HIGH, "Failed to get MAC address: %s", esp_err_to_name(ret));
        return false;
    }

    return true;
#else
    return false;
#endif
}

void Wifi::setEventCallback(WifiEventCallback callback, void* arg)
{
    m_eventCallback_ = callback;
    m_eventCallbackArg_ = arg;
}

int32_t Wifi::getConnectedStations()
{
#ifdef CONFIG_ESP_WIFI_ENABLED
    if (m_config_.mode != WifiMode::AP && m_config_.mode != WifiMode::APSTA) {
        ASF_LOGE(TAG, 2729, asf::logger::Criticality::HIGH, "WiFi not in AP mode");
        return -1;
    }

    wifi_sta_list_t staList;
    esp_err_t ret = esp_wifi_ap_get_sta_list(&staList);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2730, asf::logger::Criticality::HIGH, "Failed to get connected stations: %s", esp_err_to_name(ret));
        return -1;
    }

    return staList.num;
#else
    return -1;
#endif
}

bool Wifi::isInitialized() const
{
    return m_isInitialized_;
}

WifiStaConfig Wifi::getDefaultStaConfig()
{
    WifiStaConfig config = {};
    strcpy(config.ssid, "");
    strcpy(config.password, "");
    config.authMode = WifiAuthMode::WPA2_PSK;
    config.pmfRequired = false;
    config.channel = 0;
    config.rssiThreshold = -127;
    return config;
}

WifiApConfig Wifi::getDefaultApConfig()
{
    WifiApConfig config = {};
    strcpy(config.ssid, "ESP32-AP");
    strcpy(config.password, "12345678");
    config.authMode = WifiAuthMode::WPA2_PSK;
    config.channel = 1;
    config.maxConnections = 4;
    config.ssidHidden = false;
    return config;
}

#ifdef CONFIG_ESP_WIFI_ENABLED
wifi_mode_t Wifi::convertMode(WifiMode mode)
{
    switch (mode) {
        case WifiMode::NONE:
            return WIFI_MODE_NULL;
        case WifiMode::STA:
            return WIFI_MODE_STA;
        case WifiMode::AP:
            return WIFI_MODE_AP;
        case WifiMode::APSTA:
            return WIFI_MODE_APSTA;
        default:
            return WIFI_MODE_NULL;
    }
}

wifi_auth_mode_t Wifi::convertAuthMode(WifiAuthMode authMode)
{
    return static_cast<wifi_auth_mode_t>(authMode);
}

void Wifi::wifiEventHandler(void* arg, esp_event_base_t eventBase, int32_t eventId, void* eventData)
{
    Wifi* wifi = static_cast<Wifi*>(arg);
    
    switch (eventId) {
        case WIFI_EVENT_STA_START:
            ASF_LOGI(TAG, 2731, asf::logger::Criticality::LOW, "WiFi STA started");
            break;
            
        case WIFI_EVENT_STA_CONNECTED:
            ASF_LOGI(TAG, 2732, asf::logger::Criticality::LOW, "WiFi STA connected");
            wifi->m_connectionStatus_ = WifiConnectionStatus::CONNECTED;
            break;
            
        case WIFI_EVENT_STA_DISCONNECTED:
            ASF_LOGI(TAG, 2733, asf::logger::Criticality::LOW, "WiFi STA disconnected");
            wifi->m_connectionStatus_ = WifiConnectionStatus::DISCONNECTED;
            if (wifi->m_eventCallback_) {
                wifi->m_eventCallback_(WifiConnectionStatus::DISCONNECTED, wifi->m_eventCallbackArg_);
            }
            break;
            
        case WIFI_EVENT_AP_START:
            ASF_LOGI(TAG, 2734, asf::logger::Criticality::LOW, "WiFi AP started");
            break;
            
        case WIFI_EVENT_AP_STOP:
            ASF_LOGI(TAG, 2735, asf::logger::Criticality::LOW, "WiFi AP stopped");
            break;
            
        default:
            break;
    }
}

void Wifi::ipEventHandler(void* arg, esp_event_base_t eventBase, int32_t eventId, void* eventData)
{
    Wifi* wifi = static_cast<Wifi*>(arg);
    
    if (eventId == IP_EVENT_STA_GOT_IP) {
        ip_event_got_ip_t* event = (ip_event_got_ip_t*) eventData;
        ASF_LOGI(TAG, 2736, asf::logger::Criticality::LOW, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
        wifi->m_connectionStatus_ = WifiConnectionStatus::CONNECTED;
        
        if (wifi->m_eventCallback_) {
            wifi->m_eventCallback_(WifiConnectionStatus::CONNECTED, wifi->m_eventCallbackArg_);
        }
    }
}
#endif
