/*
 * turn_client.c — NPC gateway client for /v1/voice/turn and /v1/voice/reply.
 *
 * Uses esp_http_client open/write/fetch/read streaming so the binary WAV
 * body can be written directly to SPIFFS without a large heap buffer.
 *
 * Pattern mirrors hook_client.c but synchronous (called from conv_task).
 * Timeout is generous (30 s) because TTS synthesis adds latency.
 *
 * Stage 3 adds turn_client_reply() which POSTs captured mic audio as
 * multipart/form-data to /v1/voice/reply and streams the NPC response WAV.
 */

#include "turn_client.h"

#include "sdkconfig.h"

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

#include "esp_http_client.h"
#include "esp_log.h"

#define TAG "turn_client"

/* WAV header is 44 bytes; anything shorter is certainly not a valid response. */
#define MIN_WAV_BYTES 44

/* Read chunks when streaming the response body. */
#define CHUNK_SIZE    1024

/* Write chunks when uploading the captured WAV body (4 KB). */
#define UPLOAD_CHUNK  4096

/* Timeout for the full TTS round-trip (connect + synthesis + transfer). */
#define TIMEOUT_MS    90000  /* reply TTS (Kyutai MLX ~0.3x realtime) can take
                              * tens of seconds; was 30s → reply POST timed out
                              * (HTTP -1). 90s covers worst-case generation. */

/* Multipart boundary (must not appear in the WAV payload — 16kHz PCM is binary
 * so any fixed ASCII boundary is safe). */
#define BOUNDARY "----ZacusPlipBoundary7MA4YWxkTrZu0gW"

bool turn_client_greeting(const char *session_id,
                           const char *number,
                           const char *out_path)
{
    /* --- Build URL -------------------------------------------------------- */
    char url[256];
    snprintf(url, sizeof(url), "%s/v1/voice/turn",
             CONFIG_PLIP_GATEWAY_URL);

    /* --- Build JSON body -------------------------------------------------- */
    char body[256];
    int body_len = snprintf(body, sizeof(body),
        "{\"session_id\":\"%s\",\"number\":\"%s\",\"kind\":\"greeting\"}",
        session_id ? session_id : "",
        number     ? number     : "");

    /* --- Configure client ------------------------------------------------- */
    esp_http_client_config_t cfg = {
        .url        = url,
        .method     = HTTP_METHOD_POST,
        .timeout_ms = TIMEOUT_MS,
    };
    esp_http_client_handle_t client = esp_http_client_init(&cfg);
    if (!client) {
        ESP_LOGE(TAG, "esp_http_client_init failed");
        return false;
    }

    esp_http_client_set_header(client, "Content-Type", "application/json");

    /* Authorization header — skip if token is empty */
    const char *token = CONFIG_PLIP_GATEWAY_TOKEN;
    if (token && token[0] != '\0') {
        char auth[128];
        snprintf(auth, sizeof(auth), "Bearer %s", token);
        esp_http_client_set_header(client, "Authorization", auth);
    }

    /* --- Open connection and write body (streaming POST) ------------------ */
    esp_err_t err = esp_http_client_open(client, body_len);
    if (err != ESP_OK) {
        ESP_LOGW(TAG, "open %s failed: %s", url, esp_err_to_name(err));
        esp_http_client_cleanup(client);
        return false;
    }

    int written = esp_http_client_write(client, body, body_len);
    if (written < 0) {
        ESP_LOGW(TAG, "write body failed (written=%d)", written);
        esp_http_client_close(client);
        esp_http_client_cleanup(client);
        return false;
    }

    /* --- Fetch response headers ------------------------------------------ */
    int content_len = esp_http_client_fetch_headers(client);
    int status_code = esp_http_client_get_status_code(client);

    if (status_code != 200) {
        ESP_LOGW(TAG, "gateway returned HTTP %d (url=%s)", status_code, url);
        esp_http_client_close(client);
        esp_http_client_cleanup(client);
        return false;
    }

    ESP_LOGI(TAG, "HTTP 200 from %s (content_length=%d)", url, content_len);

    /* --- Stream binary body into SPIFFS file ------------------------------ */
    FILE *fp = fopen(out_path, "wb");
    if (!fp) {
        ESP_LOGE(TAG, "fopen(%s, wb) failed", out_path);
        esp_http_client_close(client);
        esp_http_client_cleanup(client);
        return false;
    }

    static uint8_t s_chunk[CHUNK_SIZE];   /* static: avoids stack pressure */
    int total = 0;
    int rd;
    while ((rd = esp_http_client_read(client, (char *)s_chunk, sizeof(s_chunk))) > 0) {
        size_t fw = fwrite(s_chunk, 1, (size_t)rd, fp);
        if ((int)fw != rd) {
            ESP_LOGW(TAG, "fwrite short: wrote %d of %d bytes", (int)fw, rd);
            break;
        }
        total += rd;
    }

    fclose(fp);
    esp_http_client_close(client);
    esp_http_client_cleanup(client);

    if (total <= MIN_WAV_BYTES) {
        ESP_LOGW(TAG, "greeting WAV too short (%d bytes) — ignoring", total);
        return false;
    }

    ESP_LOGI(TAG, "greeting WAV %d bytes written to %s", total, out_path);
    return true;
}

