#include "link_ble.h"
#include "remote_control.h"
#include "wifidevnum_config.h"
#include "device_nvs.h"
#include "rc_pwm_control.h"
#include "esp_log.h"
#include "esp_err.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/portmacro.h"
#include <stdlib.h>
#include <string.h>
#include "nimble/nimble_port.h"
#include "esp_bt.h"
#include "host/ble_hs.h"
#include "host/ble_gap.h"
#include "host/ble_uuid.h"
/* ESP-IDF v5.5.x NimBLE：GATT Server API（ble_gatts_* 等）由 ble_hs.h 引入，勿再包含已移除的 host/ble_gatts.h */
#include "host/util/util.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"
#include "os/os_mbuf.h"
#include <stdio.h>
#include <string.h>
#include <stdarg.h>

#include "sdkconfig.h"

/* 引入构建配置头文件，支持 Release 模式下的条件日志 */
#include "core/build_config.h"
#include "task_manager.h"
#include "heart_payload.h"
#include "ota_offer_protocol.h"
#include "ota_frame_protocol.h"

/* OTA 二进制流模块（BLE和UART共用） */
#include "ota_binary_stream.h"
#include "cJSON.h"
#include "link_dma_ble.h"

static const char *tag_s = "LINK_BLE";

#if CONFIG_APP_BLE_TX_POWER_N24
#define LINK_BLE_TX_PWR ESP_PWR_LVL_N24
#elif CONFIG_APP_BLE_TX_POWER_N21
#define LINK_BLE_TX_PWR ESP_PWR_LVL_N21
#elif CONFIG_APP_BLE_TX_POWER_N18
#define LINK_BLE_TX_PWR ESP_PWR_LVL_N18
#elif CONFIG_APP_BLE_TX_POWER_N15
#define LINK_BLE_TX_PWR ESP_PWR_LVL_N15
#elif CONFIG_APP_BLE_TX_POWER_N12
#define LINK_BLE_TX_PWR ESP_PWR_LVL_N12
#else
#define LINK_BLE_TX_PWR ESP_PWR_LVL_N9
#endif

static bool s_ble_stack_running_s;

/** 0xFFE1 JSON 接收缓冲（避免 GATT 回调栈上分配 2KB） */
static char s_ffe1_json_buf_s[REMOTE_CTRL_JSON_MAX_BYTES + 1];

#if !BUILD_IS_RELEASE
static void log_ble_rx_payload(const char *channel, const uint8_t *data, uint16_t len)
{
    if (data == NULL || len == 0) {
        ESP_LOGI(tag_s, "%s RX empty", channel);
        return;
    }

    ESP_LOGI(tag_s, "%s RX len=%u", channel, (unsigned)len);

    uint16_t text_len = len < 64 ? len : 64;
    char text[65];
    memcpy(text, data, text_len);
    text[text_len] = '\0';
    ESP_LOGI(tag_s, "%s RX text: %s%s", channel, text, (len > text_len) ? " ..." : "");
}
#endif

static const struct ble_gap_adv_params adv_params_s = {
    .conn_mode = BLE_GAP_CONN_MODE_UND,
    .disc_mode = BLE_GAP_DISC_MODE_GEN,
};

static int gap_event(struct ble_gap_event *event, void *arg);
static int gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle,
                               struct ble_gatt_access_ctxt *ctxt, void *arg);
static int gatt_svr_chr_ota_access(uint16_t conn_handle, uint16_t attr_handle,
                                   struct ble_gatt_access_ctxt *ctxt, void *arg);
static int gatt_svr_chr_hb_access(uint16_t conn_handle, uint16_t attr_handle,
                                  struct ble_gatt_access_ctxt *ctxt, void *arg);
static int gatt_svr_chr_ota_stat_access(uint16_t conn_handle, uint16_t attr_handle,
                                        struct ble_gatt_access_ctxt *ctxt, void *arg);
