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

#include "bt.hpp"
#include "logger.hpp"
#include <cstring>

static const char* TAG = "BT_WRAPPER";

// Static instance for callbacks
Bluetooth* Bluetooth::s_instance_ = nullptr;

Bluetooth::Bluetooth()
    : m_isInitialized_(false)
    , m_mode_(BtMode::BLE)
    , m_connectionState_(BleConnectionState::DISCONNECTED)
    , m_connectedDeviceCount_(0)
#ifdef CONFIG_BT_ENABLED
    , m_gattsIf_(ESP_GATT_IF_NONE)
    , m_gattcIf_(ESP_GATT_IF_NONE)
#else
    , m_gattsIf_(0)
    , m_gattcIf_(0)
#endif
    , m_appId_(0)
    , m_gapCallback_(nullptr)
    , m_gapUserData_(nullptr)
    , m_gattsCallback_(nullptr)
    , m_gattsUserData_(nullptr)
    , m_gattcCallback_(nullptr)
    , m_gattcUserData_(nullptr)
{
    s_instance_ = this;
    ASF_LOGI(TAG, 2100, asf::logger::Criticality::LOW, "Bluetooth wrapper initialized");
}

Bluetooth::~Bluetooth()
{
    deinitialize();
    s_instance_ = nullptr;
    ASF_LOGI(TAG, 2101, asf::logger::Criticality::LOW, "Bluetooth wrapper destroyed");
}

bool Bluetooth::initialize(BtMode mode)
{
#ifdef CONFIG_BT_ENABLED
    if (m_isInitialized_) {
        ASF_LOGW(TAG, 2102, asf::logger::Criticality::MEDIUM, "Bluetooth 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);

    // Release Bluetooth controller memory if not needed
    ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));

    // Initialize Bluetooth controller
    esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
    ret = esp_bt_controller_init(&bt_cfg);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2103, asf::logger::Criticality::HIGH, "Failed to initialize BT controller: %s", esp_err_to_name(ret));
        return false;
    }

    // Enable Bluetooth controller
    ret = esp_bt_controller_enable(convertMode(mode));
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2104, asf::logger::Criticality::HIGH, "Failed to enable BT controller: %s", esp_err_to_name(ret));
        return false;
    }

    // Initialize Bluedroid stack
    ret = esp_bluedroid_init();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2105, asf::logger::Criticality::HIGH, "Failed to initialize Bluedroid: %s", esp_err_to_name(ret));
        return false;
    }

    // Enable Bluedroid stack
    ret = esp_bluedroid_enable();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2106, asf::logger::Criticality::HIGH, "Failed to enable Bluedroid: %s", esp_err_to_name(ret));
        return false;
    }

    // Register GAP callback
    ret = esp_ble_gap_register_callback(gapEventHandler);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2107, asf::logger::Criticality::HIGH, "Failed to register GAP callback: %s", esp_err_to_name(ret));
        return false;
    }

    // Register GATTS callback
    ret = esp_ble_gatts_register_callback(gattsEventHandler);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2108, asf::logger::Criticality::HIGH, "Failed to register GATTS callback: %s", esp_err_to_name(ret));
        return false;
    }

    // Register GATTC callback
    ret = esp_ble_gattc_register_callback(gattcEventHandler);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2109, asf::logger::Criticality::HIGH, "Failed to register GATTC callback: %s", esp_err_to_name(ret));
        return false;
    }

    m_mode_ = mode;
    m_isInitialized_ = true;
    ASF_LOGI(TAG, 2110, asf::logger::Criticality::LOW, "Bluetooth initialized successfully");
    return true;
#else
    ASF_LOGW(TAG, 2111, asf::logger::Criticality::MEDIUM, "Bluetooth disabled in sdkconfig");
    return false;
#endif
}