/* ── turn_client_reply ─────────────────────────────────────────────────────
 *
 * POST multipart/form-data to /v1/voice/reply with the captured mic WAV.
 *
 * Multipart layout (each part is CRLF-delimited per RFC 2046):
 *
 *   --<boundary>\r\n
 *   Content-Disposition: form-data; name="session_id"\r\n\r\n
 *   <session_id>\r\n
 *   --<boundary>\r\n
 *   Content-Disposition: form-data; name="number"\r\n\r\n
 *   <number>\r\n
 *   --<boundary>\r\n
 *   Content-Disposition: form-data; name="audio"; filename="rec.wav"\r\n
 *   Content-Type: audio/wav\r\n\r\n
 *   <wav bytes>
 *   \r\n--<boundary>--\r\n
 *
 * Content-Length = len(preamble) + wav_len + len(epilogue)
 * (known exactly before writing — allows non-chunked POST).
 */
esp_err_t turn_client_reply(const char *session_id,
                             const char *number,
                             const uint8_t *wav,
                             size_t wav_len,
                             const char *out_path)
{
    if (!session_id || !number || !wav || wav_len == 0 || !out_path) {
        return ESP_ERR_INVALID_ARG;
    }

    /* --- Build URL -------------------------------------------------------- */
    char url[256];
    snprintf(url, sizeof(url), "%s/v1/voice/reply",
             CONFIG_PLIP_GATEWAY_URL);

    /* --- Build multipart preamble ---------------------------------------- */
    /* preamble = 3 parts before the raw wav bytes:
     *   part 1 — session_id (text field)
     *   part 2 — number (text field)
     *   part 3 — audio file header (up to but NOT including the file body)
     */
    char preamble[512];
    int preamble_len = snprintf(preamble, sizeof(preamble),
        "--%s\r\n"
        "Content-Disposition: form-data; name=\"session_id\"\r\n\r\n"
        "%s\r\n"
        "--%s\r\n"
        "Content-Disposition: form-data; name=\"number\"\r\n\r\n"
        "%s\r\n"
        "--%s\r\n"
        "Content-Disposition: form-data; name=\"audio\"; filename=\"rec.wav\"\r\n"
        "Content-Type: audio/wav\r\n\r\n",
        BOUNDARY,
        session_id,
        BOUNDARY,
        number,
        BOUNDARY);

    if (preamble_len <= 0 || preamble_len >= (int)sizeof(preamble)) {
        ESP_LOGE(TAG, "preamble buffer overflow (len=%d)", preamble_len);
        return ESP_ERR_INVALID_SIZE;
    }

    /* --- Build multipart epilogue ---------------------------------------- */
    /* epilogue = CRLF after the wav data + closing boundary */
    char epilogue[64];
    int epilogue_len = snprintf(epilogue, sizeof(epilogue),
        "\r\n--%s--\r\n",
        BOUNDARY);

    if (epilogue_len <= 0 || epilogue_len >= (int)sizeof(epilogue)) {
        ESP_LOGE(TAG, "epilogue buffer overflow");
        return ESP_ERR_INVALID_SIZE;
    }

    int content_length = preamble_len + (int)wav_len + epilogue_len;

    ESP_LOGI(TAG, "reply POST %s preamble=%d wav=%zu epilogue=%d total=%d",
             url, preamble_len, wav_len, epilogue_len, content_length);

    /* --- Configure client ------------------------------------------------- */
    esp_http_client_config_t cfg = {
        .url        = url,
        .method     = HTTP_METHOD_POST,
        .timeout_ms = TIMEOUT_MS,
    };
    esp_http_client_handle_t client = esp_http_client_init(&cfg);
    if (!client) {
        ESP_LOGE(TAG, "esp_http_client_init failed");
        return ESP_ERR_NO_MEM;
    }

    /* Content-Type with boundary */
    char ct_header[128];
    snprintf(ct_header, sizeof(ct_header),
             "multipart/form-data; boundary=%s", BOUNDARY);
    esp_http_client_set_header(client, "Content-Type", ct_header);

    /* Authorization header — skip if token is empty */
    const char *token = CONFIG_PLIP_GATEWAY_TOKEN;
    if (token && token[0] != '\0') {
        char auth[128];
        snprintf(auth, sizeof(auth), "Bearer %s", token);
        esp_http_client_set_header(client, "Authorization", auth);
    }

    /* --- Open connection and stream body ---------------------------------- */
    esp_err_t err = esp_http_client_open(client, content_length);
    if (err != ESP_OK) {
        ESP_LOGW(TAG, "open %s failed: %s", url, esp_err_to_name(err));
        esp_http_client_cleanup(client);
        return err;
    }

    /* Write preamble */
    int written = esp_http_client_write(client, preamble, preamble_len);
    if (written < 0) {
        ESP_LOGW(TAG, "write preamble failed (ret=%d)", written);
        esp_http_client_close(client);
        esp_http_client_cleanup(client);
        return ESP_FAIL;
    }

    /* Write WAV data in 4 KB chunks */
    size_t remaining = wav_len;
    const uint8_t *ptr = wav;
    while (remaining > 0) {
        size_t to_send = (remaining < UPLOAD_CHUNK) ? remaining : UPLOAD_CHUNK;
        int wr = esp_http_client_write(client, (const char *)ptr, (int)to_send);
        if (wr < 0) {
            ESP_LOGW(TAG, "write wav chunk failed (ret=%d)", wr);
            esp_http_client_close(client);
            esp_http_client_cleanup(client);
            return ESP_FAIL;
        }
        ptr       += (size_t)wr;
        remaining -= (size_t)wr;
    }

    /* Write epilogue */
    written = esp_http_client_write(client, epilogue, epilogue_len);
    if (written < 0) {
        ESP_LOGW(TAG, "write epilogue failed (ret=%d)", written);
        esp_http_client_close(client);
        esp_http_client_cleanup(client);
        return ESP_FAIL;
    }

    /* --- Fetch response headers ------------------------------------------ */
    int content_len = esp_http_client_fetch_headers(client);
    int status_code = esp_http_client_get_status_code(client);

    ESP_LOGI(TAG, "reply HTTP %d content_length=%d", status_code, content_len);

    if (status_code != 200) {
        ESP_LOGW(TAG, "gateway returned HTTP %d for reply", status_code);
        esp_http_client_close(client);
        esp_http_client_cleanup(client);
        return ESP_ERR_INVALID_RESPONSE;
    }

    /* --- Stream binary WAV response into SPIFFS file --------------------- */
    FILE *fp = fopen(out_path, "wb");
    if (!fp) {
        ESP_LOGE(TAG, "fopen(%s, wb) failed", out_path);
        esp_http_client_close(client);
        esp_http_client_cleanup(client);
        return ESP_ERR_NOT_FOUND;
    }

    static uint8_t s_reply_chunk[CHUNK_SIZE];  /* static: avoids stack pressure */
    int total = 0;
    int rd;
    while ((rd = esp_http_client_read(client, (char *)s_reply_chunk,
                                      sizeof(s_reply_chunk))) > 0) {
        size_t fw = fwrite(s_reply_chunk, 1, (size_t)rd, fp);
        if ((int)fw != rd) {
            ESP_LOGW(TAG, "fwrite short: wrote %d of %d bytes", (int)fw, rd);
            break;
        }
        total += rd;
    }

    fclose(fp);
    esp_http_client_close(client);
    esp_http_client_cleanup(client);

    if (total <= MIN_WAV_BYTES) {
        ESP_LOGW(TAG, "reply WAV too short (%d bytes) — ignoring", total);
        return ESP_ERR_INVALID_SIZE;
    }

    ESP_LOGI(TAG, "reply WAV %d bytes written to %s", total, out_path);
    return ESP_OK;
}