static void link_ble_gatt_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg);
static int ble_hb_notify_json(const char *json);
static void ble_heartbeat_task(void *param);
static bool ble_dispatch_json_line(const char *json);
static void ble_dma_rx_dispatch(link_dma_ble_ch_t ch, const uint8_t *data, uint16_t len, void *ctx);
static int ble_dma_tx_notify(uint16_t attr_handle, const uint8_t *data, uint16_t len, void *ctx);
static int ble_dma_process_ota_payload(const uint8_t *buf, uint16_t copy_len);
static void ble_request_low_duty_conn(uint16_t conn_handle);
static void link_ble_apply_tx_power(void);
static void ble_send_heartbeat_once(void);
static void ble_ota_notify_payload(const uint8_t *payload, uint16_t payload_len);
static void ble_ota_feed_raw_bytes(const uint8_t *data, uint16_t len);
static int ble_ota_process_packet(const uint8_t *data, uint16_t len);
void link_ble_session_reset(void);

/** GATT 0xFFE3：与 MQTT 同结构心跳 JSON，Notify 周期推送；可读为最近一次内容 */
static uint16_t hb_chr_val_handle_s;
static volatile uint16_t ble_conn_handle_s;
static volatile bool ble_link_up_s;
static uint8_t last_hb_buf_s[512];
static uint16_t last_hb_len_s;

/** GATT 0xFFE4：OTA 过程 JSON 状态，Read 为最近一次；Notify 在 begin/chunk/end 各步推送 */
static uint16_t ota_stat_chr_val_handle_s;
static char last_ota_stat_json_s[192];

#if BUILD_IS_RELEASE
static bool log_hook_installed_s;
static volatile bool log_forward_busy_s;
#endif

#ifndef BLE_HEART_PERIOD_MS
#define BLE_HEART_PERIOD_MS 10000
#endif

static void ble_adv_name_apply_mp_suffix(char *name, size_t cap)
{
    const char suf[] = "MP";
    const size_t slen = sizeof(suf) - 1U;
    size_t n = strnlen(name, cap);
    if (n >= slen && memcmp(name + n - slen, suf, slen) == 0) {
        return;
    }
    if (cap < slen + 2U) {
        return;
    }
    if (n + slen + 1U > cap) {
        size_t cut = cap - slen - 1U;
        name[cut] = '\0';
        n = cut;
    }
    memcpy(name + n, suf, slen);
    name[n + slen] = '\0';
}

/** 0xFFE3：心跳 / 告警 / 错误共用 Notify；Read 为最近一次下发内容 */
static int ble_hb_notify_json(const char *json)
{
    if (json == NULL || !ble_link_up_s || hb_chr_val_handle_s == 0U) {
        return -1;
    }
    size_t jlen = strlen(json);
    if (jlen == 0U || jlen >= sizeof(last_hb_buf_s)) {
        return -1;
    }
    memcpy(last_hb_buf_s, json, jlen);
    last_hb_buf_s[jlen] = '\0';
    last_hb_len_s = (uint16_t)jlen;

    if (!link_dma_ble_post_tx(hb_chr_val_handle_s, last_hb_buf_s, last_hb_len_s)) {
        return -1;
    }
    return 0;
}

#if BUILD_IS_RELEASE
static void ble_log_line_to_msg(const char *line, char *msg, size_t msg_cap)
{
    const char *colon = strchr(line, ':');
    if (colon != NULL && colon[1] == ' ') {
        strncpy(msg, colon + 2, msg_cap - 1U);
    } else {
        strncpy(msg, line, msg_cap - 1U);
    }
    msg[msg_cap - 1U] = '\0';
    size_t ml = strlen(msg);
    while (ml > 0U && (msg[ml - 1U] == '\n' || msg[ml - 1U] == '\r')) {
        msg[--ml] = '\0';
    }
}

static int ble_log_vprintf_hook(const char *fmt, va_list ap)
{
    (void)fmt;
    if (xPortInIsrContext() || log_forward_busy_s) {
        return 0;
    }

    char line[192];
    va_list ap_line;
    va_copy(ap_line, ap);
    int ln = vsnprintf(line, sizeof(line), fmt, ap_line);
    va_end(ap_line);
    if (ln <= 0) {
        return 0;
    }

    int msg_type = 0;
    if (line[0] == 'W') {
        msg_type = HEART_MSG_TYPE_LOG_WARN;
    } else if (line[0] == 'E') {
        msg_type = HEART_MSG_TYPE_LOG_ERROR;
    } else {
        return 0;
    }

    char msg[160];
    ble_log_line_to_msg(line, msg, sizeof(msg));

    char *json = heart_payload_alert_json_malloc(msg_type, msg);
    if (json == NULL) {
        return 0;
    }

    log_forward_busy_s = true;
    (void)ble_hb_notify_json(json);
    log_forward_busy_s = false;
    free(json);
    return 0;
}