bool Bluetooth::deinitialize()
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGW(TAG, 2112, asf::logger::Criticality::MEDIUM, "Bluetooth not initialized");
        return true;
    }

    stopAdvertising();
    stopScanning();

    // Disable Bluedroid stack
    esp_err_t ret = esp_bluedroid_disable();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2113, asf::logger::Criticality::HIGH, "Failed to disable Bluedroid: %s", esp_err_to_name(ret));
    }

    // Deinitialize Bluedroid stack
    ret = esp_bluedroid_deinit();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2114, asf::logger::Criticality::HIGH, "Failed to deinitialize Bluedroid: %s", esp_err_to_name(ret));
    }

    // Disable Bluetooth controller
    ret = esp_bt_controller_disable();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2115, asf::logger::Criticality::HIGH, "Failed to disable BT controller: %s", esp_err_to_name(ret));
    }

    // Deinitialize Bluetooth controller
    ret = esp_bt_controller_deinit();
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2116, asf::logger::Criticality::HIGH, "Failed to deinitialize BT controller: %s", esp_err_to_name(ret));
    }

    m_isInitialized_ = false;
    m_connectionState_ = BleConnectionState::DISCONNECTED;
    m_connectedDeviceCount_ = 0;
    ASF_LOGI(TAG, 2117, asf::logger::Criticality::LOW, "Bluetooth deinitialized");
    return true;
#else
    return true;
#endif
}

bool Bluetooth::setDeviceName(const char* name)
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_ || name == nullptr) {
        ASF_LOGE(TAG, 2118, asf::logger::Criticality::HIGH, "Bluetooth not initialized or invalid name");
        return false;
    }

    esp_err_t ret = esp_ble_gap_set_device_name(name);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2119, asf::logger::Criticality::HIGH, "Failed to set device name: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2120, asf::logger::Criticality::LOW, "Device name set to: %s", name);
    return true;
#else
    return false;
#endif
}

bool Bluetooth::getDeviceName(char* name, size_t maxLen)
{
    if (name == nullptr || maxLen == 0) return false;
    strncpy(name, "ESP32-BT", maxLen - 1);
    name[maxLen - 1] = '\0';
    return true;
}

bool Bluetooth::setAdvParams(const BleAdvParams& params)
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGE(TAG, 2121, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
        return false;
    }

    esp_ble_adv_params_t advParams = {};
    advParams.adv_int_min = params.advIntMin;
    advParams.adv_int_max = params.advIntMax;
    advParams.adv_type = convertAdvType(params.advType);
    advParams.own_addr_type = static_cast<esp_ble_addr_type_t>(params.ownAddrType);
    advParams.peer_addr_type = static_cast<esp_ble_addr_type_t>(params.peerAddrType);
    memcpy(advParams.peer_addr, params.peerAddr, 6);
    advParams.channel_map = static_cast<esp_ble_adv_channel_t>(params.channelMap);
    advParams.adv_filter_policy = static_cast<esp_ble_adv_filter_t>(params.advFilterPolicy);

    esp_err_t ret = esp_ble_gap_config_adv_data_raw(nullptr, 0);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2122, asf::logger::Criticality::HIGH, "Failed to set advertising parameters: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2123, asf::logger::Criticality::LOW, "Advertising parameters set successfully");
    return true;
#else
    return false;
#endif
}

bool Bluetooth::setAdvData(const BleAdvData& advData)
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGE(TAG, 2124, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
        return false;
    }

    esp_ble_adv_data_t bleAdvData = {};
    bleAdvData.set_scan_rsp = false;
    bleAdvData.include_name = advData.setName;
    bleAdvData.include_txpower = advData.setTxPower;
    bleAdvData.min_interval = 0x0006;
    bleAdvData.max_interval = 0x0010;
    bleAdvData.appearance = advData.appearance;
    bleAdvData.manufacturer_len = advData.manufacturerDataLen;
    bleAdvData.p_manufacturer_data = advData.manufacturerData;
    bleAdvData.service_data_len = 0;
    bleAdvData.p_service_data = nullptr;
    bleAdvData.service_uuid_len = advData.serviceUuidLen;
    bleAdvData.p_service_uuid = advData.serviceUuid;
    bleAdvData.flag = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_BREDR_NOT_SPT);

    esp_err_t ret = esp_ble_gap_config_adv_data(&bleAdvData);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2125, asf::logger::Criticality::HIGH, "Failed to set advertising data: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2126, asf::logger::Criticality::LOW, "Advertising data set successfully");
    return true;
