// ESP32-C3 + ESP-IDF v5.3 + NimBLE (LEGACY VHCI) - GATT mínimo, ADV “fast”, UUID no ADV
#include <stdio.h>
#include <string.h>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "sdkconfig.h"

#include "esp_log.h"
#include "nvs_flash.h"

#include "esp_bt.h"              // mem_release + tx power
#include "nimble/nimble_port.h"
#include "nimble/nimble_port_freertos.h"

#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "host/ble_store.h"
#include "services/gap/ble_svc_gap.h"
#include "services/gatt/ble_svc_gatt.h"

// IDF 5.3 pode não ter o header público; declara manualmente:
void ble_store_config_init(void);

static const char *TAG = "BLE_APP_RS";
static uint8_t own_addr_type;

/* --------- Serviço GATT de exemplo --------- */
static const ble_uuid128_t DEMO_SVC_UUID = BLE_UUID128_INIT(
    0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0,0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf1);
static const ble_uuid128_t DEMO_CHR_UUID = BLE_UUID128_INIT(
    0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf0,0x12,0x34,0x56,0x78,0x9a,0xbc,0xde,0xf2);

static int demo_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_READ_CHR) {
        const char *val = "Hello from ESP32-C3";
        os_mbuf_append(ctxt->om, val, strlen(val));
        return 0;
    }
    return BLE_ATT_ERR_UNLIKELY;
}

static const struct ble_gatt_chr_def demo_chrs[] = {
    {
        .uuid      = (ble_uuid_t*)&DEMO_CHR_UUID.u,
        .access_cb = demo_chr_access,
        .flags     = BLE_GATT_CHR_F_READ
                    // não exigir segurança para leitura/descoberta (Windows descobre serviços sem pairing)
    },
    { 0 }
};

static const struct ble_gatt_svc_def demo_svcs[] = {
    { .type=BLE_GATT_SVC_TYPE_PRIMARY, .uuid=(ble_uuid_t*)&DEMO_SVC_UUID.u, .characteristics=demo_chrs },
    { 0 }
};
/* ------------------------------------------- */

static void start_advertising(bool fast_forever);
static int  gap_event(struct ble_gap_event *event, void *arg);
static void host_task(void *param);

static void on_reset(int reason)
{
    ESP_LOGW(TAG, "ble_hs reset, reason=%d", reason);
}

static void on_sync(void)
{
    // Serviços padrão (incluem Service Changed e PPCP no GAP)
    ble_svc_gap_init();
    ble_svc_gatt_init();

    // Endereço: prefira público (ou random static gerado) — ajuda o Windows no cache
    int rc = ble_hs_id_infer_auto(0, &own_addr_type);
    if (rc != 0) { ESP_LOGE(TAG, "ble_hs_id_infer_auto rc=%d", rc); return; }

    uint8_t addr[6]; ble_hs_id_copy_addr(own_addr_type, addr, NULL);
    ESP_LOGI(TAG, "own_addr_type=%u  addr=%02X:%02X:%02X:%02X:%02X:%02X",
             own_addr_type, addr[5],addr[4],addr[3],addr[2],addr[1],addr[0]);

    // Nome do dispositivo (Windows usa no UI; nome fica no scan response)
    ble_svc_gap_device_name_set("ESP32C3-LIGHT");

    // Registra serviços do app
    rc = ble_gatts_count_cfg(demo_svcs);
    if (rc == 0) rc = ble_gatts_add_svcs(demo_svcs);
    if (rc != 0) { ESP_LOGE(TAG, "Falha registrando GATT rc=%d", rc); return; }
    ESP_LOGI(TAG, "GATT services registrados");

    // PPCP “amigável” ao Windows (intervalos ~30–50 ms, latency 0, timeout ~2 s)
    // struct ble_gap_conn_params ppcp = {
    //     .itvl_min = 0x0018,  // 30 ms
    //     .itvl_max = 0x0028,  // 50 ms
    //     .latency  = 0x0000,  // 0
    //     .supervision_timeout = 0x00C8 // 2000 ms
    // };
    // ble_svc_gap_ppcp_set(&ppcp);

    // MTU preferido (Windows costuma negociar > 23)
    ble_att_set_preferred_mtu(247);

    // Começa anunciando
    start_advertising(true);
}

static void start_advertising(bool fast_forever)
{
    struct ble_hs_adv_fields adv = {0};
    struct ble_hs_adv_fields rsp = {0};

    /* --- ADV: Flags + UUID128 (BLE-only + discoverable) --- */
    adv.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;

    adv.uuids128 = (ble_uuid128_t*)&DEMO_SVC_UUID;
    adv.num_uuids128 = 1;
    adv.uuids128_is_complete = 1;

    /* --- SCAN_RSP: nome completo --- */
    const char *full_name = "ESP32C3-LIGHT";
    rsp.name = (uint8_t*)full_name;
    rsp.name_len = (uint8_t)strlen(full_name);
    rsp.name_is_complete = 1;

    int rc = ble_gap_adv_set_fields(&adv);
    if (rc) { ESP_LOGE(TAG, "adv_set_fields rc=%d (payload invalido?)", rc); return; }

    rc = ble_gap_adv_rsp_set_fields(&rsp);
    if (rc) { ESP_LOGE(TAG, "adv_rsp_set_fields rc=%d", rc); return; }

    struct ble_gap_adv_params p = {0};
    p.conn_mode   = BLE_GAP_CONN_MODE_UND;   // conectável, não-direcionado
    p.disc_mode   = BLE_GAP_DISC_MODE_GEN;   // discoverable geral
    p.channel_map = 0x07;                    // 37/38/39

    if (fast_forever) { p.itvl_min = 32;  p.itvl_max = 48;  }   // ~20–30 ms
    else               { p.itvl_min = 80; p.itvl_max = 160; }   // ~50–100 ms

    rc = ble_gap_adv_start(own_addr_type, NULL, BLE_HS_FOREVER, &p, gap_event, NULL);
    if (rc) ESP_LOGE(TAG, "adv_start rc=%d", rc);
    else    ESP_LOGI(TAG, "ADV ON (%s), addr_type=%u", fast_forever ? "fast" : "slow", own_addr_type);
}