static void ble_log_hook_install_if_needed(void)
{
    if (log_hook_installed_s) {
        return;
    }
    (void)esp_log_set_vprintf(ble_log_vprintf_hook);
    log_hook_installed_s = true;
}
#endif

static void link_ble_gatt_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
    (void)arg;
    if (ctxt->op != BLE_GATT_REGISTER_OP_CHR) {
        return;
    }
    uint16_t u16 = ble_uuid_u16(ctxt->chr.chr_def->uuid);
    if (u16 == 0xFFE3) {
        hb_chr_val_handle_s = ctxt->chr.val_handle;
        ESP_LOGI(tag_s, "GATT: FFE3 val_handle=%u", (unsigned)hb_chr_val_handle_s);
    } else if (u16 == 0xFFE4) {
        ota_stat_chr_val_handle_s = ctxt->chr.val_handle;
        ESP_LOGI(tag_s, "GATT: FFE4 val_handle=%u", (unsigned)ota_stat_chr_val_handle_s);
    }
}

static int gatt_svr_chr_hb_access(uint16_t conn_handle, uint16_t attr_handle,
                                  struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    (void)conn_handle;
    (void)attr_handle;
    (void)arg;
    if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
        if (last_hb_len_s > 0) {
            int rc = os_mbuf_append(ctxt->om, last_hb_buf_s, last_hb_len_s);
            return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
        }
        return 0;
    }
    return BLE_ATT_ERR_UNLIKELY;
}

static int gatt_svr_chr_ota_stat_access(uint16_t conn_handle, uint16_t attr_handle,
                                        struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    (void)conn_handle;
    (void)attr_handle;
    (void)arg;
    if (ctxt->op == BLE_GATT_ACCESS_OP_READ_CHR) {
        if (last_ota_stat_json_s[0] != '\0') {
            uint16_t slen = (uint16_t)strnlen(last_ota_stat_json_s, sizeof(last_ota_stat_json_s));
            int rc = os_mbuf_append(ctxt->om, last_ota_stat_json_s, slen);
            return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
        }
        return 0;
    }
    return BLE_ATT_ERR_UNLIKELY;
}

static void ble_send_heartbeat_once(void)
{
    if (!ble_link_up_s || hb_chr_val_handle_s == 0U) {
        return;
    }
    if (ota_offer_session_busy()) {
        return;
    }
    char dev[32] = {0};
    (void)read_from_nvs(DEVICE_CFG_KEY_DEVICE_ID, dev, sizeof(dev));
    char *json = heart_payload_json_malloc(dev, NULL, false);
    if (json == NULL) {
        return;
    }
    (void)ble_hb_notify_json(json);
    free(json);
}

static void ble_heartbeat_task(void *param)
{
    (void)param;
    for (;;) {
        if (ble_link_up_s && hb_chr_val_handle_s != 0U) {
            ble_send_heartbeat_once();
        }
        vTaskDelay(pdMS_TO_TICKS(BLE_HEART_PERIOD_MS));
    }
}

/** BLE OTA（0xFFE2）：OTA 帧协议见 ota_frame_protocol.h；0xFFE4 回传整帧 ACK/NAK */

static bool ble_is_ota_opcode(uint8_t b)
{
    return (b == OTA_OP_BEGIN || b == OTA_OP_DATA || b == OTA_OP_END);
}

static void ble_ota_tx_frame(const uint8_t *frame, uint16_t len, void *ctx)
{
    (void)ctx;
    ble_ota_notify_payload(frame, len);
}

/* OTA 状态：仅缓存；进度用 1002 JSON（FFE3），固件字节确认走 FFE4 单字节 Notify */
static void ble_ota_status_callback(const char *json)
{
    if (json == NULL) {
        return;
    }
    strncpy(last_ota_stat_json_s, json, sizeof(last_ota_stat_json_s) - 1);
    last_ota_stat_json_s[sizeof(last_ota_stat_json_s) - 1] = '\0';
}