#else
    return false;
#endif
}

bool Bluetooth::startAdvertising()
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGE(TAG, 2127, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
        return false;
    }

    esp_ble_adv_params_t params = {};
    params.adv_int_min = 0x20;
    params.adv_int_max = 0x40;
    params.adv_type = ADV_TYPE_IND;
    params.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
    params.channel_map = ADV_CHNL_ALL;
    params.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY;

    esp_err_t ret = esp_ble_gap_start_advertising(&params);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2128, asf::logger::Criticality::HIGH, "Failed to start advertising: %s", esp_err_to_name(ret));
        return false;
    }

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

bool Bluetooth::stopAdvertising()
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGW(TAG, 2130, asf::logger::Criticality::MEDIUM, "Bluetooth not initialized");
        return true;
    }

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

    ASF_LOGI(TAG, 2132, asf::logger::Criticality::LOW, "BLE advertising stopped");
    return true;
#else
    return true;
#endif
}

bool Bluetooth::startScanning(uint32_t duration)
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGE(TAG, 2133, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
        return false;
    }

    esp_ble_scan_params_t scanParams = {};
    scanParams.scan_type = BLE_SCAN_TYPE_ACTIVE;
    scanParams.own_addr_type = BLE_ADDR_TYPE_PUBLIC;
    scanParams.scan_filter_policy = BLE_SCAN_FILTER_ALLOW_ALL;
    scanParams.scan_interval = 0x50;
    scanParams.scan_window = 0x30;
    scanParams.scan_duplicate = BLE_SCAN_DUPLICATE_DISABLE;

    esp_err_t ret = esp_ble_gap_set_scan_params(&scanParams);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2134, asf::logger::Criticality::HIGH, "Failed to set scan parameters: %s", esp_err_to_name(ret));
        return false;
    }

    ret = esp_ble_gap_start_scanning(duration);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2135, asf::logger::Criticality::HIGH, "Failed to start scanning: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2136, asf::logger::Criticality::LOW, "BLE scanning started (duration: %lu seconds)", duration);
    return true;
#else
    return false;
#endif
}

bool Bluetooth::stopScanning()
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGW(TAG, 2137, asf::logger::Criticality::MEDIUM, "Bluetooth not initialized");
        return true;
    }

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

    ASF_LOGI(TAG, 2139, asf::logger::Criticality::LOW, "BLE scanning stopped");
    return true;
#else
    return true;
#endif
}

uint16_t Bluetooth::createGattService(const GattService& service)
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGE(TAG, 2140, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
        return 0;
    }

    esp_gatt_srvc_id_t serviceId = {};
    serviceId.is_primary = service.isPrimary;
    serviceId.id.inst_id = service.serviceId;
    serviceId.id.uuid.len = ESP_UUID_LEN_16;
    serviceId.id.uuid.uuid.uuid16 = service.serviceUuid;

    esp_err_t ret = esp_ble_gatts_create_service(m_gattsIf_, &serviceId, service.numHandles);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2141, asf::logger::Criticality::HIGH, "Failed to create GATT service: %s", esp_err_to_name(ret));
        return 0;
    }

    ASF_LOGI(TAG, 2142, asf::logger::Criticality::LOW, "GATT service created successfully");
    return service.serviceId;
#else
    return 0;
#endif
}

