/*
 * hook_client.c — Ported from PLIP_FIRMWARE/src/zacus_hook_client.cpp.
 *
 * Uses esp_http_client (IDF) instead of Arduino HTTPClient.
 * Failure policy: 3 s timeout, one retry at +250 ms, then drop.
 */

#include "hook_client.h"

#include <string.h>
#include <stdio.h>

#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/queue.h"
#include "freertos/task.h"

#define TAG "hook_client"

#define HOOK_PATH         "/voice/hook"
#define TIMEOUT_MS        3000
#define RETRY_DELAY_MS    250
#define QUEUE_DEPTH       4
#define WIFI_WAIT_MS      1500
#define WIFI_WAIT_STEP_MS 50

typedef struct {
    char state[8];   /* "off" | "on" */
    char reason[32]; /* free-form */
} hook_event_t;

static QueueHandle_t s_queue   = NULL;
static char          s_url[192] = {0};  /* full URL: <base>/voice/hook */

/* ── Helpers ─────────────────────────────────────────────────────────────── */

static bool wifi_ready_within(uint32_t budget_ms)
{
    uint32_t waited = 0;
    wifi_ap_record_t ap;
    while (waited < budget_ms) {
        if (esp_wifi_sta_get_ap_info(&ap) == ESP_OK) return true;
        vTaskDelay(pdMS_TO_TICKS(WIFI_WAIT_STEP_MS));
        waited += WIFI_WAIT_STEP_MS;
    }
    return false;
}

static bool post_hook(const hook_event_t *ev)
{
    char body[96];
    snprintf(body, sizeof(body),
             "{\"state\":\"%s\",\"reason\":\"%s\"}",
             ev->state, ev->reason);

    esp_http_client_config_t cfg = {
        .url             = s_url,
        .method          = HTTP_METHOD_POST,
        .timeout_ms      = TIMEOUT_MS,
        .buffer_size     = 512,
    };
    esp_http_client_handle_t client = esp_http_client_init(&cfg);
    if (!client) {
        ESP_LOGW(TAG, "http_client_init failed");
        return false;
    }

    esp_http_client_set_header(client, "Content-Type", "application/json");
    esp_http_client_set_post_field(client, body, (int)strlen(body));

    esp_err_t ret = esp_http_client_perform(client);
    int code = 0;
    if (ret == ESP_OK) {
        code = esp_http_client_get_status_code(client);
    }
    esp_http_client_cleanup(client);

    if (ret == ESP_OK && code >= 200 && code < 300) {
        ESP_LOGI(TAG, "POST %s -> %d (state=%s reason=%s)",
                 s_url, code, ev->state, ev->reason);
        return true;
    }
    ESP_LOGW(TAG, "POST %s failed: ret=%s code=%d (state=%s reason=%s)",
             s_url, esp_err_to_name(ret), code, ev->state, ev->reason);
    return false;
}

/* ── Worker task ─────────────────────────────────────────────────────────── */

static void worker_task(void *arg)
{
    (void)arg;
    ESP_LOGI(TAG, "worker ready, target=%s", s_url);
    hook_event_t ev;
    for (;;) {
        if (xQueueReceive(s_queue, &ev, portMAX_DELAY) != pdTRUE) continue;

        if (!wifi_ready_within(WIFI_WAIT_MS)) {
            ESP_LOGW(TAG, "WiFi down, dropping hook event (state=%s)", ev.state);
            continue;
        }

        if (post_hook(&ev)) continue;

        vTaskDelay(pdMS_TO_TICKS(RETRY_DELAY_MS));
        if (!post_hook(&ev)) {
            ESP_LOGW(TAG, "giving up after retry (state=%s)", ev.state);
        }
    }
}

/* ── Public API ──────────────────────────────────────────────────────────── */

esp_err_t hook_client_init(const char *master_url)
{
    if (s_queue) return ESP_OK;   /* idempotent */

    const char *base = (master_url && *master_url)
                           ? master_url : CONFIG_PLIP_MASTER_URL;

    size_t blen = strnlen(base, sizeof(s_url) - sizeof(HOOK_PATH) - 1);
    if (blen == 0 || blen >= sizeof(s_url) - sizeof(HOOK_PATH)) {
        ESP_LOGE(TAG, "invalid master_url");
        return ESP_ERR_INVALID_ARG;
    }
    memcpy(s_url, base, blen);
    if (s_url[blen - 1] == '/') blen--;
    s_url[blen] = '\0';
    strncat(s_url, HOOK_PATH, sizeof(s_url) - strlen(s_url) - 1);

    s_queue = xQueueCreate(QUEUE_DEPTH, sizeof(hook_event_t));
    if (!s_queue) return ESP_ERR_NO_MEM;

    if (xTaskCreate(worker_task, "hook_client", 4096, NULL, 5, NULL) != pdPASS) {
        vQueueDelete(s_queue);
        s_queue = NULL;
        return ESP_ERR_NO_MEM;
    }
    return ESP_OK;
}

bool hook_client_report(const char *state, const char *reason)
{
    if (!s_queue) {
        ESP_LOGW(TAG, "report() before init() — dropping");
        return false;
    }
    hook_event_t ev = {0};
    strncpy(ev.state,  state  ? state  : "", sizeof(ev.state)  - 1);
    strncpy(ev.reason, reason ? reason : "", sizeof(ev.reason) - 1);

    if (xQueueSend(s_queue, &ev, 0) != pdTRUE) {
        ESP_LOGW(TAG, "queue full, dropping (state=%s)", ev.state);
        return false;
    }
    return true;
}