static void ble_ota_notify_payload(const uint8_t *payload, uint16_t payload_len)
{
    if (!ble_link_up_s || ota_stat_chr_val_handle_s == 0U || payload == NULL || payload_len == 0U) {
        return;
    }
    (void)link_dma_ble_post_tx(ota_stat_chr_val_handle_s, payload, payload_len);
}

static int ble_dma_tx_notify(uint16_t attr_handle, const uint8_t *data, uint16_t len, void *ctx)
{
    (void)ctx;
    if (!ble_link_up_s || ble_conn_handle_s == 0U || data == NULL || len == 0U) {
        return -1;
    }
    struct os_mbuf *om = ble_hs_mbuf_from_flat(data, len);
    if (om == NULL) {
        return -1;
    }
    int nrc = ble_gatts_notify_custom(ble_conn_handle_s, attr_handle, om);
    if (nrc != 0) {
        os_mbuf_free_chain(om);
    }
    return nrc;
}

static int ble_dma_process_ota_payload(const uint8_t *buf, uint16_t copy_len)
{
    if (!buf || copy_len < 1) {
        return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
    }

#if !BUILD_IS_RELEASE
    log_ble_rx_payload("FFE2", buf, copy_len);
#endif

    if (!ota_offer_is_armed() && !ota_binary_stream_is_active()) {
        ESP_LOGW(tag_s, "FFE2 忽略 OTA：尚未收到手机 message_type=0");
        return BLE_ATT_ERR_WRITE_NOT_PERMITTED;
    }

    if (ota_frame_rx_looks_framed(buf, copy_len)) {
        ota_frame_rx_feed(buf, copy_len);
        return 0;
    }

    if (copy_len >= 1U && !ble_is_ota_opcode(buf[0]) &&
        (ota_binary_stream_is_active() || ota_offer_is_armed())) {
        ble_ota_feed_raw_bytes(buf, copy_len);
        return 0;
    }

    int result = ble_ota_process_packet(buf, copy_len);
    if (result == 0) {
        return 0;
    }
    if (result == -1 || result == -2) {
        return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
    }
    return BLE_ATT_ERR_UNLIKELY;
}

static void ble_dma_rx_dispatch(link_dma_ble_ch_t ch, const uint8_t *data, uint16_t len, void *ctx)
{
    (void)ctx;
    if (!data || len == 0U) {
        return;
    }

    if (ch == LINK_DMA_BLE_CH_JSON) {
        if (len > REMOTE_CTRL_JSON_MAX_BYTES) {
            return;
        }
        memcpy(s_ffe1_json_buf_s, data, len);
        s_ffe1_json_buf_s[len] = '\0';
#if !BUILD_IS_RELEASE
        log_ble_rx_payload("FFE1", (const uint8_t *)s_ffe1_json_buf_s, len);
        ESP_LOGI(tag_s, "FFE1 JSON dispatch start");
#endif
        (void)ble_dispatch_json_line(s_ffe1_json_buf_s);
#if !BUILD_IS_RELEASE
        ESP_LOGI(tag_s, "FFE1 JSON dispatch done");
#endif
        return;
    }

    (void)ble_dma_process_ota_payload(data, len);
}

static void ble_ota_feed_raw_bytes(const uint8_t *data, uint16_t len)
{
    for (uint16_t i = 0; i < len; i++) {
        uint8_t pkt[2] = { OTA_OP_DATA, data[i] };
        if (ota_binary_stream_process_packet(pkt, 2) == 0) {
            ble_ota_notify_payload(&data[i], 1);
        }
    }
}

static int ble_ota_process_packet(const uint8_t *data, uint16_t len)
{
    if (!data || len == 0U) {
        return -1;
    }
    uint8_t op = data[0];
    int rc = ota_binary_stream_process_packet(data, len);
    if (rc == 0 && op == OTA_OP_DATA && len > 1U) {
        ble_ota_notify_payload(data + 1, (uint16_t)(len - 1U));
    }
    return rc;
}