bool Bluetooth::startGattService(uint16_t serviceHandle)
{
#ifdef CONFIG_BT_ENABLED
    if (!m_isInitialized_) {
        ASF_LOGE(TAG, 2143, asf::logger::Criticality::HIGH, "Bluetooth not initialized");
        return false;
    }

    esp_err_t ret = esp_ble_gatts_start_service(serviceHandle);
    if (ret != ESP_OK) {
        ASF_LOGE(TAG, 2144, asf::logger::Criticality::HIGH, "Failed to start GATT service: %s", esp_err_to_name(ret));
        return false;
    }

    ASF_LOGI(TAG, 2145, asf::logger::Criticality::LOW, "GATT service started successfully");
    return true;
#else
    return false;
#endif
}

BleConnectionState Bluetooth::getConnectionState() const
{
    return m_connectionState_;
}

bool Bluetooth::isConnected() const
{
    return m_connectionState_ == BleConnectionState::CONNECTED;
}

uint8_t Bluetooth::getConnectedDeviceCount() const
{
    return m_connectedDeviceCount_;
}

void Bluetooth::setGapEventCallback(BtGapEventCallback callback, void* userData)
{
    m_gapCallback_ = callback;
    m_gapUserData_ = userData;
}

void Bluetooth::setGattsEventCallback(BtGattsEventCallback callback, void* userData)
{
    m_gattsCallback_ = callback;
    m_gattsUserData_ = userData;
}

void Bluetooth::setGattcEventCallback(BtGattcEventCallback callback, void* userData)
{
    m_gattcCallback_ = callback;
    m_gattcUserData_ = userData;
}

bool Bluetooth::getMacAddress(uint8_t* mac)
{
#ifdef CONFIG_BT_ENABLED
    if (mac == nullptr) {
        ASF_LOGE(TAG, 2146, asf::logger::Criticality::HIGH, "Invalid MAC address buffer");
        return false;
    }

    memcpy(mac, esp_bt_dev_get_address(), 6);
    return true;
#else
    return false;
#endif
}

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

BleAdvParams Bluetooth::getDefaultAdvParams()
{
    BleAdvParams params = {};
    params.advIntMin = 0x20;
    params.advIntMax = 0x40;
    params.advType = BleAdvType::ADV_IND;
    params.ownAddrType = 0;
    params.peerAddrType = 0;
    memset(params.peerAddr, 0, 6);
    params.channelMap = 7;
    params.advFilterPolicy = 0;
    return params;
}

BleAdvData Bluetooth::getDefaultAdvData()
{
    BleAdvData advData = {};
    advData.setName = true;
    advData.setTxPower = true;
    advData.includeUuid = false;
    advData.setManufacturerData = false;
    advData.appearance = 0;
    advData.manufacturerId = 0;
    advData.manufacturerDataLen = 0;
    advData.manufacturerData = nullptr;
    advData.serviceUuidLen = 0;
    advData.serviceUuid = nullptr;
    advData.deviceName = nullptr;
    return advData;
}

GattService Bluetooth::getDefaultGattService()
{
    GattService service = {};
    service.serviceId = 0;
    service.serviceUuid = 0x180F;  // Battery Service UUID
    service.numHandles = 4;
    service.isPrimary = true;
    return service;
}

GattCharacteristic Bluetooth::getDefaultGattCharacteristic()
{
    GattCharacteristic characteristic = {};
    characteristic.charUuid = 0x2A19;  // Battery Level Characteristic UUID
    characteristic.properties = 0x12; // Read | Notify
    characteristic.permissions = 0x01; // Read
    characteristic.maxLen = 1;
    characteristic.autoRsp = true;
    return characteristic;
}