static int gap_event(struct ble_gap_event *ev, void *arg)
{
    (void)arg;
    switch (ev->type) {

    case BLE_GAP_EVENT_CONNECT:
        if (ev->connect.status == 0) {
            ESP_LOGI(TAG, "CONNECT OK, handle=%u", ev->connect.conn_handle);

            // Parâmetros de conexão “clássicos” (Windows gosta)
            // 30–50 ms, latency 0, supervision 4.0 s (400 * 10ms)
            {
                struct ble_gap_upd_params u = {
                    .itvl_min = 24,     // 30 ms
                    .itvl_max = 40,     // 50 ms
                    .latency  = 0,
                    .supervision_timeout = 400 // 400 * 10 ms = 4.0 s
                };
                int urc = ble_gap_update_params(ev->connect.conn_handle, &u);
                if (urc) ESP_LOGW(TAG, "gap_update_params rc=%d", urc);
            }
        } else {
            ESP_LOGW(TAG, "CONNECT FAIL status=0x%X - retomando ADV", ev->connect.status);
            start_advertising(true);
        }
        return 0;

    case BLE_GAP_EVENT_DISCONNECT:
        ESP_LOGW(TAG, "DISCONNECT reason=0x%X - retomando ADV", ev->disconnect.reason);
        start_advertising(true);
        return 0;

    case BLE_GAP_EVENT_ADV_COMPLETE: {
        uint16_t r = ev->adv_complete.reason; // 0x0D == BLE_HS_ETIME (timeout)
        ESP_LOGW(TAG, "ADV COMPLETE reason=0x%02X", r);
        start_advertising(true);              // mantém anúncio contínuo
        return 0;
    }

    case BLE_GAP_EVENT_SCAN_REQ_RCVD:
        ESP_LOGI(TAG, "SCAN_REQ recebido");
        return 0;

    case BLE_GAP_EVENT_MTU:
        ESP_LOGI(TAG, "MTU=%d", ev->mtu.value);
        return 0;

    case BLE_GAP_EVENT_SUBSCRIBE:
        ESP_LOGI(TAG, "SUBSCRIBE: attr=%u notify=%d indicate=%d",
                 ev->subscribe.attr_handle,
                 ev->subscribe.cur_notify,
                 ev->subscribe.cur_indicate);
        return 0;

    case BLE_GAP_EVENT_REPEAT_PAIRING:
        // Se habilitar bonding depois, isso ajuda a “reparear” quando pares divergirem
        if (ev->repeat_pairing.conn_handle != BLE_HS_CONN_HANDLE_NONE) {
            struct ble_gap_conn_desc d;
            if (ble_gap_conn_find(ev->repeat_pairing.conn_handle, &d) == 0) {
                ble_store_util_delete_peer(&d.peer_id_addr);
            }
        }
        return BLE_GAP_REPEAT_PAIRING_RETRY;

    default:
        return 0;
    }
}

static void host_task(void *param)
{
    (void)param;
    nimble_port_run();                 // nunca retorna
    nimble_port_freertos_deinit();
    vTaskDelete(NULL);
}

void app_main(void)
{
    ESP_LOGI(TAG, "Inicializando...");

    // 1) NVS (com fallback de erase)
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_LOGW(TAG, "NVS precisa de erase (0x%x). Apagando...", err);
        ESP_ERROR_CHECK(nvs_flash_erase());
        ESP_ERROR_CHECK(nvs_flash_init());
    } else {
        ESP_ERROR_CHECK(err);
    }

    // 2) BLE only: libera memória do BT clássico
    err = esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
    if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
        ESP_LOGE(TAG, "mem_release(CLASSIC) falhou: %s", esp_err_to_name(err));
        return;
    }

#if CONFIG_BT_NIMBLE_LEGACY_VHCI_ENABLE
    ESP_LOGI(TAG, "LEGACY VHCI ativo -> pulando controller/HCI manuais");
#else
    ESP_LOGW(TAG, "LEGACY VHCI DESATIVADO. Ative em menuconfig para este app_main.");
#endif

    // 3) NimBLE host
    ESP_ERROR_CHECK(nimble_port_init());

    // 4) Callbacks + storage + segurança “liberal” (sem pairing) p/ testes no Windows
    ble_hs_cfg.reset_cb        = on_reset;
    ble_hs_cfg.sync_cb         = on_sync;
    ble_hs_cfg.store_status_cb = ble_store_util_status_rr;

    ble_hs_cfg.sm_bonding = 0;                 // bonding OFF (teste mais simples)
    ble_hs_cfg.sm_mitm    = 0;
    ble_hs_cfg.sm_sc      = 0;                 // pode ligar depois
    ble_hs_cfg.sm_io_cap  = BLE_HS_IO_NO_INPUT_OUTPUT;

    ble_store_config_init();

    // (Opcional) limpar pares/CCCDs antigos durante testes:
    // ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_PEER_SEC, NULL);
    // ble_store_util_delete_all(BLE_STORE_OBJ_TYPE_CCCD,     NULL);

    // (Opcional) TX power antes de anunciar
    (void)esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_ADV,     ESP_PWR_LVL_P9);
    (void)esp_ble_tx_power_set(ESP_BLE_PWR_TYPE_DEFAULT, ESP_PWR_LVL_P9);

    // 5) Task do host
    nimble_port_freertos_init(host_task);
}