/* BLE OTA 初始化 */
static int ble_ota_init(void)
{
    esp_err_t err = ota_binary_stream_init(OTA_TRANSPORT_BLE, ble_ota_status_callback, NULL);
    if (err != ESP_OK) {
        ESP_LOGE(tag_s, "BLE OTA init failed: %s", esp_err_to_name(err));
        return -1;
    }
    ota_frame_protocol_init(ble_ota_tx_frame, NULL);
    return 0;
}

static int gatt_svr_chr_ota_access(uint16_t conn_handle, uint16_t attr_handle,
                                   struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    (void)conn_handle;
    (void)attr_handle;
    (void)arg;
#if !CONFIG_APP_BLE_OTA
    return BLE_ATT_ERR_WRITE_NOT_PERMITTED;
#endif
    if (ctxt->op != BLE_GATT_ACCESS_OP_WRITE_CHR) {
        return BLE_ATT_ERR_UNLIKELY;
    }
    uint16_t len = OS_MBUF_PKTLEN(ctxt->om);
    if (len < 1) {
        return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
    }
    uint8_t buf[512];
    uint16_t copy_len = 0;
    int rc = ble_hs_mbuf_to_flat(ctxt->om, buf, sizeof(buf), &copy_len);
    if (rc != 0) {
        return BLE_ATT_ERR_UNLIKELY;
    }

    if (!link_dma_ble_post_rx(LINK_DMA_BLE_CH_OTA, buf, copy_len)) {
        return BLE_ATT_ERR_INSUFFICIENT_RES;
    }
    return 0;
}

static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
    {
        .type = BLE_GATT_SVC_TYPE_PRIMARY,
        .uuid = BLE_UUID16_DECLARE(0xFFE0),
        .characteristics =
            (struct ble_gatt_chr_def[]){
                {
                    .uuid = BLE_UUID16_DECLARE(0xFFE1),
                    .access_cb = gatt_svr_chr_access,
                    .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP,
                },
                {
                    .uuid = BLE_UUID16_DECLARE(0xFFE2),
                    .access_cb = gatt_svr_chr_ota_access,
                    .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_WRITE_NO_RSP,
                },
                {
                    .uuid = BLE_UUID16_DECLARE(0xFFE3),
                    .access_cb = gatt_svr_chr_hb_access,
                    .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
                },
                {
                    .uuid = BLE_UUID16_DECLARE(0xFFE4),
                    .access_cb = gatt_svr_chr_ota_stat_access,
                    .flags = BLE_GATT_CHR_F_READ | BLE_GATT_CHR_F_NOTIFY,
                },
                {
                    0,
                },
            },
    },
    {
        0,
    },
};

static int gatt_svr_chr_access(uint16_t conn_handle, uint16_t attr_handle,
                               struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    (void)conn_handle;
    (void)attr_handle;
    (void)arg;
    if (ctxt->op == BLE_GATT_ACCESS_OP_WRITE_CHR) {
        uint16_t len = OS_MBUF_PKTLEN(ctxt->om);
        if (len > REMOTE_CTRL_JSON_MAX_BYTES) {
            return BLE_ATT_ERR_INVALID_ATTR_VALUE_LEN;
        }
        uint16_t copy_len = 0;
        int rc = ble_hs_mbuf_to_flat(ctxt->om, s_ffe1_json_buf_s, REMOTE_CTRL_JSON_MAX_BYTES, &copy_len);
        if (rc != 0) {
            return BLE_ATT_ERR_UNLIKELY;
        }
        if (!link_dma_ble_post_rx(LINK_DMA_BLE_CH_JSON, (const uint8_t *)s_ffe1_json_buf_s, copy_len)) {
            return BLE_ATT_ERR_INSUFFICIENT_RES;
        }
        return 0;
    }
    return BLE_ATT_ERR_UNLIKELY;
}