#ifdef CONFIG_BT_ENABLED
void Bluetooth::gapEventHandler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param)
{
    if (s_instance_ && s_instance_->m_gapCallback_) {
        s_instance_->m_gapCallback_(event, param, s_instance_->m_gapUserData_);
    }

    switch (event) {
        case ESP_GAP_BLE_ADV_DATA_SET_COMPLETE_EVT:
            ASF_LOGI(TAG, 2147, asf::logger::Criticality::LOW, "GAP: Advertising data set complete");
            break;
        case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
            ASF_LOGI(TAG, 2148, asf::logger::Criticality::LOW, "GAP: Advertising start complete");
            break;
        case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
            ASF_LOGI(TAG, 2149, asf::logger::Criticality::LOW, "GAP: Advertising stop complete");
            break;
        default:
            break;
    }
}

void Bluetooth::gattsEventHandler(esp_gatts_cb_event_t event, esp_gatt_if_t gatts_if, esp_ble_gatts_cb_param_t* param)
{
    if (s_instance_ && s_instance_->m_gattsCallback_) {
        s_instance_->m_gattsCallback_(event, gatts_if, param, s_instance_->m_gattsUserData_);
    }

    switch (event) {
        case ESP_GATTS_REG_EVT:
            ASF_LOGI(TAG, 2150, asf::logger::Criticality::LOW, "GATTS: Register complete");
            if (s_instance_) {
                s_instance_->m_gattsIf_ = gatts_if;
            }
            break;
        case ESP_GATTS_CONNECT_EVT:
            ASF_LOGI(TAG, 2151, asf::logger::Criticality::LOW, "GATTS: Device connected");
            if (s_instance_) {
                s_instance_->m_connectionState_ = BleConnectionState::CONNECTED;
                s_instance_->m_connectedDeviceCount_++;
            }
            break;
        case ESP_GATTS_DISCONNECT_EVT:
            ASF_LOGI(TAG, 2152, asf::logger::Criticality::LOW, "GATTS: Device disconnected");
            if (s_instance_) {
                s_instance_->m_connectionState_ = BleConnectionState::DISCONNECTED;
                if (s_instance_->m_connectedDeviceCount_ > 0) {
                    s_instance_->m_connectedDeviceCount_--;
                }
            }
            break;
        default:
            break;
    }
}

void Bluetooth::gattcEventHandler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if, esp_ble_gattc_cb_param_t* param)
{
    if (s_instance_ && s_instance_->m_gattcCallback_) {
        s_instance_->m_gattcCallback_(event, gattc_if, param, s_instance_->m_gattcUserData_);
    }

    switch (event) {
        case ESP_GATTC_REG_EVT:
            ASF_LOGI(TAG, 2153, asf::logger::Criticality::LOW, "GATTC: Register complete");
            if (s_instance_) {
                s_instance_->m_gattcIf_ = gattc_if;
            }
            break;
        default:
            break;
    }
}

esp_bt_mode_t Bluetooth::convertMode(BtMode mode)
{
    return static_cast<esp_bt_mode_t>(mode);
}

esp_ble_adv_type_t Bluetooth::convertAdvType(BleAdvType advType)
{
    return static_cast<esp_ble_adv_type_t>(advType);
}
#endif

bool Bluetooth::stopGattService(uint16_t serviceHandle) { return true; }
bool Bluetooth::sendNotification(uint16_t connId, uint16_t attrHandle, const uint8_t* data, size_t dataLen) { return true; }
bool Bluetooth::sendIndication(uint16_t connId, uint16_t attrHandle, const uint8_t* data, size_t dataLen) { return true; }
bool Bluetooth::connectToDevice(const uint8_t* remoteAddr, uint8_t addrType) { return true; }
bool Bluetooth::disconnectDevice(uint16_t connId) { return true; }
#ifdef CONFIG_BT_ENABLED
bool Bluetooth::setTxPower(esp_ble_power_type_t powerType, esp_power_level_t powerLevel) { return true; }
esp_power_level_t Bluetooth::getTxPower(esp_ble_power_type_t powerType) { return ESP_PWR_LVL_N0; }
#endif