static void ble_ota_done_report(bool success, uint32_t bytes_written, const char *err_step)
{
    char devid[32] = {0};
    read_from_nvs(DEVICE_CFG_KEY_DEVICE_ID, devid, sizeof(devid));

    char *json = ota_offer_result_json_malloc(devid, success, bytes_written, err_step);
    if (json) {
        for (int i = 0; i < OTA_DONE_REPORT_COUNT; i++) {
            (void)ble_hb_notify_json(json);
            ESP_LOGI(tag_s, "BLE TX OTA done (1002) %d/%d", i + 1, OTA_DONE_REPORT_COUNT);
            if (i + 1 < OTA_DONE_REPORT_COUNT) {
                vTaskDelay(pdMS_TO_TICKS(OTA_DONE_REPORT_GAP_MS));
            }
        }
        free(json);
    }
    if (!success) {
        ble_send_heartbeat_once();
    }
}

static void ble_send_ota_active_notice(void)
{
    if (!ble_link_up_s || hb_chr_val_handle_s == 0U) {
        return;
    }

    char devid[32] = {0};
    read_from_nvs(DEVICE_CFG_KEY_DEVICE_ID, devid, sizeof(devid));

    char *json = ota_offer_active_json_malloc(devid);
    if (json) {
        (void)ble_hb_notify_json(json);
        ESP_LOGI(tag_s, "BLE TX OTA 进行中 (1002/active): %s", json);
        free(json);
    }
}

/** 手机 message_type=6：回复 OTA 询问（每次连接后 App 发 6 即回 1002） */
static void ble_send_ota_query_on_link_ready(void)
{
    if (!ble_link_up_s || hb_chr_val_handle_s == 0U) {
        return;
    }

    char devid[32] = {0};
    read_from_nvs(DEVICE_CFG_KEY_DEVICE_ID, devid, sizeof(devid));

    char *json = ota_offer_query_json_malloc(devid);
    if (json) {
        (void)ble_hb_notify_json(json);
        ESP_LOGI(tag_s, "BLE TX OTA 询问 (1002，已收 link_ready=6): %s", json);
        free(json);
    }
}

static bool ble_dispatch_json_line(const char *json)
{
    if (!json || json[0] == '\0') {
        return false;
    }

    cJSON *root = cJSON_Parse(json);
    if (!root) {
        remote_control_apply_json(json);
        return true;
    }

    cJSON *head = cJSON_GetObjectItem(root, "head");
    cJSON *m_type = (head && cJSON_IsObject(head)) ? cJSON_GetObjectItem(head, "message_type") : NULL;
    if (m_type && cJSON_IsNumber(m_type) && m_type->valueint == REMOTE_CTRL_MSG_LINK_RESET) {
        link_ble_session_reset_ex("reset");
        ESP_LOGI(tag_s, "收到 message_type=2，已清空 BLE 会话");
        cJSON_Delete(root);
        return true;
    }

    if (m_type && cJSON_IsNumber(m_type) && (int)m_type->valuedouble == REMOTE_CTRL_MSG_LINK_READY) {
        ble_send_ota_query_on_link_ready();
        cJSON_Delete(root);
        return true;
    }

    if (ota_offer_parse_phone_response(root)) {
        ESP_LOGI(tag_s, "手机同意 OTA (head.message_type=0)，可写 0xFFE2");
        ble_send_ota_active_notice();
        cJSON_Delete(root);
        return true;
    }

    remote_control_apply_json_root(root, json);
    cJSON_Delete(root);
    return true;
}

static int gap_event(struct ble_gap_event *event, void *arg)
{
    (void)arg;
    switch (event->type) {
    case BLE_GAP_EVENT_CONNECT:
        ESP_LOGI(tag_s, "GAP connect status=%d", event->connect.status);
        if (event->connect.status == 0) {
            ble_conn_handle_s = event->connect.conn_handle;
            link_dma_ble_set_conn_handle(event->connect.conn_handle);
            ble_link_up_s = true;
            link_ble_session_reset();
            link_ble_apply_tx_power();
            ble_request_low_duty_conn(event->connect.conn_handle);
            if (remote_control_watchdog_start() != ESP_OK) {
                ESP_LOGW(tag_s, "Failed to start RC watchdog");
            }
            ble_send_heartbeat_once();
        } else {
            ble_link_up_s = false;
            ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params_s, gap_event, NULL);
        }
        break;
    case BLE_GAP_EVENT_DISCONNECT:
        ESP_LOGI(tag_s, "GAP disconnect reason=%d", event->disconnect.reason);
        ble_link_up_s = false;
        ble_conn_handle_s = 0U;
        link_dma_ble_set_conn_handle(0);
        remote_control_watchdog_stop();
        link_ble_session_reset_ex("disconnect");
        ESP_LOGI(tag_s, "BLE disconnected, session cleared");
        ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params_s, gap_event, NULL);
        break;
    case BLE_GAP_EVENT_ADV_COMPLETE:
        ESP_LOGI(tag_s, "ADV complete reason=%d", event->adv_complete.reason);
        break;
    default:
        break;
    }
    return 0;
}

static void ble_on_reset(int reason)
{
    ESP_LOGE(tag_s, "ble reset reason=%d", reason);
}

static void ble_on_sync(void)
{
    int rc = ble_hs_util_ensure_addr(0);
    if (rc != 0) {
        ESP_LOGE(tag_s, "ble_hs_util_ensure_addr rc=%d", rc);
        return;
    }
    uint8_t own_addr_type = BLE_OWN_ADDR_PUBLIC;
    rc = ble_hs_id_infer_auto(0, &own_addr_type);
    if (rc != 0) {
        ESP_LOGE(tag_s, "ble_hs_id_infer_auto rc=%d", rc);
        return;
    }

    char name[32] = {0};
    if (read_from_nvs(DEVICE_CFG_KEY_BLE_ADV_NAME, name, sizeof(name)) != ESP_OK || strlen(name) == 0) {
        strncpy(name, "ESP32-BLE", sizeof(name) - 1);
    }
    ble_adv_name_apply_mp_suffix(name, sizeof(name));

    ble_svc_gap_device_name_set(name);

    struct ble_hs_adv_fields fields;
    memset(&fields, 0, sizeof(fields));
    fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
    fields.name = (uint8_t *)name;
    fields.name_len = (uint8_t)strlen(name);
    fields.name_is_complete = 1;

    rc = ble_gap_adv_set_fields(&fields);
    if (rc != 0) {
        ESP_LOGE(tag_s, "adv_set_fields rc=%d", rc);
        return;
    }

    rc = ble_gap_adv_start(BLE_OWN_ADDR_PUBLIC, NULL, BLE_HS_FOREVER, &adv_params_s, gap_event, NULL);
    if (rc != 0) {
        ESP_LOGE(tag_s, "adv_start rc=%d", rc);
    } else {
        ESP_LOGI(tag_s, "BLE 广播已启动: %s", name);
    }
}

static void ble_host_task(void *param)
{
    (void)param;
    nimble_port_run();
}

static void link_ble_apply_tx_power(void)
{
    static const esp_ble_power_type_t k_pwr_types[] = {
        ESP_BLE_PWR_TYPE_DEFAULT,
        ESP_BLE_PWR_TYPE_ADV,
        ESP_BLE_PWR_TYPE_SCAN,
        ESP_BLE_PWR_TYPE_CONN_HDL0,
        ESP_BLE_PWR_TYPE_CONN_HDL1,
        ESP_BLE_PWR_TYPE_CONN_HDL2,
        ESP_BLE_PWR_TYPE_CONN_HDL3,
        ESP_BLE_PWR_TYPE_CONN_HDL4,
        ESP_BLE_PWR_TYPE_CONN_HDL5,
        ESP_BLE_PWR_TYPE_CONN_HDL6,
        ESP_BLE_PWR_TYPE_CONN_HDL7,
        ESP_BLE_PWR_TYPE_CONN_HDL8,
    };
    for (size_t i = 0; i < sizeof(k_pwr_types) / sizeof(k_pwr_types[0]); i++) {
        esp_err_t e = esp_ble_tx_power_set(k_pwr_types[i], LINK_BLE_TX_PWR);
        if (e != ESP_OK) {
            ESP_LOGW(tag_s, "tx_power type=%d: %s", (int)k_pwr_types[i], esp_err_to_name(e));
        }
    }
    ESP_LOGI(tag_s, "BLE 发射功率已设为 %d（贴机降干扰）", (int)LINK_BLE_TX_PWR);
}

static void ble_request_low_duty_conn(uint16_t conn_handle)
{
    struct ble_gap_upd_params params = {
        .itvl_min = BLE_GAP_CONN_ITVL_MS(45),
        .itvl_max = BLE_GAP_CONN_ITVL_MS(60),
        .latency = 6,
        .supervision_timeout = 400,
    };
    int rc = ble_gap_update_params(conn_handle, &params);
    if (rc != 0) {
        ESP_LOGW(tag_s, "conn param update rc=%d", rc);
    }
}

static void link_ble_session_reset_ex(const char *ota_abort_err)
{
    if (ota_abort_err != NULL) {
        ota_offer_report_abort(ota_abort_err);
    }
    ota_frame_protocol_reset();
    remote_control_link_reset();
    memset(s_ffe1_json_buf_s, 0, sizeof(s_ffe1_json_buf_s));
    last_hb_len_s = 0;
    last_hb_buf_s[0] = '\0';
    last_ota_stat_json_s[0] = '\0';
}

void link_ble_session_reset(void)
{
    link_ble_session_reset_ex(NULL);
}

void link_ble_stop(void)
{
    if (!s_ble_stack_running_s) {
        return;
    }
    link_dma_ble_stop();
    ble_link_up_s = false;
    remote_control_watchdog_stop();
    link_ble_session_reset();
    (void)ble_gap_adv_stop();
    if (ble_conn_handle_s != 0U) {
        (void)ble_gap_terminate(ble_conn_handle_s, BLE_ERR_REM_USER_CONN_TERM);
        ble_conn_handle_s = 0U;
    }
    int rc = nimble_port_stop();
    if (rc == 0) {
        nimble_port_deinit();
    } else {
        ESP_LOGW(tag_s, "nimble_port_stop rc=%d", rc);
    }
    s_ble_stack_running_s = false;
    ESP_LOGI(tag_s, "BLE 已停止（配网/关 WiFi 时使用）");
}

esp_err_t link_ble_start(void)
{
    if (s_ble_stack_running_s) {
        return ESP_OK;
    }
    ota_offer_reset_boot();
    ota_offer_set_done_callback(ble_ota_done_report);

    /* 初始化 BLE OTA 模块 */
    ble_ota_init();

    esp_err_t dma_err = link_dma_ble_start(ble_dma_rx_dispatch, NULL, ble_dma_tx_notify, NULL);
    if (dma_err != ESP_OK) {
        ESP_LOGE(tag_s, "BLE DMA queue init failed: %s", esp_err_to_name(dma_err));
        return dma_err;
    }

    /* ESP-IDF 5.x：控制器与 VHCI 由 nimble_port_init() 内部完成，勿再调用已移除的 esp_nimble_hci_and_controller_init */
    esp_err_t err = nimble_port_init();
    if (err != ESP_OK) {
        ESP_LOGE(tag_s, "nimble_port_init: %s", esp_err_to_name(err));
        return err;
    }
    link_ble_apply_tx_power();
    s_ble_stack_running_s = true;
    ble_hs_cfg.reset_cb = ble_on_reset;
    ble_hs_cfg.sync_cb = ble_on_sync;
    ble_hs_cfg.gatts_register_cb = link_ble_gatt_register_cb;
#if BUILD_IS_RELEASE
    ble_log_hook_install_if_needed();
#endif

    ble_svc_gap_init();
    ble_svc_gatt_init();

    int rc = ble_gatts_count_cfg(gatt_svr_svcs);
    if (rc != 0) {
        ESP_LOGE(tag_s, "gatts_count_cfg %d", rc);
        link_ble_stop();
        return ESP_FAIL;
    }
    rc = ble_gatts_add_svcs(gatt_svr_svcs);
    if (rc != 0) {
        ESP_LOGE(tag_s, "gatts_add_svcs %d", rc);
        link_ble_stop();
        return ESP_FAIL;
    }

    if (app_task_start(APP_TASK_BLE_HEARTBEAT, ble_heartbeat_task, NULL, NULL) != ESP_OK) {
        ESP_LOGE(tag_s, "ble heartbeat task create failed");
        link_ble_stop();
        return ESP_FAIL;
    }

    if (app_task_start_nimble_host(ble_host_task) != ESP_OK) {
        ESP_LOGE(tag_s, "ble host task create failed");
        link_ble_stop();
        return ESP_FAIL;
    }
    return ESP_OK;
}
